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:
+67
-15
@@ -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,18 +309,41 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Name/Gruppe bei Sonderführung */}
|
||||
{/* Name/Gruppe und Spende bei Sonderführung */}
|
||||
{artFuehrung === 'SF' && (
|
||||
<div>
|
||||
<label className={labelCls}>Name / Gruppe</label>
|
||||
<input
|
||||
type="text"
|
||||
value={sonderName}
|
||||
onChange={(e) => setSonderName(e.target.value.slice(0, 200))}
|
||||
maxLength={200}
|
||||
placeholder="Name oder Gruppe der Sonderführung"
|
||||
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 className="flex flex-wrap gap-3 items-end">
|
||||
<div className="flex-1 min-w-[160px]">
|
||||
<label className={labelCls}>Name / Gruppe</label>
|
||||
<input
|
||||
type="text"
|
||||
value={sonderName}
|
||||
onChange={(e) => setSonderName(e.target.value.slice(0, 200))}
|
||||
maxLength={200}
|
||||
placeholder="Name oder Gruppe der Sonderführung"
|
||||
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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user