continued the kita-planer
This commit is contained in:
+247
-16
@@ -1,6 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import { Users, CalendarCheck2, ShieldCheck, AlertTriangle } from "lucide-react";
|
||||
import { UserRole, NotdienstPlanStatus } from "@prisma/client";
|
||||
import { format } from "date-fns";
|
||||
import { de } from "date-fns/locale";
|
||||
import { Users, CalendarCheck2, ShieldCheck, AlertTriangle, ClipboardList } from "lucide-react";
|
||||
import { DutyAssignmentStatus, UserRole, NotdienstPlanStatus } from "@prisma/client";
|
||||
|
||||
import { requireKitaSession } from "@/lib/auth-utils";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -13,7 +15,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { OnboardingDialog } from "@/components/OnboardingDialog";
|
||||
import { AlertButton } from "./alert-button";
|
||||
import { AbsenceCard } from "./absence-card";
|
||||
import { NewsTicker } from "./news-ticker";
|
||||
|
||||
export const metadata = { title: "Übersicht · Kita-Planer" };
|
||||
|
||||
@@ -30,22 +35,166 @@ export default async function DashboardPage() {
|
||||
},
|
||||
});
|
||||
|
||||
// Schnelle Statistiken für die Übersicht
|
||||
const [familyCount, childCount] = await Promise.all([
|
||||
prisma.user.count({
|
||||
where: { kitaId: session.user.kitaId, role: UserRole.ELTERN },
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
// Schnelle Statistiken und persönliche Dienste für die Übersicht.
|
||||
const [
|
||||
familyCount,
|
||||
childCount,
|
||||
upcomingDuties,
|
||||
onboardingUser,
|
||||
absenceChildren,
|
||||
upcomingAbsences,
|
||||
latestAnnouncements,
|
||||
] = await Promise.all([
|
||||
prisma.family.count({
|
||||
where: { kitaId: session.user.kitaId },
|
||||
}),
|
||||
prisma.child.count({
|
||||
where: { kitaId: session.user.kitaId },
|
||||
}),
|
||||
session.user.familyId
|
||||
? prisma.dutyAssignment.findMany({
|
||||
where: {
|
||||
kitaId: session.user.kitaId,
|
||||
familyId: session.user.familyId,
|
||||
status: DutyAssignmentStatus.PLANNED,
|
||||
endDate: { gte: today },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
dutyType: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { startDate: "asc" },
|
||||
take: 5,
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
kitaId: session.user.kitaId,
|
||||
},
|
||||
select: {
|
||||
phone: true,
|
||||
street: true,
|
||||
postalCode: true,
|
||||
city: true,
|
||||
familyId: true,
|
||||
role: true,
|
||||
family: {
|
||||
select: {
|
||||
_count: {
|
||||
select: {
|
||||
children: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
session.user.familyId
|
||||
? prisma.child.findMany({
|
||||
where: {
|
||||
kitaId: session.user.kitaId,
|
||||
familyId: session.user.familyId,
|
||||
active: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
orderBy: [{ lastName: "asc" }, { firstName: "asc" }],
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
session.user.familyId
|
||||
? prisma.absence.findMany({
|
||||
where: {
|
||||
kitaId: session.user.kitaId,
|
||||
endDate: { gte: today },
|
||||
child: {
|
||||
familyId: session.user.familyId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
reason: true,
|
||||
note: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
child: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [{ startDate: "asc" }],
|
||||
take: 6,
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
prisma.announcement.findMany({
|
||||
where: { kitaId: session.user.kitaId },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
author: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
attachments: {
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
fileUrl: true,
|
||||
fileType: true,
|
||||
},
|
||||
},
|
||||
reads: {
|
||||
where: { userId: session.user.id },
|
||||
select: { id: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 3,
|
||||
}),
|
||||
]);
|
||||
|
||||
const isAdmin =
|
||||
session.user.role === UserRole.ADMIN ||
|
||||
session.user.role === UserRole.SUPERADMIN;
|
||||
const canReportAbsences = session.user.role !== UserRole.ERZIEHER;
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<OnboardingDialog
|
||||
user={{
|
||||
phone: onboardingUser.phone,
|
||||
street: onboardingUser.street,
|
||||
postalCode: onboardingUser.postalCode,
|
||||
city: onboardingUser.city,
|
||||
familyId: onboardingUser.familyId,
|
||||
role: onboardingUser.role,
|
||||
}}
|
||||
family={
|
||||
onboardingUser.family
|
||||
? { childrenCount: onboardingUser.family._count.children }
|
||||
: null
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Willkommen, {session.user.name?.split(" ")[0] ?? "zusammen"}!
|
||||
@@ -55,6 +204,25 @@ export default async function DashboardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<NewsTicker
|
||||
items={latestAnnouncements.map((announcement) => ({
|
||||
id: announcement.id,
|
||||
title: announcement.title,
|
||||
content: announcement.content,
|
||||
createdAt: format(announcement.createdAt, "dd.MM.yyyy", {
|
||||
locale: de,
|
||||
}),
|
||||
authorName: `${announcement.author.firstName} ${announcement.author.lastName}`,
|
||||
isUnread: announcement.reads.length === 0,
|
||||
attachments: announcement.attachments.map((attachment) => ({
|
||||
id: attachment.id,
|
||||
fileName: attachment.fileName,
|
||||
fileUrl: attachment.fileUrl,
|
||||
fileType: attachment.fileType,
|
||||
})),
|
||||
}))}
|
||||
/>
|
||||
|
||||
{/* Heutiger Notdienst (Nur für Admins/Koordinatoren) */}
|
||||
{isAdmin && (
|
||||
<div className="mb-8">
|
||||
@@ -62,10 +230,53 @@ export default async function DashboardPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{canReportAbsences && (
|
||||
<AbsenceCard
|
||||
today={format(today, "yyyy-MM-dd")}
|
||||
childOptions={absenceChildren.map((child) => ({
|
||||
id: child.id,
|
||||
name: `${child.firstName} ${child.lastName}`,
|
||||
}))}
|
||||
absences={upcomingAbsences.map((absence) => ({
|
||||
id: absence.id,
|
||||
childName: `${absence.child.firstName} ${absence.child.lastName}`,
|
||||
reason: absence.reason,
|
||||
note: absence.note,
|
||||
startDate: format(absence.startDate, "yyyy-MM-dd"),
|
||||
endDate: format(absence.endDate, "yyyy-MM-dd"),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{upcomingDuties.length > 0 && (
|
||||
<Card className="mb-8 border-emerald-200 bg-emerald-50/50">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<ClipboardList className="h-5 w-5 text-emerald-700" />
|
||||
Deine anstehenden Dienste
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Die nächsten Einteilungen für deinen Haushalt.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{upcomingDuties.map((duty) => (
|
||||
<div key={duty.id} className="rounded-md border bg-background p-3">
|
||||
<p className="font-medium">{duty.dutyType.name}</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{format(duty.startDate, "dd.MM.", { locale: de })} bis{" "}
|
||||
{format(duty.endDate, "dd.MM.yyyy", { locale: de })}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Statistik-Kacheln */}
|
||||
<div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<StatCard
|
||||
label="Elternteile"
|
||||
label="Familien"
|
||||
value={familyCount}
|
||||
description="registrierte Familien"
|
||||
icon={<Users className="h-5 w-5 text-muted-foreground" />}
|
||||
@@ -147,15 +358,38 @@ async function TodaysNotdienstCard({ kitaId }: { kitaId: string }) {
|
||||
date: today,
|
||||
plan: { status: NotdienstPlanStatus.PUBLISHED },
|
||||
},
|
||||
include: {
|
||||
child: { include: { parentLinks: { include: { user: true } } } },
|
||||
alerts: true, // Um zu sehen, ob schon ein Alarm ausgelöst wurde
|
||||
select: {
|
||||
id: true,
|
||||
child: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
family: {
|
||||
select: {
|
||||
name: true,
|
||||
users: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
alerts: {
|
||||
select: {
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!assignment) return null;
|
||||
|
||||
const parent = assignment.child.parentLinks[0]?.user;
|
||||
const parentNames = assignment.child.family.users
|
||||
.map((parent) => `${parent.firstName} ${parent.lastName}`)
|
||||
.join(", ");
|
||||
const existingAlert = assignment.alerts[0]; // Kann PENDING, CONFIRMED oder CANCELLED sein
|
||||
|
||||
return (
|
||||
@@ -172,7 +406,7 @@ async function TodaysNotdienstCard({ kitaId }: { kitaId: string }) {
|
||||
<CardContent className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="font-medium text-base">
|
||||
{parent ? `${parent.firstName} ${parent.lastName}` : "Unbekannt"}
|
||||
{parentNames || assignment.child.family.name}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Kind: {assignment.child.firstName} {assignment.child.lastName}
|
||||
@@ -198,10 +432,7 @@ async function TodaysNotdienstCard({ kitaId }: { kitaId: string }) {
|
||||
: "Abgebrochen"}
|
||||
</Badge>
|
||||
) : (
|
||||
<AlertButton
|
||||
assignmentId={assignment.id}
|
||||
parentUserId={parent?.id || ""}
|
||||
/>
|
||||
<AlertButton assignmentId={assignment.id} />
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user