Closes the procedural-events rename loop opened by m/paliad#93. The admin surface now lives under its canonical URL; the legacy paths remain reachable for one deprecation cycle via 301 redirects so bookmarks, audit-log entries, and curl scripts keep working. * internal/handlers/handlers.go — - Registers the 12 canonical routes under /admin/procedural-events* (page paths and JSON API). Same handlers — just the new URL slot. - Registers the 12 legacy /admin/rules* routes as 301 redirects. * internal/handlers/admin_rules.go — - redirectToProceduralEvents(dst) — fixed-destination redirect for paths without an {id}. - redirectToProceduralEventEdit — page redirect carrying the {id}. - redirectToProceduralEventAPI(suffix) — JSON API redirect carrying {id} + optional suffix (/clone-as-draft, /publish, /archive, /restore, /audit, /preview). Query string is preserved on every redirect. - All three helpers add the IETF Deprecation header + a Link header pointing at the successor-version path. * frontend internal nav + URL strings — Sidebar.tsx, admin.tsx, admin-rules-list.tsx, admin-rules-edit.tsx, client/admin-rules-list.ts, client/admin-rules-edit.ts: every `/admin/rules*` reference flipped to `/admin/procedural-events*`. In-app navigation now hits the canonical paths directly without a redirect round-trip; external callers keep working via the 301s. * frontend .tsx i18n rebind — 9 admin .tsx i18n bindings rebound to the canonical `admin.procedural_events.*` keys that already exist as aliases in i18n.ts (per Slice A from t-paliad-262). Specifically: admin.rules.list.title → admin.procedural_events.list.title admin.rules.list.heading → admin.procedural_events.list.heading admin.rules.list.new → admin.procedural_events.list.new admin.rules.col.submission_code → admin.procedural_events.col.code admin.rules.edit.title → admin.procedural_events.edit.title admin.rules.edit.breadcrumb → admin.procedural_events.edit.breadcrumb admin.rules.edit.field.submission_code → admin.procedural_events.edit.field.code admin.rules.edit.field.event_type → admin.procedural_events.edit.field.event_kind admin.rules.edit.field.parent → admin.procedural_events.edit.field.parent The remaining ~142 admin.rules.* keys do NOT yet have procedural_events aliases. Migrating them is a follow-up slice — each needs a new alias entry in i18n.ts (DE + EN) before the .tsx reference can be flipped. The 9 keys touched here are the most visible (page titles + edit-page field labels) so the admin UI immediately reads as "Verfahrensschritte" everywhere. * frontend/src/client/i18n.ts header comment updated to reflect that the URL rename has shipped (Slice B.6 done) and to flag the remaining i18n-key migration as the next step. Scope (documented, paliadin authorised): - "go everything" applied: backend routes + frontend nav + .tsx rebind of the 9 keys whose canonical aliases exist. - Full migration of all 142 admin.rules.* keys deferred — would require seeding ~142 new alias entries in i18n.ts (DE + EN) plus another 142 .tsx rebinds. Out of scope for tonight; flag as follow-up `feat(i18n): finish admin.rules.* → admin.procedural_events.* alias migration`. - 12 legacy /admin/rules routes still hit a handler (the redirect helper) — they don't 404 yet. Once a deprecation window passes with no traffic on the old paths, a future slice can drop them outright. Build + vet clean. TestMigrations_NoDuplicateSlot passes. This concludes the m/paliad#93 procedural-events rename slice train (Slices A through B.6). curie stays parked persistently for any follow-up the deploy / monitor cycle surfaces.
127 lines
8.0 KiB
TypeScript
127 lines
8.0 KiB
TypeScript
import { h } from "./jsx";
|
|
import { Sidebar } from "./components/Sidebar";
|
|
import { PaliadinWidget } from "./components/PaliadinWidget";
|
|
import { BottomNav } from "./components/BottomNav";
|
|
import { Footer } from "./components/Footer";
|
|
import { PWAHead } from "./components/PWAHead";
|
|
|
|
const ICON_USERS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>';
|
|
const ICON_BUILDING = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v16"/><path d="M16 9h3a2 2 0 0 1 2 2v10"/><path d="M9 7h2"/><path d="M9 11h2"/><path d="M9 15h2"/></svg>';
|
|
const ICON_LOG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>';
|
|
const ICON_MAIL = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><polyline points="3 7 12 13 21 7"/></svg>';
|
|
const ICON_FLAG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>';
|
|
const ICON_TABLE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/></svg>';
|
|
const ICON_SHIELD = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/></svg>';
|
|
|
|
interface PlannedCard {
|
|
icon: string;
|
|
i18nTitle: string;
|
|
i18nDesc: string;
|
|
fallbackTitle: string;
|
|
fallbackDesc: string;
|
|
}
|
|
|
|
const PLANNED: PlannedCard[] = [
|
|
{
|
|
icon: ICON_FLAG,
|
|
i18nTitle: "admin.card.feature_flags.title",
|
|
i18nDesc: "admin.card.feature_flags.desc",
|
|
fallbackTitle: "Feature-Flags",
|
|
fallbackDesc: "Funktionen pro Standort, Partner Unit oder Rolle aktivieren.",
|
|
},
|
|
];
|
|
|
|
export function renderAdmin(): string {
|
|
return "<!DOCTYPE html>" + (
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#BFF355" />
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
|
<PWAHead />
|
|
<title data-i18n="admin.title">Admin-Bereich — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar">
|
|
<Sidebar currentPath="/admin" />
|
|
<BottomNav currentPath="/admin" />
|
|
|
|
<main>
|
|
<section className="tool-page">
|
|
<div className="container">
|
|
<div className="tool-header">
|
|
<h1 data-i18n="admin.heading">Admin-Bereich</h1>
|
|
<p className="tool-subtitle" data-i18n="admin.subtitle">
|
|
Werkzeuge zur Verwaltung von Paliad. Nur für Administrator:innen sichtbar.
|
|
</p>
|
|
</div>
|
|
|
|
<h3 className="section-heading" data-i18n="admin.section.available">Verfügbar</h3>
|
|
<div className="grid grid-2">
|
|
<a href="/admin/team" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_USERS }} />
|
|
<h2 data-i18n="admin.card.team.title">Team-Verwaltung</h2>
|
|
<p data-i18n="admin.card.team.desc">Benutzer:innen anlegen, bearbeiten, löschen.</p>
|
|
</a>
|
|
<a href="/admin/partner-units" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_BUILDING }} />
|
|
<h2 data-i18n="admin.card.partner_units.title">Partner Units</h2>
|
|
<p data-i18n="admin.card.partner_units.desc">Strukturelle Partnereinheiten anlegen und Mitglieder zuordnen.</p>
|
|
</a>
|
|
<a href="/admin/audit-log" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_LOG }} />
|
|
<h2 data-i18n="admin.card.audit.title">Audit-Log</h2>
|
|
<p data-i18n="admin.card.audit.desc">Wer hat wann was geändert? Nachvollziehbarkeit für sicherheitsrelevante Aktionen.</p>
|
|
</a>
|
|
<a href="/admin/email-templates" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_MAIL }} />
|
|
<h2 data-i18n="admin.card.email_templates.title">Email-Templates</h2>
|
|
<p data-i18n="admin.card.email_templates.desc">Vorlagen für Einladungen, Erinnerungen und Layout anpassen.</p>
|
|
</a>
|
|
<a href="/admin/event-types" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_TABLE }} />
|
|
<h2 data-i18n="admin.card.event_types.title">Event-Typen</h2>
|
|
<p data-i18n="admin.card.event_types.desc">Firmenweite Event-Typen moderieren: archivieren, zusammenführen, befördern.</p>
|
|
</a>
|
|
<a href="/admin/broadcasts" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_MAIL }} />
|
|
<h2 data-i18n="admin.card.broadcasts.title">Broadcasts</h2>
|
|
<p data-i18n="admin.card.broadcasts.desc">Versendete Massen-E-Mails an Teamauswahlen einsehen.</p>
|
|
</a>
|
|
<a href="/admin/approval-policies" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_SHIELD }} />
|
|
<h2 data-i18n="admin.card.approval_policies.title">Genehmigungspflichten</h2>
|
|
<p data-i18n="admin.card.approval_policies.desc">4-Augen-Prüfung pro Projekt und Partner Unit konfigurieren.</p>
|
|
</a>
|
|
<a href="/admin/procedural-events" className="card card-link">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_TABLE }} />
|
|
<h2 data-i18n="admin.card.rules.title">Regeln verwalten</h2>
|
|
<p data-i18n="admin.card.rules.desc">Fristen-Regeln anlegen, bearbeiten, publishen. Audit-Log, Preview, Migration-Export.</p>
|
|
</a>
|
|
</div>
|
|
|
|
<h3 className="section-heading admin-section-planned" data-i18n="admin.section.planned">Geplant</h3>
|
|
<div className="grid grid-2">
|
|
{PLANNED.map((c) => (
|
|
<div className="card admin-card-soon" title="Kommt bald" data-i18n-title="admin.coming_soon">
|
|
<div className="card-icon" dangerouslySetInnerHTML={{ __html: c.icon }} />
|
|
<h2 data-i18n={c.i18nTitle}>{c.fallbackTitle}</h2>
|
|
<p data-i18n={c.i18nDesc}>{c.fallbackDesc}</p>
|
|
<span className="admin-soon-badge" data-i18n="admin.coming_soon">Kommt bald</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
<PaliadinWidget />
|
|
<script src="/assets/admin.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|