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

51 lines
5.4 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
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.ts``lib/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.