import Link from "next/link"; import { addMonths, getYear, getMonth, format } from "date-fns"; import { de } from "date-fns/locale"; import { TerminType, UserRole } from "@prisma/client"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { requireRole } from "@/lib/auth-utils"; import { prisma } from "@/lib/prisma"; import { getWorkingDaysOfMonth } from "@/lib/date-utils"; import { PlanView } from "./plan-view"; import { SettingsModal } from "./_components/settings-modal"; export const metadata = { title: "Plan-Generierung · Kita-Planer" }; export default async function PlanungsZentralePage({ searchParams, }: { searchParams: Promise<{ year?: string; month?: string }>; }) { const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]); const kitaId = session.user.kitaId!; const resolvedSearchParams = await searchParams; const now = new Date(); const nextMonth = addMonths(now, 1); const targetYear = resolvedSearchParams.year ? parseInt(resolvedSearchParams.year, 10) : getYear(nextMonth); const targetMonth = resolvedSearchParams.month ? parseInt(resolvedSearchParams.month, 10) : (getMonth(nextMonth) + 1); const monthDate = new Date(targetYear, targetMonth - 1, 1); const monthName = format(monthDate, "MMMM", { locale: de }); const workingDays = getWorkingDaysOfMonth(targetYear, targetMonth); const monthStart = new Date(targetYear, targetMonth - 1, 1); const monthEnd = new Date(targetYear, targetMonth, 1); const [plan, families, availabilities, closureCount, kita] = await Promise.all([ prisma.notdienstPlan.findUnique({ where: { kitaId_year_month: { kitaId, year: targetYear, month: targetMonth }, }, select: { id: true, status: true, assignments: { select: { id: true, childId: true, date: true, }, }, }, }), prisma.family.findMany({ where: { kitaId, children: { some: { active: true } }, }, select: { id: true, name: true, children: { where: { active: true }, select: { id: true }, }, }, orderBy: { name: "asc" }, }), prisma.notdienstAvailability.findMany({ where: { kitaId, date: { gte: monthStart, lt: monthEnd, }, }, select: { date: true, child: { select: { familyId: true, }, }, }, orderBy: { date: "asc" }, }), prisma.termin.count({ where: { kitaId, type: TerminType.SCHLIESSTAG, startDate: { gte: monthStart, lt: monthEnd, }, }, }), prisma.kita.findUniqueOrThrow({ where: { id: kitaId }, select: { notdienstMinPerChildPerMonth: true, notdienstReminderDaysBefore: true, }, }), ]); // Wir laden auch alle aktiven Kinder (inkl. Eltern-Namen) für das manuelle Dropdown. // Idealerweise gruppiert man das nach "Verfügbar an diesem Tag" und "Alle", // aber für MVP reichen alle aktiven Kinder. const allChildrenRaw = await prisma.child.findMany({ where: { kitaId, active: true }, select: { id: true, firstName: true, lastName: true, family: { select: { name: true, users: { select: { firstName: true, lastName: true, }, }, }, }, }, orderBy: { lastName: "asc" }, }); const allChildren = allChildrenRaw.map((c) => { const parentName = c.family.users .map((parent) => `${parent.firstName} ${parent.lastName}`) .join(", ") || c.family.name; return { id: c.id, name: `${c.firstName} ${c.lastName}`, parentName, }; }); const availabilityDatesByFamily = new Map>(); availabilities.forEach((availability) => { const familyId = availability.child.familyId; const dates = availabilityDatesByFamily.get(familyId) ?? new Set(); dates.add(availability.date.toISOString()); availabilityDatesByFamily.set(familyId, dates); }); const familyStatuses = families.map((family) => { const dates = Array.from(availabilityDatesByFamily.get(family.id) ?? []); return { id: family.id, name: family.name, selectedDays: dates.map((date) => ({ iso: date, label: new Intl.DateTimeFormat("de-DE", { weekday: "short", day: "2-digit", month: "2-digit", }).format(new Date(date)), })), childCount: family.children.length, }; }); const respondedFamilies = familyStatuses.filter( (family) => family.selectedDays.length > 0, ).length; const openFamilies = Math.max(familyStatuses.length - respondedFamilies, 0); const totalAvailabilitySlots = availabilities.length; const requiredDays = workingDays.length; const coverageFactor = requiredDays > 0 ? totalAvailabilitySlots / requiredDays : 0; // Wir mappen die Assignments auf die Werktage const daysWithAssignments = workingDays.map((day) => { const assignment = plan?.assignments.find( (a) => a.date.getTime() === day.getTime() ); return { date: day.toISOString(), assignmentId: assignment?.id || null, childId: assignment?.childId || null, }; }); return (

NOTDIENST · PLANUNG

Notdienst{" "} zuweisen

Koordiniere und generiere den Notdienst-Plan.

{monthName} {targetYear}
); }