diff --git a/frontend/src/client/agenda.ts b/frontend/src/client/agenda.ts index 0f02bd0..722716e 100644 --- a/frontend/src/client/agenda.ts +++ b/frontend/src/client/agenda.ts @@ -207,18 +207,35 @@ function groupByDay(items: AgendaItem[]): DayBucket[] { } function renderDay(bucket: DayBucket): string { + const expected = expectedUrgency(bucket.day); return `

${esc(relativeDayLabel(bucket.day))} ${esc(fullDateLabel(bucket.day))}

`; } -function renderItem(it: AgendaItem): string { +// F-32: an item's urgency tag duplicates the day-bucket heading in the +// common case (a "Heute" item under HEUTE, a "Diese Woche" item under "in 3 +// Tagen"). The tag stays only when it disagrees with the bucket — e.g. an +// "Überfällig" deadline that lands in today's bucket because of a filter +// quirk. expectedUrgency mirrors the server's bucketing rule against the +// bucket's day. +function expectedUrgency(day: Date): Urgency { + const today = startOfToday(); + const diff = Math.round((day.getTime() - today.getTime()) / 86400000); + if (diff < 0) return "overdue"; + if (diff === 0) return "today"; + if (diff === 1) return "tomorrow"; + if (diff <= 6) return "this_week"; + return "later"; +} + +function renderItem(it: AgendaItem, bucketUrgency: Urgency): string { const urgencyClass = `agenda-item-${it.urgency}`; const typeClass = `agenda-item-type-${it.type}`; const iconHTML = it.type === "deadline" ? deadlineIcon() : appointmentIcon(); @@ -230,7 +247,9 @@ function renderItem(it: AgendaItem): string { const timePart = it.type === "appointment" ? `${esc(formatAppointmentTime(it))}` : ""; - const urgencyTag = `${esc(t(`agenda.urgency.${it.urgency}`))}`; + const urgencyTag = it.urgency !== bucketUrgency + ? `${esc(t(`agenda.urgency.${it.urgency}`))}` + : ""; const locationPart = it.type === "appointment" && it.location ? `${esc(it.location)}` : ""; diff --git a/frontend/src/client/bottom-nav.ts b/frontend/src/client/bottom-nav.ts index 5545792..7e874af 100644 --- a/frontend/src/client/bottom-nav.ts +++ b/frontend/src/client/bottom-nav.ts @@ -1,4 +1,5 @@ import { toggleMobileSidebar } from "./sidebar"; +import { t } from "./i18n"; const KEYBOARD_THRESHOLD_PX = 100; const BADGE_REFRESH_MS = 60_000; @@ -99,11 +100,22 @@ function initAgendaBadge(): void { if (total <= 0) { badge!.style.display = "none"; badge!.classList.remove("bottom-nav-badge-overdue"); + badge!.removeAttribute("title"); + badge!.removeAttribute("aria-label"); return; } badge!.textContent = total > 9 ? "9+" : String(total); badge!.style.display = ""; badge!.classList.toggle("bottom-nav-badge-overdue", overdue > 0); + // F-38: the badge counts "actionable" items only — overdue + due + // today. The accessible label spells that out so the "2" never + // reads as ambiguous (e.g. "2 things this week"). + const label = t("bottomnav.badge.deadlines") + .replace("{overdue}", String(overdue)) + .replace("{today}", String(today)); + badge!.setAttribute("title", label); + badge!.setAttribute("aria-label", label); + badge!.setAttribute("aria-hidden", "false"); }) .catch(() => { // Badge is decorative; never break the page. diff --git a/frontend/src/client/deadlines.ts b/frontend/src/client/deadlines.ts index 36a3948..2c62b3a 100644 --- a/frontend/src/client/deadlines.ts +++ b/frontend/src/client/deadlines.ts @@ -209,11 +209,18 @@ function render() { ${esc(f.project_title)} ${ruleLabel} - ${esc(statusLabel)} + ${esc(statusLabel)} `; }) .join(""); + // F-23: when every visible row carries the same status, hide the column. + // The class is dropped as soon as the user widens the filter and variety + // reappears, so the header is never permanently removed from the DOM. + const statusUnique = new Set(allDeadlines.map((f) => f.status)).size; + const table = document.getElementById("deadlines-table"); + table?.classList.toggle("akten-table--hide-status", statusUnique <= 1); + tbody.querySelectorAll(".frist-row").forEach((row) => { const id = row.dataset.id!; row.addEventListener("click", (e) => { diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts index 7c7cc05..ce00dc1 100644 --- a/frontend/src/client/i18n.ts +++ b/frontend/src/client/i18n.ts @@ -50,6 +50,7 @@ const translations: Record> = { "bottomnav.add.project": "Projekt anlegen", "bottomnav.add.project.sub": "Neues Mandat / Verfahren / Patent", "bottomnav.add.cancel": "Abbrechen", + "bottomnav.badge.deadlines": "{overdue} überfällig + {today} heute fällig", // Changelog (What's New) — t-paliad-027 "changelog.title": "Neuigkeiten — Paliad", @@ -252,8 +253,8 @@ const translations: Record> = { "glossar.subtitle": "Zweisprachiges Glossar der wichtigsten Begriffe im Patentrecht.", "glossar.search.placeholder": "Suchen...", "glossar.filter.all": "Alle", - "glossar.filter.litigation": "Litigation", - "glossar.filter.prosecution": "Prosecution", + "glossar.filter.litigation": "Streitsachen", + "glossar.filter.prosecution": "Erteilungsverfahren", "glossar.filter.general": "Allgemein", "glossar.col.de": "Deutsch", "glossar.col.en": "English", @@ -1431,6 +1432,7 @@ const translations: Record> = { "bottomnav.add.project": "New project", "bottomnav.add.project.sub": "New matter / case / patent", "bottomnav.add.cancel": "Cancel", + "bottomnav.badge.deadlines": "{overdue} overdue + {today} due today", // Changelog (What's New) — t-paliad-027 "changelog.title": "What's New — Paliad", diff --git a/frontend/src/client/projects.ts b/frontend/src/client/projects.ts index 5cd588b..8a565eb 100644 --- a/frontend/src/client/projects.ts +++ b/frontend/src/client/projects.ts @@ -148,12 +148,19 @@ function render() { ${esc(typeLabel)} ${refCell} ${clientMatterCell} - ${esc(statusLabel)} + ${esc(statusLabel)} ${fmtDate(p.updated_at)} `; }) .join(""); + // F-23: when every visible row shares the same status, hide the column to + // cut redundant noise. The toggle re-runs on every filter change, so the + // column comes back as soon as the rows mix again. + const statusUnique = new Set(filtered.map((p) => p.status)).size; + const table = document.getElementById("akten-table"); + table?.classList.toggle("akten-table--hide-status", statusUnique <= 1); + tbody.querySelectorAll(".akten-row").forEach((row) => { row.addEventListener("click", () => { const id = row.dataset.id!; diff --git a/frontend/src/deadlines.tsx b/frontend/src/deadlines.tsx index afe567b..57f7328 100644 --- a/frontend/src/deadlines.tsx +++ b/frontend/src/deadlines.tsx @@ -100,7 +100,7 @@ export function renderDeadlines(): string { Titel Projekt Regel - Status + Status diff --git a/frontend/src/glossary.tsx b/frontend/src/glossary.tsx index ba25d2b..e529e4d 100644 --- a/frontend/src/glossary.tsx +++ b/frontend/src/glossary.tsx @@ -57,8 +57,8 @@ export function renderGlossary(): string {
- - + + @@ -109,8 +109,8 @@ export function renderGlossary(): string {