fix(t-paliad-061): rename residue + small i18n cleanups (PR-D)

Per docs/audit-polish-2026-04-27.md PR-D batch:
- F-11 office labels on /projects/{id}/team — use t("office."+key) so
  "duesseldorf"/"munich" render as "Düsseldorf"/"München"
- F-17 "Lead" → "Leitung" in DE on the Rolle column and /projects/new
  subtitle (EN keeps "Lead")
- F-18 admin.team.permission.global_admin → "Globaler Admin" (DE) plus
  matching "globaler Admin" in last_admin error
- F-19 rename DOM IDs: projekt-type → project-type, projekt-view →
  project-view, akten-status → project-status (markup + all
  getElementById/$ callsites in client modules)
- F-26 Akte filter dropdown on /deadlines + /appointments → "Projekt"
  / "Alle Projekte" in DE (column headers stay for PR-A/F-12)
- F-44 admin card "Departments / Dezernate" → "Dezernate"
- F-45 "Dezernat / Partner" → "Dezernat oder Partner" on settings +
  onboarding profile fields

go build/vet/test clean; frontend bun run build clean.
This commit is contained in:
m
2026-04-27 19:28:05 +02:00
parent de2788c2d7
commit c9054ed753
11 changed files with 38 additions and 37 deletions

View File

@@ -23,7 +23,7 @@ const PLANNED: PlannedCard[] = [
icon: ICON_BUILDING,
i18nTitle: "admin.card.departments.title",
i18nDesc: "admin.card.departments.desc",
fallbackTitle: "Departments / Dezernate",
fallbackTitle: "Dezernate",
fallbackDesc: "Dezernate anlegen und Mitglieder verwalten.",
},
{

View File

@@ -72,9 +72,9 @@ export function renderAppointments(): string {
<option value="deadline_hearing" data-i18n="termine.type.deadline_hearing">Fristverhandlung</option>
</select>
<label className="akten-filter-label" htmlFor="appointment-filter-project" data-i18n="termine.filter.akte">Akte</label>
<label className="akten-filter-label" htmlFor="appointment-filter-project" data-i18n="termine.filter.akte">Projekt</label>
<select id="appointment-filter-project" className="akten-select">
<option value="" data-i18n="termine.filter.akte.all">Alle Akten &amp; pers&ouml;nlich</option>
<option value="" data-i18n="termine.filter.akte.all">Alle Projekte &amp; pers&ouml;nlich</option>
<option value="__personal__" data-i18n="termine.filter.akte.personal">Nur pers&ouml;nliche</option>
</select>

View File

@@ -526,14 +526,14 @@ const translations: Record<Lang, Record<string, string>> = {
"fristen.summary.upcoming": "Sp\u00e4ter",
"fristen.summary.completed": "Erledigt",
"fristen.filter.status": "Status",
"fristen.filter.akte": "Akte",
"fristen.filter.akte": "Projekt",
"fristen.filter.all": "Alle (offen & erledigt)",
"fristen.filter.pending": "Alle offenen",
"fristen.filter.overdue": "\u00dcberf\u00e4llig",
"fristen.filter.thisweek": "Diese Woche",
"fristen.filter.upcoming": "Sp\u00e4ter",
"fristen.filter.completed": "Erledigt",
"fristen.filter.akte.all": "Alle Akten",
"fristen.filter.akte.all": "Alle Projekte",
"fristen.col.due": "F\u00e4llig",
"fristen.col.title": "Titel",
"fristen.col.akte": "Akte",
@@ -743,7 +743,7 @@ const translations: Record<Lang, Record<string, string>> = {
"onboarding.office.placeholder": "Bitte ausw\u00e4hlen",
"onboarding.job_title": "Berufsbezeichnung",
"onboarding.job_title.placeholder": "z.B. Associate, Partner, PA",
"onboarding.dezernat": "Dezernat / Partner",
"onboarding.dezernat": "Dezernat oder Partner",
"onboarding.dezernat.placeholder": "z.B. Dr. M\u00fcller, Team Schmidt",
"onboarding.optional": "(optional)",
"onboarding.submit": "Profil anlegen",
@@ -866,7 +866,7 @@ const translations: Record<Lang, Record<string, string>> = {
"projekte.submit": "Projekt anlegen",
"projekte.neu.title": "Neues Projekt \u2014 Paliad",
"projekte.neu.heading": "Neues Projekt anlegen",
"projekte.neu.subtitle": "Mandant, Streitsache, Patent, Verfahren oder generisches Projekt \u2014 hierarchisch einordnen. Sichtbarkeit folgt dem Team (Sie werden als \u201eLead\u201c automatisch hinzugef\u00fcgt).",
"projekte.neu.subtitle": "Mandant, Streitsache, Patent, Verfahren oder generisches Projekt \u2014 hierarchisch einordnen. Sichtbarkeit folgt dem Team (Sie werden als \u201eLeitung\u201c automatisch hinzugef\u00fcgt).",
"projekte.field.type": "Typ",
"projekte.field.parent": "\u00dcbergeordnetes Projekt",
"projekte.field.parent.placeholder": "Titel eingeben, um ein \u00dcberprojekt zu suchen...",
@@ -955,7 +955,7 @@ const translations: Record<Lang, Record<string, string>> = {
"projekte.type.patent": "Patent",
"projekte.type.case": "Verfahren",
"projekte.type.project": "Projekt",
"projekte.team.role.lead": "Lead",
"projekte.team.role.lead": "Leitung",
"projekte.team.role.associate": "Associate",
"projekte.team.role.pa": "PA",
"projekte.team.role.of_counsel": "Of Counsel",
@@ -983,7 +983,7 @@ const translations: Record<Lang, Record<string, string>> = {
"einstellungen.profil.office": "B\u00fcro",
"einstellungen.profil.job_title": "Berufsbezeichnung",
"einstellungen.profil.job_title.placeholder": "z.B. Associate, Partner, PA",
"einstellungen.profil.dezernat": "Dezernat / Partner",
"einstellungen.profil.dezernat": "Dezernat oder Partner",
"einstellungen.profil.dezernat.placeholder": "z.B. Dr. M\u00fcller, Team Schmidt",
"einstellungen.profil.lang": "Sprache",
"einstellungen.profil.lang.de": "Deutsch",
@@ -1029,8 +1029,8 @@ const translations: Record<Lang, Record<string, string>> = {
"termine.summary.later": "Sp\u00e4ter",
"termine.filter.type": "Typ",
"termine.filter.type.all": "Alle Typen",
"termine.filter.akte": "Akte",
"termine.filter.akte.all": "Alle Akten & pers\u00f6nlich",
"termine.filter.akte": "Projekt",
"termine.filter.akte.all": "Alle Projekte & pers\u00f6nlich",
"termine.filter.akte.personal": "Nur pers\u00f6nliche",
"termine.filter.from": "Von",
"termine.filter.to": "Bis",
@@ -1196,7 +1196,7 @@ const translations: Record<Lang, Record<string, string>> = {
"admin.coming_soon": "Kommt bald",
"admin.card.team.title": "Team-Verwaltung",
"admin.card.team.desc": "Benutzer:innen anlegen, bearbeiten, löschen.",
"admin.card.departments.title": "Departments / Dezernate",
"admin.card.departments.title": "Dezernate",
"admin.card.departments.desc": "Dezernate anlegen und Mitglieder verwalten.",
"admin.card.audit.title": "Audit-Log",
"admin.card.audit.desc": "Wer hat wann was geändert? Nachvollziehbarkeit für sicherheitsrelevante Aktionen.",
@@ -1241,8 +1241,8 @@ const translations: Record<Lang, Record<string, string>> = {
"admin.team.direct_add.job_title": "Berufsbezeichnung",
"admin.team.direct_add.dezernat": "Dezernat (optional)",
"admin.team.permission.standard": "Standard",
"admin.team.permission.global_admin": "Global Admin",
"admin.team.permission.last_admin": "Der letzte Global Admin kann nicht degradiert werden.",
"admin.team.permission.global_admin": "Globaler Admin",
"admin.team.permission.last_admin": "Der letzte globale Admin kann nicht degradiert werden.",
"admin.team.direct_add.cancel": "Abbrechen",
"admin.team.direct_add.submit": "Anlegen",
@@ -2435,7 +2435,7 @@ const translations: Record<Lang, Record<string, string>> = {
"admin.coming_soon": "Coming soon",
"admin.card.team.title": "Team Management",
"admin.card.team.desc": "Create, edit and delete user accounts.",
"admin.card.departments.title": "Departments / Dezernate",
"admin.card.departments.title": "Dezernate",
"admin.card.departments.desc": "Create departments and manage their members.",
"admin.card.audit.title": "Audit Log",
"admin.card.audit.desc": "Who changed what, and when. Traceability for security-relevant actions.",

View File

@@ -110,10 +110,10 @@ export function initParentPicker() {
});
}
// wireTypeChange wires the <select id="projekt-type"> change handler and runs
// wireTypeChange wires the <select id="project-type"> change handler and runs
// the visibility pass once with the current value.
export function wireTypeChange() {
const typeSel = $("projekt-type") as HTMLSelectElement;
const typeSel = $("project-type") as HTMLSelectElement;
showFieldsForType(typeSel.value);
typeSel.addEventListener("change", () => showFieldsForType(typeSel.value));
}
@@ -131,7 +131,7 @@ export function readPayload(
msg: HTMLElement,
opts: { omitEmpty: boolean; mode: "create" | "edit" },
): Record<string, unknown> | null {
const type = ($("projekt-type") as HTMLSelectElement).value;
const type = ($("project-type") as HTMLSelectElement).value;
const title = ($("project-title") as HTMLInputElement).value.trim();
if (!title) {
msg.textContent = t("projekte.error.title_required") || "Title required";
@@ -198,7 +198,7 @@ export function prefillForm(p: Record<string, unknown>) {
const getTA = (id: string) => $(id) as HTMLTextAreaElement;
const type = String(p.type ?? "project");
getSel("projekt-type").value = type;
getSel("project-type").value = type;
showFieldsForType(type);
get("project-title").value = String(p.title ?? "");

View File

@@ -774,7 +774,7 @@ function openEditModal() {
// Type changes are allowed (t-paliad-056). Wire the warning that lists
// which fields will be NULL'd server-side when the user picks a new
// type.
const typeSel = document.getElementById("projekt-type") as HTMLSelectElement | null;
const typeSel = document.getElementById("project-type") as HTMLSelectElement | null;
if (typeSel) {
typeSel.disabled = false;
typeSel.onchange = () => {
@@ -817,7 +817,7 @@ const TYPE_SPECIFIC_FIELDS: Record<string, { key: string; i18n: string }[]> = {
function renderTypeChangeWarning() {
const wrap = document.getElementById("project-edit-type-warning") as HTMLDivElement | null;
const fieldsSpan = document.getElementById("project-edit-type-warning-fields") as HTMLSpanElement | null;
const typeSel = document.getElementById("projekt-type") as HTMLSelectElement | null;
const typeSel = document.getElementById("project-type") as HTMLSelectElement | null;
if (!wrap || !fieldsSpan || !typeSel || !project) return;
const newType = typeSel.value;
@@ -1238,9 +1238,10 @@ function renderTeam() {
!m.inherited && canRemoveTeamMember(m)
? `<button type="button" class="btn-ghost btn-small team-remove-btn" data-user-id="${esc(m.user_id)}">${esc(t("projekte.detail.team.remove") || "Entfernen")}</button>`
: "";
const officeLabel = m.user_office ? t("office." + m.user_office) || m.user_office : "";
return `<tr>
<td><strong>${esc(m.user_display_name || m.user_email)}</strong>
<span class="form-hint">&middot; ${esc(m.user_email)}${m.user_office ? " &middot; " + esc(m.user_office) : ""}</span></td>
<span class="form-hint">&middot; ${esc(m.user_email)}${officeLabel ? " &middot; " + esc(officeLabel) : ""}</span></td>
<td><span class="projekt-team-role">${esc(roleLabel)}</span></td>
<td>${source}</td>
<td>${removeBtn}</td>

View File

@@ -60,7 +60,7 @@ async function applyParentFromQueryString() {
($("projekt-parent-id") as HTMLInputElement).value = p.id;
($("projekt-parent-input") as HTMLInputElement).value = p.title;
// Default to 'case' under a non-root parent; user can override.
const typeSel = $("projekt-type") as HTMLSelectElement;
const typeSel = $("project-type") as HTMLSelectElement;
if (typeSel.value === "client") {
typeSel.value = "case";
showFieldsForType(typeSel.value);

View File

@@ -176,9 +176,9 @@ function initSearch() {
}
function initFilters() {
const typeSel = document.getElementById("projekt-type") as HTMLSelectElement;
const status = document.getElementById("akten-status") as HTMLSelectElement;
const view = document.getElementById("projekt-view") as HTMLSelectElement;
const typeSel = document.getElementById("project-type") as HTMLSelectElement;
const status = document.getElementById("project-status") as HTMLSelectElement;
const view = document.getElementById("project-view") as HTMLSelectElement;
typeSel.addEventListener("change", () => {
typeFilter = typeSel.value;
render();

View File

@@ -13,8 +13,8 @@ export function ProjectFormFields(): string {
return (
<div className="project-form-fields">
<div className="form-field">
<label htmlFor="projekt-type" data-i18n="projekte.field.type">Typ</label>
<select id="projekt-type" required>
<label htmlFor="project-type" data-i18n="projekte.field.type">Typ</label>
<select id="project-type" required>
<option value="client" data-i18n="projekte.type.client">Mandant (Wurzel)</option>
<option value="litigation" data-i18n="projekte.type.litigation">Streitsache</option>
<option value="patent" data-i18n="projekte.type.patent">Patent</option>

View File

@@ -78,9 +78,9 @@ export function renderDeadlines(): string {
<option value="completed" data-i18n="fristen.filter.completed">Erledigt</option>
</select>
<label className="akten-filter-label" htmlFor="deadline-filter-project" data-i18n="fristen.filter.akte">Akte</label>
<label className="akten-filter-label" htmlFor="deadline-filter-project" data-i18n="fristen.filter.akte">Projekt</label>
<select id="deadline-filter-project" className="akten-select">
<option value="" data-i18n="fristen.filter.akte.all">Alle Akten</option>
<option value="" data-i18n="fristen.filter.akte.all">Alle Projekte</option>
</select>
</div>
</div>

View File

@@ -58,8 +58,8 @@ export function renderProjects(): string {
</div>
<div className="akten-filter-row">
<label className="akten-filter-label" htmlFor="projekt-type" data-i18n="projekte.filter.type">Typ</label>
<select id="projekt-type" className="akten-select">
<label className="akten-filter-label" htmlFor="project-type" data-i18n="projekte.filter.type">Typ</label>
<select id="project-type" className="akten-select">
<option value="" data-i18n="projekte.filter.type.all">Alle Typen</option>
<option value="client" data-i18n="projekte.type.client">Mandant</option>
<option value="litigation" data-i18n="projekte.type.litigation">Streitsache</option>
@@ -68,16 +68,16 @@ export function renderProjects(): string {
<option value="project" data-i18n="projekte.type.project">Projekt</option>
</select>
<label className="akten-filter-label" htmlFor="akten-status" data-i18n="projekte.filter.status">Status</label>
<select id="akten-status" className="akten-select">
<label className="akten-filter-label" htmlFor="project-status" data-i18n="projekte.filter.status">Status</label>
<select id="project-status" className="akten-select">
<option value="" data-i18n="projekte.filter.status.all">Alle Status</option>
<option value="active" data-i18n="projekte.filter.status.active">Aktiv</option>
<option value="archived" data-i18n="projekte.filter.status.archived">Archiviert</option>
<option value="closed" data-i18n="projekte.filter.status.closed">Abgeschlossen</option>
</select>
<label className="akten-filter-label" htmlFor="projekt-view" data-i18n="projekte.filter.view">Ansicht</label>
<select id="projekt-view" className="akten-select">
<label className="akten-filter-label" htmlFor="project-view" data-i18n="projekte.filter.view">Ansicht</label>
<select id="project-view" className="akten-select">
<option value="flat" data-i18n="projekte.view.flat">Flache Liste</option>
<option value="tree" data-i18n="projekte.view.tree">Baumansicht</option>
<option value="roots" data-i18n="projekte.view.roots">Nur Wurzeln</option>

View File

@@ -102,7 +102,7 @@ export function renderSettings(): string {
<div className="form-field">
<label htmlFor="profil-dezernat" data-i18n="einstellungen.profil.dezernat">
Dezernat / Partner <span className="login-label-optional" data-i18n="einstellungen.optional">(optional)</span>
Dezernat oder Partner <span className="login-label-optional" data-i18n="einstellungen.optional">(optional)</span>
</label>
<input
type="text"