"use client";
import { useId, useState, useTransition, type ReactNode } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
DAY_NAMES,
clockMinute,
minuteClock,
todayKey,
} from "@/lib/staff/calculations";
import { staffAction, type StaffActionResult } from "./actions";
const control =
"w-full rounded-lg border border-[var(--hairline)] bg-[var(--surface)] px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-mid)]";
export function Field({
label,
name,
type = "text",
value,
required = true,
min,
max,
step,
}: {
label: string;
name: string;
type?: string;
value?: string | number;
required?: boolean;
min?: string | number;
max?: string | number;
step?: string | number;
}) {
const id = useId();
return (
);
}
function transform(command: string, form: FormData) {
const text = (key: string) => String(form.get(key) ?? "");
const num = (key: string) => Number(text(key));
const hours = (key: string) => Math.round(num(key) * 60);
switch (command) {
case "setup":
return {
educatorId: text("educatorId"),
employmentStart: text("employmentStart"),
employmentEnd: text("employmentEnd") || null,
trackingStart: text("trackingStart"),
openingMinutes: hours("openingHours"),
reason: text("reason"),
};
case "invite":
return { email: text("email") };
case "employmentEnd":
return {
employmentEnd: text("employmentEnd") || null,
reason: text("reason"),
};
case "pattern":
return {
effectiveFrom: text("effectiveFrom"),
dailyMinutes: DAY_NAMES.map((_, i) => hours(`day${i}`)),
reason: text("reason"),
};
case "account":
return {
year: num("year"),
entitlementUnits: Math.round(num("entitlement") * 2),
carryUnits: Math.round(num("carry") * 2),
reason: text("reason"),
};
case "override":
return {
date: text("date"),
minutes: hours("hours"),
reason: text("reason"),
};
case "adjust":
return {
date: text("date"),
minutes: hours("hours"),
reason: text("reason"),
};
case "holiday":
return { date: text("date"), name: text("name") };
case "time":
return {
date: text("date"),
startMinute: clockMinute(text("start")),
endMinute: clockMinute(text("end")),
breakMinutes: num("breakMinutes"),
note: text("note"),
};
case "leave":
return {
kind: text("kind"),
startDate: text("startDate"),
endDate: text("endDate"),
halfDay: form.get("halfDay") === "on",
...(text("kind") === "TIME_OFF" ? { minutes: hours("hours") } : {}),
note: text("note"),
};
case "leaveReview":
case "weekReview":
return { reason: text("reason") };
default:
return {};
}
}
function StaffForm({
command,
profileId,
extra = {},
children,
label = "Speichern",
onSuccess,
}: {
command: string;
profileId?: string;
extra?: Record;
children: ReactNode;
label?: string;
onSuccess?: (result: StaffActionResult) => void;
}) {
const [pending, start] = useTransition();
const [error, setError] = useState("");
const router = useRouter();
return (
);
}
export function CommandButton({
command,
profileId,
data,
children,
variant = "outline",
}: {
command: string;
profileId: string;
data: Record;
children: ReactNode;
variant?: "outline" | "ghost" | "default";
}) {
const [pending, start] = useTransition();
const router = useRouter();
return (
);
}
export function ReviewButton({
command,
profileId,
data,
label,
approve,
}: {
command: "leaveReview" | "weekReview";
profileId: string;
data: Record;
label: string;
approve: boolean;
}) {
const [open, setOpen] = useState(false);
return (
);
}
export function SetupForm({
educatorId,
values,
}: {
educatorId: string;
values?: {
employmentStart: string;
employmentEnd: string | null;
trackingStart: string;
openingMinutes: number;
};
}) {
return (
);
}
export function TimeForm({
profileId,
initial,
}: {
profileId: string;
initial?: {
id: string;
date: string;
startMinute: number;
endMinute: number;
breakMinutes: number;
note: string | null;
};
}) {
const [open, setOpen] = useState(false);
return (
);
}
export function LeaveForm({
profileId,
admin,
initial,
}: {
profileId: string;
admin: boolean;
initial?: {
id: string;
kind: string;
startDate: string;
endDate: string;
halfDay: boolean;
minutes: number | null;
note: string | null;
};
}) {
const [open, setOpen] = useState(false);
const [kind, setKind] = useState(initial?.kind ?? "VACATION");
const id = useId();
return (
);
}
function SettingsSection({
title,
children,
}: {
title: string;
children: ReactNode;
}) {
return (
{title}
{children}
);
}
export function SettingsPanel({
profileId,
educatorId,
employment,
email,
verified,
pattern,
year,
account,
}: {
profileId: string;
educatorId: string;
employment: {
employmentStart: string;
employmentEnd: string | null;
trackingStart: string;
openingMinutes: number;
};
email?: string;
verified: boolean;
pattern?: { effectiveFrom: string; dailyMinutes: number[] };
year: number;
account?: { entitlementUnits: number; carryUnits: number };
}) {
const [invitePath, setInvitePath] = useState("");
return (
{verified ? (
Aktives Konto: {email}
) : (
setInvitePath(r.invitePath ?? "")}
>
Erstellt oder verknüpft einen ErzieherInnen-Account. Der
persönliche Link ist sieben Tage gültig. Ein neuer Link ersetzt
den vorherigen.
)}
{invitePath && (
)}
{DAY_NAMES.map((day, i) => (
))}
0 Stunden = frei. Stunden sind Nettozeiten ohne Pause. Änderungen
gelten ab dem gewählten Datum; ältere Modelle bleiben erhalten.
Anspruch individuell für die Arbeitstage dieser Person hinterlegen.
Der Übertrag wird ausdrücklich festgelegt.
Bei einem Tausch beide Tage erfassen: den ursprünglichen Tag auf 0
setzen und den neuen Arbeitstag mit Sollstunden hinterlegen.
Korrekturen bleiben im Verlauf erhalten. Falsche Buchungen durch
eine Gegenbuchung berichtigen.
Die Grunddaten sind geschützt, sobald Zeiten, Wochen oder Anträge
erfasst wurden.
);
}
export function HolidayForm() {
return (
);
}