Bereiche mit Farbe, Arbeitsfenster und Tageskapazität – die erste schreibende Funktion und damit der Beleg, dass die Kette trägt: zod prüft jede Eingabe, die Rolle wird in der Seite und noch einmal in der Server Action geprüft, jede Änderung hinterlässt einen Audit-Eintrag. Die Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name nach Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt. Das gilt auch in verschachtelten Objekten und Listen. Dazu die Protokollseite für die Verwaltung und die Startseite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import { z } from 'zod'
|
|
import { ausUhrzeit } from '@/lib/zeit'
|
|
|
|
/**
|
|
* Regeln fuer Bereiche. Reine Logik, damit Formular und Server Action
|
|
* dieselbe Antwort geben.
|
|
*/
|
|
|
|
export const WOCHENTAGE = [
|
|
'Montag',
|
|
'Dienstag',
|
|
'Mittwoch',
|
|
'Donnerstag',
|
|
'Freitag',
|
|
'Samstag',
|
|
'Sonntag',
|
|
] as const
|
|
|
|
export const KAPAZITAET_FELDER = [
|
|
'kapazitaetMo',
|
|
'kapazitaetDi',
|
|
'kapazitaetMi',
|
|
'kapazitaetDo',
|
|
'kapazitaetFr',
|
|
'kapazitaetSa',
|
|
'kapazitaetSo',
|
|
] as const
|
|
|
|
export type KapazitaetFeld = (typeof KAPAZITAET_FELDER)[number]
|
|
|
|
/** Hex-Farbe mit Raute, drei- oder sechsstellig. */
|
|
export const farbeSchema = z
|
|
.string()
|
|
.trim()
|
|
.regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, 'Bitte eine Farbe wie #2563eb angeben.')
|
|
.transform((f) => f.toLowerCase())
|
|
|
|
const uhrzeitSchema = z
|
|
.string()
|
|
.trim()
|
|
.refine((w) => ausUhrzeit(w) !== null, 'Bitte eine Uhrzeit wie 08:00 angeben.')
|
|
.transform((w) => ausUhrzeit(w) as number)
|
|
|
|
/** Eine Tageskapazitaet: 0 bis 24 Stunden, in Minuten. */
|
|
const kapazitaetSchema = z.coerce
|
|
.number()
|
|
.int('Bitte ganze Minuten angeben.')
|
|
.min(0, 'Nicht weniger als 0 Minuten.')
|
|
.max(1440, 'Nicht mehr als 1440 Minuten, also 24 Stunden.')
|
|
|
|
export const bereichSchema = z
|
|
.object({
|
|
name: z
|
|
.string()
|
|
.trim()
|
|
.min(2, 'Mindestens zwei Zeichen.')
|
|
.max(40, 'Höchstens 40 Zeichen.'),
|
|
farbe: farbeSchema,
|
|
reihenfolge: z.coerce.number().int().min(0).max(999).default(0),
|
|
aktiv: z.coerce.boolean().default(true),
|
|
arbeitsfensterVon: uhrzeitSchema,
|
|
arbeitsfensterBis: uhrzeitSchema,
|
|
kapazitaetMo: kapazitaetSchema,
|
|
kapazitaetDi: kapazitaetSchema,
|
|
kapazitaetMi: kapazitaetSchema,
|
|
kapazitaetDo: kapazitaetSchema,
|
|
kapazitaetFr: kapazitaetSchema,
|
|
kapazitaetSa: kapazitaetSchema,
|
|
kapazitaetSo: kapazitaetSchema,
|
|
})
|
|
.refine((b) => b.arbeitsfensterVon < b.arbeitsfensterBis, {
|
|
path: ['arbeitsfensterBis'],
|
|
message: 'Das Arbeitsfenster muss nach seinem Beginn enden.',
|
|
})
|
|
.refine(
|
|
(b) =>
|
|
KAPAZITAET_FELDER.every(
|
|
(feld) => b[feld] <= b.arbeitsfensterBis - b.arbeitsfensterVon,
|
|
),
|
|
{
|
|
path: ['kapazitaetMo'],
|
|
message: 'Keine Tageskapazität darf länger sein als das Arbeitsfenster.',
|
|
},
|
|
)
|
|
|
|
export type BereichEingabe = z.infer<typeof bereichSchema>
|
|
|
|
/** Summe der Wochenkapazitaet in Minuten. */
|
|
export function wochenKapazitaet(bereich: Record<KapazitaetFeld, number>): number {
|
|
return KAPAZITAET_FELDER.reduce((summe, feld) => summe + bereich[feld], 0)
|
|
}
|
|
|
|
/** Kapazitaet des Wochentags, 0 = Montag. */
|
|
export function kapazitaetAmTag(
|
|
bereich: Record<KapazitaetFeld, number>,
|
|
wochentagIndex: number,
|
|
): number {
|
|
const feld = KAPAZITAET_FELDER[wochentagIndex]
|
|
return feld ? bereich[feld] : 0
|
|
}
|