Add RSVP events and Unsplash image support

This commit is contained in:
t.indorf
2026-05-15 21:28:36 +02:00
parent ed6786b6ea
commit 9542def530
13 changed files with 1392 additions and 34 deletions
+156
View File
@@ -0,0 +1,156 @@
"use server";
import { revalidatePath } from "next/cache";
import {
EventParticipationStatus,
TerminStatus,
UserRole,
} from "@prisma/client";
import { z } from "zod";
import { requireKitaSession, requireRole } from "@/lib/auth-utils";
import { prisma } from "@/lib/prisma";
const submitRsvpSchema = z.object({
eventId: z.string().min(1),
childId: z.string().min(1),
status: z.nativeEnum(EventParticipationStatus),
});
export type EventParticipantDto = {
childId: string;
childName: string;
familyName: string;
status: EventParticipationStatus | null;
};
export async function submitEventRsvp(rawPayload: unknown) {
const session = await requireKitaSession();
const parsed = submitRsvpSchema.safeParse(rawPayload);
if (!parsed.success) {
return { error: "Ungültige Eingabedaten." };
}
if (!session.user.familyId) {
return { error: "Dein Account ist noch keinem Haushalt zugeordnet." };
}
const { eventId, childId, status } = parsed.data;
const event = await prisma.termin.findFirst({
where: {
id: eventId,
kitaId: session.user.kitaId,
},
select: {
id: true,
status: true,
requiresRsvp: true,
rsvpDeadline: true,
startDate: true,
},
});
if (!event || !event.requiresRsvp || event.status !== TerminStatus.CONFIRMED) {
return { error: "Für diesen Termin ist keine Rückmeldung aktiv." };
}
const deadline = event.rsvpDeadline ?? event.startDate;
if (deadline < new Date()) {
return { error: "Die Rückmeldefrist ist bereits abgelaufen." };
}
const child = await prisma.child.findFirst({
where: {
id: childId,
kitaId: session.user.kitaId,
familyId: session.user.familyId,
active: true,
},
select: { id: true },
});
if (!child) {
return { error: "Dieses Kind gehört nicht zu deinem Haushalt." };
}
await prisma.eventParticipation.upsert({
where: {
eventId_childId: {
eventId,
childId,
},
},
create: {
kitaId: session.user.kitaId,
eventId,
childId,
status,
},
update: {
status,
},
});
revalidatePath("/dashboard");
revalidatePath("/dashboard/kalender");
revalidatePath("/dashboard/admin/kalender");
return { success: true };
}
export async function getEventParticipants(
eventId: string,
): Promise<{ participants?: EventParticipantDto[]; error?: string }> {
const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]);
const kitaId = session.user.kitaId;
if (!kitaId) {
return { error: "Kein Mandant zugeordnet." };
}
const event = await prisma.termin.findFirst({
where: { id: eventId, kitaId },
select: { id: true },
});
if (!event) {
return { error: "Termin wurde nicht gefunden." };
}
const children = await prisma.child.findMany({
where: {
kitaId,
active: true,
},
select: {
id: true,
firstName: true,
lastName: true,
family: {
select: {
name: true,
},
},
eventParticipations: {
where: { eventId },
select: { status: true },
take: 1,
},
},
orderBy: [
{ family: { name: "asc" } },
{ lastName: "asc" },
{ firstName: "asc" },
],
});
return {
participants: children.map((child) => ({
childId: child.id,
childName: `${child.firstName} ${child.lastName}`,
familyName: child.family.name,
status: child.eventParticipations[0]?.status ?? null,
})),
};
}