import { DutyAssignmentStatus, EventParticipationStatus, NotdienstSwapStatus, TerminStatus, UserRole, } from "@prisma/client"; import { HelpCircle } from "lucide-react"; import { requireKitaSession } from "@/lib/auth-utils"; import { prisma } from "@/lib/prisma"; import { canRequestNotdienstSwap } from "@/lib/notdienst-swaps"; import { getRsvpEvents } from "@/lib/rsvp-events"; import { AdminTerminModal } from "./_components/admin-termin-modal"; import { PendingAnfragen, type PendingTerminDto, } from "./_components/pending-anfragen"; import { type CalendarListItemDto, type NotdienstSwapOptionDto, } from "./_components/termin-list"; import { TerminRequestModal } from "./_components/termin-request-modal"; import { CalendarOverviewClient, type NotdienstSwapRequestDto, } from "./_components/calendar-overview-client"; export const metadata = { title: "Terminkalender · Kita-Planer" }; export default async function KalenderPage({ searchParams, }: { searchParams: Promise<{ tab?: string }>; }) { const session = await requireKitaSession(); const isAdmin = session.user.role === UserRole.ADMIN || session.user.role === UserRole.KOORDINATOR; const familyIdForParticipation = session.user.familyId ?? "__no_family__"; const resolvedSearchParams = await searchParams; const currentTab = resolvedSearchParams.tab || "übersicht"; const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const [rsvpEventRows, confirmedTermineRows] = await Promise.all([ getRsvpEvents(session), prisma.termin.findMany({ where: { kitaId: session.user.kitaId, status: TerminStatus.CONFIRMED, }, select: { id: true, title: true, description: true, type: true, status: true, startDate: true, endDate: true, allDay: true, mitbringselListEnabled: true, requiresRsvp: true, rsvpMode: true, rsvpDeadline: true, _count: { select: { participations: true, familyParticipations: true }, }, familyParticipations: { where: { familyId: familyIdForParticipation }, select: { status: true }, }, participantInfo: true, mitbringselItems: { select: { id: true, userId: true, content: true, user: { select: { firstName: true, lastName: true, }, }, }, }, participations: { where: { child: { familyId: familyIdForParticipation, }, }, select: { childId: true, status: true, }, }, }, orderBy: { startDate: "asc" }, }), ]); const myPendingTermineRows = await prisma.termin.findMany({ where: { kitaId: session.user.kitaId, status: TerminStatus.PENDING, createdById: session.user.id, }, select: { id: true, title: true, description: true, type: true, status: true, startDate: true, endDate: true, allDay: true, mitbringselListEnabled: true, requiresRsvp: true, rsvpMode: true, rsvpDeadline: true, _count: { select: { participations: true, familyParticipations: true } }, familyParticipations: { where: { familyId: familyIdForParticipation }, select: { status: true }, }, participantInfo: true, mitbringselItems: { select: { id: true, userId: true, content: true, user: { select: { firstName: true, lastName: true, }, }, }, }, participations: { where: { child: { familyId: familyIdForParticipation, }, }, select: { childId: true, status: true, }, }, }, orderBy: { startDate: "asc" }, }); const dutyAssignmentRows = await prisma.dutyAssignment.findMany({ where: { kitaId: session.user.kitaId, status: DutyAssignmentStatus.PLANNED, endDate: { gte: now }, }, select: { id: true, startDate: true, endDate: true, status: true, familyId: true, dutyType: { select: { name: true, }, }, family: { select: { name: true, }, }, }, orderBy: { startDate: "asc" }, }); const notdienstAssignmentRows = await prisma.notdienstAssignment.findMany({ where: { kitaId: session.user.kitaId, plan: { status: "PUBLISHED", }, }, select: { id: true, date: true, planId: true, plan: { select: { year: true, month: true, }, }, child: { select: { firstName: true, lastName: true, familyId: true, family: { select: { name: true, }, }, }, }, }, orderBy: { date: "asc" }, }); const pendingSwapRows = session.user.familyId && canRequestNotdienstSwap(session.user.role, session.user.familyId) ? await prisma.notdienstSwapRequest.findMany({ where: { kitaId: session.user.kitaId, status: NotdienstSwapStatus.PENDING, OR: [ { requesterFamilyId: session.user.familyId }, { targetAssignment: { child: { familyId: session.user.familyId, }, }, }, ], }, select: { id: true, requesterFamilyId: true, message: true, targetAssignment: { select: { date: true, child: { select: { firstName: true, lastName: true, family: { select: { name: true } }, }, }, }, }, offeredAssignment: { select: { date: true, child: { select: { firstName: true, lastName: true, family: { select: { name: true } }, }, }, }, }, }, orderBy: { createdAt: "desc" }, }) : []; const rsvpEvents = new Map(rsvpEventRows.map((event) => [event.id, event])); const mapTermin = ( termin: (typeof confirmedTermineRows)[number], ): CalendarListItemDto => { const showParticipantInfo = isAdmin || termin.familyParticipations.some( (row) => row.status === EventParticipationStatus.ATTENDING, ) || termin.participations.some( (row) => row.status === EventParticipationStatus.ATTENDING, ); const { _count, familyParticipations, ...data } = termin; void familyParticipations; return { ...data, kind: "termin", showParticipantInfo, participantInfo: showParticipantInfo ? termin.participantInfo : null, hasRsvpResponses: _count.participations + _count.familyParticipations > 0, rsvpEvent: rsvpEvents.get(termin.id), }; }; const allUserTermine: CalendarListItemDto[] = [ ...confirmedTermineRows.map(mapTermin), ...myPendingTermineRows.map(mapTermin), ...dutyAssignmentRows.map((assignment) => { const isOwn = assignment.familyId === familyIdForParticipation; return { kind: "duty" as const, id: assignment.id, title: isOwn ? `Mein Elterndienst (${assignment.dutyType.name})` : `${assignment.dutyType.name} (${assignment.family.name})`, familyName: assignment.family.name, status: assignment.status, startDate: assignment.startDate, endDate: assignment.endDate, isOwn, }; }), ...notdienstAssignmentRows.map((assignment) => { const isOwn = assignment.child.familyId === familyIdForParticipation; const canRequestSwap = canRequestNotdienstSwap(session.user.role, session.user.familyId) && !isOwn && assignment.date >= today; return { kind: "notdienst" as const, id: assignment.id, planId: assignment.planId, planYear: assignment.plan.year, planMonth: assignment.plan.month, title: isOwn ? `Mein Notdienst (${assignment.child.firstName})` : `Notdienst (${assignment.child.family.name})`, familyId: assignment.child.familyId, familyName: assignment.child.family.name, childName: `${assignment.child.firstName} ${assignment.child.lastName}`, isOwn, canRequestSwap, startDate: assignment.date, endDate: assignment.date, }; }), ].sort((a, b) => a.startDate.getTime() - b.startDate.getTime()); const notdienstSwapOptions: NotdienstSwapOptionDto[] = notdienstAssignmentRows .filter( (assignment) => assignment.child.familyId === familyIdForParticipation && assignment.date >= today, ) .map((assignment) => ({ id: assignment.id, date: assignment.date, childName: `${assignment.child.firstName} ${assignment.child.lastName}`, familyName: assignment.child.family.name, })); const pendingSwapRequests: NotdienstSwapRequestDto[] = pendingSwapRows.map( (request) => ({ id: request.id, direction: request.requesterFamilyId === session.user.familyId ? "outgoing" : "incoming", targetDate: request.targetAssignment.date, targetFamilyName: request.targetAssignment.child.family.name, targetChildName: `${request.targetAssignment.child.firstName} ${request.targetAssignment.child.lastName}`, offeredDate: request.offeredAssignment.date, offeredFamilyName: request.offeredAssignment.child.family.name, offeredChildName: `${request.offeredAssignment.child.firstName} ${request.offeredAssignment.child.lastName}`, message: request.message, }), ); let allPendingTermine: PendingTerminDto[] = []; if (isAdmin) { const pendingRows = await prisma.termin.findMany({ where: { kitaId: session.user.kitaId, status: TerminStatus.PENDING, }, select: { id: true, title: true, startDate: true, createdAt: true, type: true, allDay: true, createdBy: { select: { firstName: true, lastName: true, }, }, }, orderBy: { createdAt: "desc" }, }); allPendingTermine = pendingRows.map((termin) => ({ ...termin, ageInDays: Math.floor( (now.getTime() - termin.createdAt.getTime()) / (1000 * 60 * 60 * 24), ), })); } return (
TERMINKALENDER
Alle anstehenden Termine, Feste, Dienste und Schließtage im Überblick.