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:
@@ -564,25 +564,28 @@ 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) => {
|
||||||
<div key={s.key} className="p-4 flex items-center gap-4">
|
const isDate = s.key === 'saison_start' || s.key === 'saison_ende';
|
||||||
<div className="flex-1">
|
return (
|
||||||
<div className="font-medium text-sm">{s.beschreibung || s.key}</div>
|
<div key={s.key} className="p-4 flex items-center gap-4">
|
||||||
<div className="text-[11px] text-text-muted font-mono">{s.key}</div>
|
<div className="flex-1">
|
||||||
|
<div className="font-medium text-sm">{s.beschreibung || s.key}</div>
|
||||||
|
<div className="text-[11px] text-text-muted font-mono">{s.key}</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type={isDate ? 'date' : 'text'}
|
||||||
|
defaultValue={s.value}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (e.target.value !== s.value) {
|
||||||
|
updateSetting(s.key, e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="border border-border rounded-xl px-3 py-2.5 text-sm w-48 text-right"
|
||||||
|
aria-label={s.beschreibung || s.key}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
);
|
||||||
type="text"
|
})}
|
||||||
defaultValue={s.value}
|
|
||||||
onBlur={(e) => {
|
|
||||||
if (e.target.value !== s.value) {
|
|
||||||
updateSetting(s.key, e.target.value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="border border-border rounded-xl px-3 py-2.5 text-sm w-48 text-right"
|
|
||||||
aria-label={s.beschreibung || s.key}
|
|
||||||
/>
|
|
||||||
</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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user