Files
paliad/internal/handlers/appointments_pages.go
mAi 0f98d2cd39 refactor(calendar): t-paliad-224 — retire standalone calendar pages + prune dead code
Delete the four orphan files behind /deadlines/calendar +
/appointments/calendar:
- frontend/src/{deadlines,appointments}-calendar.tsx
- frontend/src/client/{deadlines,appointments}-calendar.ts
The standalone pages were unreachable from the UI since t-paliad-110
(Sidebar/BottomNav point at /events?type=…); their only role was as
bookmark targets.

Handlers in internal/handlers/{deadlines,appointments}_pages.go now
301-redirect to /events?type=…&view=calendar so bookmarks still
work. Route registrations in handlers.go remain unchanged — the
gate + redirect pair gives us the same URL surface with one canonical
renderer.

build.ts: drop the renderDeadlinesCalendar / renderAppointmentsCalendar
imports + entry-point bundle paths + dist HTML writes.

frontend/src/client/paliadin-context.ts: drop the two route-key
matches for the standalone URLs (the client never sees those
pathnames any more — 301 fires server-side).

Dead CSS pruned in frontend/src/styles/global.css (~180 lines):
- .frist-calendar, .frist-cal-{controls,month-label,grid,cell,…}
  block (lines 7464-7613 pre-refactor)
- @media (max-width: 700px) { .frist-cal-cell { min-height: 64px; } }
- .termin-cal-legend{,-item}
- .frist-cal-popup-time
- .frist-cal-dot.events-cal-dot-appointment

All verified by grep across frontend/ + internal/ to have no
non-calendar consumers before deletion.

Dead i18n keys removed (DE + EN + i18n-keys.ts union type):
- deadlines.kalender.{title,heading,subtitle,list,today,empty}
- appointments.kalender.{title,heading,subtitle,list,empty}
- deadlines.list.calendar, appointments.list.calendar (button labels
  on the deleted standalone routes)
- events.calendar.empty (replaced by cal.day.no_entries inside
  mountCalendar's day view)

Per head decisions §11 Q1 + Q8 (drop standalone pages as 301s; drop
dead i18n now).

Tests: go build ./... clean; go test ./internal/... 9 packages pass;
cd frontend && bun run build clean (2535 i18n keys); bun test
frontend/src/client/{calendar,views}/ all 73/73 pass.
2026-05-20 15:23:28 +02:00

68 lines
2.9 KiB
Go

package handlers
import "net/http"
// Server-rendered page endpoints for the Phase F Appointments UI.
// HTML is generated at build time by frontend/build.ts; the per-page
// client TS bundles call /api/appointments* to populate the DOM and read
// id/project_id from window.location.
// handleAppointmentsListRedirect 301-redirects the legacy /appointments
// list URL to the canonical /events?type=appointment (t-paliad-115).
// Detail page /appointments/{id} stays type-specific. Drop this redirect
// once we're confident no caches / bookmarks / external links still hit
// the old URL.
func handleAppointmentsListRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/events?type=appointment", http.StatusMovedPermanently)
}
func handleAppointmentsNewPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/appointments-new.html")
}
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.Redirect(w, r, "/events?type=appointment&view=calendar", http.StatusMovedPermanently)
}
// handleSettingsPage serves the unified settings page with tabs for
// Profil / Benachrichtigungen / CalDAV. The active tab is picked
// client-side from ?tab=<name> so switching tabs doesn't round-trip.
func handleSettingsPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/settings.html")
}
// settingsTabAliases maps every supported /settings/<slug> deep-link to its
// canonical ?tab=<name> value the client TS understands. Both the German tab
// IDs (profil/benachrichtigungen) and intuitive English aliases
// (profile/notifications) are accepted so bookmarks, smoke tests, and
// manually-typed URLs all land on the right tab.
var settingsTabAliases = map[string]string{
"profil": "profil",
"profile": "profil",
"benachrichtigungen": "benachrichtigungen",
"notifications": "benachrichtigungen",
"caldav": "caldav",
}
// handleSettingsTabRedirect turns /settings/<slug> into /settings?tab=<canonical>
// as 301 Moved Permanently. Unknown slugs fall back to the bare /settings page
// (the client picks the default tab) so a typo doesn't 404.
func handleSettingsTabRedirect(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("tab")
canonical, ok := settingsTabAliases[slug]
if !ok {
http.Redirect(w, r, "/settings", http.StatusMovedPermanently)
return
}
http.Redirect(w, r, "/settings?tab="+canonical, http.StatusMovedPermanently)
}