add process stepper dashboard with phase cards, contact stats

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 11:13:37 +01:00
parent 39060c4e8e
commit b884e4e8c4
7 changed files with 258 additions and 50 deletions
@@ -0,0 +1,53 @@
import { Badge } from "@/shared/components/ui/badge";
import { Card, CardContent } from "@/shared/components/ui/card";
import { cn } from "@/shared/lib/utils";
type PhaseStatus = "erledigt" | "aktuell" | "offen";
interface PhaseCardProps {
label: string;
beschreibung: string;
status: PhaseStatus;
index: number;
}
export function PhaseCard({
label,
beschreibung,
status,
index,
}: PhaseCardProps) {
return (
<Card
className={cn(
"transition-all",
status === "aktuell" && "ring-2 ring-primary border-primary",
status === "erledigt" && "opacity-60",
)}
>
<CardContent className="flex items-start gap-4">
<div
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold",
status === "erledigt" &&
"border-primary bg-primary text-primary-foreground",
status === "aktuell" && "border-primary text-primary",
status === "offen" &&
"border-muted-foreground/40 text-muted-foreground/40",
)}
>
{status === "erledigt" ? "✓" : index + 1}
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span className="font-medium">{label}</span>
{status === "aktuell" && <Badge>Aktuell</Badge>}
</div>
{status === "aktuell" && (
<p className="text-sm text-muted-foreground">{beschreibung}</p>
)}
</div>
</CardContent>
</Card>
);
}
@@ -0,0 +1,71 @@
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/shared/components/ui/card";
import type { ProzessSchritt } from "@/shared/db/schema";
import { PROZESS_SCHRITTE } from "@/shared/lib/constants";
import { PhaseCard } from "./phase-card";
interface ProcessStepperProps {
aktuellerSchritt: ProzessSchritt;
kontaktGesamt: number;
absagen: number;
}
export function ProcessStepper({
aktuellerSchritt,
kontaktGesamt,
absagen,
}: ProcessStepperProps) {
const currentIndex = PROZESS_SCHRITTE.findIndex(
(s) => s.key === aktuellerSchritt,
);
return (
<div className="mx-auto flex w-full max-w-lg flex-col gap-6 p-4">
<div className="flex items-baseline justify-between">
<h1 className="text-2xl font-bold">Dein Fortschritt</h1>
<span className="text-sm text-muted-foreground">
Schritt {currentIndex + 1} von {PROZESS_SCHRITTE.length}
</span>
</div>
<div className="flex flex-col gap-3">
{PROZESS_SCHRITTE.map((schritt, i) => {
const status =
i < currentIndex
? "erledigt"
: i === currentIndex
? "aktuell"
: "offen";
return (
<PhaseCard
key={schritt.key}
label={schritt.label}
beschreibung={schritt.beschreibung}
status={status}
index={i}
/>
);
})}
</div>
{currentIndex >= 3 && (
<Card>
<CardHeader>
<CardTitle>Kontaktübersicht</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{kontaktGesamt} Kontaktversuche insgesamt, davon {absagen}{" "}
Absagen.
</p>
</CardContent>
</Card>
)}
</div>
);
}
+40
View File
@@ -0,0 +1,40 @@
import type { ProzessSchritt } from "@/shared/db/schema";
import { dbExec, useDbQuery } from "@/shared/hooks/use-db";
interface NutzerRow {
id: number;
name: string;
aktueller_schritt: ProzessSchritt;
dringlichkeitscode: boolean;
tss_beantragt: boolean;
krankenkasse: string;
}
interface KontaktStats {
gesamt: number;
absagen: number;
warteliste: number;
keine_antwort: number;
}
export function useNutzer() {
return useDbQuery<NutzerRow>("SELECT * FROM nutzer LIMIT 1");
}
export function useKontaktStats() {
return useDbQuery<KontaktStats>(`
SELECT
COUNT(*) as gesamt,
COUNT(*) FILTER (WHERE ergebnis = 'absage') as absagen,
COUNT(*) FILTER (WHERE ergebnis = 'warteliste') as warteliste,
COUNT(*) FILTER (WHERE ergebnis = 'keine_antwort') as keine_antwort
FROM kontakt
`);
}
export async function updateSchritt(schritt: ProzessSchritt) {
await dbExec(
"UPDATE nutzer SET aktueller_schritt = $1, aktualisiert_am = NOW() WHERE id = 1",
[schritt],
);
}
+2
View File
@@ -0,0 +1,2 @@
export { ProcessStepper } from "./components/process-stepper";
export { updateSchritt, useKontaktStats, useNutzer } from "./hooks";