Apply Anpassung_1: art-specific object logic, responsive design, remove Kuppel field

- Sonnenführung: fixes Sonne as sole object, no selector shown
- BEO-Sitzung / Technischer Dienst: object field hidden, DB stays empty
- Kuppel field removed from form (determined by active tab)
- Responsive layout: single-column on mobile, side-by-side on sm+
- Touch-friendly input sizes (py-3, text-base)
- Compact header and tab labels on small screens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 18:05:40 +02:00
parent 12be2f1db2
commit 6a655212bf
4 changed files with 125 additions and 85 deletions

View File

@@ -24,6 +24,9 @@ function nowLocalDatetime(): string {
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
}
const NO_OBJEKTE_ARTEN: ArtFuehrung[] = ['BEO-Sitzung', 'Technischer Dienst'];
const SONNE_ART: ArtFuehrung = 'Sonnenführung';
export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved }: Props) {
const [artFuehrung, setArtFuehrung] = useState<ArtFuehrung>('Reguläre Führung');
const [beginn, setBeginn] = useState(nowLocalDatetime());
@@ -37,6 +40,9 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
const showObjekte = !NO_OBJEKTE_ARTEN.includes(artFuehrung);
const isSonne = artFuehrung === SONNE_ART;
useEffect(() => {
fetch('/api/wetter')
.then((r) => { if (!r.ok) throw new Error(); return r.json(); })
@@ -94,6 +100,15 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
}
}, [editEntry]);
// Objekte-Vorauswahl je nach Art der Führung
useEffect(() => {
if (artFuehrung === SONNE_ART) {
setObjekte([{ ID: null, Name: 'Sonne' }]);
} else if (NO_OBJEKTE_ARTEN.includes(artFuehrung)) {
setObjekte([]);
}
}, [artFuehrung]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
@@ -107,7 +122,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
Ende: ende,
Besucher: besucher,
beoIds: beos.map((b) => b.ID),
objekte,
objekte: showObjekte ? objekte : [],
Bemerkungen: bemerkungen,
Wetter: wetter,
};
@@ -139,80 +154,89 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-2xl">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Art der Führung</label>
<select
value={artFuehrung}
onChange={(e) => setArtFuehrung(e.target.value as ArtFuehrung)}
className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
>
{ARTEN.map((a) => (
<option key={a} value={a}>{a}</option>
))}
</select>
</div>
const inputCls = 'w-full px-3 py-3 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none';
const labelCls = 'block text-sm font-medium text-gray-700 mb-1';
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kuppel</label>
<input
type="text"
value={kuppel}
readOnly
className="w-full px-3 py-2 border-2 border-gray-300 rounded-lg bg-gray-100 text-sm text-gray-600"
/>
</div>
return (
<form onSubmit={handleSubmit} className="space-y-5 max-w-2xl">
{/* Art der Führung — volle Breite */}
<div>
<label className={labelCls}>Art der Führung</label>
<select
value={artFuehrung}
onChange={(e) => setArtFuehrung(e.target.value as ArtFuehrung)}
className={inputCls}
>
{ARTEN.map((a) => (
<option key={a} value={a}>{a}</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-4">
{/* Beginn / Ende — nebeneinander auf Desktop, untereinander auf Mobile */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beginn</label>
<label className={labelCls}>Beginn</label>
<input
type="datetime-local"
value={beginn}
onChange={(e) => setBeginn(e.target.value)}
required
className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
className={inputCls}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Ende</label>
<label className={labelCls}>Ende</label>
<input
type="datetime-local"
value={ende}
onChange={(e) => setEnde(e.target.value)}
required
className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
className={inputCls}
/>
</div>
</div>
{/* Besucher */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Anzahl Besucher</label>
<label className={labelCls}>Anzahl Besucher</label>
<input
type="number"
value={besucher}
onChange={(e) => setBesucher(parseInt(e.target.value) || 0)}
min={0}
max={9999}
className="w-32 px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
className="w-32 px-3 py-3 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none"
/>
</div>
{/* BEOs */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">BEOs</label>
<label className={labelCls}>BEOs</label>
<BeoSelector selected={beos} onChange={setBeos} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beobachtete Objekte</label>
<ObjektSelector selected={objekte} onChange={setObjekte} />
</div>
{/* Objekte — abhängig von der Art der Führung */}
{showObjekte && (
<div>
<label className={labelCls}>Beobachtete Objekte</label>
{isSonne ? (
<div className="flex items-center gap-2">
<span className="inline-flex items-center bg-green-100 text-green-800 text-sm px-3 py-1.5 rounded-full">
Sonne
</span>
<span className="text-xs text-gray-500">(bei Sonnenführung fest vorgegeben)</span>
</div>
) : (
<ObjektSelector selected={objekte} onChange={setObjekte} />
)}
</div>
)}
{/* Bemerkungen */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<label className={labelCls}>
Bemerkungen
<span className="ml-2 text-gray-400 font-normal text-xs">({bemerkungen.length}/500)</span>
</label>
@@ -220,15 +244,16 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
value={bemerkungen}
onChange={(e) => setBemerkungen(e.target.value.slice(0, 500))}
rows={3}
className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none resize-y"
className="w-full px-3 py-3 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none resize-y"
placeholder="Freier Text (max. 500 Zeichen)"
/>
</div>
{/* Wetter */}
{wetter && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Wetter (aktuell)</label>
<div className="flex gap-4 text-sm text-gray-600 bg-gray-50 border border-gray-200 rounded-lg px-3 py-2">
<label className={labelCls}>Wetter (aktuell)</label>
<div className="flex flex-wrap gap-4 text-sm text-gray-600 bg-gray-50 border border-gray-200 rounded-lg px-3 py-2">
<span>🌡 {wetter.temp} °C</span>
<span>💧 {wetter.feuchte} %</span>
<span>🌬 {wetter.druck} hPa</span>
@@ -247,11 +272,12 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
</div>
)}
<div className="flex gap-3">
{/* Buttons — volle Breite auf Mobile */}
<div className="flex flex-col sm:flex-row gap-3">
<button
type="submit"
disabled={saving}
className="px-6 py-2 bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium rounded-lg transition-colors disabled:opacity-50 text-sm"
className="w-full sm:w-auto px-6 py-3 bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium rounded-lg transition-colors disabled:opacity-50 text-base"
>
{saving ? 'Speichern...' : editEntry ? 'Änderungen speichern' : 'Eintrag speichern'}
</button>
@@ -259,7 +285,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
<button
type="button"
onClick={onSaved}
className="px-6 py-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium rounded-lg transition-colors text-sm"
className="w-full sm:w-auto px-6 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium rounded-lg transition-colors text-base"
>
Abbrechen
</button>