mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 21:41:17 +00:00
140 lines
5 KiB
TypeScript
140 lines
5 KiB
TypeScript
import { z } from "zod";
|
|
import { VALID_FREQUENCIES } from "../utils/generate-instances";
|
|
|
|
const frequencyEnum = z.enum(VALID_FREQUENCIES as [string, ...string[]], {
|
|
message: "Ungültige Häufigkeit",
|
|
});
|
|
|
|
const weekdaySlotSchema = z.object({ timeFrom: z.string(), timeTo: z.string() });
|
|
|
|
// Neues Format: mehrere Zeitfenster pro Tag. Das alte Einzelobjekt-Format
|
|
// ({ monday: { timeFrom, timeTo } }) wird für Abwärtskompatibilität weiterhin
|
|
// akzeptiert und in ein Array normalisiert.
|
|
const weekdayTimingsSchema = z
|
|
.record(z.string(), z.union([z.array(weekdaySlotSchema), weekdaySlotSchema]))
|
|
.transform((timings) => {
|
|
const normalized: Record<string, { timeFrom: string; timeTo: string }[]> = {};
|
|
for (const [day, value] of Object.entries(timings)) {
|
|
const slots = Array.isArray(value) ? value : [value];
|
|
const filled = slots.filter((s) => s.timeFrom || s.timeTo);
|
|
if (filled.length > 0) normalized[day] = filled;
|
|
}
|
|
return normalized;
|
|
})
|
|
.refine(
|
|
(timings) =>
|
|
Object.values(timings).every((slots) =>
|
|
slots.every(({ timeFrom, timeTo }) => !timeFrom || !timeTo || timeFrom < timeTo)
|
|
),
|
|
{ message: "Endzeit muss nach der Anfangszeit liegen (Wochentag-Uhrzeiten)" }
|
|
)
|
|
.optional();
|
|
|
|
const isoDateOptional = z.union([z.iso.date(), z.literal("")]).optional();
|
|
|
|
// Basis-Schema mit allen Feldern optional — für Entwürfe
|
|
export const courseSchema = z
|
|
.object({
|
|
name: z.string().min(1, "Kursname ist erforderlich").optional().or(z.literal("")),
|
|
sport: z.array(z.string()).optional(),
|
|
level: z.string().optional(),
|
|
date: isoDateOptional,
|
|
timeFrom: z.string().optional().or(z.literal("")),
|
|
timeTo: z.string().optional().or(z.literal("")),
|
|
trainers: z.array(z.string()).optional(),
|
|
room: z.string().optional().or(z.literal("")),
|
|
description: z.string().optional(),
|
|
coverImage: z.string().optional(),
|
|
maxParticipants: z.number().max(100).optional(),
|
|
price: z.number().max(9999.99).optional(),
|
|
isStandingOrder: z.boolean(),
|
|
frequency: frequencyEnum.optional(),
|
|
weekdays: z.array(z.string()).optional(),
|
|
weekdayTimings: weekdayTimingsSchema,
|
|
endDate: isoDateOptional,
|
|
businessId: z.string().optional(),
|
|
spotBooking: z.boolean().optional(),
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
if (data.timeFrom && data.timeTo && data.timeFrom >= data.timeTo) return false;
|
|
return true;
|
|
},
|
|
{ message: "Endzeit muss nach der Anfangszeit liegen", path: ["timeTo"] }
|
|
);
|
|
|
|
// Strenges Schema für veröffentlichte Kurse — alle Pflichtfelder erforderlich
|
|
export const publishedCourseSchema = z
|
|
.object({
|
|
name: z.string().min(1, "Kursname ist erforderlich"),
|
|
sport: z.array(z.string()).min(1, "Mindestens eine Sportart ist erforderlich"),
|
|
level: z.string().optional(),
|
|
date: z.iso.date().min(1, "Datum ist erforderlich"),
|
|
timeFrom: z.string().min(1, "Anfangszeit ist erforderlich"),
|
|
timeTo: z.string().min(1, "Endzeit ist erforderlich"),
|
|
trainers: z
|
|
.array(z.string())
|
|
.min(1, "Mindestens ein Trainer muss ausgewählt werden")
|
|
.optional(),
|
|
room: z.string().min(1, "Raum ist erforderlich"),
|
|
description: z.string().optional(),
|
|
coverImage: z.string().optional(),
|
|
maxParticipants: z
|
|
.number()
|
|
.min(1, "Mindestens 1 Teilnehmer erforderlich")
|
|
.max(100, "Maximal 100 Teilnehmer erlaubt"),
|
|
price: z
|
|
.number()
|
|
.min(0, "Preis muss eine gültige positive Zahl sein")
|
|
.max(9999.99, "Preis darf maximal 9.999,99 € betragen"),
|
|
isStandingOrder: z.boolean(),
|
|
frequency: frequencyEnum.optional(),
|
|
weekdays: z.array(z.string()).optional(),
|
|
weekdayTimings: weekdayTimingsSchema,
|
|
endDate: isoDateOptional,
|
|
businessId: z.string().optional(),
|
|
spotBooking: z.boolean().optional(),
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
if (data.isStandingOrder && !data.frequency) return false;
|
|
return true;
|
|
},
|
|
{ message: "Häufigkeit ist erforderlich bei Daueraufträgen", path: ["frequency"] }
|
|
)
|
|
.refine(
|
|
(data) => {
|
|
if (data.isStandingOrder && !data.endDate) return false;
|
|
return true;
|
|
},
|
|
{ message: "Enddatum ist erforderlich bei Daueraufträgen", path: ["endDate"] }
|
|
)
|
|
.refine(
|
|
(data) => {
|
|
if (data.isStandingOrder && data.date && data.endDate) {
|
|
return new Date(data.endDate) > new Date(data.date);
|
|
}
|
|
return true;
|
|
},
|
|
{ message: "Enddatum muss nach dem Startdatum liegen", path: ["endDate"] }
|
|
)
|
|
.refine(
|
|
(data) => {
|
|
if (data.frequency === "custom" && (!data.weekdays || data.weekdays.length === 0))
|
|
return false;
|
|
return true;
|
|
},
|
|
{
|
|
message: "Mindestens ein Tag muss ausgewählt werden bei individueller Häufigkeit",
|
|
path: ["weekdays"],
|
|
}
|
|
)
|
|
.refine(
|
|
(data) => {
|
|
if (data.timeFrom && data.timeTo && data.timeFrom >= data.timeTo) return false;
|
|
return true;
|
|
},
|
|
{ message: "Endzeit muss nach der Anfangszeit liegen", path: ["timeTo"] }
|
|
);
|
|
|
|
export type CourseFormData = z.infer<typeof courseSchema>;
|