feat(t-paliad-154) commit 4/5: admin /admin/approval-policies page

New TSX page shell + client orchestration + admin-index card + CSS for
the matrix + i18n keys (DE+EN).

Page structure:
- Section 1 'Partner-Unit-Standards': accordion list, each <details>
  block expandable into the 8-cell matrix for that partner unit.
- Section 2 'Projekt-spezifisch': search-driven project picker → matrix
  showing the EFFECTIVE policy per cell with attribution chips
  (Projekt / Geerbt / Standard) per source.
- Bulk-apply modal: 'Auf Unterprojekte anwenden' button per project; lists
  affected descendants; POST to /api/admin/approval-policies/apply-to-descendants.

Cell semantics:
- Select per cell with options: '— keine Regel —' (= DELETE), partner /
  of_counsel / associate / senior_pa / pa / 'Keine Genehmigung' (= 'none'
  sentinel, project-row only).
- Change → PUT for any value, DELETE for empty. Re-fetch the affected
  scope so attribution chips reflect the new state.

CSS: matrix grid on desktop (≥700px); two stacked sections (Fristen /
Termine) below 700px via media query — both rendered in DOM, CSS toggles.
All tokens are existing --color-* / --status-* / --hlc-*-rgb (no bare
--surface / --text-muted / --bg-subtle).

i18n: 42 new keys × 2 languages = 84 entries. Total i18n keys: 1924.

Build: bun run build clean (i18n codegen updated, IIFE wrapping enforced).
This commit is contained in:
m
2026-05-08 02:27:54 +02:00
parent 0f87d73b1b
commit 028423b32f
6 changed files with 1101 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ const ICON_LOG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" str
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;
@@ -88,6 +89,11 @@ export function renderAdmin(): string {
<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&uuml;fung pro Projekt und Partner Unit konfigurieren.</p>
</a>
</div>
<h3 className="section-heading admin-section-planned" data-i18n="admin.section.planned">Geplant</h3>