feat: add parent announcements and family or child event responses

This commit is contained in:
t.indorf
2026-10-09 21:44:19 +02:00
parent 2d3b82eb80
commit 979f795341
29 changed files with 2031 additions and 472 deletions
+95 -66
View File
@@ -9,9 +9,16 @@ import { HelpCircle } from "lucide-react";
import { requireKitaSession } from "@/lib/auth-utils";
import { prisma } from "@/lib/prisma";
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 {
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,
@@ -36,50 +43,62 @@ export default async function KalenderPage({
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
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,
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,
participations: {
where: {
child: {
familyId: familyIdForParticipation,
},
},
select: {
childId: true,
status: true,
},
},
select: {
childId: true,
status: true,
},
},
},
orderBy: { startDate: "asc" },
});
orderBy: { startDate: "asc" },
}),
]);
const myPendingTermineRows = await prisma.termin.findMany({
where: {
@@ -98,6 +117,13 @@ export default async function KalenderPage({
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: {
@@ -236,27 +262,32 @@ export default async function KalenderPage({
})
: [];
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((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,
),
})),
...confirmedTermineRows.map(mapTermin),
...myPendingTermineRows.map(mapTermin),
...dutyAssignmentRows.map((assignment) => {
const isOwn = assignment.familyId === familyIdForParticipation;
return {
@@ -316,7 +347,9 @@ export default async function KalenderPage({
(request) => ({
id: request.id,
direction:
request.requesterFamilyId === session.user.familyId ? "outgoing" : "incoming",
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}`,
@@ -367,9 +400,7 @@ export default async function KalenderPage({
</p>
<h1 className="mt-2 text-[36px] font-normal leading-tight tracking-[-0.025em] text-[var(--ink)]">
Was steht{" "}
<em className="font-normal italic text-[var(--accent-deep)]">
an
</em>
<em className="font-normal italic text-[var(--accent-deep)]">an</em>
?
</h1>
<p className="mt-2 max-w-2xl text-[15px] leading-6 text-[var(--ink-soft)]">
@@ -433,8 +464,6 @@ export default async function KalenderPage({
);
}
function TabBar({
currentTab,
overviewCount,