Prevents "M.forEach is not a function" crashes when API returns error
objects or unexpected shapes instead of arrays. Guards all useQuery
consumers with Array.isArray checks and safe defaults for object props.
Files fixed: DeadlineList, AppointmentList, TenantSwitcher,
DeadlineTrafficLights, UpcomingTimeline, CaseOverviewGrid,
AISummaryCard, TeamSettings, and all page-level components
(dashboard, cases, fristen, termine, ai/extract).
Frontend api.ts baseUrl is already "/api", so paths like
"/api/cases" produced "/api/api/cases". Stripped the redundant
prefix from all component calls. Rewrite destination correctly
adds /api/ back for the Go backend.
The middleware was intercepting API proxy requests and redirecting
to /login. API routes should pass through to the Go backend which
handles its own JWT auth.
Backend: PUT /api/tenants/{id}/settings endpoint for updating tenant
settings (JSONB merge). Frontend: /einstellungen page with CalDAV
config (URL, credentials, calendar path, sync toggle, interval),
manual sync button, live sync status display. /einstellungen/team
page with member list, invite-by-email, role management.
- Responsive sidebar: collapses on mobile with hamburger menu, slide-in animation
- Skeleton loaders: dashboard cards, case table, case detail page
- Empty states: friendly messages with icons for cases, deadlines, parties, documents
- Error states: retry button on dashboard, proper error message on case not found
- Form validation: inline error messages on case creation form
- German language: fix all missing umlauts (Zurück, wählen, Anhängig, Verfügung, etc.)
- Status labels: display German translations instead of raw status values
- Transitions: fade-in animations on page load, hover/transition-colors on all interactive elements
- Focus states: focus-visible ring for keyboard accessibility
- Mobile layout: stacking for filters, forms, tabs; horizontal scroll for tables
- Extraction results: card layout on mobile, table on desktop
- Missing types: add DashboardData, DeadlineSummary, CaseSummary, ExtractedDeadline etc.
- Fix QuickActions links to use correct routes (/cases/new, /ai/extract)
- Consistent input focus styles across all forms
Implements CalDAV sync using github.com/emersion/go-webdav:
- CalDAVService with background polling (configurable per-tenant interval)
- Push: deadlines -> VTODO, appointments -> VEVENT on create/update/delete
- Pull: periodic fetch from CalDAV, reconcile with local DB
- Conflict resolution: KanzlAI wins dates/status, CalDAV wins notes/description
- Conflicts logged as case_events with caldav_conflict type
- UID pattern: kanzlai-{deadline|appointment}-{uuid}@kanzlai.msbls.de
- CalDAV config per tenant in tenants.settings JSONB
Endpoints:
- POST /api/caldav/sync — trigger full sync for current tenant
- GET /api/caldav/status — last sync time, item counts, errors
8 unit tests for UID generation, parsing, path construction, config parsing.
- /termine page with list/calendar view toggle
- AppointmentList: date-grouped list with type/case filtering, summary cards
- AppointmentCalendar: month grid with colored type dots, clickable days/appointments
- AppointmentModal: create/edit/delete with case linking, type selection, location
- DocumentUpload: dropzone with multi-file support, upload via
POST /api/cases/{id}/documents, progress feedback with toast
- DocumentList: type badges, file size, upload date, download links,
delete with inline confirmation
- Integrated as Dokumente tab in case detail page with count badge
- Eagerly fetches document count for tab badge display
- Case list page (/cases) with search, status/type filters, status badges
- Case creation page (/cases/new) with reusable CaseForm component
- Case detail page (/cases/[id]) with tabs: Timeline, Deadlines, Documents, Parties
- CaseTimeline component for chronological case_events display
- PartyList component with inline party CRUD (add/delete)
- Updated sidebar navigation to route to /cases
Dashboard page at /dashboard with 5 components:
- DeadlineTrafficLights: RED/AMBER/GREEN cards with animated counts and pulse for overdue
- CaseOverviewGrid: active/new/closed case counts
- UpcomingTimeline: merged deadlines + appointments for next 7 days, grouped by day
- AISummaryCard: natural language summary generated from dashboard data
- QuickActions: shortcuts to create cases, deadlines, AI analysis, CalDAV sync
3-column responsive grid layout. Root / redirects to /dashboard.
Fetches from GET /api/dashboard with 60s auto-refresh via react-query.
- ExtractionForm: PDF dropzone (react-dropzone) + text textarea + case selector
- ExtractionResults: review table with edit/remove per row, confidence color-coding
- Page at /ai/extract: upload -> analyze -> review -> adopt deadlines to case
- Extended API client with postFormData for multipart uploads
- Added ExtractedDeadline and ExtractionResponse types
Add two Claude API-powered endpoints:
- POST /api/ai/extract-deadlines: accepts PDF upload or JSON text, extracts
legal deadlines using Claude tool_use for structured output
- POST /api/ai/summarize-case: generates AI summary from case events/deadlines,
caches result in cases.ai_summary
New files:
- internal/services/ai_service.go: AIService with Anthropic SDK integration
- internal/handlers/ai.go: HTTP handlers for both endpoints
- internal/services/ai_service_test.go: tool schema and serialization tests
Uses anthropic-sdk-go v1.27.1 with Claude Sonnet 4.5. AI service is optional —
endpoints only registered when ANTHROPIC_API_KEY is set.
GET /api/dashboard returns aggregated data:
- deadline_summary: overdue, due this/next week, ok counts
- case_summary: active, new this month, closed counts
- upcoming_deadlines: next 7 days with case info
- upcoming_appointments: next 7 days
- recent_activity: last 10 case events
Uses efficient CTE query for summaries. Also fixes duplicate
writeJSON/writeError declarations in appointments handler.
- Holiday service with German federal holidays, Easter calculation, DB loading
- Deadline calculator adapted from youpc.org (duration calc + non-working day adjustment)
- Deadline CRUD service (tenant-scoped: list, create, update, complete, delete)
- Deadline rule service (list, filter by proceeding type, hierarchical rule trees)
- HTTP handlers for all endpoints with tenant resolution via X-Tenant-ID header
- Router wired with all new endpoints under /api/
- Tests for holiday and calculator services (8 passing)
Tenant management:
- POST /api/tenants — create tenant (creator becomes owner)
- GET /api/tenants — list tenants for authenticated user
- GET /api/tenants/:id — tenant details with access check
- POST /api/tenants/:id/invite — invite user by email (owner/admin)
- DELETE /api/tenants/:id/members/:uid — remove member
- GET /api/tenants/:id/members — list members
New packages:
- internal/services/tenant_service.go — CRUD on tenants + user_tenants
- internal/handlers/tenant_handler.go — HTTP handlers with auth checks
- internal/auth/tenant_resolver.go — X-Tenant-ID header middleware,
defaults to user's first tenant for scoped routes
Authorization: owners/admins can invite and remove members. Cannot
remove the last owner. Users can remove themselves. TenantResolver
applies to resource routes (cases, deadlines, etc.) but not tenant
management routes.
- CaseService: list (paginated, filterable), get detail (with parties,
events, deadline count), create, update, soft-delete (archive)
- PartyService: list by case, create, update, delete
- Auto-create case_events on case creation, status change, party add,
and case archive
- Auth middleware now resolves tenant_id from user_tenants table
- All operations scoped to tenant_id from auth context
- AppointmentService with tenant-scoped List, GetByID, Create, Update, Delete
- List supports filtering by case_id, appointment_type, and date range (start_from/start_to)
- AppointmentHandler with JSON request/response handling and input validation
- Router wired up: GET/POST /api/appointments, PUT/DELETE /api/appointments/{id}
New direction: law firm management (Fristen, Termine, case tracking)
instead of UPC case law search. Updated all references, Go module
path, and deployment info.
Port 3000 conflicts with Dokploy. Traefik routes traffic via
Docker network, so expose is sufficient. Also remove env_file
refs since Dokploy injects env vars directly.