Add non-destructive dev seed on startup
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
import Link from "next/link";
|
||||
import { Users, CalendarCheck2, ShieldCheck, AlertTriangle } from "lucide-react";
|
||||
import { UserRole, NotdienstPlanStatus } from "@prisma/client";
|
||||
|
||||
import { requireKitaSession } from "@/lib/auth-utils";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { AlertButton } from "./alert-button";
|
||||
|
||||
export const metadata = { title: "Übersicht · Kita-Planer" };
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await requireKitaSession();
|
||||
|
||||
const kita = await prisma.kita.findUniqueOrThrow({
|
||||
where: { id: session.user.kitaId },
|
||||
select: {
|
||||
name: true,
|
||||
notdienstModuleEnabled: true,
|
||||
terminModuleEnabled: true,
|
||||
adressbuchModuleEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Schnelle Statistiken für die Übersicht
|
||||
const [familyCount, childCount] = await Promise.all([
|
||||
prisma.user.count({
|
||||
where: { kitaId: session.user.kitaId, role: UserRole.ELTERN },
|
||||
}),
|
||||
prisma.child.count({
|
||||
where: { kitaId: session.user.kitaId },
|
||||
}),
|
||||
]);
|
||||
|
||||
const isAdmin =
|
||||
session.user.role === UserRole.ADMIN ||
|
||||
session.user.role === UserRole.SUPERADMIN;
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Willkommen, {session.user.name?.split(" ")[0] ?? "zusammen"}!
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Hier ist die Übersicht für <strong>{kita.name}</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Heutiger Notdienst (Nur für Admins/Koordinatoren) */}
|
||||
{isAdmin && (
|
||||
<div className="mb-8">
|
||||
<TodaysNotdienstCard kitaId={session.user.kitaId!} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Statistik-Kacheln */}
|
||||
<div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<StatCard
|
||||
label="Elternteile"
|
||||
value={familyCount}
|
||||
description="registrierte Familien"
|
||||
icon={<Users className="h-5 w-5 text-muted-foreground" />}
|
||||
/>
|
||||
<StatCard
|
||||
label="Kinder"
|
||||
value={childCount}
|
||||
description="in der Einrichtung"
|
||||
icon={<ShieldCheck className="h-5 w-5 text-muted-foreground" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Schnell-Aktionen für Admins */}
|
||||
{isAdmin && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Schnellstart</CardTitle>
|
||||
<CardDescription>
|
||||
Die nächsten Schritte, um deine Kita einzurichten.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap gap-3">
|
||||
<Button asChild size="sm">
|
||||
<Link href="/dashboard/families">
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
Familien verwalten
|
||||
</Link>
|
||||
</Button>
|
||||
{kita.terminModuleEnabled && (
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<Link href="/dashboard/termine">
|
||||
<CalendarCheck2 className="mr-2 h-4 w-4" />
|
||||
Termine (bald verfügbar)
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
label,
|
||||
value,
|
||||
description,
|
||||
icon,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
{label}
|
||||
</CardTitle>
|
||||
{icon}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold">{value}</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">{description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
async function TodaysNotdienstCard({ kitaId }: { kitaId: string }) {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
// Wir suchen das Assignment für heute, aber nur wenn der Plan PUBLISHED ist
|
||||
const assignment = await prisma.notdienstAssignment.findFirst({
|
||||
where: {
|
||||
kitaId,
|
||||
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
|
||||
},
|
||||
});
|
||||
|
||||
if (!assignment) return null;
|
||||
|
||||
const parent = assignment.child.parentLinks[0]?.user;
|
||||
const existingAlert = assignment.alerts[0]; // Kann PENDING, CONFIRMED oder CANCELLED sein
|
||||
|
||||
return (
|
||||
<Card className="border-primary/20 bg-primary/5">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-primary" />
|
||||
Heutiger Notdienst
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Laut dem veröffentlichten Plan ist heute folgende Familie eingeteilt:
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<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"}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Kind: {assignment.child.firstName} {assignment.child.lastName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{existingAlert ? (
|
||||
<Badge
|
||||
variant={
|
||||
existingAlert.status === "CONFIRMED"
|
||||
? "success"
|
||||
: existingAlert.status === "PENDING"
|
||||
? "warning"
|
||||
: "destructive"
|
||||
}
|
||||
className="px-3 py-1 text-sm"
|
||||
>
|
||||
{existingAlert.status === "CONFIRMED"
|
||||
? "Einsatz bestätigt"
|
||||
: existingAlert.status === "PENDING"
|
||||
? "Wartet auf Bestätigung"
|
||||
: "Abgebrochen"}
|
||||
</Badge>
|
||||
) : (
|
||||
<AlertButton
|
||||
assignmentId={assignment.id}
|
||||
parentUserId={parent?.id || ""}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user