Kalender-Warnschwelle konfigurierbar + Saison-Datepicker

- kalender_warnung_prozent Setting: Admin kann Grün→Orange-Schwelle einstellen
- Verfügbarkeits-API liefert Warnschwelle mit, BookingCalendar nutzt dynamischen Wert
- Saison-Start/Ende als Date-Picker statt Textfeld im Admin-Dashboard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael
2026-03-03 20:24:44 +01:00
parent ccc6a9b4ec
commit bb20461cc2
3 changed files with 38 additions and 25 deletions

View File

@@ -564,14 +564,16 @@ export default function AdminDashboardPage() {
<div className="space-y-5 max-w-xl"> <div className="space-y-5 max-w-xl">
<h3 className="text-lg font-bold text-primary">Einstellungen</h3> <h3 className="text-lg font-bold text-primary">Einstellungen</h3>
<div className="bg-white rounded-2xl border border-border divide-y divide-border/50"> <div className="bg-white rounded-2xl border border-border divide-y divide-border/50">
{settings.map((s) => ( {settings.map((s) => {
const isDate = s.key === 'saison_start' || s.key === 'saison_ende';
return (
<div key={s.key} className="p-4 flex items-center gap-4"> <div key={s.key} className="p-4 flex items-center gap-4">
<div className="flex-1"> <div className="flex-1">
<div className="font-medium text-sm">{s.beschreibung || s.key}</div> <div className="font-medium text-sm">{s.beschreibung || s.key}</div>
<div className="text-[11px] text-text-muted font-mono">{s.key}</div> <div className="text-[11px] text-text-muted font-mono">{s.key}</div>
</div> </div>
<input <input
type="text" type={isDate ? 'date' : 'text'}
defaultValue={s.value} defaultValue={s.value}
onBlur={(e) => { onBlur={(e) => {
if (e.target.value !== s.value) { if (e.target.value !== s.value) {
@@ -582,7 +584,8 @@ export default function AdminDashboardPage() {
aria-label={s.beschreibung || s.key} aria-label={s.beschreibung || s.key}
/> />
</div> </div>
))} );
})}
</div> </div>
<p className="text-xs text-text-muted"> <p className="text-xs text-text-muted">
Änderungen werden beim Verlassen des Feldes gespeichert. Änderungen werden beim Verlassen des Feldes gespeichert.

View File

@@ -37,18 +37,24 @@ export async function GET(request: NextRequest) {
summe_m3: info.summe_m3, summe_m3: info.summe_m3,
})); }));
// Max m³ pro Tag laden // Settings laden (max_m3_per_day + kalender_warnung_prozent)
const { data: setting } = await supabase const { data: settingsData } = await supabase
.from('settings') .from('settings')
.select('value') .select('key, value')
.eq('key', 'max_m3_per_day') .in('key', ['max_m3_per_day', 'kalender_warnung_prozent']);
.single();
const maxM3PerDay = setting ? parseInt(setting.value) : 150; const settingsMap: Record<string, string> = {};
settingsData?.forEach((s: { key: string; value: string }) => {
settingsMap[s.key] = s.value;
});
const maxM3PerDay = settingsMap['max_m3_per_day'] ? parseInt(settingsMap['max_m3_per_day']) : 150;
const kalenderWarnungProzent = settingsMap['kalender_warnung_prozent'] ? parseInt(settingsMap['kalender_warnung_prozent']) : 30;
return NextResponse.json({ return NextResponse.json({
verfuegbarkeit, verfuegbarkeit,
max_m3_per_day: maxM3PerDay, max_m3_per_day: maxM3PerDay,
kalender_warnung_prozent: kalenderWarnungProzent,
}); });
} catch (err) { } catch (err) {
console.error('Verfügbarkeit Error:', err); console.error('Verfügbarkeit Error:', err);

View File

@@ -37,6 +37,7 @@ export default function BookingCalendar({ onDateSelect, selectedDate, requestedM
const [viewYear] = useState(currentYear); const [viewYear] = useState(currentYear);
const [auslastungM3, setAuslastungM3] = useState<Record<string, number>>({}); const [auslastungM3, setAuslastungM3] = useState<Record<string, number>>({});
const [maxM3PerDay, setMaxM3PerDay] = useState(150); const [maxM3PerDay, setMaxM3PerDay] = useState(150);
const [warnungProzent, setWarnungProzent] = useState(30);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const isBrunnen = wasserquelle === 'brunnen'; const isBrunnen = wasserquelle === 'brunnen';
@@ -55,6 +56,9 @@ export default function BookingCalendar({ onDateSelect, selectedDate, requestedM
if (data.max_m3_per_day) { if (data.max_m3_per_day) {
setMaxM3PerDay(data.max_m3_per_day); setMaxM3PerDay(data.max_m3_per_day);
} }
if (data.kalender_warnung_prozent != null) {
setWarnungProzent(data.kalender_warnung_prozent);
}
} catch { } catch {
// silent // silent
} finally { } finally {
@@ -97,7 +101,7 @@ export default function BookingCalendar({ onDateSelect, selectedDate, requestedM
const freeM3 = maxM3PerDay - usedM3; const freeM3 = maxM3PerDay - usedM3;
if (requestedM3 && freeM3 < requestedM3) return 'full'; if (requestedM3 && freeM3 < requestedM3) return 'full';
if (freeM3 <= maxM3PerDay * 0.3) return 'partial'; if (freeM3 <= maxM3PerDay * (warnungProzent / 100)) return 'partial';
return 'available'; return 'available';
} }