174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import {
|
|
ArrowRight,
|
|
CheckCircle2,
|
|
Home,
|
|
MapPin,
|
|
Phone,
|
|
Sparkles,
|
|
} from "lucide-react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { Progress } from "@/components/ui/progress";
|
|
|
|
type UserRoleDto =
|
|
| "SUPERADMIN"
|
|
| "ADMIN"
|
|
| "KOORDINATOR"
|
|
| "ERZIEHER"
|
|
| "ELTERN";
|
|
|
|
type OnboardingDialogProps = {
|
|
user: {
|
|
phone: string | null;
|
|
street: string | null;
|
|
postalCode: string | null;
|
|
city: string | null;
|
|
familyId: string | null;
|
|
role: UserRoleDto;
|
|
};
|
|
family: {
|
|
childrenCount: number;
|
|
} | null;
|
|
};
|
|
|
|
const HIDE_ONBOARDING_DIALOG_KEY = "hideOnboardingDialog";
|
|
|
|
export function OnboardingDialog({ user, family }: OnboardingDialogProps) {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const status = useMemo(() => {
|
|
const hasPhone = !!user.phone?.trim();
|
|
const hasAddress =
|
|
!!user.street?.trim() && !!user.postalCode?.trim() && !!user.city?.trim();
|
|
const needsFamilyData = user.role !== "ERZIEHER";
|
|
const hasFamilyAndChildren =
|
|
!needsFamilyData || (!!user.familyId && !!family?.childrenCount);
|
|
|
|
const missingItems = [
|
|
!hasPhone ? { label: "Telefonnummer fehlt", icon: Phone } : null,
|
|
!hasAddress ? { label: "Adresse fehlt", icon: MapPin } : null,
|
|
!hasFamilyAndChildren
|
|
? { label: "Haushalts-/Kinderdaten unvollständig", icon: Home }
|
|
: null,
|
|
].filter(Boolean) as { label: string; icon: typeof Phone }[];
|
|
|
|
const completedCount = [hasPhone, hasAddress, hasFamilyAndChildren].filter(
|
|
Boolean,
|
|
).length;
|
|
const progress = completedCount === 3 ? 100 : completedCount * 33;
|
|
|
|
return {
|
|
completedCount,
|
|
progress,
|
|
missingItems,
|
|
hasFamilyAndChildren,
|
|
isComplete: completedCount === 3,
|
|
};
|
|
}, [family?.childrenCount, user]);
|
|
|
|
useEffect(() => {
|
|
if (status.isComplete) return;
|
|
|
|
const hidden = localStorage.getItem(HIDE_ONBOARDING_DIALOG_KEY);
|
|
if (hidden !== "true") {
|
|
const timer = window.setTimeout(() => setOpen(true), 0);
|
|
return () => window.clearTimeout(timer);
|
|
}
|
|
}, [status.isComplete]);
|
|
|
|
if (status.isComplete) {
|
|
return null;
|
|
}
|
|
|
|
const ctaLabel = status.hasFamilyAndChildren
|
|
? "Kontaktdaten hinterlegen"
|
|
: "Haushalt & Kinder einrichten";
|
|
|
|
function hidePermanently() {
|
|
localStorage.setItem(HIDE_ONBOARDING_DIALOG_KEY, "true");
|
|
setOpen(false);
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogContent className="overflow-hidden p-0 sm:max-w-xl">
|
|
<div className="bg-emerald-950 px-6 py-6 text-white">
|
|
<div className="mb-5 flex h-11 w-11 items-center justify-center rounded-full bg-emerald-300 text-emerald-950">
|
|
<Sparkles className="h-5 w-5" />
|
|
</div>
|
|
<DialogHeader className="space-y-2 text-left">
|
|
<DialogTitle className="text-2xl leading-tight">
|
|
Dein Kita-Profil ist fast bereit
|
|
</DialogTitle>
|
|
<DialogDescription className="text-emerald-50/80">
|
|
Vervollständige die wichtigsten Daten, damit Notdienst,
|
|
Adressbuch und Familienverwaltung reibungslos funktionieren.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
</div>
|
|
|
|
<div className="space-y-5 px-6 py-6">
|
|
<div>
|
|
<div className="mb-2 flex items-center justify-between gap-3 text-sm">
|
|
<span className="font-medium">Profil-Fortschritt</span>
|
|
<span className="text-muted-foreground">
|
|
{status.completedCount}/3 erledigt
|
|
</span>
|
|
</div>
|
|
<Progress value={status.progress} />
|
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
{status.progress}% vollständig
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-md border bg-muted/30 p-4">
|
|
<p className="mb-3 text-sm font-medium">Noch offen</p>
|
|
<ul className="space-y-2">
|
|
{status.missingItems.map(({ label, icon: Icon }) => (
|
|
<li key={label} className="flex items-center gap-2 text-sm">
|
|
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-destructive/10 text-destructive">
|
|
<Icon className="h-3.5 w-3.5" />
|
|
</span>
|
|
{label}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-2 rounded-md bg-emerald-50 p-3 text-sm text-emerald-950">
|
|
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0" />
|
|
<p>
|
|
Du kannst die App weiter nutzen. Diese Erinnerung hilft nur beim
|
|
sauberen Einrichten der wichtigsten MVP-Daten.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2 border-t px-6 py-4 sm:justify-between sm:space-x-0">
|
|
<Button type="button" variant="ghost" onClick={hidePermanently}>
|
|
Nicht mehr anzeigen
|
|
</Button>
|
|
<Button asChild onClick={() => setOpen(false)}>
|
|
<Link href="/dashboard/profil">
|
|
{ctaLabel}
|
|
<ArrowRight className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|