Add non-destructive dev seed on startup
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState, useState } from "react";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { addFamilyAction, type AddFamilyState } from "./actions";
|
||||
|
||||
const initialState: AddFamilyState = {};
|
||||
|
||||
// =====================================================================
|
||||
// AddFamilyDialog · Client Component
|
||||
// ---------------------------------------------------------------------
|
||||
// Verwaltet:
|
||||
// • Dialog-Open/Close-State
|
||||
// • Dynamische Kinderliste (min. 1, max. 10)
|
||||
// • useActionState → Server-Action-Fehler anzeigen
|
||||
// • Bei Erfolg (state.success) Dialog automatisch schließen
|
||||
// =====================================================================
|
||||
|
||||
export function AddFamilyDialog() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [childCount, setChildCount] = useState(1);
|
||||
const [state, formAction, pending] = useActionState(
|
||||
addFamilyAction,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Dialog schließen, wenn die Action erfolgreich war
|
||||
if (state.success && open) {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function handleOpenChange(nextOpen: boolean) {
|
||||
setOpen(nextOpen);
|
||||
// State beim Schließen zurücksetzen würde useActionState erfordern —
|
||||
// stattdessen schließen wir einfach und der State bleibt bis zur nächsten
|
||||
// Aktion erhalten (unkritisch).
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Familie hinzufügen
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Familie hinzufügen</DialogTitle>
|
||||
<DialogDescription>
|
||||
Lege das Elternteil und die zugehörigen Kinder an. Der Elternteil
|
||||
erhält einen Einladungslink per E-Mail, um sein Passwort zu setzen.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form action={formAction} className="space-y-6">
|
||||
{/* Anzahl Kinder als verstecktes Feld für die Server Action */}
|
||||
<input type="hidden" name="childCount" value={childCount} />
|
||||
|
||||
{/* ── Elternteil ─────────────────────────────────────────── */}
|
||||
<fieldset className="space-y-4">
|
||||
<legend className="text-sm font-semibold">Elternteil</legend>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<FormField
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
label="Vorname"
|
||||
autoComplete="given-name"
|
||||
error={state.errors?.firstName?.[0]}
|
||||
/>
|
||||
<FormField
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
label="Nachname"
|
||||
autoComplete="family-name"
|
||||
error={state.errors?.lastName?.[0]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
label="E-Mail-Adresse"
|
||||
autoComplete="email"
|
||||
error={state.errors?.email?.[0]}
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
{/* ── Kinder ─────────────────────────────────────────────── */}
|
||||
<fieldset className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<legend className="text-sm font-semibold">
|
||||
Kinder ({childCount})
|
||||
</legend>
|
||||
{childCount < 10 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setChildCount((c) => c + 1)}
|
||||
>
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
Kind hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{Array.from({ length: childCount }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="relative rounded-md border p-4"
|
||||
>
|
||||
{childCount > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
// Kind entfernen — childCount reduzieren reicht, da die
|
||||
// Felder nach Index benannt sind und der letzte entfernt wird.
|
||||
setChildCount((c) => c - 1);
|
||||
}}
|
||||
className="absolute right-3 top-3 rounded p-0.5 text-muted-foreground hover:text-destructive"
|
||||
aria-label={`Kind ${i + 1} entfernen`}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
<p className="mb-3 text-xs font-medium text-muted-foreground">
|
||||
Kind {i + 1}
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<FormField
|
||||
id={`childFirstName_${i}`}
|
||||
name={`childFirstName_${i}`}
|
||||
label="Vorname"
|
||||
/>
|
||||
<FormField
|
||||
id={`childLastName_${i}`}
|
||||
name={`childLastName_${i}`}
|
||||
label="Nachname"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{state.errors?.children?.[0] && (
|
||||
<p className="text-xs text-destructive">
|
||||
{state.errors.children[0]}
|
||||
</p>
|
||||
)}
|
||||
</fieldset>
|
||||
|
||||
{/* Globaler Fehler */}
|
||||
{state.errors?._form?.[0] && (
|
||||
<p className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
|
||||
{state.errors._form[0]}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
disabled={pending}
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={pending}>
|
||||
{pending ? "Wird angelegt…" : "Familie anlegen"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Hilfskomponente ────────────────────────────────────────────────────
|
||||
|
||||
function FormField({
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
type = "text",
|
||||
autoComplete,
|
||||
error,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
autoComplete?: string;
|
||||
error?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
<Input
|
||||
id={id}
|
||||
name={name}
|
||||
type={type}
|
||||
autoComplete={autoComplete}
|
||||
required
|
||||
aria-invalid={!!error}
|
||||
/>
|
||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user