import { DutyAssignmentStatus, EventParticipationStatus, TerminStatus, TerminType, UserRole, } from "@prisma/client"; import { HelpCircle, ListFilter, Search } from "lucide-react"; import { requireKitaSession } from "@/lib/auth-utils"; import { prisma } from "@/lib/prisma"; import { Input } from "@/components/ui/input"; import { AdminTerminModal } from "./_components/admin-termin-modal"; import { PendingAnfragen, type PendingTerminDto } from "./_components/pending-anfragen"; import { TerminList, type CalendarListItemDto } from "./_components/termin-list"; import { TerminRequestModal } from "./_components/termin-request-modal"; 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 confirmedTermineRows = await 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, 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, 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, dutyType: { select: { name: true, }, }, family: { select: { name: true, }, }, }, orderBy: { startDate: "asc" }, }); const allUserTermine: CalendarListItemDto[] = [ ...confirmedTermineRows.map((termin) => ({ ...termin, kind: "termin" as const, showParticipantInfo: isAdmin || termin.participations.some( (participation) => participation.status === EventParticipationStatus.ATTENDING, ), })), ...myPendingTermineRows.map((termin) => ({ ...termin, kind: "termin" as const, showParticipantInfo: isAdmin || termin.participations.some( (participation) => participation.status === EventParticipationStatus.ATTENDING, ), })), ...dutyAssignmentRows.map((assignment) => ({ kind: "duty" as const, id: assignment.id, title: assignment.dutyType.name, familyName: assignment.family.name, status: assignment.status, startDate: assignment.startDate, endDate: assignment.endDate, })), ].sort((a, b) => a.startDate.getTime() - b.startDate.getTime()); 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

Was steht{" "} an ?

Alle anstehenden Termine, Feste, Dienste und Schließtage im Überblick.

Anfragen müssen vom Vorstand bestätigt werden, direkte Anlage erscheint sofort im Kalender. {isAdmin && }
{isAdmin ? (
{currentTab === "übersicht" ? ( ) : currentTab === "anfragen" ? ( ) : ( )}
) : ( )}
); } function CalendarOverview({ termine, userId, isAdmin, }: { termine: CalendarListItemDto[]; userId: string; isAdmin: boolean; }) { return (
{["Liste", "Monat", "Agenda"].map((label, index) => ( ))}
); } function TabBar({ currentTab, overviewCount, pendingCount, }: { currentTab: string; overviewCount: number; pendingCount: number; }) { return (
Übersicht 0} > Ausstehende Anfragen Vergangene
); } function TabLink({ href, active, count, attention, children, }: { href: string; active: boolean; count?: number; attention?: boolean; children: React.ReactNode; }) { return ( {children} {typeof count === "number" && ( {count} )} ); } function CategoryChips() { const categories = [ ["Schließtag", TerminType.SCHLIESSTAG], ["Teamtag", TerminType.TEAMTAG], ["Elternabend", TerminType.ELTERNABEND], ["Fest", TerminType.KITA_FEST], ["Geburtstag", TerminType.GEBURTSTAG_INTERN], ["Sonstiges", TerminType.SONSTIGES], ] as const; return (
Filter {categories.map(([label, type]) => ( {label} ))}
); } function getCategoryColor(type: TerminType) { switch (type) { case TerminType.SCHLIESSTAG: case TerminType.TEAMTAG: return "var(--danger)"; case TerminType.ELTERNABEND: return "oklch(0.50 0.10 280)"; case TerminType.KITA_FEST: return "var(--good)"; case TerminType.GEBURTSTAG_INTERN: return "oklch(0.50 0.10 320)"; default: return "var(--ink-muted)"; } }