Root cause of the URGENT bug: parseAkteID() in
frontend/src/client/projects-detail.ts only accepted /projekte/{id} and
/akten/{id} URL prefixes. After t-paliad-025 renamed pages to /projects/{id},
parts[0] === "projects" failed both checks → null id → notfound branch
fired before any /api/projects/{id} fetch. The 200 from curl was real;
the page just never asked.
Fix: parseProjectID() now reads /projects/{id}. Old bookmark tab slugs
(verlauf, parteien, fristen, …) are mapped to their English successors so
deep links don't silently fall back to the default tab.
Bundled cleanup — every per-project subpath the client TS still hit was a
404 because the rename only touched top-level routes. Lockstep rename of
URLs, function names, DOM IDs, and the TabId union in projects-detail.ts
+ projects-detail.tsx:
- /api/projects/{id}/parteien|fristen|termine|notizen|checklisten →
/parties|deadlines|appointments|notes|checklists
- loadParteien/loadFristen/loadTermine/loadAkte/parseAkteID →
loadParties/loadDeadlines/loadAppointments/loadProject/parseProjectID
(the old loadParteien/loadFristen/loadTermine bodies even assigned to
undeclared `parteien`/`fristen`/`termine` — would have thrown
ReferenceError as soon as the catch branch ran)
- DOM IDs: akten-detail-* → project-detail-*, parteien-* → parties-*,
partei-* → party-*, project-fristen-* → project-deadlines-*,
project-termin(e)-* → project-appointment(s)-*,
project-checklisten-* → project-checklists-*, akten-events-* →
project-events-*, kinder-* → children-*, projekt-breadcrumb →
project-breadcrumb, frist-add-link → deadline-add-link,
termin-add-btn → appointment-add-btn
- Tab slugs in URL + data-tab + tab-* IDs: verlauf/kinder/parteien/
fristen/termine/notizen/checklisten →
history/children/parties/deadlines/appointments/notes/checklists
- frist-add-link href: /projects/{id}/fristen/neu →
/projects/{id}/deadlines/new
Sweep across the rest of frontend/src/client/:
- notes.ts: NotizParentType → NotesParentType, "frist"/"termin" →
"deadline"/"appointment", baseURL paths /…/notizen → /…/notes; updated
callers in deadlines-detail.ts and appointments-detail.ts.
- deadlines-new.ts: undeclared `akten` reference (loadAkten was assigning
to a never-declared name) replaced with `projects`; URL /…/fristen →
/…/deadlines; path-parsing of /akten/{id}/fristen/neu rewritten as
/projects/{id}/deadlines/new; preselectedAkteID → preselectedProjectID;
Project.aktenzeichen field (no longer emitted by API) → reference.
- fristenrechner.ts: bulk endpoint /…/fristen/bulk → /…/deadlines/bulk;
request body { fristen } → { deadlines } (server expects "deadlines"
key); ProjectOption interface now uses reference instead of
aktenzeichen.
- deadlines.ts, appointments.ts, deadlines-detail.ts, appointments-detail.ts,
checklists-detail.ts, appointments-new.ts: Project interface field
aktenzeichen → reference (the API returns "reference"; the old field
rendered as undefined in select options and detail headers).
i18n key strings (akten.detail.*, projekte.*, fristen.*, termine.*,
checklisten.*, notizen.*) intentionally kept in German per the
t-paliad-025 convention. CSS class names (frist-row, akten-table-wrap,
termin-dot, etc.) untouched — separate stylistic cleanup.
Verified: go build/vet/test clean, bun run build clean, dist HTML +
bundled JS contain only the new English IDs (remaining German strings
are i18n keys).
361 lines
21 KiB
TypeScript
361 lines
21 KiB
TypeScript
import { h } from "./jsx";
|
|
import { Sidebar } from "./components/Sidebar";
|
|
import { Footer } from "./components/Footer";
|
|
|
|
// Project detail shell (v2). DOM IDs use the English `project-*` /
|
|
// `parties-*` / `deadlines-*` / `appointments-*` / `notes-*` / `checklists-*`
|
|
// naming. The client TS in client/projects-detail.ts queries these IDs in
|
|
// lockstep — keep names in sync.
|
|
export function renderProjectsDetail(): string {
|
|
return "<!DOCTYPE html>" + (
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title data-i18n="projekte.detail.title">Projekt — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar">
|
|
<Sidebar currentPath="/projects" />
|
|
|
|
<main>
|
|
<section className="tool-page">
|
|
<div className="container">
|
|
<a href="/projects" className="akten-back-link" data-i18n="projekte.detail.back">← Zurück zur Übersicht</a>
|
|
|
|
<nav className="projekt-breadcrumb" id="project-breadcrumb" aria-label="Breadcrumb" />
|
|
|
|
<div id="project-detail-loading" className="akten-loading">
|
|
<p data-i18n="projekte.detail.loading">Lädt…</p>
|
|
</div>
|
|
|
|
<div id="project-detail-notfound" className="akten-empty" style="display:none">
|
|
<p data-i18n="projekte.detail.notfound">Projekt nicht gefunden oder keine Berechtigung.</p>
|
|
</div>
|
|
|
|
<div id="project-detail-body" style="display:none">
|
|
<header className="akten-detail-header">
|
|
<div className="akten-detail-title-row">
|
|
<div className="akten-detail-title-col">
|
|
<h1 id="project-title-display" />
|
|
<input type="text" id="project-title-edit" className="akten-title-input" style="display:none" />
|
|
<div className="akten-detail-meta">
|
|
<span id="project-type-chip" className="akten-type-chip" />
|
|
<span className="akten-ref" id="project-ref-display" />
|
|
<span id="project-clientmatter" className="akten-ref" />
|
|
<span id="project-status-chip" className="akten-status-chip" />
|
|
<a id="project-netdocs" className="akten-netdocs-link" target="_blank" rel="noopener" style="display:none">netDocuments ↗</a>
|
|
</div>
|
|
</div>
|
|
<div className="akten-detail-actions">
|
|
<button id="project-edit-btn" className="btn-icon" type="button" aria-label="Bearbeiten" data-i18n-title="projekte.detail.edit" title="Bearbeiten">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
</svg>
|
|
</button>
|
|
<button id="project-save-btn" className="btn-primary btn-cta-lime" type="button" style="display:none" data-i18n="projekte.detail.save">Speichern</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="akten-detail-description">
|
|
<h3 data-i18n="projekte.detail.description.heading">Notizen</h3>
|
|
<p id="project-description-display" className="akten-detail-description-text" />
|
|
<textarea id="project-description-edit" className="akten-detail-description-input" rows={4} style="display:none" />
|
|
</div>
|
|
|
|
<nav className="akten-tabs" id="project-tabs">
|
|
<a className="akten-tab" data-tab="history" href="#" data-i18n="projekte.detail.tab.verlauf">Verlauf</a>
|
|
<a className="akten-tab" data-tab="team" href="#" data-i18n="projekte.detail.tab.team">Team</a>
|
|
<a className="akten-tab" data-tab="children" href="#" data-i18n="projekte.detail.tab.kinder">Untergeordnet</a>
|
|
<a className="akten-tab" data-tab="parties" href="#" data-i18n="projekte.detail.tab.parteien">Parteien</a>
|
|
<a className="akten-tab" data-tab="deadlines" href="#" data-i18n="projekte.detail.tab.fristen">Fristen</a>
|
|
<a className="akten-tab" data-tab="appointments" href="#" data-i18n="projekte.detail.tab.termine">Termine</a>
|
|
<a className="akten-tab" data-tab="notes" href="#" data-i18n="projekte.detail.tab.notizen">Notizen</a>
|
|
<a className="akten-tab" data-tab="checklists" href="#" data-i18n="projekte.detail.tab.checklisten">Checklisten</a>
|
|
</nav>
|
|
|
|
{/* History (Verlauf) */}
|
|
<section className="akten-tab-panel" id="tab-history">
|
|
<ul className="akten-events" id="project-events-list" />
|
|
<p className="akten-events-empty" id="project-events-empty" style="display:none" data-i18n="projekte.detail.verlauf.empty">
|
|
Noch keine Ereignisse aufgezeichnet.
|
|
</p>
|
|
<div className="akten-events-loadmore" id="project-events-loadmore-wrap" style="display:none">
|
|
<button type="button" className="btn-secondary" id="project-events-loadmore" data-i18n="projekte.detail.verlauf.loadMore">
|
|
Mehr laden
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Team */}
|
|
<section className="akten-tab-panel" id="tab-team" style="display:none">
|
|
<div className="akten-parteien-controls">
|
|
<button id="team-add-btn" className="btn-primary btn-cta-lime btn-small" type="button" data-i18n="projekte.detail.team.add">
|
|
Mitglied hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
<form id="team-form" className="akten-form akten-partei-form" style="display:none" autocomplete="off">
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="team-user-input" data-i18n="projekte.detail.team.form.user">Benutzer</label>
|
|
<input type="text" id="team-user-input" placeholder="Name oder E-Mail..." autocomplete="off" />
|
|
<input type="hidden" id="team-user-id" />
|
|
<div id="team-user-suggestions" className="akten-collab-suggestions" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="team-role" data-i18n="projekte.detail.team.form.role">Rolle</label>
|
|
<select id="team-role">
|
|
<option value="lead" data-i18n="projekte.team.role.lead">Lead</option>
|
|
<option value="associate" selected data-i18n="projekte.team.role.associate">Associate</option>
|
|
<option value="pa" data-i18n="projekte.team.role.pa">PA</option>
|
|
<option value="of_counsel" data-i18n="projekte.team.role.of_counsel">Of Counsel</option>
|
|
<option value="local_counsel" data-i18n="projekte.team.role.local_counsel">Local Counsel</option>
|
|
<option value="expert" data-i18n="projekte.team.role.expert">Experte</option>
|
|
<option value="observer" data-i18n="projekte.team.role.observer">Beobachter</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="team-cancel" data-i18n="projekte.detail.team.form.cancel">Abbrechen</button>
|
|
<button type="submit" className="btn-primary btn-cta-lime" data-i18n="projekte.detail.team.form.submit">Hinzufügen</button>
|
|
</div>
|
|
<p className="form-msg" id="team-msg" />
|
|
</form>
|
|
|
|
<table className="akten-parteien-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="projekte.detail.team.col.name">Name</th>
|
|
<th data-i18n="projekte.detail.team.col.role">Rolle</th>
|
|
<th data-i18n="projekte.detail.team.col.source">Herkunft</th>
|
|
<th />
|
|
</tr>
|
|
</thead>
|
|
<tbody id="team-body" />
|
|
</table>
|
|
|
|
<p className="akten-events-empty" id="team-empty" style="display:none" data-i18n="projekte.detail.team.empty">
|
|
Noch keine Teammitglieder.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Children (Untergeordnet) */}
|
|
<section className="akten-tab-panel" id="tab-children" style="display:none">
|
|
<div className="akten-parteien-controls">
|
|
<a id="child-add-link" className="btn-primary btn-cta-lime btn-small" href="/projects/new" data-i18n="projekte.detail.kinder.add">
|
|
Untervorhaben anlegen
|
|
</a>
|
|
</div>
|
|
<ul id="children-list" className="projekt-children-list" />
|
|
<p className="akten-events-empty" id="children-empty" style="display:none" data-i18n="projekte.detail.kinder.empty">
|
|
Keine untergeordneten Projekte.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Parties (Parteien) */}
|
|
<section className="akten-tab-panel" id="tab-parties" style="display:none">
|
|
<div className="akten-parteien-controls">
|
|
<button id="party-add-btn" className="btn-primary btn-cta-lime btn-small" type="button" data-i18n="projekte.detail.parteien.add">
|
|
Partei hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
<form id="party-form" className="akten-form akten-partei-form" style="display:none" autocomplete="off">
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="party-name" data-i18n="projekte.detail.parteien.form.name">Name</label>
|
|
<input type="text" id="party-name" required />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="party-role" data-i18n="projekte.detail.parteien.form.role">Rolle</label>
|
|
<select id="party-role">
|
|
<option value="claimant" data-i18n="projekte.detail.parteien.role.claimant">Kläger</option>
|
|
<option value="defendant" data-i18n="projekte.detail.parteien.role.defendant">Beklagter</option>
|
|
<option value="thirdparty" data-i18n="projekte.detail.parteien.role.thirdparty">Streitverkündeter / Drittpartei</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="party-rep" data-i18n="projekte.detail.parteien.form.rep">Vertreter (optional)</label>
|
|
<input type="text" id="party-rep" />
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="party-cancel" data-i18n="projekte.detail.parteien.form.cancel">Abbrechen</button>
|
|
<button type="submit" className="btn-primary btn-cta-lime" data-i18n="projekte.detail.parteien.form.submit">Hinzufügen</button>
|
|
</div>
|
|
<p className="form-msg" id="party-msg" />
|
|
</form>
|
|
|
|
<table className="akten-parteien-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="projekte.detail.parteien.col.name">Name</th>
|
|
<th data-i18n="projekte.detail.parteien.col.role">Rolle</th>
|
|
<th data-i18n="projekte.detail.parteien.col.rep">Vertreter</th>
|
|
<th />
|
|
</tr>
|
|
</thead>
|
|
<tbody id="parties-body" />
|
|
</table>
|
|
|
|
<p className="akten-events-empty" id="parties-empty" style="display:none" data-i18n="projekte.detail.parteien.empty">
|
|
Noch keine Parteien eingetragen.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Deadlines (Fristen) */}
|
|
<section className="akten-tab-panel" id="tab-deadlines" style="display:none">
|
|
<div className="akten-parteien-controls">
|
|
<a id="deadline-add-link" className="btn-primary btn-cta-lime btn-small" data-i18n="projekte.detail.fristen.add" href="#">
|
|
Frist hinzufügen
|
|
</a>
|
|
</div>
|
|
<div className="akten-table-wrap" id="project-deadlines-tablewrap">
|
|
<table className="akten-table fristen-table">
|
|
<thead>
|
|
<tr>
|
|
<th />
|
|
<th data-i18n="fristen.col.due">Fällig</th>
|
|
<th data-i18n="fristen.col.title">Titel</th>
|
|
<th data-i18n="fristen.col.rule">Regel</th>
|
|
<th data-i18n="fristen.col.status">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="project-deadlines-body" />
|
|
</table>
|
|
</div>
|
|
<p className="akten-events-empty" id="project-deadlines-empty" style="display:none" data-i18n="projekte.detail.fristen.empty">
|
|
Für dieses Projekt sind noch keine Fristen erfasst.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Appointments (Termine) */}
|
|
<section className="akten-tab-panel" id="tab-appointments" style="display:none">
|
|
<div className="akten-parteien-controls">
|
|
<button type="button" id="appointment-add-btn" className="btn-primary btn-cta-lime btn-small" data-i18n="projekte.detail.termine.add">
|
|
Termin hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
<form id="project-appointment-form" className="akten-partei-form" style="display:none">
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-appointment-title" data-i18n="termine.field.title">Titel</label>
|
|
<input type="text" id="project-appointment-title" required />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-appointment-type" data-i18n="termine.field.type">Typ</label>
|
|
<select id="project-appointment-type">
|
|
<option value="" data-i18n="termine.field.type.none">Kein Typ</option>
|
|
<option value="hearing" data-i18n="termine.type.hearing">Verhandlung</option>
|
|
<option value="meeting" data-i18n="termine.type.meeting">Besprechung</option>
|
|
<option value="consultation" data-i18n="termine.type.consultation">Beratung</option>
|
|
<option value="deadline_hearing" data-i18n="termine.type.deadline_hearing">Fristverhandlung</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-appointment-start" data-i18n="termine.field.start">Beginn</label>
|
|
<input type="datetime-local" id="project-appointment-start" required />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-appointment-end" data-i18n="termine.field.end">Ende (optional)</label>
|
|
<input type="datetime-local" id="project-appointment-end" />
|
|
</div>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-appointment-location" data-i18n="termine.field.location">Ort</label>
|
|
<input type="text" id="project-appointment-location" />
|
|
</div>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="project-appointment-cancel" data-i18n="projekte.detail.termine.form.cancel">Abbrechen</button>
|
|
<button type="submit" className="btn-primary btn-cta-lime" data-i18n="projekte.detail.termine.form.submit">Hinzufügen</button>
|
|
</div>
|
|
<p className="form-msg" id="project-appointment-msg" />
|
|
</form>
|
|
|
|
<div className="akten-table-wrap" id="project-appointments-tablewrap">
|
|
<table className="akten-table fristen-table">
|
|
<thead>
|
|
<tr>
|
|
<th />
|
|
<th data-i18n="termine.col.start">Beginn</th>
|
|
<th data-i18n="termine.col.title">Titel</th>
|
|
<th data-i18n="termine.col.location">Ort</th>
|
|
<th data-i18n="termine.col.type">Typ</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="project-appointments-body" />
|
|
</table>
|
|
</div>
|
|
<p className="akten-events-empty" id="project-appointments-empty" style="display:none" data-i18n="projekte.detail.termine.empty">
|
|
Für dieses Projekt sind noch keine Termine erfasst.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Notes (Notizen) */}
|
|
<section className="akten-tab-panel" id="tab-notes" style="display:none">
|
|
<div id="notes-container" className="notiz-container" data-parent-type="project" />
|
|
</section>
|
|
|
|
{/* Checklists (Checklisten) */}
|
|
<section className="akten-tab-panel" id="tab-checklists" style="display:none">
|
|
<p id="project-checklists-empty" className="akten-events-empty" style="display:none" data-i18n="projekte.detail.checklisten.empty">
|
|
Für dieses Projekt sind noch keine Checklisten-Instanzen erfasst.
|
|
</p>
|
|
<div className="akten-table-wrap" id="project-checklists-tablewrap" style="display:none">
|
|
<table className="akten-table">
|
|
<thead>
|
|
<tr>
|
|
<th data-i18n="projekte.detail.checklisten.col.template">Vorlage</th>
|
|
<th data-i18n="projekte.detail.checklisten.col.name">Name</th>
|
|
<th data-i18n="projekte.detail.checklisten.col.progress">Fortschritt</th>
|
|
<th data-i18n="projekte.detail.checklisten.col.created">Angelegt</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="project-checklists-body" />
|
|
</table>
|
|
</div>
|
|
<p className="tool-subtitle akten-checklisten-hint" data-i18n="projekte.detail.checklisten.hint">
|
|
Instanzen werden auf der Vorlagen-Seite unter <a href="/checklists">Checklisten</a> angelegt.
|
|
</p>
|
|
</section>
|
|
|
|
<div className="akten-detail-footer" id="project-delete-wrap" style="display:none">
|
|
<button id="project-delete-btn" className="btn-danger" type="button" data-i18n="projekte.detail.delete">
|
|
Projekt archivieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Delete confirmation modal */}
|
|
<div className="modal-overlay" id="delete-modal" style="display:none">
|
|
<div className="modal-card">
|
|
<div className="modal-header">
|
|
<h2 data-i18n="projekte.detail.delete.confirm.title">Projekt wirklich archivieren?</h2>
|
|
<button className="modal-close" id="delete-modal-close" type="button">×</button>
|
|
</div>
|
|
<p data-i18n="projekte.detail.delete.confirm.body">
|
|
Das Projekt wird archiviert. Es kann nicht direkt wiederhergestellt werden.
|
|
</p>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="delete-modal-cancel" data-i18n="projekte.detail.delete.confirm.cancel">Abbrechen</button>
|
|
<button type="button" className="btn-danger" id="delete-modal-confirm" data-i18n="projekte.detail.delete.confirm.ok">Archivieren</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
<script src="/assets/projects-detail.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|