feat: implement emergency duty (Notdienst) swapping for parents

This commit is contained in:
t.indorf
2026-05-23 19:18:30 +02:00
parent 4cd16773c1
commit 3ce5638449
9 changed files with 1638 additions and 119 deletions
+108 -5
View File
@@ -1,8 +1,8 @@
import {
DutyAssignmentStatus,
EventParticipationStatus,
NotdienstSwapStatus,
TerminStatus,
TerminType,
UserRole,
} from "@prisma/client";
import { HelpCircle } from "lucide-react";
@@ -11,9 +11,12 @@ import { requireKitaSession } from "@/lib/auth-utils";
import { prisma } from "@/lib/prisma";
import { AdminTerminModal } from "./_components/admin-termin-modal";
import { PendingAnfragen, type PendingTerminDto } from "./_components/pending-anfragen";
import { type CalendarListItemDto } from "./_components/termin-list";
import { type CalendarListItemDto, type NotdienstSwapOptionDto } from "./_components/termin-list";
import { TerminRequestModal } from "./_components/termin-request-modal";
import { CalendarOverviewClient } from "./_components/calendar-overview-client";
import {
CalendarOverviewClient,
type NotdienstSwapRequestDto,
} from "./_components/calendar-overview-client";
export const metadata = { title: "Terminkalender · Kita-Planer" };
@@ -31,6 +34,7 @@ export default async function KalenderPage({
const resolvedSearchParams = await searchParams;
const currentTab = resolvedSearchParams.tab || "übersicht";
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const confirmedTermineRows = await prisma.termin.findMany({
where: {
@@ -159,6 +163,13 @@ export default async function KalenderPage({
select: {
id: true,
date: true,
planId: true,
plan: {
select: {
year: true,
month: true,
},
},
child: {
select: {
firstName: true,
@@ -175,6 +186,56 @@ export default async function KalenderPage({
orderBy: { date: "asc" },
});
const pendingSwapRows =
!isAdmin && 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 allUserTermine: CalendarListItemDto[] = [
...confirmedTermineRows.map((termin) => ({
...termin,
@@ -213,21 +274,59 @@ export default async function KalenderPage({
}),
...notdienstAssignmentRows.map((assignment) => {
const isOwn = assignment.child.familyId === familyIdForParticipation;
const canRequestSwap =
!isAdmin &&
!isOwn &&
!!session.user.familyId &&
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({
@@ -305,6 +404,8 @@ export default async function KalenderPage({
termine={allUserTermine}
userId={session.user.id}
isAdmin={isAdmin}
notdienstSwapOptions={notdienstSwapOptions}
pendingSwapRequests={pendingSwapRequests}
/>
) : currentTab === "anfragen" ? (
<PendingAnfragen termine={allPendingTermine} />
@@ -313,6 +414,8 @@ export default async function KalenderPage({
termine={[]}
userId={session.user.id}
isAdmin={isAdmin}
notdienstSwapOptions={notdienstSwapOptions}
pendingSwapRequests={pendingSwapRequests}
/>
)}
</div>
@@ -322,6 +425,8 @@ export default async function KalenderPage({
termine={allUserTermine}
userId={session.user.id}
isAdmin={isAdmin}
notdienstSwapOptions={notdienstSwapOptions}
pendingSwapRequests={pendingSwapRequests}
/>
)}
</div>
@@ -405,5 +510,3 @@ function TabLink({
</a>
);
}