Compare commits

..

2 Commits

5 changed files with 317 additions and 44 deletions
+301 -22
View File
@@ -1,5 +1,7 @@
import {
AbsenceReason,
AuditTargetType,
EventParticipationStatus,
InvitationStatus,
NotdienstAlertStatus,
NotdienstPlanStatus,
@@ -35,6 +37,9 @@ const DEMO_USER_EMAILS = [
"schmidt@waldameisen.local",
"yilmaz@waldameisen.local",
"pending@waldameisen.local",
"fischer@waldameisen.local",
"kowalski@waldameisen.local",
"weber@waldameisen.local",
];
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 {
kita,
familyMueller,
familySchmidtYilmaz,
familyFischer,
familyKowalski,
familyWeber,
superAdmin,
admin,
erzieherUser,
@@ -305,6 +386,9 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) {
elternSchmidt,
elternYilmaz,
pendingParent,
elternFischer,
elternKowalski,
elternWeber,
};
}
@@ -313,6 +397,8 @@ async function createChildren({
familyMueller,
familySchmidtYilmaz,
familyFischer,
familyKowalski,
familyWeber,
}: SeedContext) {
const anna = await prisma.child.create({
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) {
@@ -530,11 +636,9 @@ async function createInvites({ kita, admin, pendingParent }: SeedContext) {
});
}
async function createAnnouncements({
kita,
admin,
koordinator,
}: SeedContext) {
async function createAnnouncements(context: SeedContext) {
const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer } = context;
const sommerfest = await prisma.announcement.create({
data: {
kitaId: kita.id,
@@ -545,7 +649,7 @@ async function createAnnouncements({
},
});
await prisma.announcement.create({
const garderobe = await prisma.announcement.create({
data: {
kitaId: kita.id,
title: "Neue Garderoben-Regelung",
@@ -555,21 +659,30 @@ async function createAnnouncements({
},
});
await prisma.announcementRead.create({
await prisma.attachment.create({
data: {
userId: koordinator.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({
kita,
admin,
koordinator,
elternSchmidt,
elternYilmaz,
}: SeedContext) {
async function createTermine(
context: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>,
) {
const { kita, admin, koordinator, elternSchmidt, elternYilmaz, elternFischer, elternKowalski } = context;
const now = new Date();
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({
data: [
{
@@ -650,15 +822,34 @@ async function createTermine({
userId: elternSchmidt.id,
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(
{ kita, koordinator, elternSchmidt, elternYilmaz }: SeedContext,
context: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>,
educators: Awaited<ReturnType<typeof createEducators>>,
) {
const { kita, koordinator, elternSchmidt, elternYilmaz, elternWeber, elternKowalski } = context;
const today = dateOnly(new Date());
const currentMonth = startOfMonth(today);
const previousMonth = getPreviousMonth(today);
@@ -670,6 +861,8 @@ async function createNotdienstData(
{ childId: children.clara.id, userId: elternSchmidt.id },
{ childId: children.emil.id, userId: elternYilmaz.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(
@@ -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({
data: {
kitaId: kita.id,
@@ -744,6 +961,60 @@ async function createNotdienstData(
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() {
@@ -760,8 +1031,9 @@ async function createDemoData() {
await createAbsences(context, children);
await createInvites(context);
await createAnnouncements(context);
await createTermine(context);
await createTermine(context, children);
await createNotdienstData(context, children, educators);
await createAuditLogs(context);
printSummary(context, children);
}
@@ -776,7 +1048,10 @@ async function seedIfEmpty() {
}
function printSummary(
{
context: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>,
) {
const {
kita,
superAdmin,
admin,
@@ -784,10 +1059,11 @@ function printSummary(
koordinator,
elternSchmidt,
elternYilmaz,
elternFischer,
elternKowalski,
elternWeber,
pendingParent,
}: SeedContext,
children: Awaited<ReturnType<typeof createChildren>>,
) {
} = context;
console.log("Seed complete");
console.log("");
console.log(` Kita: ${kita.name} (${kita.slug})`);
@@ -799,6 +1075,9 @@ function printSummary(
console.log(` Koordinator: ${koordinator.email}`);
console.log(` Eltern: ${elternSchmidt.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(
` 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 { AbsenceReason } from "@prisma/client";
import { Baby, CalendarX2, Check, ChevronRight, Loader2 } from "lucide-react";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
import { toast } from "sonner";
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 { AbsenceReason } from "@prisma/client";
import { CalendarDays, Circle, MessageSquareText, UserRound } from "lucide-react";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
import { Badge } from "@/components/ui/badge";
import {
@@ -176,17 +177,7 @@ function getInitials(name: string) {
.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) {
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 {
AbsenceTable,
reasonColor,
reasonLabel,
type AbsenceTableItem,
} from "./absence-table";
import { reasonColor, reasonLabel } from "@/lib/absence-utils";
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)";
}