Files
logbuch/CLAUDE.md
T
admin b3fbc391b1 feat: Tab „Führungen" für ausgefallene und verschobene Sonderführungen — Version 1.14.0
Fällt eine Sonderführung aus oder wird sie verschoben, gibt es dafür keinen
Logbuch-Eintrag. Der neue Tab listet die kommenden zugesagten Führungen aus
SoFue2 und bietet je Zeile „Ausgefallen" und „Verschoben auf …" — offen für
jeden angemeldeten BEO.

Absage setzt status=3 und lässt den Termin stehen. Beim Verschieben wandert
der bisherige wtermin in die bis dahin ungenutzte Spalte 'verlegt'; beide
Aktionen hängen eine datierte Zeile mit dem Kürzel an 'bemerkung' an, statt
sie zu ersetzen. Damit ist die Terminhistorie erstmals nachvollziehbar.

Die Anleitung wird nun aus ANLEITUNG.md erzeugt (scripts/build-anleitung.mjs,
Design in scripts/anleitung.template.html), automatisch vor jedem Build.
public/anleitung.html ist deshalb nicht mehr in git.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:03:43 +02:00

5.4 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"> + <TimePicker5>. Beginn date change syncs Ende date automatically.
  • 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/.

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.