diff --git a/frontend/build.ts b/frontend/build.ts
index d5436e4..5886621 100644
--- a/frontend/build.ts
+++ b/frontend/build.ts
@@ -20,10 +20,8 @@ import { renderProjectsChart } from "./src/projects-chart";
import { renderEvents } from "./src/events";
import { renderDeadlinesNew } from "./src/deadlines-new";
import { renderDeadlinesDetail } from "./src/deadlines-detail";
-import { renderDeadlinesCalendar } from "./src/deadlines-calendar";
import { renderAppointmentsNew } from "./src/appointments-new";
import { renderAppointmentsDetail } from "./src/appointments-detail";
-import { renderAppointmentsCalendar } from "./src/appointments-calendar";
import { renderSettings } from "./src/settings";
import { renderDashboard } from "./src/dashboard";
import { renderAgenda } from "./src/agenda";
@@ -255,10 +253,8 @@ async function build() {
join(import.meta.dir, "src/client/events.ts"),
join(import.meta.dir, "src/client/deadlines-new.ts"),
join(import.meta.dir, "src/client/deadlines-detail.ts"),
- join(import.meta.dir, "src/client/deadlines-calendar.ts"),
join(import.meta.dir, "src/client/appointments-new.ts"),
join(import.meta.dir, "src/client/appointments-detail.ts"),
- join(import.meta.dir, "src/client/appointments-calendar.ts"),
join(import.meta.dir, "src/client/settings.ts"),
join(import.meta.dir, "src/client/dashboard.ts"),
join(import.meta.dir, "src/client/agenda.ts"),
@@ -384,10 +380,8 @@ async function build() {
await Bun.write(join(DIST, "events.html"), renderEvents());
await Bun.write(join(DIST, "deadlines-new.html"), renderDeadlinesNew());
await Bun.write(join(DIST, "deadlines-detail.html"), renderDeadlinesDetail());
- await Bun.write(join(DIST, "deadlines-calendar.html"), renderDeadlinesCalendar());
await Bun.write(join(DIST, "appointments-new.html"), renderAppointmentsNew());
await Bun.write(join(DIST, "appointments-detail.html"), renderAppointmentsDetail());
- await Bun.write(join(DIST, "appointments-calendar.html"), renderAppointmentsCalendar());
await Bun.write(join(DIST, "settings.html"), renderSettings());
await Bun.write(join(DIST, "dashboard.html"), renderDashboard());
await Bun.write(join(DIST, "agenda.html"), renderAgenda());
diff --git a/frontend/src/appointments-calendar.tsx b/frontend/src/appointments-calendar.tsx
deleted file mode 100644
index 8f17ea9..0000000
--- a/frontend/src/appointments-calendar.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { h } from "./jsx";
-import { Sidebar } from "./components/Sidebar";
-import { PaliadinWidget } from "./components/PaliadinWidget";
-import { BottomNav } from "./components/BottomNav";
-import { Footer } from "./components/Footer";
-import { PWAHead } from "./components/PWAHead";
-
-export function renderAppointmentsCalendar(): string {
- return "" + (
-
-
-
-
-
-
-
-
- Terminkalender — Paliad
-
-
-
-
-
-
-
-
-
-
-
-
-
Terminkalender
-
- Monatsübersicht aller Termine.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Verhandlung
-
-
-
- Besprechung
-
-
-
- Beratung
-
-
-
- Fristverhandlung
-
-
-
-
-
Mo
-
Di
-
Mi
-
Do
-
Fr
-
Sa
-
So
-
-
-
-
- Keine Termine im ausgewählten Zeitraum.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/frontend/src/client/appointments-calendar.ts b/frontend/src/client/appointments-calendar.ts
deleted file mode 100644
index 04a92d1..0000000
--- a/frontend/src/client/appointments-calendar.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-import { initI18n, onLangChange, t, tDyn, getLang } from "./i18n";
-import { initSidebar } from "./sidebar";
-
-interface Appointment {
- id: string;
- project_id?: string;
- title: string;
- start_at: string;
- end_at?: string;
- appointment_type?: string;
- project_reference?: string;
- project_title?: string;
-}
-
-let allAppointments: Appointment[] = [];
-let viewYear = 0;
-let viewMonth = 0;
-
-function esc(s: string): string {
- const d = document.createElement("div");
- d.textContent = s;
- return d.innerHTML;
-}
-
-function fmtMonth(year: number, month: number): string {
- return `${tDyn(`cal.month.${month}`)} ${year}`;
-}
-
-function isoDate(year: number, month: number, day: number): string {
- const m = String(month + 1).padStart(2, "0");
- const d = String(day).padStart(2, "0");
- return `${year}-${m}-${d}`;
-}
-
-async function loadAppointments() {
- // Pull a wide window (current month plus a little buffer either side).
- // We could narrow this, but the user typically navigates ±1-2 months
- // and the dataset is small.
- try {
- const resp = await fetch("/api/appointments");
- if (resp.ok) allAppointments = await resp.json();
- } catch {
- /* non-fatal */
- }
-}
-
-function appointmentsForDate(iso: string): Appointment[] {
- return allAppointments.filter((t) => t.start_at.slice(0, 10) === iso);
-}
-
-function typeClass(t?: string): string {
- return t ? `termin-type-${t}` : "termin-type-default";
-}
-
-function fmtTime(iso: string): string {
- try {
- const d = new Date(iso);
- return d.toLocaleTimeString(getLang() === "de" ? "de-DE" : "en-GB", {
- hour: "2-digit",
- minute: "2-digit",
- });
- } catch {
- return iso;
- }
-}
-
-function render() {
- document.getElementById("cal-month-label")!.textContent = fmtMonth(viewYear, viewMonth);
-
- const firstDay = new Date(viewYear, viewMonth, 1);
- const jsWeekday = firstDay.getDay();
- const offset = (jsWeekday + 6) % 7;
- const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
- const today = new Date();
- const todayISO = isoDate(today.getFullYear(), today.getMonth(), today.getDate());
-
- const cells: string[] = [];
- for (let i = 0; i < offset; i++) {
- cells.push(``);
- }
- for (let day = 1; day <= daysInMonth; day++) {
- const iso = isoDate(viewYear, viewMonth, day);
- const items = appointmentsForDate(iso);
- const isToday = iso === todayISO;
-
- const dots = items
- .slice(0, 4)
- .map((tt) => ``)
- .join("");
- const more = items.length > 4 ? `+${items.length - 4}` : "";
-
- cells.push(
- ` 0 ? " frist-cal-cell-has" : ""}" data-iso="${iso}">
-
${day}
-
${dots}${more}
-
`,
- );
- }
-
- const grid = document.getElementById("appointment-cal-grid")!;
- grid.innerHTML = cells.join("");
-
- grid.querySelectorAll(".frist-cal-cell-has").forEach((cell) => {
- cell.addEventListener("click", () => openPopup(cell.dataset.iso!));
- });
-
- const monthStart = isoDate(viewYear, viewMonth, 1);
- const monthEnd = isoDate(viewYear, viewMonth, daysInMonth);
- const hasInMonth = allAppointments.some((tt) => {
- const iso = tt.start_at.slice(0, 10);
- return iso >= monthStart && iso <= monthEnd;
- });
- const empty = document.getElementById("appointment-cal-empty")!;
- empty.style.display = hasInMonth ? "none" : "";
-}
-
-function openPopup(iso: string) {
- const items = appointmentsForDate(iso);
- if (items.length === 0) return;
- const popup = document.getElementById("cal-popup")!;
- const dateEl = document.getElementById("cal-popup-date")!;
- const list = document.getElementById("cal-popup-list")!;
-
- const d = new Date(iso + "T00:00:00");
- dateEl.textContent = d.toLocaleDateString(getLang() === "de" ? "de-DE" : "en-GB", {
- weekday: "long",
- year: "numeric",
- month: "long",
- day: "numeric",
- });
-
- list.innerHTML = items
- .map((tt) => {
- const akteRef = tt.project_id
- ? ``
- : `${esc(t("appointments.personal"))}`;
- return ``;
- })
- .join("");
- popup.style.display = "flex";
-}
-
-function initPopup() {
- const popup = document.getElementById("cal-popup")!;
- const close = document.getElementById("cal-popup-close")!;
- close.addEventListener("click", () => (popup.style.display = "none"));
- popup.addEventListener("click", (e) => {
- if (e.target === e.currentTarget) popup.style.display = "none";
- });
-}
-
-function initNav() {
- document.getElementById("cal-prev")!.addEventListener("click", () => {
- viewMonth -= 1;
- if (viewMonth < 0) {
- viewMonth = 11;
- viewYear -= 1;
- }
- render();
- });
- document.getElementById("cal-next")!.addEventListener("click", () => {
- viewMonth += 1;
- if (viewMonth > 11) {
- viewMonth = 0;
- viewYear += 1;
- }
- render();
- });
- document.getElementById("cal-today")!.addEventListener("click", () => {
- const now = new Date();
- viewYear = now.getFullYear();
- viewMonth = now.getMonth();
- render();
- });
-}
-
-document.addEventListener("DOMContentLoaded", async () => {
- initI18n();
- initSidebar();
- const now = new Date();
- viewYear = now.getFullYear();
- viewMonth = now.getMonth();
- initNav();
- initPopup();
- onLangChange(render);
- await loadAppointments();
- render();
-});
diff --git a/frontend/src/client/deadlines-calendar.ts b/frontend/src/client/deadlines-calendar.ts
deleted file mode 100644
index 0a0284a..0000000
--- a/frontend/src/client/deadlines-calendar.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import { initI18n, onLangChange, t, tDyn, getLang } from "./i18n";
-import { initSidebar } from "./sidebar";
-
-interface Deadline {
- id: string;
- project_id: string;
- title: string;
- due_date: string;
- status: string;
- project_reference: string;
- project_title: string;
-}
-
-let allDeadlines: Deadline[] = [];
-let viewYear = 0;
-let viewMonth = 0; // 0-11
-
-function esc(s: string): string {
- const d = document.createElement("div");
- d.textContent = s;
- return d.innerHTML;
-}
-
-function fmtMonth(year: number, month: number): string {
- return `${tDyn(`cal.month.${month}`)} ${year}`;
-}
-
-function urgencyClass(due: string, status: string): string {
- if (status === "completed") return "frist-urgency-done";
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- const d = new Date(due.slice(0, 10) + "T00:00:00");
- const diffDays = Math.floor((d.getTime() - today.getTime()) / 86400000);
- if (diffDays < 0) return "frist-urgency-overdue";
- if (diffDays <= 7) return "frist-urgency-soon";
- return "frist-urgency-later";
-}
-
-async function loadDeadlines() {
- try {
- const resp = await fetch("/api/deadlines?status=all");
- if (resp.ok) allDeadlines = await resp.json();
- } catch {
- /* non-fatal */
- }
-}
-
-function deadlinesForDate(iso: string): Deadline[] {
- return allDeadlines.filter((f) => f.due_date.slice(0, 10) === iso);
-}
-
-function isoDate(year: number, month: number, day: number): string {
- const m = String(month + 1).padStart(2, "0");
- const d = String(day).padStart(2, "0");
- return `${year}-${m}-${d}`;
-}
-
-function render() {
- document.getElementById("cal-month-label")!.textContent = fmtMonth(viewYear, viewMonth);
-
- const firstDay = new Date(viewYear, viewMonth, 1);
- const jsWeekday = firstDay.getDay();
- const offset = (jsWeekday + 6) % 7;
- const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
- const today = new Date();
- const todayISO = isoDate(today.getFullYear(), today.getMonth(), today.getDate());
-
- const cells: string[] = [];
- for (let i = 0; i < offset; i++) {
- cells.push(``);
- }
- for (let day = 1; day <= daysInMonth; day++) {
- const iso = isoDate(viewYear, viewMonth, day);
- const items = deadlinesForDate(iso);
- const isToday = iso === todayISO;
-
- const dots = items
- .slice(0, 4)
- .map((f) => ``)
- .join("");
- const more = items.length > 4 ? `+${items.length - 4}` : "";
-
- cells.push(
- ` 0 ? " frist-cal-cell-has" : ""}" data-iso="${iso}">
-
${day}
-
${dots}${more}
-
`,
- );
- }
-
- const grid = document.getElementById("deadline-cal-grid")!;
- grid.innerHTML = cells.join("");
-
- grid.querySelectorAll(".frist-cal-cell-has").forEach((cell) => {
- cell.addEventListener("click", () => openPopup(cell.dataset.iso!));
- });
-
- const monthStart = isoDate(viewYear, viewMonth, 1);
- const monthEnd = isoDate(viewYear, viewMonth, daysInMonth);
- const hasInMonth = allDeadlines.some((f) => {
- const iso = f.due_date.slice(0, 10);
- return iso >= monthStart && iso <= monthEnd;
- });
- const empty = document.getElementById("deadline-cal-empty")!;
- empty.style.display = hasInMonth ? "none" : "";
-}
-
-function openPopup(iso: string) {
- const items = deadlinesForDate(iso);
- if (items.length === 0) return;
- const popup = document.getElementById("cal-popup")!;
- const dateEl = document.getElementById("cal-popup-date")!;
- const list = document.getElementById("cal-popup-list")!;
-
- const d = new Date(iso + "T00:00:00");
- dateEl.textContent = d.toLocaleDateString(getLang() === "de" ? "de-DE" : "en-GB", {
- weekday: "long",
- year: "numeric",
- month: "long",
- day: "numeric",
- });
-
- list.innerHTML = items
- .map((f) => {
- const cls = urgencyClass(f.due_date, f.status);
- return ``;
- })
- .join("");
- popup.style.display = "flex";
-}
-
-function initPopup() {
- const popup = document.getElementById("cal-popup")!;
- const close = document.getElementById("cal-popup-close")!;
- close.addEventListener("click", () => (popup.style.display = "none"));
- popup.addEventListener("click", (e) => {
- if (e.target === e.currentTarget) popup.style.display = "none";
- });
-}
-
-function initNav() {
- document.getElementById("cal-prev")!.addEventListener("click", () => {
- viewMonth -= 1;
- if (viewMonth < 0) {
- viewMonth = 11;
- viewYear -= 1;
- }
- render();
- });
- document.getElementById("cal-next")!.addEventListener("click", () => {
- viewMonth += 1;
- if (viewMonth > 11) {
- viewMonth = 0;
- viewYear += 1;
- }
- render();
- });
- document.getElementById("cal-today")!.addEventListener("click", () => {
- const now = new Date();
- viewYear = now.getFullYear();
- viewMonth = now.getMonth();
- render();
- });
-}
-
-document.addEventListener("DOMContentLoaded", async () => {
- initI18n();
- initSidebar();
- const now = new Date();
- viewYear = now.getFullYear();
- viewMonth = now.getMonth();
- initNav();
- initPopup();
- onLangChange(render);
- await loadDeadlines();
- render();
-});
diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts
index c4aa623..976b808 100644
--- a/frontend/src/client/i18n.ts
+++ b/frontend/src/client/i18n.ts
@@ -694,7 +694,6 @@ const translations: Record> = {
"deadlines.list.heading": "Fristen",
"deadlines.list.subtitle": "Persistente Fristen f\u00fcr Ihre Akten. \u00dcberf\u00e4llig, heute, diese Woche, n\u00e4chste Woche \u2014 auf einen Blick.",
"deadlines.list.new": "Neue Frist",
- "deadlines.list.calendar": "Kalenderansicht",
"deadlines.summary.overdue": "\u00dcberf\u00e4llig",
"deadlines.summary.today": "Heute",
"deadlines.summary.thisweek": "Diese Woche",
@@ -817,12 +816,6 @@ const translations: Record> = {
"deadlines.source.caldav": "CalDAV",
"deadlines.source.imported": "Import",
- "deadlines.kalender.title": "Fristenkalender \u2014 Paliad",
- "deadlines.kalender.heading": "Fristenkalender",
- "deadlines.kalender.subtitle": "Monats\u00fcbersicht aller Fristen Ihrer Akten.",
- "deadlines.kalender.list": "Listenansicht",
- "deadlines.kalender.today": "Heute",
- "deadlines.kalender.empty": "Keine Fristen im ausgew\u00e4hlten Zeitraum.",
"cal.day.mon": "Mo",
"cal.day.tue": "Di",
"cal.day.wed": "Mi",
@@ -1600,7 +1593,6 @@ const translations: Record> = {
"appointments.list.title": "Termine \u2014 Paliad",
"appointments.list.heading": "Termine",
"appointments.list.subtitle": "Verhandlungen, Besprechungen, Beratungen \u2014 pers\u00f6nlich oder aktenbezogen.",
- "appointments.list.calendar": "Kalenderansicht",
"appointments.list.new": "Neuer Termin",
"appointments.summary.today": "Heute",
"appointments.summary.thisweek": "Diese Woche",
@@ -1656,11 +1648,6 @@ const translations: Record> = {
"appointments.detail.saved": "Gespeichert.",
"appointments.detail.delete": "Termin l\u00f6schen",
"appointments.detail.delete.confirm": "Diesen Termin wirklich l\u00f6schen?",
- "appointments.kalender.title": "Terminkalender \u2014 Paliad",
- "appointments.kalender.heading": "Terminkalender",
- "appointments.kalender.subtitle": "Monats\u00fcbersicht aller Termine.",
- "appointments.kalender.list": "Listenansicht",
- "appointments.kalender.empty": "Keine Termine im ausgew\u00e4hlten Zeitraum.",
// t-paliad-110 \u2014 unified Events page (rendered on both /deadlines and
// /appointments). The user-facing "Fristen" / "Termine" branding stays;
@@ -1684,7 +1671,6 @@ const translations: Record> = {
"events.view.cards": "Karten",
"events.view.list": "Liste",
"events.view.calendar": "Kalender",
- "events.calendar.empty": "Keine Eintr\u00e4ge im ausgew\u00e4hlten Zeitraum.",
"caldav.title": "CalDAV-Synchronisation \u2014 Paliad",
"caldav.heading": "CalDAV-Synchronisation",
"caldav.subtitle": "Synchronisieren Sie Ihre Paliad-Termine mit Ihrem externen Kalender (Nextcloud, iCloud, Outlook, mailcow\u2026). Das Passwort wird verschl\u00fcsselt gespeichert und nie zur\u00fcckgegeben.",
@@ -3437,7 +3423,6 @@ const translations: Record> = {
"deadlines.list.heading": "Deadlines",
"deadlines.list.subtitle": "Persistent deadlines for your matters. Overdue, today, this week, next week \u2014 at a glance.",
"deadlines.list.new": "New deadline",
- "deadlines.list.calendar": "Calendar view",
"deadlines.summary.overdue": "Overdue",
"deadlines.summary.today": "Today",
"deadlines.summary.thisweek": "This week",
@@ -3560,12 +3545,6 @@ const translations: Record> = {
"deadlines.source.caldav": "CalDAV",
"deadlines.source.imported": "Import",
- "deadlines.kalender.title": "Deadline calendar \u2014 Paliad",
- "deadlines.kalender.heading": "Deadline calendar",
- "deadlines.kalender.subtitle": "Monthly view of all deadlines on your matters.",
- "deadlines.kalender.list": "List view",
- "deadlines.kalender.today": "Today",
- "deadlines.kalender.empty": "No deadlines in the selected period.",
"cal.day.mon": "Mon",
"cal.day.tue": "Tue",
"cal.day.wed": "Wed",
@@ -4330,7 +4309,6 @@ const translations: Record> = {
"appointments.list.title": "Appointments \u2014 Paliad",
"appointments.list.heading": "Appointments",
"appointments.list.subtitle": "Hearings, meetings, consultations \u2014 personal or matter-linked.",
- "appointments.list.calendar": "Calendar view",
"appointments.list.new": "New appointment",
"appointments.summary.today": "Today",
"appointments.summary.thisweek": "This week",
@@ -4386,11 +4364,6 @@ const translations: Record> = {
"appointments.detail.saved": "Saved.",
"appointments.detail.delete": "Delete appointment",
"appointments.detail.delete.confirm": "Really delete this appointment?",
- "appointments.kalender.title": "Appointment calendar \u2014 Paliad",
- "appointments.kalender.heading": "Appointment calendar",
- "appointments.kalender.subtitle": "Monthly overview of all appointments.",
- "appointments.kalender.list": "List view",
- "appointments.kalender.empty": "No appointments in the selected period.",
// t-paliad-110 \u2014 unified Events page (rendered on /deadlines + /appointments).
"events.toggle.deadline": "Deadlines",
@@ -4411,7 +4384,6 @@ const translations: Record> = {
"events.view.cards": "Cards",
"events.view.list": "List",
"events.view.calendar": "Calendar",
- "events.calendar.empty": "No entries in the selected period.",
"caldav.title": "CalDAV sync \u2014 Paliad",
"caldav.heading": "CalDAV sync",
"caldav.subtitle": "Sync your Paliad appointments with your external calendar (Nextcloud, iCloud, Outlook, mailcow\u2026). The password is stored encrypted and never returned.",
diff --git a/frontend/src/client/paliadin-context.ts b/frontend/src/client/paliadin-context.ts
index 171bacc..165e629 100644
--- a/frontend/src/client/paliadin-context.ts
+++ b/frontend/src/client/paliadin-context.ts
@@ -93,12 +93,13 @@ export function routeNameFor(pathname: string): string {
if (/^\/projects\/[^/]+\/settings/.test(pathname)) return "projects.settings";
if (/^\/deadlines\/[^/]+$/.test(pathname)) return "deadlines.detail";
if (pathname === "/deadlines/new") return "deadlines.new";
- if (pathname === "/deadlines/calendar") return "deadlines.calendar";
if (pathname === "/deadlines") return "deadlines.list";
if (/^\/appointments\/[^/]+$/.test(pathname)) return "appointments.detail";
if (pathname === "/appointments/new") return "appointments.new";
- if (pathname === "/appointments/calendar") return "appointments.calendar";
if (pathname === "/appointments") return "appointments.list";
+ // /deadlines/calendar + /appointments/calendar are 301 redirects to
+ // /events?type=…&view=calendar since t-paliad-224 — the client never
+ // sees those pathnames any more.
if (pathname === "/agenda") return "agenda";
if (pathname === "/inbox") return "inbox";
if (pathname === "/dashboard" || pathname === "/") return "dashboard";
diff --git a/frontend/src/deadlines-calendar.tsx b/frontend/src/deadlines-calendar.tsx
deleted file mode 100644
index 041e781..0000000
--- a/frontend/src/deadlines-calendar.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { h } from "./jsx";
-import { Sidebar } from "./components/Sidebar";
-import { PaliadinWidget } from "./components/PaliadinWidget";
-import { BottomNav } from "./components/BottomNav";
-import { Footer } from "./components/Footer";
-import { PWAHead } from "./components/PWAHead";
-
-export function renderDeadlinesCalendar(): string {
- return "" + (
-
-
-
-
-
-
-
-
- Fristenkalender — Paliad
-
-
-
-
-
-
-
-
-
-
-
-
-
Fristenkalender
-
- Monatsübersicht aller Fristen Ihrer Akten.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Mo
-
Di
-
Mi
-
Do
-
Fr
-
Sa
-
So
-
-
-
-
- Keine Fristen im ausgewählten Zeitraum.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/frontend/src/i18n-keys.ts b/frontend/src/i18n-keys.ts
index d51f65b..c373039 100644
--- a/frontend/src/i18n-keys.ts
+++ b/frontend/src/i18n-keys.ts
@@ -571,12 +571,6 @@ export type I18nKey =
| "appointments.filter.type"
| "appointments.filter.type.all"
| "appointments.form.approval_hint"
- | "appointments.kalender.empty"
- | "appointments.kalender.heading"
- | "appointments.kalender.list"
- | "appointments.kalender.subtitle"
- | "appointments.kalender.title"
- | "appointments.list.calendar"
| "appointments.list.heading"
| "appointments.list.new"
| "appointments.list.subtitle"
@@ -1137,13 +1131,6 @@ export type I18nKey =
| "deadlines.inbox.label"
| "deadlines.inbox.posteingang"
| "deadlines.inbox.posteingang.title"
- | "deadlines.kalender.empty"
- | "deadlines.kalender.heading"
- | "deadlines.kalender.list"
- | "deadlines.kalender.subtitle"
- | "deadlines.kalender.title"
- | "deadlines.kalender.today"
- | "deadlines.list.calendar"
| "deadlines.list.heading"
| "deadlines.list.new"
| "deadlines.list.subtitle"
@@ -1471,7 +1458,6 @@ export type I18nKey =
| "event_types.picker.no_match"
| "event_types.picker.remove"
| "event_types.picker.search"
- | "events.calendar.empty"
| "events.col.appointment_type"
| "events.col.date"
| "events.col.location"
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
index f9748e8..d202e48 100644
--- a/frontend/src/styles/global.css
+++ b/frontend/src/styles/global.css
@@ -7470,158 +7470,10 @@ dialog.modal::backdrop {
max-width: 22rem;
}
-/* Calendar view */
-
-.frist-calendar-controls {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- margin: 0 0 1rem;
-}
-
-.frist-cal-month-label {
- font-size: 1.15rem;
- margin: 0;
- min-width: 11rem;
- text-align: center;
-}
-
-.frist-calendar {
- display: grid;
- grid-template-columns: repeat(7, minmax(0, 1fr));
- gap: 1px;
- background: var(--color-border);
- border: 1px solid var(--color-border);
- border-radius: var(--radius);
- overflow: hidden;
-}
-
-.frist-cal-weekday {
- background: var(--color-surface-2);
- color: var(--color-text-muted);
- font-size: 0.78rem;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.04em;
- padding: 0.4rem 0.6rem;
- text-align: center;
-}
-
-.frist-cal-grid {
- grid-column: 1 / -1;
- display: grid;
- grid-template-columns: repeat(7, minmax(0, 1fr));
- gap: 1px;
- background: var(--color-border);
-}
-
-.frist-cal-cell {
- background: var(--color-surface);
- min-height: 88px;
- padding: 0.4rem 0.5rem;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- cursor: default;
-}
-
-.frist-cal-cell-empty {
- background: var(--color-bg-subtle);
-}
-
-.frist-cal-cell-has {
- cursor: pointer;
-}
-
-.frist-cal-cell-has:hover {
- background: var(--color-bg-lime-tint);
-}
-
-.frist-cal-day {
- font-size: 0.8rem;
- color: var(--color-text-muted);
- font-variant-numeric: tabular-nums;
-}
-
-.frist-cal-today .frist-cal-day {
- background: var(--color-accent);
- color: var(--color-accent-dark);
- border-radius: 999px;
- width: 1.5rem;
- height: 1.5rem;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-weight: 600;
-}
-
-.frist-cal-dots {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 3px;
-}
-
-.frist-cal-dot {
- display: inline-block;
- width: 7px;
- height: 7px;
- border-radius: 50%;
- background: var(--frist-grey);
-}
-
-.frist-cal-dot.frist-urgency-overdue { background: var(--frist-red); }
-.frist-cal-dot.frist-urgency-soon { background: var(--frist-amber); }
-.frist-cal-dot.frist-urgency-later { background: var(--frist-green); }
-.frist-cal-dot.frist-urgency-done { background: var(--frist-grey); }
-
-.frist-cal-more {
- font-size: 0.7rem;
- color: var(--color-text-muted);
- margin-left: 0.2rem;
-}
-
-.frist-cal-popup-list {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.frist-cal-popup-item {
- display: grid;
- grid-template-columns: auto 1fr auto;
- align-items: center;
- gap: 0.6rem;
- padding: 0.4rem 0;
- border-bottom: 1px solid var(--color-border);
-}
-
-.frist-cal-popup-item:last-child {
- border-bottom: none;
-}
-
-.frist-cal-popup-title {
- color: var(--color-text);
- text-decoration: none;
- font-weight: 500;
-}
-
-.frist-cal-popup-title:hover {
- text-decoration: underline;
-}
-
-.frist-cal-popup-akte {
- color: var(--color-text-muted);
- font-size: 0.8rem;
- text-decoration: none;
-}
-
-.frist-cal-popup-akte:hover {
- color: var(--color-text);
-}
+/* Calendar view styles live in .views-calendar-* (search for that
+ prefix). The /events Kalender tab and Custom Views shape=calendar
+ both mount the same component (frontend/src/client/calendar/
+ mount-calendar.ts, t-paliad-224). */
/* Fristenrechner save-to-Akte modal */
@@ -8027,9 +7879,6 @@ dialog.modal::backdrop {
.frist-summary-cards {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
- .frist-cal-cell {
- min-height: 64px;
- }
}
/* ========================================================================
@@ -8688,27 +8537,6 @@ dialog.modal::backdrop {
.termin-card-week .frist-summary-dot { background: #2563eb; }
.termin-card-later .frist-summary-dot { background: #475569; }
-.termin-cal-legend {
- display: flex;
- gap: 1.2rem;
- flex-wrap: wrap;
- margin: 0.5rem 0 1rem;
- color: #475569;
- font-size: 0.85rem;
-}
-.termin-cal-legend-item {
- display: inline-flex;
- align-items: center;
- gap: 0.35rem;
-}
-
-/* Calendar popup: extra time column for termine (vs. the deadline popup). */
-.frist-cal-popup-time {
- color: #475569;
- font-variant-numeric: tabular-nums;
- margin-right: 0.5rem;
-}
-
/* CalDAV settings page */
.caldav-status-card {
background: var(--color-surface-muted);
@@ -11527,18 +11355,13 @@ dialog.quick-add-sheet::backdrop {
box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));
}
-/* Calendar view (t-paliad-115). Reuses the existing .frist-calendar
- styles — only the appointment dot colour is new. The frist-cal-dot
- urgency variants already cover the deadline palette; we just need a
- distinct hue for appointments so a mixed-type cell reads at a glance. */
+/* Calendar host — mountCalendar() (t-paliad-224) builds the toolbar +
+ grid into this wrapper when the user picks the Kalender chip. All
+ internal styling lives in .views-calendar-* (search for that prefix). */
.events-calendar-wrap {
margin: 0.25rem 0 1rem;
}
-.frist-cal-dot.events-cal-dot-appointment {
- background: var(--bucket-next-week, #1d4ed8);
-}
-
/* Add-modal styling — extends the existing .modal-overlay/.modal pattern. */
.event-type-add-modal {
width: 28rem;
diff --git a/internal/handlers/appointments_pages.go b/internal/handlers/appointments_pages.go
index 3761e50..f6671e4 100644
--- a/internal/handlers/appointments_pages.go
+++ b/internal/handlers/appointments_pages.go
@@ -24,8 +24,13 @@ func handleAppointmentsDetailPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/appointments-detail.html")
}
+// handleAppointmentsCalendarPage 301-redirects the legacy standalone
+// calendar route to the canonical /events Kalender tab (t-paliad-224 /
+// m/paliad#55). Counterpart of handleDeadlinesCalendarPage — same
+// reasoning: the standalone page was orphaned in navigation since
+// t-paliad-110, the canonical calendar lives inside /events.
func handleAppointmentsCalendarPage(w http.ResponseWriter, r *http.Request) {
- http.ServeFile(w, r, "dist/appointments-calendar.html")
+ http.Redirect(w, r, "/events?type=appointment&view=calendar", http.StatusMovedPermanently)
}
// handleSettingsPage serves the unified settings page with tabs for
diff --git a/internal/handlers/deadlines_pages.go b/internal/handlers/deadlines_pages.go
index acdb3c7..52ab049 100644
--- a/internal/handlers/deadlines_pages.go
+++ b/internal/handlers/deadlines_pages.go
@@ -23,6 +23,13 @@ func handleDeadlinesDetailPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/deadlines-detail.html")
}
+// handleDeadlinesCalendarPage 301-redirects the legacy standalone
+// calendar route to the canonical /events Kalender tab (t-paliad-224 /
+// m/paliad#55). The standalone page was orphaned in navigation since
+// t-paliad-110 — Sidebar/BottomNav already point at /events?type=…, and
+// the canonical calendar lives inside that page's view chip. The
+// redirect preserves bookmarks and external links without a duplicate
+// rendering pipeline.
func handleDeadlinesCalendarPage(w http.ResponseWriter, r *http.Request) {
- http.ServeFile(w, r, "dist/deadlines-calendar.html")
+ http.Redirect(w, r, "/events?type=deadline&view=calendar", http.StatusMovedPermanently)
}