feat(t-paliad-110): refactor Dashboard rails — drop Erledigt card, add Später + Termine rail

PR-4 of the Fristen+Termine unification, closing out t-paliad-110.

Fristen rail (was 5 cards):
- Erledigt card removed (status=completed stays reachable via the EventsPage
  filter dropdown — no card on the rail per the new model)
- Später card added (pending deadlines past Mon-week-after, click filters
  to /deadlines?status=later)
- 4+1 final shape: Überfällig (conditional alarm) · Heute · Diese Woche ·
  Nächste Woche · Später

Termine rail (new): 3 cards — Heute · Diese Woche · Später. No Überfällig
(past appointments aren't urgent), no Nächste Woche (low-value distinction
for appointments per the design rationale). Cards click through to
/appointments?status=… so users land in the matching EventsPage view.

Backend (DashboardService.loadSummary):
- DeadlineSummary.CompletedThisWeek dropped, .Later added
- AppointmentSummary added (Today / ThisWeek / Later)
- One CTE-based query computes both rails alongside MatterSummary; bucket
  cutoffs share computeDeadlineBucketBounds with /api/events/summary +
  /api/deadlines/summary so all three surfaces stay in lockstep

Frontend:
- dashboard.tsx: Erledigt card removed, Später card + Termine section added
- client/dashboard.ts: types updated, renderAppointmentSummary added
- 4 new i18n keys (DE+EN): dashboard.summary.later +
  dashboard.appointment_summary.heading
- CSS: .dashboard-card-later (muted blue) + 3 .dashboard-card-appt-* rules
  reusing the existing --bucket-* tokens

go build/vet/test ./... clean. bun run build clean (1396 keys).
This commit is contained in:
m
2026-05-04 13:52:49 +02:00
parent 50ac065c7d
commit 57237a55a3
6 changed files with 114 additions and 27 deletions

View File

@@ -14,7 +14,13 @@ interface DeadlineSummary {
today: number;
this_week: number;
next_week: number;
completed_this_week: number;
later: number;
}
interface AppointmentSummary {
today: number;
this_week: number;
later: number;
}
interface MatterSummary {
@@ -60,6 +66,7 @@ interface ActivityEntry {
interface DashboardData {
user: DashboardUser | null;
deadline_summary: DeadlineSummary;
appointment_summary: AppointmentSummary;
matter_summary: MatterSummary;
upcoming_deadlines: UpcomingDeadline[];
upcoming_appointments: UpcomingAppointment[];
@@ -98,6 +105,7 @@ function render(): void {
if (!data) return;
renderGreeting(data.user);
renderSummary(data.deadline_summary);
renderAppointmentSummary(data.appointment_summary);
renderMatters(data.matter_summary);
renderDeadlines(data.upcoming_deadlines);
renderAppointments(data.upcoming_appointments);
@@ -133,17 +141,23 @@ function renderSummary(s: DeadlineSummary): void {
setCount("dashboard-count-today", s.today);
setCount("dashboard-count-this-week", s.this_week);
setCount("dashboard-count-next-week", s.next_week);
setCount("dashboard-count-completed", s.completed_this_week);
setCount("dashboard-count-later", s.later);
// Überfällig is an emergency category — hide the card entirely on a clean
// slate (the .dashboard-summary-grid uses auto-fit so the row re-flows to
// 4 cards) and trip the alarm styling when there's anything overdue. See
// t-paliad-105 / t-paliad-106.
// t-paliad-105 / t-paliad-106 / t-paliad-110.
const overdueCard = document.getElementById("dashboard-card-overdue")!;
overdueCard.classList.toggle("dashboard-card-overdue-hidden", s.overdue === 0);
overdueCard.classList.toggle("dashboard-card-alarm", s.overdue > 0);
}
function renderAppointmentSummary(s: AppointmentSummary): void {
setCount("dashboard-count-appt-today", s.today);
setCount("dashboard-count-appt-this-week", s.this_week);
setCount("dashboard-count-appt-later", s.later);
}
function renderMatters(s: MatterSummary): void {
setCount("dashboard-matter-active", s.active);
setCount("dashboard-matter-archived", s.archived);

View File

@@ -667,6 +667,8 @@ const translations: Record<Lang, Record<string, string>> = {
"dashboard.summary.this_week": "Diese Woche",
"dashboard.summary.next_week": "N\u00e4chste Woche",
"dashboard.summary.completed": "Erledigt",
"dashboard.summary.later": "Später",
"dashboard.appointment_summary.heading": "Termine auf einen Blick",
"dashboard.matters.heading": "Meine Akten",
"dashboard.matters.active": "Aktiv",
"dashboard.matters.archived": "Archiviert",
@@ -2165,6 +2167,8 @@ const translations: Record<Lang, Record<string, string>> = {
"dashboard.summary.this_week": "This week",
"dashboard.summary.next_week": "Next week",
"dashboard.summary.completed": "Done",
"dashboard.summary.later": "Later",
"dashboard.appointment_summary.heading": "Appointments at a glance",
"dashboard.matters.heading": "My matters",
"dashboard.matters.active": "Active",
"dashboard.matters.archived": "Archived",

View File

@@ -57,7 +57,7 @@ export function renderDashboard(): string {
</p>
</div>
{/* Traffic-light deadline summary */}
{/* Traffic-light deadline summary (4+1: Überfällig conditional + 4 universal — t-paliad-110) */}
<section className="dashboard-summary" aria-labelledby="dashboard-summary-heading">
<h2 id="dashboard-summary-heading" className="dashboard-section-heading" data-i18n="dashboard.summary.heading">
Fristen auf einen Blick
@@ -79,9 +79,30 @@ export function renderDashboard(): string {
<div className="dashboard-card-count" id="dashboard-count-next-week">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.next_week">N&auml;chste Woche</div>
</a>
<a href="/deadlines?status=completed" className="dashboard-card dashboard-card-done" id="dashboard-card-completed">
<div className="dashboard-card-count" id="dashboard-count-completed">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.completed">Erledigt</div>
<a href="/deadlines?status=later" className="dashboard-card dashboard-card-later" id="dashboard-card-later">
<div className="dashboard-card-count" id="dashboard-count-later">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.later">Sp&auml;ter</div>
</a>
</div>
</section>
{/* Termine summary rail — 3 cards: Heute · Diese Woche · Später (t-paliad-110) */}
<section className="dashboard-summary" aria-labelledby="dashboard-appointment-summary-heading">
<h2 id="dashboard-appointment-summary-heading" className="dashboard-section-heading" data-i18n="dashboard.appointment_summary.heading">
Termine auf einen Blick
</h2>
<div className="dashboard-summary-grid">
<a href="/appointments?status=today" className="dashboard-card dashboard-card-appt-today" id="dashboard-card-appt-today">
<div className="dashboard-card-count" id="dashboard-count-appt-today">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.today">Heute</div>
</a>
<a href="/appointments?status=this_week" className="dashboard-card dashboard-card-appt-week" id="dashboard-card-appt-thisweek">
<div className="dashboard-card-count" id="dashboard-count-appt-this-week">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.this_week">Diese Woche</div>
</a>
<a href="/appointments?status=later" className="dashboard-card dashboard-card-appt-later" id="dashboard-card-appt-later">
<div className="dashboard-card-count" id="dashboard-count-appt-later">0</div>
<div className="dashboard-card-label" data-i18n="dashboard.summary.later">Sp&auml;ter</div>
</a>
</div>
</section>

View File

@@ -505,6 +505,7 @@ export type I18nKey =
| "dashboard.activity.event"
| "dashboard.activity.heading"
| "dashboard.activity.system"
| "dashboard.appointment_summary.heading"
| "dashboard.appointments.empty"
| "dashboard.appointments.heading"
| "dashboard.deadlines.empty"
@@ -517,6 +518,7 @@ export type I18nKey =
| "dashboard.onboarding"
| "dashboard.summary.completed"
| "dashboard.summary.heading"
| "dashboard.summary.later"
| "dashboard.summary.next_week"
| "dashboard.summary.overdue"
| "dashboard.summary.this_week"

View File

@@ -5702,6 +5702,17 @@ input[type="range"]::-moz-range-thumb {
.dashboard-card-green { border-left: 3px solid var(--bucket-next-week); }
.dashboard-card-done .dashboard-card-count { color: var(--bucket-done); }
.dashboard-card-done { border-left: 3px solid var(--bucket-done); }
/* Später bucket on the Fristen rail — muted blue (t-paliad-110). */
.dashboard-card-later .dashboard-card-count { color: var(--bucket-later); }
.dashboard-card-later { border-left: 3px solid var(--bucket-later); }
/* Termine rail uses the same urgency token family — keep the palette
consistent across rails so users read both at the same glance. */
.dashboard-card-appt-today .dashboard-card-count { color: var(--bucket-today); }
.dashboard-card-appt-today { border-left: 3px solid var(--bucket-today); }
.dashboard-card-appt-week .dashboard-card-count { color: var(--bucket-week); }
.dashboard-card-appt-week { border-left: 3px solid var(--bucket-week); }
.dashboard-card-appt-later .dashboard-card-count { color: var(--bucket-later); }
.dashboard-card-appt-later { border-left: 3px solid var(--bucket-later); }
/* Überfällig alarm — see t-paliad-105 / t-paliad-106.
When overdue > 0 the card flips from a calm border-left accent to a