continued the kita-planer
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, 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 { updateFamilyAction } from "./actions";
|
||||
|
||||
type EditableParent = {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
type EditableChild = {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
|
||||
type DraftParent = EditableParent & {
|
||||
kind: "existing" | "new";
|
||||
};
|
||||
|
||||
type DraftChild = EditableChild & {
|
||||
kind: "existing" | "new";
|
||||
};
|
||||
|
||||
type EditableFamily = {
|
||||
id: string;
|
||||
name: string;
|
||||
parents: EditableParent[];
|
||||
children: EditableChild[];
|
||||
};
|
||||
|
||||
export function EditFamilyDialog({ family }: { family: EditableFamily }) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const initialParents = useMemo(
|
||||
() =>
|
||||
family.parents.map((parent) => ({
|
||||
...parent,
|
||||
kind: "existing" as const,
|
||||
})),
|
||||
[family.parents],
|
||||
);
|
||||
const initialChildren = useMemo(
|
||||
() =>
|
||||
family.children.map((child) => ({
|
||||
...child,
|
||||
kind: "existing" as const,
|
||||
})),
|
||||
[family.children],
|
||||
);
|
||||
const [parents, setParents] = useState<DraftParent[]>(initialParents);
|
||||
const [children, setChildren] = useState<DraftChild[]>(initialChildren);
|
||||
const [removedChildIds, setRemovedChildIds] = useState<string[]>([]);
|
||||
|
||||
function resetState() {
|
||||
setParents(initialParents);
|
||||
setChildren(initialChildren);
|
||||
setRemovedChildIds([]);
|
||||
}
|
||||
|
||||
function addParent() {
|
||||
if (parents.length >= 2) return;
|
||||
setParents((current) => [
|
||||
...current,
|
||||
{
|
||||
id: `new-parent-${crypto.randomUUID()}`,
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
kind: "new",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
function removeNewParent(parent: DraftParent) {
|
||||
if (parent.kind !== "new") return;
|
||||
setParents((current) => current.filter((item) => item.id !== parent.id));
|
||||
}
|
||||
|
||||
function addChild() {
|
||||
setChildren((current) => [
|
||||
...current,
|
||||
{
|
||||
id: `new-child-${crypto.randomUUID()}`,
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
kind: "new",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
function removeChild(child: DraftChild) {
|
||||
setChildren((current) => current.filter((item) => item.id !== child.id));
|
||||
if (child.kind === "existing") {
|
||||
setRemovedChildIds((current) => [...current, child.id]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(formData: FormData) {
|
||||
const payload = {
|
||||
familyId: family.id,
|
||||
familyName: String(formData.get("familyName") ?? ""),
|
||||
parents: parents
|
||||
.filter((parent) => parent.kind === "existing")
|
||||
.map((parent) => ({
|
||||
id: parent.id,
|
||||
firstName: String(formData.get(`parentFirstName_${parent.id}`) ?? ""),
|
||||
lastName: String(formData.get(`parentLastName_${parent.id}`) ?? ""),
|
||||
email: String(formData.get(`parentEmail_${parent.id}`) ?? ""),
|
||||
})),
|
||||
newParents: parents
|
||||
.filter((parent) => parent.kind === "new")
|
||||
.map((parent) => ({
|
||||
firstName: String(formData.get(`parentFirstName_${parent.id}`) ?? ""),
|
||||
lastName: String(formData.get(`parentLastName_${parent.id}`) ?? ""),
|
||||
email: String(formData.get(`parentEmail_${parent.id}`) ?? ""),
|
||||
})),
|
||||
children: children
|
||||
.filter((child) => child.kind === "existing")
|
||||
.map((child) => ({
|
||||
id: child.id,
|
||||
firstName: String(formData.get(`childFirstName_${child.id}`) ?? ""),
|
||||
lastName: String(formData.get(`childLastName_${child.id}`) ?? ""),
|
||||
})),
|
||||
newChildren: children
|
||||
.filter((child) => child.kind === "new")
|
||||
.map((child) => ({
|
||||
firstName: String(formData.get(`childFirstName_${child.id}`) ?? ""),
|
||||
lastName: String(formData.get(`childLastName_${child.id}`) ?? ""),
|
||||
})),
|
||||
removedChildIds,
|
||||
};
|
||||
|
||||
startTransition(async () => {
|
||||
const result = await updateFamilyAction(payload);
|
||||
if (result.error) {
|
||||
toast.error(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Familie aktualisiert.");
|
||||
setRemovedChildIds([]);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={(open) => !open && resetState()}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="h-8 gap-1">
|
||||
<Pencil className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Details</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-2xl">
|
||||
<form action={handleSubmit}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Familie bearbeiten</DialogTitle>
|
||||
<DialogDescription>
|
||||
Aktualisiere Haushalt, Elternteile und Kinder.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-6 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor={`familyName-${family.id}`}>Familienname</Label>
|
||||
<Input
|
||||
id={`familyName-${family.id}`}
|
||||
name="familyName"
|
||||
defaultValue={family.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<fieldset className="grid gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<legend className="text-sm font-semibold">Elternteile</legend>
|
||||
{parents.length < 2 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 gap-1"
|
||||
onClick={addParent}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Elternteil hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{parents.map((parent, index) => (
|
||||
<div key={parent.id} className="rounded-md border p-3">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
Elternteil {index + 1}
|
||||
</p>
|
||||
{parent.kind === "new" && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 gap-1 px-2 text-destructive hover:text-destructive"
|
||||
onClick={() => removeNewParent(parent)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
Entfernen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<Field
|
||||
id={`parentFirstName-${parent.id}`}
|
||||
name={`parentFirstName_${parent.id}`}
|
||||
label="Vorname"
|
||||
defaultValue={parent.firstName}
|
||||
/>
|
||||
<Field
|
||||
id={`parentLastName-${parent.id}`}
|
||||
name={`parentLastName_${parent.id}`}
|
||||
label="Nachname"
|
||||
defaultValue={parent.lastName}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Field
|
||||
id={`parentEmail-${parent.id}`}
|
||||
name={`parentEmail_${parent.id}`}
|
||||
label="E-Mail-Adresse"
|
||||
type="email"
|
||||
defaultValue={parent.email}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="grid gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<legend className="text-sm font-semibold">Kinder</legend>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 gap-1"
|
||||
onClick={addChild}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Kind hinzufügen
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{children.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Keine Kinder verknüpft.
|
||||
</p>
|
||||
) : (
|
||||
children.map((child, index) => (
|
||||
<div key={child.id} className="rounded-md border p-3">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
Kind {index + 1}
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 gap-1 px-2 text-destructive hover:text-destructive"
|
||||
onClick={() => removeChild(child)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
Entfernen
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<Field
|
||||
id={`childFirstName-${child.id}`}
|
||||
name={`childFirstName_${child.id}`}
|
||||
label="Vorname"
|
||||
defaultValue={child.firstName}
|
||||
/>
|
||||
<Field
|
||||
id={`childLastName-${child.id}`}
|
||||
name={`childLastName_${child.id}`}
|
||||
label="Nachname"
|
||||
defaultValue={child.lastName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
{isPending ? "Speichert..." : "Speichern"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function Field({
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
type = "text",
|
||||
defaultValue,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
defaultValue: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
<Input
|
||||
id={id}
|
||||
name={name}
|
||||
type={type}
|
||||
defaultValue={defaultValue}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user