Compare commits

..

2 Commits

5 changed files with 317 additions and 44 deletions
+301 -22
View File
@@ -1,5 +1,7 @@
import { import {
AbsenceReason, AbsenceReason,
AuditTargetType,
EventParticipationStatus,
InvitationStatus, InvitationStatus,
NotdienstAlertStatus, NotdienstAlertStatus,
NotdienstPlanStatus, NotdienstPlanStatus,
@@ -35,6 +37,9 @@ const DEMO_USER_EMAILS = [
"schmidt@waldameisen.local", "schmidt@waldameisen.local",
"yilmaz@waldameisen.local", "yilmaz@waldameisen.local",
"pending@waldameisen.local", "pending@waldameisen.local",
"fischer@waldameisen.local",
"kowalski@waldameisen.local",
"weber@waldameisen.local",
]; ];
const DEMO_VERIFICATION_TOKENS = [ const DEMO_VERIFICATION_TOKENS = [
@@ -293,11 +298,87 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) {
}, },
}); });
const familyKowalski = await prisma.family.create({
data: {
kitaId: kita.id,
name: "Familie Kowalski",
},
});
const familyWeber = await prisma.family.create({
data: {
kitaId: kita.id,
name: "Familie Weber",
},
});
const elternFischer = await prisma.user.create({
data: {
kitaId: kita.id,
familyId: familyFischer.id,
email: "fischer@waldameisen.local",
passwordHash,
firstName: "Frank",
lastName: "Fischer",
role: UserRole.ELTERN,
privacyPolicyAcceptedAt: consentAt,
privacyPolicyVersion: PRIVACY_POLICY_VERSION,
directoryOptInAt: consentAt,
emailVerifiedAt: consentAt,
phone: "+49 30 5555 1234",
street: "Fischerweg 4",
postalCode: "10247",
city: "Berlin",
},
});
const elternKowalski = await prisma.user.create({
data: {
kitaId: kita.id,
familyId: familyKowalski.id,
email: "kowalski@waldameisen.local",
passwordHash,
firstName: "Jan",
lastName: "Kowalski",
role: UserRole.ELTERN,
privacyPolicyAcceptedAt: consentAt,
privacyPolicyVersion: PRIVACY_POLICY_VERSION,
directoryOptInAt: consentAt,
emailVerifiedAt: consentAt,
phone: "+49 30 5555 5678",
street: "Grenzallee 12",
postalCode: "12057",
city: "Berlin",
},
});
const elternWeber = await prisma.user.create({
data: {
kitaId: kita.id,
familyId: familyWeber.id,
email: "weber@waldameisen.local",
passwordHash,
firstName: "Sabine",
lastName: "Weber",
role: UserRole.ELTERN,
privacyPolicyAcceptedAt: consentAt,
privacyPolicyVersion: PRIVACY_POLICY_VERSION,
directoryOptInAt: consentAt,
emailVerifiedAt: consentAt,
phone: "+49 30 5555 9012",
street: "Weberstrasse 7",
postalCode: "10249",
city: "Berlin",
},
});
return { return {
kita, kita,
familyMueller, familyMueller,
familySchmidtYilmaz, familySchmidtYilmaz,
familyFischer, familyFischer,
familyKowalski,
familyWeber,
superAdmin, superAdmin,
admin, admin,
erzieherUser, erzieherUser,
@@ -305,6 +386,9 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) {
elternSchmidt, elternSchmidt,
elternYilmaz, elternYilmaz,
pendingParent, pendingParent,
elternFischer,
elternKowalski,
elternWeber,
}; };
} }
@@ -313,6 +397,8 @@ async function createChildren({
familyMueller, familyMueller,
familySchmidtYilmaz, familySchmidtYilmaz,
familyFischer, familyFischer,
familyKowalski,
familyWeber,
}: SeedContext) { }: SeedContext) {
const anna = await prisma.child.create({ const anna = await prisma.child.create({
data: { data: {
@@ -365,7 +451,27 @@ async function createChildren({
}, },
}); });
return { anna, ben, clara, emil, nina }; const leo = await prisma.child.create({
data: {
kitaId: kita.id,
familyId: familyKowalski.id,
firstName: "Leo",
lastName: "Kowalski",
dateOfBirth: new Date("2022-08-12"),
},
});
const mia = await prisma.child.create({
data: {
kitaId: kita.id,
familyId: familyWeber.id,
firstName: "Mia",
lastName: "Weber",
dateOfBirth: new Date("2021-05-18"),
},
});
return { anna, ben, clara, emil, nina, leo, mia };
} }
async function createEducators(kitaId: string) { async function createEducators(kitaId: string) {
@@ -530,11 +636,9 @@ async function createInvites({ kita, admin, pendingParent }: SeedContext) {
}); });
} }
async function createAnnouncements({ async function createAnnouncements(context: SeedContext) {
kita, const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer } = context;
admin,
koordinator,
}: SeedContext) {
const sommerfest = await prisma.announcement.create({ const sommerfest = await prisma.announcement.create({
data: { data: {
kitaId: kita.id, kitaId: kita.id,
@@ -545,7 +649,7 @@ async function createAnnouncements({
}, },
}); });
await prisma.announcement.create({ const garderobe = await prisma.announcement.create({
data: { data: {
kitaId: kita.id, kitaId: kita.id,
title: "Neue Garderoben-Regelung", title: "Neue Garderoben-Regelung",
@@ -555,21 +659,30 @@ async function createAnnouncements({
}, },
}); });
await prisma.announcementRead.create({ await prisma.attachment.create({
data: { data: {
userId: koordinator.id,
announcementId: sommerfest.id, announcementId: sommerfest.id,
fileName: "sommerfest_ablauf.pdf",
fileUrl: "https://example.com/sommerfest_ablauf.pdf",
fileType: "application/pdf",
}, },
}); });
await prisma.announcementRead.createMany({
data: [
{ userId: koordinator.id, announcementId: sommerfest.id },
{ userId: elternSchmidt.id, announcementId: sommerfest.id },
{ userId: elternYilmaz.id, announcementId: sommerfest.id },
{ userId: elternFischer.id, announcementId: garderobe.id },
],
});
} }
async function createTermine({ async function createTermine(
kita, context: SeedContext,
admin, children: Awaited<ReturnType<typeof createChildren>>,
koordinator, ) {
elternSchmidt, const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer, elternKowalski } = context;
elternYilmaz,
}: SeedContext) {
const now = new Date(); const now = new Date();
const elternabend = await prisma.termin.create({ const elternabend = await prisma.termin.create({
@@ -636,6 +749,65 @@ async function createTermine({
}, },
}); });
const zooAusflug = await prisma.termin.create({
data: {
kitaId: kita.id,
title: "Ausflug in den Zoo",
description: "Gemeinsamer Ausflug der gesamten Kita-Gruppe in den Berliner Zoo.",
type: TerminType.SONSTIGES,
status: TerminStatus.CONFIRMED,
startDate: addDays(now, 15),
endDate: addDays(now, 15),
allDay: true,
requiresRsvp: true,
rsvpDeadline: addDays(now, 5),
participantInfo: "Bitte wetterfeste Kleidung und Rucksack-Verpflegung mitbringen.",
createdById: admin.id,
approvedById: admin.id,
approvedAt: now,
},
});
await prisma.eventParticipation.createMany({
data: [
{ kitaId: kita.id, eventId: zooAusflug.id, childId: children.anna.id, status: EventParticipationStatus.ATTENDING },
{ kitaId: kita.id, eventId: zooAusflug.id, childId: children.clara.id, status: EventParticipationStatus.ATTENDING },
{ kitaId: kita.id, eventId: zooAusflug.id, childId: children.emil.id, status: EventParticipationStatus.NOT_ATTENDING },
{ kitaId: kita.id, eventId: zooAusflug.id, childId: children.leo.id, status: EventParticipationStatus.ATTENDING },
],
});
await prisma.termin.create({
data: {
kitaId: kita.id,
title: "Mitmach-Tag (Garten)",
description: "Gemeinsames Herrichten des Gartens fuer die warme Jahreszeit. (Abgesagt wegen Dauerregen)",
type: TerminType.MITMACH_TAG,
status: TerminStatus.CANCELLED,
startDate: addDays(now, 2),
endDate: addDays(now, 2),
allDay: false,
createdById: admin.id,
},
});
const sommerfestEvent = await prisma.termin.create({
data: {
kitaId: kita.id,
title: "Sommerfest Waldameisen",
description: "Unser jaehrliches grosses Sommerfest im Kita-Garten mit Buffet und Spielstationen.",
type: TerminType.KITA_FEST,
status: TerminStatus.CONFIRMED,
startDate: addDays(now, 30),
endDate: addDays(now, 30),
allDay: false,
mitbringselListEnabled: true,
createdById: admin.id,
approvedById: admin.id,
approvedAt: now,
},
});
await prisma.mitbringselItem.createMany({ await prisma.mitbringselItem.createMany({
data: [ data: [
{ {
@@ -650,15 +822,34 @@ async function createTermine({
userId: elternSchmidt.id, userId: elternSchmidt.id,
content: "Brot und Aufstrich", content: "Brot und Aufstrich",
}, },
{
kitaId: kita.id,
terminId: sommerfestEvent.id,
userId: elternYilmaz.id,
content: "Nudelsalat (vegan)",
},
{
kitaId: kita.id,
terminId: sommerfestEvent.id,
userId: elternFischer.id,
content: "Mehrweg-Becher (ca. 30 Stk.)",
},
{
kitaId: kita.id,
terminId: sommerfestEvent.id,
userId: elternKowalski.id,
content: "Apfelsaft & Wasser",
},
], ],
}); });
} }
async function createNotdienstData( async function createNotdienstData(
{ kita, koordinator, elternSchmidt, elternYilmaz }: SeedContext, context: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>, children: Awaited<ReturnType<typeof createChildren>>,
educators: Awaited<ReturnType<typeof createEducators>>, educators: Awaited<ReturnType<typeof createEducators>>,
) { ) {
const { kita, koordinator, elternSchmidt, elternYilmaz, elternWeber, elternKowalski } = context;
const today = dateOnly(new Date()); const today = dateOnly(new Date());
const currentMonth = startOfMonth(today); const currentMonth = startOfMonth(today);
const previousMonth = getPreviousMonth(today); const previousMonth = getPreviousMonth(today);
@@ -670,6 +861,8 @@ async function createNotdienstData(
{ childId: children.clara.id, userId: elternSchmidt.id }, { childId: children.clara.id, userId: elternSchmidt.id },
{ childId: children.emil.id, userId: elternYilmaz.id }, { childId: children.emil.id, userId: elternYilmaz.id },
{ childId: children.ben.id, userId: koordinator.id }, { childId: children.ben.id, userId: koordinator.id },
{ childId: children.leo.id, userId: elternKowalski.id },
{ childId: children.mia.id, userId: elternWeber.id },
]; ];
const targetWorkingDays = getWorkingDays( const targetWorkingDays = getWorkingDays(
@@ -710,6 +903,30 @@ async function createNotdienstData(
}, },
}); });
const alertAssignmentDate = addDays(today, 3);
const pendingAlertAssignment = await prisma.notdienstAssignment.create({
data: {
kitaId: kita.id,
planId: currentPlan.id,
childId: children.emil.id,
date: alertAssignmentDate,
},
});
await prisma.notdienstAlert.create({
data: {
kitaId: kita.id,
assignmentId: pendingAlertAssignment.id,
parentUserId: elternYilmaz.id,
triggeredById: koordinator.id,
educatorId: educators.petra.id,
status: NotdienstAlertStatus.PENDING,
confirmationToken: "demo-pending-alert-token",
triggeredAt: today,
notes: "Es fehlen zwei Kraefte. Bitte Notdienst besetzen.",
},
});
const previousPlan = await prisma.notdienstPlan.create({ const previousPlan = await prisma.notdienstPlan.create({
data: { data: {
kitaId: kita.id, kitaId: kita.id,
@@ -744,6 +961,60 @@ async function createNotdienstData(
notes: "Beispiel-Alarm aus Seed-Daten.", notes: "Beispiel-Alarm aus Seed-Daten.",
}, },
}); });
const draftPlan = await prisma.notdienstPlan.create({
data: {
kitaId: kita.id,
year: targetMonth.getFullYear(),
month: targetMonth.getMonth() + 1,
status: NotdienstPlanStatus.DRAFT,
createdById: koordinator.id,
},
});
}
async function createAuditLogs({ kita, superAdmin, admin }: SeedContext) {
await prisma.auditLog.createMany({
data: [
{
actorUserId: superAdmin.id,
actorEmail: superAdmin.email,
action: "KITA_CREATE",
targetType: AuditTargetType.KITA,
targetId: kita.id,
targetLabel: kita.name,
targetKitaId: kita.id,
metadata: { name: kita.name, slug: kita.slug },
createdAt: addDays(new Date(), -30),
},
{
actorUserId: superAdmin.id,
actorEmail: superAdmin.email,
action: "USER_ROLE_UPDATE",
targetType: AuditTargetType.USER,
targetId: admin.id,
targetLabel: admin.email,
targetKitaId: kita.id,
metadata: { oldRole: "ELTERN", newRole: "ADMIN" },
createdAt: addDays(new Date(), -28),
},
{
actorUserId: admin.id,
actorEmail: admin.email,
action: "KITA_MODULE_UPDATE",
targetType: AuditTargetType.KITA,
targetId: kita.id,
targetLabel: kita.name,
targetKitaId: kita.id,
metadata: {
notdienstModuleEnabled: true,
terminModuleEnabled: true,
adressbuchModuleEnabled: true,
},
createdAt: addDays(new Date(), -25),
},
],
});
} }
async function createDemoData() { async function createDemoData() {
@@ -760,8 +1031,9 @@ async function createDemoData() {
await createAbsences(context, children); await createAbsences(context, children);
await createInvites(context); await createInvites(context);
await createAnnouncements(context); await createAnnouncements(context);
await createTermine(context); await createTermine(context, children);
await createNotdienstData(context, children, educators); await createNotdienstData(context, children, educators);
await createAuditLogs(context);
printSummary(context, children); printSummary(context, children);
} }
@@ -776,7 +1048,10 @@ async function seedIfEmpty() {
} }
function printSummary( function printSummary(
{ context: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>,
) {
const {
kita, kita,
superAdmin, superAdmin,
admin, admin,
@@ -784,10 +1059,11 @@ function printSummary(
koordinator, koordinator,
elternSchmidt, elternSchmidt,
elternYilmaz, elternYilmaz,
elternFischer,
elternKowalski,
elternWeber,
pendingParent, pendingParent,
}: SeedContext, } = context;
children: Awaited<ReturnType<typeof createChildren>>,
) {
console.log("Seed complete"); console.log("Seed complete");
console.log(""); console.log("");
console.log(` Kita: ${kita.name} (${kita.slug})`); console.log(` Kita: ${kita.name} (${kita.slug})`);
@@ -799,6 +1075,9 @@ function printSummary(
console.log(` Koordinator: ${koordinator.email}`); console.log(` Koordinator: ${koordinator.email}`);
console.log(` Eltern: ${elternSchmidt.email}`); console.log(` Eltern: ${elternSchmidt.email}`);
console.log(` Eltern: ${elternYilmaz.email}`); console.log(` Eltern: ${elternYilmaz.email}`);
console.log(` Eltern (Fischer): ${elternFischer.email}`);
console.log(` Eltern (Kowalski): ${elternKowalski.email}`);
console.log(` Eltern (Weber): ${elternWeber.email}`);
console.log(""); console.log("");
console.log( console.log(
` Offener Invite: ${pendingParent.email} -> /invite/demo-pending-parent-token`, ` Offener Invite: ${pendingParent.email} -> /invite/demo-pending-parent-token`,
+1 -10
View File
@@ -4,6 +4,7 @@ import Link from "next/link";
import { useMemo, useTransition } from "react"; import { useMemo, useTransition } from "react";
import { AbsenceReason } from "@prisma/client"; import { AbsenceReason } from "@prisma/client";
import { Baby, CalendarX2, Check, ChevronRight, Loader2 } from "lucide-react"; import { Baby, CalendarX2, Check, ChevronRight, Loader2 } from "lucide-react";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
import { toast } from "sonner"; import { toast } from "sonner";
import { reportAbsence } from "@/actions/absences"; import { reportAbsence } from "@/actions/absences";
@@ -331,14 +332,4 @@ function AbsenceRow({ absence }: { absence: AbsenceListItem }) {
); );
} }
function reasonLabel(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "Krank";
if (reason === AbsenceReason.VACATION) return "Urlaub";
return "Sonstiges";
}
function reasonColor(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "var(--danger)";
if (reason === AbsenceReason.VACATION) return "var(--warn)";
return "var(--accent)";
}
@@ -3,6 +3,7 @@
import { useState } from "react"; import { useState } from "react";
import { AbsenceReason } from "@prisma/client"; import { AbsenceReason } from "@prisma/client";
import { CalendarDays, Circle, MessageSquareText, UserRound } from "lucide-react"; import { CalendarDays, Circle, MessageSquareText, UserRound } from "lucide-react";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
@@ -176,17 +177,7 @@ function getInitials(name: string) {
.toUpperCase(); .toUpperCase();
} }
export function reasonLabel(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "Krank";
if (reason === AbsenceReason.VACATION) return "Urlaub";
return "Sonstiges";
}
export function reasonColor(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "var(--danger)";
if (reason === AbsenceReason.VACATION) return "var(--warn)";
return "var(--accent)";
}
export function formatDateRange(startDate: string, endDate: string) { export function formatDateRange(startDate: string, endDate: string) {
const start = new Date(`${startDate}T00:00:00`); const start = new Date(`${startDate}T00:00:00`);
@@ -21,10 +21,9 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
AbsenceTable, AbsenceTable,
reasonColor,
reasonLabel,
type AbsenceTableItem, type AbsenceTableItem,
} from "./absence-table"; } from "./absence-table";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
export const metadata = { title: "Abwesenheiten · Kita-Planer" }; export const metadata = { title: "Abwesenheiten · Kita-Planer" };
+13
View File
@@ -0,0 +1,13 @@
import { AbsenceReason } from "@prisma/client";
export function reasonLabel(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "Krank";
if (reason === AbsenceReason.VACATION) return "Urlaub";
return "Sonstiges";
}
export function reasonColor(reason: AbsenceReason) {
if (reason === AbsenceReason.ILLNESS) return "var(--danger)";
if (reason === AbsenceReason.VACATION) return "var(--warn)";
return "var(--accent)";
}