Files
logbuch/CLAUDE.md
T
admin 38e403bd39 fix: BEOs und Objekte bleiben beim Wechsel der Art erhalten — Version 1.16.1
Zwei getrennte Ursachen dafür, dass ausgefüllte Felder beim nachträglichen
Wählen der Art der Führung verschwanden:

- Objekte wurden bei jedem Wechsel der Art unbedingt geleert. Jetzt nur noch,
  wenn die Objektkategorie tatsächlich umschlägt (SonF ↔ Rest).
- currentUserBeo wurde in MainClient bei jedem Rendern neu erzeugt und steht in
  den Abhängigkeiten des Vorbelegungs-Effekts in LogbuchForm. Dadurch setzte
  jedes Neurendern der Elternkomponente das ganze Formular zurück — BEOs auf den
  eigenen Namen, dazu Objekte, Zeiten und Bemerkungen. Ein konkreter Auslöser
  ist die Backup-Schaltfläche auf demselben Bildschirm. Das Objekt ist jetzt
  useMemo-stabil.

Nebeneffekt: beim Bearbeiten lief das bisherige setObjekte([]) gegen das
asynchrone Nachladen der gespeicherten Objekte — dieser Wettlauf entfällt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:38:46 +02:00

6.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

npm run dev       # Development server
npm run build     # Production build (run after every change to verify)
npm run lint      # ESLint

No test suite exists. Deploy via ./deploy.sh [tag] — builds multiplatform Docker image (amd64 + arm64) and pushes to docker.citysensor.de.

Architecture

Next.js 16 App Router application. All pages are server components; interactive parts are Client Components in app/MainClient.tsx and components/.

Auth flow: Users come from the existing MySQL beos table (not a separate users table). Login via app/login/actions.tslib/auth.ts (bcryptjs). Sessions are JWT cookies via jose (lib/session.ts, 1-hour expiry). If pw IS NULL, the default password is welzheim and mustChangePassword is forced to true. Middleware lives in proxy.ts (Next.js 16 convention) and exports middleware (not proxy).

Database: MySQL, database name sternwarte, via lib/db.ts connection pool. The pre-existing beos table has non-standard columns: `kürzel` (umlaut → always needs backticks), pw, id (all lowercase). The DB charset is utf8mb4 (collation utf8mb4_unicode_ci); connection pool uses charset: 'utf8mb4'.

SQL in JS: MySQL backticks inside JS template literals cause parse errors. Write complex queries using string concatenation (+), not template literals. LIMIT cannot be a parameterized placeholder in complex grouped queries — embed it directly after validating: LIST_SQL + \ LIMIT ${limit}``.

API routes (app/api/): all check getSession() and return 401 if unauthenticated. The logbuch list query uses GROUP_CONCAT to aggregate BEOs and Objekte into comma-separated strings per entry.

Key components

  • CustomSelect: replaces native <select> everywhere — iOS/Android native popups ignore CSS sizing. Supports keepOpen prop for multi-select use cases (BEOs, Objekte).
  • TimePicker5: custom time picker, no native <input type="time">. Shows HH:MM with ▲/▼ buttons, 5-minute steps, auto-repeat on hold (400 ms delay → 1-hour steps at 350 ms). Keyboard: ↑/↓.
  • LogbuchForm: Beginn/Ende stored as "YYYY-MM-DDTHH:MM" strings. Date and time are split into separate <input type="date"> + <TimeInput>. The single date field is the Beginn date; Ende's date is derived, never entered. Rule for sessions crossing midnight: start time-of-day later than end time-of-day ⇒ Beginn is the previous day, anchored on Ende (the moment the form is filled in, hence the reliable one) — beginnAusEnde() / endeAusBeginn(). So typing a start time can move the visible date back a day; a grey line below the row explains it. A duration over 12 h shows a soft hint but never blocks saving. Entries created before 1.16.0 may have Beginn > Ende; migrate_zeiten.sql fixes them. Changing ArtFuehrung only clears what no longer fits — Objekte just when the category flips (SonF ↔ rest), never BEOs. The currentUserBeo prop must be referentially stable (it is useMemo'd in MainClient): it sits in the prefill effect's deps, so a fresh object on every parent render would wipe the form mid-entry.
  • LogbuchList: accepts compact and limit props. Compact mode used for the 5-entry preview below the form on desktop (hidden lg:block).

Data model

ArtFuehrung is stored as abbreviations in the DB (RF, SF, PrF, BEOS, SonF, TD, Beob, ToT, Sonst). Display names are in ARTEN_MAP in types/logbuch.ts. BEOS and TD hide the Besucher and Objekte fields. SonF pre-selects "Sonne" as the only object. SF (Sonderführung) additionally shows SonderName plus the mandatory Spende select (bar, ueberw, kasse, keine — labels in SPENDE_MAP); SpendeBetrag is only filled for bar. Both columns stay NULL for every other Art, enforced client-side in LogbuchForm and server-side in spendeValues() in DB4js_all.php.

Saving an SF entry also writes back into the Sonderführungs register SoFue2 (stattgefunden, anzahl_echt, bezahlt, remarks) via RepoSoFue::ausLogbuch() in DB4js_all.php — matched by DATE(wtermin) against the entry's Beginn, nearest time wins. It runs in the dispatcher after the logbuch transaction commits and never throws; the outcome travels back as a sofue field in the API response and is shown above the form by MainClient. Note SoFue2 is latin1 while logbuch is utf8mb4, and remarks holds only 100 chars — cp1252Safe() handles both.

Führungen that fall out never produce a logbuch entry. The Führungen tab (components/Fuehrungen.tsx, open to every logged-in BEO) lists upcoming status=2 rows and offers cancel (status=3, date kept) and postpone (old wtermin saved into the long-unused verlegt column, new date written). Both append a dated line with the user's Kürzel to bemerkung rather than replacing it. Backed by LB_SOFUE_TERMINE / LB_SOFUE_ABSAGEN / LB_SOFUE_VERSCHIEBEN in DB4js_all.php and the routes under app/api/sofue/.

Weather

app/api/wetter/route.ts proxies stwwetter.fuerst-stuttgart.de/api/weather. Without params it returns /latest; with ?zeit=YYYY-MM-DDTHH:MM (local time) it queries /range ±20 min around that moment and returns the closest reading, or 404 when the station has nothing (future dates, outages). The weather API treats naive query params as UTC, so the route converts Europe/Berlin → UTC via Intl offsets — without that, summer readings are off by two hours. LogbuchForm refetches whenever ende changes (date or Endzeit), skipping the first run so an edited entry keeps its stored values.

Documentation

ANLEITUNG.md is the single source for the user manual — edit only this file. public/anleitung.html (linked from the app footer) is generated by scripts/build-anleitung.mjs and is untracked (.gitignore); never hand-edit it. npm run anleitung builds it; a prebuild hook runs the same script before every next build, so the Docker image always gets a current copy (.dockerignore excludes *.md but re-includes ANLEITUNG.md for exactly this).

The page design lives in scripts/anleitung.template.html (placeholders {{H1}}, {{SUBTITLE}}, {{TOC}}, {{SECTIONS}}). Markdown conventions the generator relies on: ## N. Titel becomes <section id="sN"> with a numbered heading, the ## Inhaltsverzeichnis list feeds the TOC, in-page links like (#7-drucken) are rewritten to #s7, and blockquotes become callouts — > [!WARNUNG] yellow, > [!ACHTUNG] red, plain or > [!HINWEIS] blue. Renumbering a section means updating both its heading and the TOC entry.

Deployment

output: 'standalone' is set in next.config.ts for Docker. The MySQL container name in production is db — set DB_HOST=db in the server's environment.