continued the kita-planer

This commit is contained in:
t.indorf
2026-05-08 14:32:14 +02:00
parent b686e714ff
commit 7aff691803
85 changed files with 9434 additions and 588 deletions
@@ -0,0 +1,243 @@
"use client";
import { useState, useTransition } from "react";
import { Pencil, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { createMyChild, deleteMyChild, updateMyChild } from "../actions";
export type MyChild = {
id: string;
firstName: string;
lastName: string;
dateOfBirth: string | null;
};
export function MyChildrenManager({
items,
canManage,
}: {
items: MyChild[];
canManage: boolean;
}) {
const [createOpen, setCreateOpen] = useState(false);
const [editingChild, setEditingChild] = useState<MyChild | null>(null);
const [isPending, startTransition] = useTransition();
function payloadFromForm(formData: FormData) {
return {
firstName: String(formData.get("firstName") ?? ""),
lastName: String(formData.get("lastName") ?? ""),
dateOfBirth: String(formData.get("dateOfBirth") ?? ""),
};
}
function handleCreate(formData: FormData) {
startTransition(async () => {
const result = await createMyChild(payloadFromForm(formData));
if (result.error) {
toast.error(result.error);
return;
}
toast.success("Kind angelegt.");
setCreateOpen(false);
});
}
function handleUpdate(formData: FormData) {
if (!editingChild) return;
startTransition(async () => {
const result = await updateMyChild(
editingChild.id,
payloadFromForm(formData),
);
if (result.error) {
toast.error(result.error);
return;
}
toast.success("Kind aktualisiert.");
setEditingChild(null);
});
}
function handleDelete(child: MyChild) {
const confirmed = confirm(
`${child.firstName} ${child.lastName} wirklich entfernen?`,
);
if (!confirmed) return;
startTransition(async () => {
const result = await deleteMyChild(child.id);
if (result.error) {
toast.error(result.error);
return;
}
toast.success("Kind entfernt.");
});
}
return (
<div className="space-y-3">
{items.length === 0 ? (
<p className="text-sm text-muted-foreground">Keine Kinder verknüpft.</p>
) : (
<div className="flex flex-col gap-2">
{items.map((child) => (
<div
key={child.id}
className="flex items-center justify-between gap-3 rounded-md bg-muted/50 p-2"
>
<div>
<p className="font-medium">
{child.firstName} {child.lastName}
</p>
{child.dateOfBirth && (
<p className="text-xs text-muted-foreground">
Geboren am {new Date(child.dateOfBirth).toLocaleDateString("de-DE")}
</p>
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setEditingChild(child)}
disabled={isPending || !canManage}
>
<Pencil className="h-4 w-4" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className="text-destructive hover:text-destructive"
onClick={() => handleDelete(child)}
disabled={isPending || !canManage}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
{canManage ? (
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
<DialogTrigger asChild>
<Button type="button" variant="outline" size="sm" className="gap-2">
<Plus className="h-4 w-4" />
Kind hinzufügen
</Button>
</DialogTrigger>
<DialogContent>
<form action={handleCreate}>
<DialogHeader>
<DialogTitle>Kind hinzufügen</DialogTitle>
<DialogDescription>
Dieses Kind wird mit deinem Haushalt verknüpft.
</DialogDescription>
</DialogHeader>
<ChildFields />
<DialogFooter>
<Button
type="button"
variant="ghost"
onClick={() => setCreateOpen(false)}
disabled={isPending}
>
Abbrechen
</Button>
<Button type="submit" disabled={isPending}>
Speichern
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
) : (
<p className="text-xs text-muted-foreground">
Dein Account ist noch keinem Haushalt zugeordnet.
</p>
)}
<Dialog
open={!!editingChild}
onOpenChange={(open) => !open && setEditingChild(null)}
>
<DialogContent>
<form action={handleUpdate}>
<DialogHeader>
<DialogTitle>Kind bearbeiten</DialogTitle>
</DialogHeader>
{editingChild && <ChildFields child={editingChild} />}
<DialogFooter>
<Button
type="button"
variant="ghost"
onClick={() => setEditingChild(null)}
disabled={isPending}
>
Abbrechen
</Button>
<Button type="submit" disabled={isPending}>
Aktualisieren
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
);
}
function ChildFields({ child }: { child?: MyChild }) {
return (
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="child-firstName">Vorname</Label>
<Input
id="child-firstName"
name="firstName"
defaultValue={child?.firstName ?? ""}
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="child-lastName">Nachname</Label>
<Input
id="child-lastName"
name="lastName"
defaultValue={child?.lastName ?? ""}
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="child-dateOfBirth">Geburtsdatum</Label>
<Input
id="child-dateOfBirth"
name="dateOfBirth"
type="date"
defaultValue={child?.dateOfBirth ?? ""}
/>
</div>
</div>
);
}