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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user