import Link from "next/link"; import { format } from "date-fns"; import { de } from "date-fns/locale"; import { Users, CalendarCheck2, ShieldCheck, AlertTriangle, ClipboardList } from "lucide-react"; import { DutyAssignmentStatus, UserRole, NotdienstPlanStatus, TerminStatus } from "@prisma/client"; import { requireKitaSession } from "@/lib/auth-utils"; import { prisma } from "@/lib/prisma"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { OnboardingDialog } from "@/components/OnboardingDialog"; import { AlertButton } from "./alert-button"; import { AbsenceCard } from "./absence-card"; import { NewsTicker } from "./news-ticker"; import { RsvpActionCard } from "./rsvp-action-card"; export const metadata = { title: "Übersicht · Kita-Planer" }; export default async function DashboardPage() { const session = await requireKitaSession(); const now = new Date(); const kita = await prisma.kita.findUniqueOrThrow({ where: { id: session.user.kitaId }, select: { name: true, notdienstModuleEnabled: true, terminModuleEnabled: true, adressbuchModuleEnabled: true, }, }); const today = new Date(); today.setHours(0, 0, 0, 0); // Schnelle Statistiken und persönliche Dienste für die Übersicht. const [ familyCount, childCount, upcomingDuties, onboardingUser, absenceChildren, upcomingAbsences, latestAnnouncements, ] = await Promise.all([ prisma.family.count({ where: { kitaId: session.user.kitaId }, }), prisma.child.count({ where: { kitaId: session.user.kitaId }, }), session.user.familyId ? prisma.dutyAssignment.findMany({ where: { kitaId: session.user.kitaId, familyId: session.user.familyId, status: DutyAssignmentStatus.PLANNED, endDate: { gte: today }, }, select: { id: true, startDate: true, endDate: true, dutyType: { select: { name: true, }, }, }, orderBy: { startDate: "asc" }, take: 5, }) : Promise.resolve([]), prisma.user.findFirstOrThrow({ where: { id: session.user.id, kitaId: session.user.kitaId, }, select: { phone: true, street: true, postalCode: true, city: true, familyId: true, role: true, family: { select: { _count: { select: { children: true, }, }, }, }, }, }), session.user.familyId ? prisma.child.findMany({ where: { kitaId: session.user.kitaId, familyId: session.user.familyId, active: true, }, select: { id: true, firstName: true, lastName: true, }, orderBy: [{ lastName: "asc" }, { firstName: "asc" }], }) : Promise.resolve([]), session.user.familyId ? prisma.absence.findMany({ where: { kitaId: session.user.kitaId, endDate: { gte: today }, child: { familyId: session.user.familyId, }, }, select: { id: true, reason: true, note: true, startDate: true, endDate: true, child: { select: { firstName: true, lastName: true, }, }, }, orderBy: [{ startDate: "asc" }], take: 6, }) : Promise.resolve([]), prisma.announcement.findMany({ where: { kitaId: session.user.kitaId }, select: { id: true, title: true, content: true, createdAt: true, author: { select: { firstName: true, lastName: true, }, }, attachments: { select: { id: true, fileName: true, fileUrl: true, fileType: true, }, }, reads: { where: { userId: session.user.id }, select: { id: true }, take: 1, }, }, orderBy: { createdAt: "desc" }, take: 3, }), ]); const familyChildIds = absenceChildren.map((child) => child.id); const openRsvpTermine = session.user.familyId && familyChildIds.length > 0 ? await prisma.termin.findMany({ where: { kitaId: session.user.kitaId, status: TerminStatus.CONFIRMED, requiresRsvp: true, OR: [ { rsvpDeadline: { gt: now } }, { rsvpDeadline: null, startDate: { gt: now } }, ], }, select: { id: true, title: true, startDate: true, rsvpDeadline: true, participations: { where: { childId: { in: familyChildIds }, }, select: { childId: true, status: true, }, }, }, orderBy: { startDate: "asc" }, take: 5, }) : []; const rsvpActionEvents = openRsvpTermine .map((termin) => { const statusByChildId = new Map( termin.participations.map((participation) => [ participation.childId, participation.status, ]), ); return { id: termin.id, title: termin.title, startDate: termin.startDate.toISOString(), rsvpDeadline: termin.rsvpDeadline?.toISOString() ?? null, children: absenceChildren.map((child) => ({ id: child.id, name: `${child.firstName} ${child.lastName}`, status: statusByChildId.get(child.id) ?? null, })), }; }) .filter((termin) => termin.children.some((child) => !child.status)); const isAdmin = session.user.role === UserRole.ADMIN || session.user.role === UserRole.SUPERADMIN; const canReportAbsences = session.user.role !== UserRole.ERZIEHER; return (

Willkommen, {session.user.name?.split(" ")[0] ?? "zusammen"}!

Hier ist die Übersicht für {kita.name}.

({ id: announcement.id, title: announcement.title, content: announcement.content, createdAt: format(announcement.createdAt, "dd.MM.yyyy", { locale: de, }), authorName: `${announcement.author.firstName} ${announcement.author.lastName}`, isUnread: announcement.reads.length === 0, attachments: announcement.attachments.map((attachment) => ({ id: attachment.id, fileName: attachment.fileName, fileUrl: attachment.fileUrl, fileType: attachment.fileType, })), }))} /> {/* Heutiger Notdienst (Nur für Admins/Koordinatoren) */} {isAdmin && (
)} {canReportAbsences && ( ({ id: child.id, name: `${child.firstName} ${child.lastName}`, }))} absences={upcomingAbsences.map((absence) => ({ id: absence.id, childName: `${absence.child.firstName} ${absence.child.lastName}`, reason: absence.reason, note: absence.note, startDate: format(absence.startDate, "yyyy-MM-dd"), endDate: format(absence.endDate, "yyyy-MM-dd"), }))} /> )} {upcomingDuties.length > 0 && ( Deine anstehenden Dienste Die nächsten Einteilungen für deinen Haushalt. {upcomingDuties.map((duty) => (

{duty.dutyType.name}

{format(duty.startDate, "dd.MM.", { locale: de })} bis{" "} {format(duty.endDate, "dd.MM.yyyy", { locale: de })}

))}
)} {/* Statistik-Kacheln */}
} /> } />
{/* Schnell-Aktionen für Admins */} {isAdmin && ( Schnellstart Die nächsten Schritte, um deine Kita einzurichten. {kita.terminModuleEnabled && ( )} )}
); } function StatCard({ label, value, description, icon, }: { label: string; value: number; description: string; icon: React.ReactNode; }) { return ( {label} {icon}
{value}

{description}

); } async function TodaysNotdienstCard({ kitaId }: { kitaId: string }) { const today = new Date(); today.setHours(0, 0, 0, 0); // Wir suchen das Assignment für heute, aber nur wenn der Plan PUBLISHED ist const assignment = await prisma.notdienstAssignment.findFirst({ where: { kitaId, date: today, plan: { status: NotdienstPlanStatus.PUBLISHED }, }, select: { id: true, child: { select: { firstName: true, lastName: true, family: { select: { name: true, users: { select: { firstName: true, lastName: true, }, }, }, }, }, }, alerts: { select: { status: true, }, }, }, }); if (!assignment) return null; const parentNames = assignment.child.family.users .map((parent) => `${parent.firstName} ${parent.lastName}`) .join(", "); const existingAlert = assignment.alerts[0]; // Kann PENDING, CONFIRMED oder CANCELLED sein return ( Heutiger Notdienst Laut dem veröffentlichten Plan ist heute folgende Familie eingeteilt:

{parentNames || assignment.child.family.name}

Kind: {assignment.child.firstName} {assignment.child.lastName}

{existingAlert ? ( {existingAlert.status === "CONFIRMED" ? "Einsatz bestätigt" : existingAlert.status === "PENDING" ? "Wartet auf Bestätigung" : "Abgebrochen"} ) : ( )}
); }