import Link from "next/link"; import { BellRing } from "lucide-react"; import { prisma } from "@/lib/prisma"; import { requireAlarmActor } from "@/lib/alarms/auth"; import { managerRoles, importLegacyAlarms } from "@/lib/alarms/service"; import { alarmLabel, dateKey, durationLabel, instantLabel, needsClarification, todayKey, } from "@/lib/alarms/format"; import { addDays, dateOnly } from "@/lib/staff/calculations"; import { AlarmCreateForm, AlarmCommand, AlarmSimpleAction, AlarmRefresh, } from "./forms"; export const metadata = { title: "Notdienstalarm · Kita-Planer" }; const historyLabels: Record = { CONFIRMED: "Übernahme bestätigt", DECLINED: "Absage", LATE_CONFIRMATION: "Verspätete Zusage – Freigabe erforderlich", }; export default async function AlarmPage({ searchParams, }: { searchParams: Promise<{ alarm?: string; assignment?: string }>; }) { const actor = await requireAlarmActor(false), manager = managerRoles.includes(actor.role); const params = await searchParams, now = new Date(), today = todayKey(now); if (manager) await importLegacyAlarms(actor.kitaId); const user = await prisma.user.findUniqueOrThrow({ where: { id: actor.id }, select: { familyId: true }, }); const [kita, alarms, assignments, educators, notifications] = await Promise.all([ prisma.kita.findUniqueOrThrow({ where: { id: actor.kitaId }, select: { name: true, notdienstModuleEnabled: true }, }), prisma.notdienstAlarm.findMany({ where: { kitaId: actor.kitaId, ...(manager ? {} : { familyId: user.familyId ?? "" }), ...(params.alarm ? { id: params.alarm } : { date: { gte: dateOnly(addDays(today, -30)) } }), }, include: { recipients: { include: { user: { select: { email: true, firstName: true } } }, }, emails: { orderBy: { createdAt: "desc" } }, history: manager ? { orderBy: { createdAt: "desc" }, take: 20 } : false, }, orderBy: [{ date: "desc" }, { triggeredAt: "desc" }], take: 100, }), manager ? prisma.notdienstAssignment.findMany({ where: { kitaId: actor.kitaId, date: { gte: dateOnly(today), lt: dateOnly(addDays(today, 93)) }, plan: { status: "PUBLISHED" }, alarms: { none: { activeAssignmentId: { not: null } } }, }, include: { child: { include: { family: { include: { users: { where: { role: "ELTERN", kitaId: actor.kitaId }, select: { email: true }, }, }, }, }, }, }, orderBy: { date: "asc" }, }) : Promise.resolve([]), manager ? prisma.educator.findMany({ where: { kitaId: actor.kitaId, active: true }, select: { id: true, firstName: true, lastName: true }, orderBy: { lastName: "asc" }, }) : Promise.resolve([]), prisma.notdienstAlarmNotification.findMany({ where: { userId: actor.id, alarm: { kitaId: actor.kitaId, ...(manager ? {} : { familyId: user.familyId ?? "" }), }, readAt: null, }, orderBy: { createdAt: "desc" }, take: 20, }), ]); const candidates = assignments.map((a) => ({ id: a.id, label: `${dateKey(a.date).split("-").reverse().join(".")} · ${a.child.family.name}`, emails: a.child.family.users.map((u) => u.email), })); return (

NOTDIENST · {kita.name}

Notdienst alarm

{manager ? "Familien anfragen, Rückmeldungen verfolgen und offene Einsätze klären." : "Eure Rückmeldung gilt für den gesamten Haushalt."}

{manager && kita.notdienstModuleEnabled && ( ({ id: e.id, name: `${e.firstName} ${e.lastName}`, }))} selected={params.assignment} /> )}
{manager && (!process.env.NOTDIENST_JOB_SECRET || process.env.NOTDIENST_JOB_SECRET.length < 32) && (

Die automatische Fristprüfung ist noch nicht eingerichtet. Für Erinnerungen bei geschlossener App muss die Hosting-Konfiguration ergänzt werden. Fristabläufe sind hier bereits sichtbar.

)} {!kita.notdienstModuleEnabled && (

Das Notdienstmodul ist deaktiviert; vorhandene Alarme bleiben einsehbar.

)} {notifications.length > 0 && (
{notifications.length} neue Hinweise
{notifications.map((n) => (
{n.message} Gelesen
))}
)} {params.alarm && ( Alle Alarme anzeigen )} {manager && !candidates.length && !params.alarm && (

Neue Alarme benötigen eine aktuelle oder zukünftige Einteilung in einem veröffentlichten Plan.{" "} Notdienstplan öffnen

)}
{alarms.length ? ( alarms.map((a) => { const clarify = needsClarification(a, now), ended = a.expiresAt <= now; const pending = a.emails.filter((e) => !e.sentAt && !e.skippedAt); const ownRecipient = a.recipients.find( (r) => r.userId === actor.id, ); return (

{dateKey(a.date) === today ? "Heute · " : ""} {dateKey(a.date).split("-").reverse().join(".")}

{a.familyName}

{durationLabel(a)} {ended ? " · Einsatz beendet" : ""}

{alarmLabel(a, now)}

Antwortfrist
{instantLabel(a.deadline)} Uhr

Ausgelöst
{instantLabel(a.triggeredAt)} Uhr

{a.respondedAt && (

Rückmeldung
{instantLabel(a.respondedAt)} Uhr ·{" "} {a.responseSource === "PHONE" ? "Telefonisch dokumentiert" : a.responseSource === "COORDINATION" ? "Freigabe durch Koordination" : a.responseSource === "LEGACY" ? "Bisherige Alarmierung" : "Haushaltsantwort"}

)}
{manager ? ( <>
{a.status === "LATE_CONFIRMATION" && !ended && ( )} {a.status !== "CANCELLED" && !ended && ( )} {a.status !== "CANCELLED" && ( )} {pending.length > 0 && ( Ausstehende E-Mails erneut senden ({pending.length}) )}
{clarify && (

Bitte den Haushalt kontaktieren oder Ersatz manuell organisieren. Weitere Familien werden nicht automatisch alarmiert.

)}
Versandstand ( {a.emails.filter((e) => e.sentAt).length} angenommen,{" "} {pending.length} ausstehend)
{a.emails.map((e) => (

{a.recipients.find((r) => r.userId === e.userId) ?.user.email ?? "Koordination / Admin"}{" "} ·{" "} {e.kind === "REQUEST" ? "Anfrage" : e.kind === "CANCEL" ? "Stornierung" : "Hinweis"} :{" "} {e.sentAt ? "Vom Maildienst angenommen" : e.skippedAt ? "Nicht mehr erforderlich / Empfänger geändert" : e.failed ? "Versand fehlgeschlagen – wiederholbar" : "Versand ausstehend"}

))}
Interne Details und Verlauf
{a.educatorId && (

Ausgefallene Fachkraft:{" "} {educators.find((e) => e.id === a.educatorId) ?.firstName ?? "Ehemalige / inaktive Fachkraft"}{" "} {educators.find((e) => e.id === a.educatorId) ?.lastName ?? ""}

)} {a.notes &&

Interne Notiz: {a.notes}

} {a.cancelReason && (

Stornierungsgrund: {a.cancelReason}

)} {a.history.map((h) => (

{instantLabel(h.createdAt)} · {h.action}:{" "} {historyLabels[h.details] ?? h.details}

))}
) : ( <> {ownRecipient && a.status === "PENDING" && !ended && ( Für unseren Haushalt antworten )}

{a.status === "LATE_CONFIRMATION" ? "Eure verspätete Zusage wartet auf Freigabe durch die Koordination." : a.status === "CONFIRMED" ? "Die Kita rechnet mit euch. Änderungen bitte mit der Koordination klären." : a.status === "CANCELLED" ? "Ihr müsst für diesen Alarm nicht einspringen." : a.status === "DECLINED" ? "Eure Absage ist gespeichert. Die Koordination organisiert Ersatz." : "Eine Antwort eines Elternteils gilt für euren ganzen Haushalt."}

)}
); }) ) : (
{params.alarm ? "Kein zugänglicher Alarm gefunden." : "Noch keine Notdienstalarme in diesem Zeitraum."}
)}
); }