feat: Spende-Feld bei Sonderführungen — Version 1.12.0
Neue Spalten Spende (bar/ueberw/kasse/keine) und SpendeBetrag in der Tabelle logbuch. Im Formular erscheint die Auswahl nur bei Art 'SF', rechts neben Name/Gruppe; bei 'bar' zusätzlich ein Pflichtfeld für den Betrag. Anzeige in der Liste als Unterzeile unter der Art. Migration migrate_spende.sql muss vor dem Deploy manuell eingespielt werden; die zugehörige SQL-Änderung liegt in DB4js_all.php im sternwarte-Repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,8 @@ Unterhalb der Kuppelauswahl gibt es drei Tabs:
|
||||
|
||||
**Name / Gruppe** – erscheint nur bei Sonderführung; Name der Gruppe oder Person.
|
||||
|
||||
**Spende** – erscheint nur bei Sonderführung, rechts neben Name / Gruppe. Auswahl zwischen `bar`, `überw.`, `kasse` und `keine`; die Angabe ist bei Sonderführungen verpflichtend. Bei `bar` erscheint zusätzlich ein Feld **Betrag (€)**, in das der erhaltene Betrag eingetragen werden muss (Komma oder Punkt als Dezimaltrennzeichen, z. B. `25,50`).
|
||||
|
||||
**BEOs** – beteiligte Beobachter. Der eigene Name ist automatisch vorausgewählt. Weitere BEOs können über das Suchfeld hinzugefügt werden; ein Klick auf × entfernt sie wieder.
|
||||
|
||||
**Beobachtete Objekte** – nicht sichtbar bei BEOS und TD; bei Sonnenführungen fest auf „Sonne" gesetzt. Für alle anderen Arten:
|
||||
|
||||
@@ -33,7 +33,7 @@ Next.js 16 App Router application. All pages are server components; interactive
|
||||
|
||||
## 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.
|
||||
`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`.
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { Kuppel, ArtFuehrung, SonderName, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
const { Kuppel, ArtFuehrung, SonderName, Spende, SpendeBetrag, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
|
||||
await phpdb.updateLogbuch(logbuchId, session.beoId, session.role ?? '', {
|
||||
Kuppel, ArtFuehrung, SonderName, Beginn, Ende,
|
||||
Spende: Spende ?? null,
|
||||
SpendeBetrag: SpendeBetrag ?? null,
|
||||
Besucher: Besucher ?? 0,
|
||||
beoIds: beoIds ?? [],
|
||||
objekte: objekte ?? [],
|
||||
|
||||
@@ -30,10 +30,12 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { Kuppel, ArtFuehrung, SonderName, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
const { Kuppel, ArtFuehrung, SonderName, Spende, SpendeBetrag, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
|
||||
const result = await phpdb.createLogbuch({
|
||||
Kuppel, ArtFuehrung, SonderName, Beginn, Ende,
|
||||
Spende: Spende ?? null,
|
||||
SpendeBetrag: SpendeBetrag ?? null,
|
||||
Besucher: Besucher ?? 0,
|
||||
beoIds: beoIds ?? [],
|
||||
objekte: objekte ?? [],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { Kuppel, ArtFuehrung, BeoOption, SelectedObjekt, Wetter, LogbuchEintrag } from '@/types/logbuch';
|
||||
import { ARTEN, ARTEN_MAP, artLabel } from '@/types/logbuch';
|
||||
import type { Kuppel, ArtFuehrung, SpendeArt, BeoOption, SelectedObjekt, Wetter, LogbuchEintrag } from '@/types/logbuch';
|
||||
import { ARTEN, ARTEN_MAP, artLabel, SPENDE_ARTEN, spendeLabel } from '@/types/logbuch';
|
||||
import BeoSelector from './BeoSelector';
|
||||
import ObjektSelector from './ObjektSelector';
|
||||
import CustomSelect from './CustomSelect';
|
||||
@@ -46,6 +46,8 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
const [objekte, setObjekte] = useState<SelectedObjekt[]>([]);
|
||||
const [bemerkungen, setBemerkungen] = useState('');
|
||||
const [sonderName, setSonderName] = useState('');
|
||||
const [spende, setSpende] = useState<SpendeArt | ''>('');
|
||||
const [spendeBetragRaw, setSpendeBetragRaw] = useState('');
|
||||
const [wetter, setWetter] = useState<Wetter>({ temp: 0, feuchte: 0, druck: 0 });
|
||||
const [tempRaw, setTempRaw] = useState('0');
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -55,12 +57,13 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
const editEntryRef = useRef(editEntry);
|
||||
editEntryRef.current = editEntry;
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const prevArtRef = useRef<ArtFuehrung | null>(null);
|
||||
|
||||
function focusNext(current: HTMLElement) {
|
||||
if (!formRef.current) return;
|
||||
const fields = Array.from(
|
||||
formRef.current.querySelectorAll<HTMLElement>(
|
||||
'input:not([disabled]), textarea:not([disabled]), #art-select'
|
||||
'input:not([disabled]), textarea:not([disabled]), #art-select, #spende-select'
|
||||
)
|
||||
);
|
||||
const idx = fields.indexOf(current);
|
||||
@@ -91,6 +94,8 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
setBesucher(editEntry.Besucher ?? '');
|
||||
setBemerkungen(editEntry.Bemerkungen ?? '');
|
||||
setSonderName(editEntry.SonderName ?? '');
|
||||
setSpende(editEntry.Spende ?? '');
|
||||
setSpendeBetragRaw(editEntry.SpendeBetrag != null ? String(editEntry.SpendeBetrag) : '');
|
||||
if (editEntry.WetterTemp !== null) {
|
||||
const t = editEntry.WetterTemp ?? 0;
|
||||
setWetter({ temp: t, feuchte: Math.round(editEntry.WetterFeuchte ?? 0), druck: Math.round(editEntry.WetterDruck ?? 0) });
|
||||
@@ -105,6 +110,8 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
setObjekte([]);
|
||||
setBemerkungen('');
|
||||
setSonderName('');
|
||||
setSpende('');
|
||||
setSpendeBetragRaw('');
|
||||
setBesucher('');
|
||||
}
|
||||
}, [editEntry, currentUserBeo]);
|
||||
@@ -137,12 +144,20 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
}
|
||||
}, [editEntry]);
|
||||
|
||||
// Objekte und Besucher zurücksetzen beim Wechsel der Art der Führung
|
||||
// Objekte, Besucher und Spende zurücksetzen beim Wechsel der Art der Führung
|
||||
useEffect(() => {
|
||||
const vorherigeArt = prevArtRef.current;
|
||||
prevArtRef.current = artFuehrung;
|
||||
setObjekte([]);
|
||||
if (NO_OBJEKTE_ARTEN.includes(artFuehrung)) {
|
||||
setBesucher('');
|
||||
}
|
||||
// Nicht beim ersten Lauf leeren — sonst überschreibt der Start-Wert 'RF'
|
||||
// die aus editEntry vorbelegte Spende.
|
||||
if (vorherigeArt !== null && artFuehrung !== 'SF') {
|
||||
setSpende('');
|
||||
setSpendeBetragRaw('');
|
||||
}
|
||||
}, [artFuehrung]);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
@@ -162,6 +177,15 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
setError('Start- und Endzeit sind identisch.');
|
||||
return;
|
||||
}
|
||||
const spendeBetrag = parseFloat(spendeBetragRaw.replace(',', '.'));
|
||||
if (artFuehrung === 'SF' && spende === '') {
|
||||
setError('Bitte Spende auswählen.');
|
||||
return;
|
||||
}
|
||||
if (artFuehrung === 'SF' && spende === 'bar' && !(spendeBetrag > 0)) {
|
||||
setError('Bitte Spendenbetrag eingeben.');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
|
||||
@@ -169,6 +193,8 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
Kuppel: kuppel,
|
||||
ArtFuehrung: artFuehrung,
|
||||
SonderName: artFuehrung === 'SF' ? sonderName : null,
|
||||
Spende: artFuehrung === 'SF' && spende !== '' ? spende : null,
|
||||
SpendeBetrag: artFuehrung === 'SF' && spende === 'bar' ? spendeBetrag : null,
|
||||
Beginn: beginn,
|
||||
Ende: ende,
|
||||
Besucher: besucher === '' ? 0 : besucher,
|
||||
@@ -197,6 +223,9 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
setBeos([currentUserBeo]);
|
||||
setObjekte([]);
|
||||
setBemerkungen('');
|
||||
setSonderName('');
|
||||
setSpende('');
|
||||
setSpendeBetragRaw('');
|
||||
}
|
||||
onSaved();
|
||||
} catch {
|
||||
@@ -280,9 +309,10 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Name/Gruppe bei Sonderführung */}
|
||||
{/* Name/Gruppe und Spende bei Sonderführung */}
|
||||
{artFuehrung === 'SF' && (
|
||||
<div>
|
||||
<div className="flex flex-wrap gap-3 items-end">
|
||||
<div className="flex-1 min-w-[160px]">
|
||||
<label className={labelCls}>Name / Gruppe</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -293,6 +323,28 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
className="w-full px-2 py-1 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-900 focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0 w-32">
|
||||
<label className={labelCls}>Spende</label>
|
||||
<CustomSelect
|
||||
id="spende-select"
|
||||
placeholder={spende === '' ? 'bitte wählen' : spendeLabel(spende)}
|
||||
options={SPENDE_ARTEN.map((s) => ({ value: s, label: spendeLabel(s) }))}
|
||||
onChange={(v) => setSpende(v as SpendeArt)}
|
||||
/>
|
||||
</div>
|
||||
{spende === 'bar' && (
|
||||
<div className="shrink-0">
|
||||
<label className={labelCls}>Betrag (€)</label>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={spendeBetragRaw}
|
||||
onChange={(e) => setSpendeBetragRaw(e.target.value)}
|
||||
className="w-24 px-2 py-1 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-900 focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* BEOs */}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { Kuppel, LogbuchEintrag } from '@/types/logbuch';
|
||||
import { spendeLabel } from '@/types/logbuch';
|
||||
|
||||
interface Props {
|
||||
kuppel: Kuppel;
|
||||
@@ -255,6 +256,13 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, currentUserKue
|
||||
<td className={cell}>
|
||||
<div>{e.ArtFuehrung}</div>
|
||||
{e.SonderName && <div className="text-xs text-gray-500">{e.SonderName}</div>}
|
||||
{e.Spende && (
|
||||
<div className="text-xs text-gray-500">
|
||||
Spende: {spendeLabel(e.Spende)}
|
||||
{e.Spende === 'bar' && e.SpendeBetrag != null &&
|
||||
' ' + Number(e.SpendeBetrag).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className={`${cell} text-center`}>{e.Besucher || ''}</td>
|
||||
<td className={cell}>
|
||||
|
||||
@@ -85,6 +85,8 @@ export interface CreateLogbuchData {
|
||||
Kuppel: string;
|
||||
ArtFuehrung: string;
|
||||
SonderName?: string | null;
|
||||
Spende?: string | null;
|
||||
SpendeBetrag?: number | null;
|
||||
Beginn: string;
|
||||
Ende: string;
|
||||
Besucher?: number;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Migration: Spende-Feld für Sonderführungen (Version 1.12.0)
|
||||
-- Spende wird nur bei ArtFuehrung = 'SF' gesetzt, sonst NULL.
|
||||
-- SpendeBetrag ist nur bei Spende = 'bar' gefüllt.
|
||||
-- Hinweis: In der DB steht der ASCII-Schlüssel 'ueberw', angezeigt wird "überw.".
|
||||
ALTER TABLE logbuch
|
||||
ADD COLUMN Spende ENUM('bar','ueberw','kasse','keine') DEFAULT NULL,
|
||||
ADD COLUMN SpendeBetrag DECIMAL(8,2) DEFAULT NULL;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "logbuch",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -240,6 +240,8 @@
|
||||
|
||||
<p><strong>Name / Gruppe</strong> – erscheint nur bei Sonderführung; Name der Gruppe oder Person.</p>
|
||||
|
||||
<p><strong>Spende</strong> – erscheint nur bei Sonderführung, rechts neben Name / Gruppe. Auswahl zwischen <code>bar</code>, <code>überw.</code>, <code>kasse</code> und <code>keine</code>; die Angabe ist bei Sonderführungen verpflichtend. Bei <code>bar</code> erscheint zusätzlich ein Feld <strong>Betrag (€)</strong>, in das der erhaltene Betrag eingetragen werden muss (Komma oder Punkt als Dezimaltrennzeichen, z. B. <code>25,50</code>).</p>
|
||||
|
||||
<p><strong>BEOs</strong> – beteiligte Beobachter. Der eigene Name ist automatisch vorausgewählt. Weitere BEOs können über das Suchfeld hinzugefügt werden; ein Klick auf × entfernt sie wieder.</p>
|
||||
|
||||
<p><strong>Beobachtete Objekte</strong> – nicht sichtbar bei BEOS und TD; bei Sonnenführungen fest auf „Sonne" gesetzt. Für alle anderen Arten:</p>
|
||||
|
||||
@@ -21,6 +21,21 @@ export function artLabel(a: ArtFuehrung): string {
|
||||
return ARTEN_MAP[a] || a;
|
||||
}
|
||||
|
||||
export type SpendeArt = 'bar' | 'ueberw' | 'kasse' | 'keine';
|
||||
|
||||
export const SPENDE_MAP: Record<SpendeArt, string> = {
|
||||
bar: 'bar',
|
||||
ueberw: 'überw.',
|
||||
kasse: 'kasse',
|
||||
keine: 'keine',
|
||||
};
|
||||
|
||||
export const SPENDE_ARTEN = Object.keys(SPENDE_MAP) as SpendeArt[];
|
||||
|
||||
export function spendeLabel(s: SpendeArt): string {
|
||||
return SPENDE_MAP[s] || s;
|
||||
}
|
||||
|
||||
export interface BeoOption {
|
||||
ID: number;
|
||||
Kuerzel: string;
|
||||
@@ -48,6 +63,8 @@ export interface LogbuchEintrag {
|
||||
Kuppel: Kuppel;
|
||||
ArtFuehrung: ArtFuehrung;
|
||||
SonderName: string | null;
|
||||
Spende: SpendeArt | null;
|
||||
SpendeBetrag: number | null;
|
||||
Beginn: string;
|
||||
Ende: string;
|
||||
Besucher: number;
|
||||
|
||||
Reference in New Issue
Block a user