From 1714b788d26937a8ba558835ba510dac8f974087 Mon Sep 17 00:00:00 2001 From: mAi Date: Mon, 25 May 2026 13:33:14 +0200 Subject: [PATCH] =?UTF-8?q?feat(projects-detail):=20t-paliad-245=20?= =?UTF-8?q?=E2=80=94=20demote=20Daten=20Export=20into=20Verwaltung=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit m/paliad#76. The export button no longer pokes out of the tabs nav with a non-tab styling — instead it lives inside a new "Verwaltung" tab (last in the project tab list) as a normal section with heading, description, and a plain btn-secondary trigger. Same gate as before (canExportProject). Archive co-locates in the same tab as a pointer to the Edit-modal danger zone: click "Bearbeiten öffnen" → modal opens scrolled to the archive button. Single source of truth for the destructive action stays in the modal; the Verwaltung pointer just gives it discoverability. If neither sub-section is visible to the caller (no export entitlement, not global_admin), the Verwaltung tab hides itself — an empty tab is worse UX than no tab. --- frontend/src/client/i18n.ts | 12 ++++++ frontend/src/client/projects-detail.ts | 57 ++++++++++++++++++++------ frontend/src/i18n-keys.ts | 6 +++ frontend/src/projects-detail.tsx | 50 +++++++++++++++------- frontend/src/styles/global.css | 14 +++++++ 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts index 5b03e60..c85bbc9 100644 --- a/frontend/src/client/i18n.ts +++ b/frontend/src/client/i18n.ts @@ -1426,8 +1426,14 @@ const translations: Record> = { "projects.detail.tab.notizen": "Notizen", "projects.detail.tab.checklisten": "Checklisten", "projects.detail.tab.submissions": "Schriftsätze", + "projects.detail.tab.settings": "Verwaltung", "projects.detail.export.button": "Daten exportieren", "projects.detail.export.tooltip": "Daten dieses Projekts (mit Unter-Projekten) als Excel + JSON + CSV herunterladen.", + "projects.detail.settings.export.heading": "Daten exportieren", + "projects.detail.settings.export.description": "Lade alle Daten dieses Projekts (inkl. Unter-Projekten) als Excel + JSON + CSV-Archiv herunter.", + "projects.detail.settings.archive.heading": "Projekt archivieren", + "projects.detail.settings.archive.description": "Archivieren erfolgt aus dem Bearbeiten-Dialog (Gefahrenbereich).", + "projects.detail.settings.archive.cta": "Bearbeiten öffnen", "projects.detail.submissions.empty": "Es sind aktuell keine Schriftsatzvorlagen hinterlegt.", "projects.detail.submissions.empty.no_proceeding": "Für dieses Projekt ist noch kein Verfahrenstyp gesetzt — der Katalog unten zeigt trotzdem alle Vorlagen.", "projects.detail.submissions.empty.no_proceeding.cta": "Projekt bearbeiten", @@ -4347,8 +4353,14 @@ const translations: Record> = { "projects.detail.tab.notizen": "Notes", "projects.detail.tab.checklisten": "Checklists", "projects.detail.tab.submissions": "Submissions", + "projects.detail.tab.settings": "Settings", "projects.detail.export.button": "Export data", "projects.detail.export.tooltip": "Download this project's data (including sub-projects) as Excel + JSON + CSV.", + "projects.detail.settings.export.heading": "Export data", + "projects.detail.settings.export.description": "Download all data for this project (including sub-projects) as an Excel + JSON + CSV archive.", + "projects.detail.settings.archive.heading": "Archive project", + "projects.detail.settings.archive.description": "Archiving happens in the edit dialog (danger zone).", + "projects.detail.settings.archive.cta": "Open edit dialog", "projects.detail.submissions.empty": "No submission templates are configured yet.", "projects.detail.submissions.empty.no_proceeding": "No proceeding type is set for this project yet — the catalog below still lists every template.", "projects.detail.submissions.empty.no_proceeding.cta": "Edit project", diff --git a/frontend/src/client/projects-detail.ts b/frontend/src/client/projects-detail.ts index b0f860d..1f0116d 100644 --- a/frontend/src/client/projects-detail.ts +++ b/frontend/src/client/projects-detail.ts @@ -175,7 +175,8 @@ type TabId = | "appointments" | "notes" | "checklists" - | "submissions"; + | "submissions" + | "settings"; const VALID_TABS: TabId[] = [ "history", @@ -187,6 +188,7 @@ const VALID_TABS: TabId[] = [ "notes", "checklists", "submissions", + "settings", ]; // Legacy German tab slugs that may appear in bookmarked URLs after the @@ -1185,13 +1187,16 @@ function renderHeader() { netdocs.style.display = "none"; } - // Delete visibility: partner/admin only + // Delete visibility: partner/admin only. The Verwaltung tab's archive + // sub-section mirrors the same gate (t-paliad-245) — it only points at + // the Edit-modal danger zone, so it's pointless to show when the danger + // zone itself is hidden. const deleteWrap = document.getElementById("project-delete-wrap")!; - if (me && (me.global_role === "global_admin")) { - deleteWrap.style.display = ""; - } else { - deleteWrap.style.display = "none"; - } + const archiveSection = document.getElementById("project-settings-archive"); + const canArchive = !!me && me.global_role === "global_admin"; + deleteWrap.style.display = canArchive ? "" : "none"; + if (archiveSection) archiveSection.style.display = canArchive ? "" : "none"; + updateSettingsTabVisibility(); } // wrapEventTitleLink — kept for the dashboard activity feed which reuses @@ -2045,6 +2050,17 @@ function initEditModal() { }); } + // Verwaltung → Projekt archivieren — opens the edit modal scrolled to + // the danger-zone archive button (t-paliad-245). + const archiveLink = document.getElementById( + "project-settings-archive-link", + ) as HTMLButtonElement | null; + if (archiveLink) { + archiveLink.addEventListener("click", () => { + openEditModal("project-delete-btn"); + }); + } + form.addEventListener("submit", async (e) => { e.preventDefault(); if (!project) return; @@ -2991,17 +3007,21 @@ function canExportProject(): boolean { ); } -// wireExportButton reveals + hooks up the project-export button on the -// tabs nav. Triggers a download via a transient — same -// pattern as the personal export in client/settings.ts. +// wireExportButton reveals the Export sub-section of the Verwaltung tab +// (t-paliad-245) and hooks up the project-export button. Triggers a +// download via a transient — same pattern as the personal +// export in client/settings.ts. function wireExportButton(projectID: string): void { + const section = document.getElementById("project-settings-export") as HTMLElement | null; const btn = document.getElementById("project-export-btn") as HTMLButtonElement | null; - if (!btn) return; + if (!section || !btn) return; if (!canExportProject()) { - btn.style.display = "none"; + section.style.display = "none"; + updateSettingsTabVisibility(); return; } - btn.style.display = ""; + section.style.display = ""; + updateSettingsTabVisibility(); btn.addEventListener("click", () => { const a = document.createElement("a"); a.href = `/api/projects/${encodeURIComponent(projectID)}/export`; @@ -3012,6 +3032,17 @@ function wireExportButton(projectID: string): void { }); } +// updateSettingsTabVisibility hides the Verwaltung tab when none of its +// sub-sections are visible to the current user — an empty tab is worse +// UX than no tab. Called whenever a sub-section's visibility flips. +function updateSettingsTabVisibility(): void { + const tab = document.querySelector('.entity-tab[data-tab="settings"]'); + if (!tab) return; + const exportShown = document.getElementById("project-settings-export")?.style.display !== "none"; + const archiveShown = document.getElementById("project-settings-archive")?.style.display !== "none"; + tab.style.display = exportShown || archiveShown ? "" : "none"; +} + function canRemoveTeamMember(m: ProjectTeamMember): boolean { if (!me) return false; if (m.user_id === me.id) return true; diff --git a/frontend/src/i18n-keys.ts b/frontend/src/i18n-keys.ts index de511f0..4528ce9 100644 --- a/frontend/src/i18n-keys.ts +++ b/frontend/src/i18n-keys.ts @@ -2188,6 +2188,11 @@ export type I18nKey = | "projects.detail.parteien.role.defendant" | "projects.detail.parteien.role.thirdparty" | "projects.detail.save" + | "projects.detail.settings.archive.cta" + | "projects.detail.settings.archive.description" + | "projects.detail.settings.archive.heading" + | "projects.detail.settings.export.description" + | "projects.detail.settings.export.heading" | "projects.detail.smarttimeline.add.cancel" | "projects.detail.smarttimeline.add.choice.amend" | "projects.detail.smarttimeline.add.choice.appointment" @@ -2277,6 +2282,7 @@ export type I18nKey = | "projects.detail.tab.kinder" | "projects.detail.tab.notizen" | "projects.detail.tab.parteien" + | "projects.detail.tab.settings" | "projects.detail.tab.submissions" | "projects.detail.tab.team" | "projects.detail.tab.termine" diff --git a/frontend/src/projects-detail.tsx b/frontend/src/projects-detail.tsx index dd1ff70..74f07f6 100644 --- a/frontend/src/projects-detail.tsx +++ b/frontend/src/projects-detail.tsx @@ -89,20 +89,9 @@ export function renderProjectsDetail(): string { Notizen Checklisten Schriftsätze - {/* t-paliad-214 Slice 2 — project-subtree export button. - Sits at the end of the tab nav. Hidden by default; the - client unhides it after /api/me confirms the caller can - extract (responsibility ∈ {lead, member} OR global_admin). */} - + {/* Verwaltung — rare admin actions (export, archive). Sits + last in the tab list per t-paliad-245. */} + Verwaltung {/* History (Verlauf) — t-paliad-171 SmartTimeline Slice 1. @@ -666,6 +655,39 @@ export function renderProjectsDetail(): string { Schriftsätze werden direkt aus dem Projekt heraus als .docx generiert. Anpassen, drucken, einreichen.

+ + {/* Verwaltung — rare admin actions (export, archive). Each + sub-section hides itself if the caller is not entitled + (export: §4 gate; archive: global_admin). */} + {/* Full edit modal — same form as /projects/new, pre-filled. */} diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index f00ef82..d22c866 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -7291,6 +7291,20 @@ dialog.modal::backdrop { padding: 0.5rem 0 2rem; } +/* Verwaltung tab — rare admin actions (export, archive) live here as + stacked sections. No accent, no oversized buttons (t-paliad-245). */ +.settings-section { + margin-bottom: 2rem; +} + +.settings-section:last-child { + margin-bottom: 0; +} + +.settings-section .tool-subtitle { + margin-bottom: 0.75rem; +} + .entity-events { list-style: none; padding: 0;