feat: show planning availability and manage Kita access roles
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { UserRole } from "@prisma/client";
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { availableKitaRoles, kitaRoleLabels } from "@/lib/kita-roles";
|
||||
import { changeKitaRoleAction } from "./actions";
|
||||
|
||||
const descriptions: Record<string, string> = {
|
||||
ELTERN: "Eigener Haushalt, Verfügbarkeiten, Diensttausch und Kita-Alltag.",
|
||||
KOORDINATOR: "Zusätzlich Notdienstpläne, Alarme und Termine koordinieren.",
|
||||
ADMIN:
|
||||
"Familien, Team, Zugriffsrechte und alle Kita-Verwaltungsbereiche verwalten.",
|
||||
ERZIEHER:
|
||||
"Personalbereich, eigene Arbeitszeit und Anträge sowie Kinder-Abwesenheiten.",
|
||||
};
|
||||
|
||||
export type RolePerson = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: UserRole;
|
||||
familyId: string | null;
|
||||
hasStaffProfile: boolean;
|
||||
};
|
||||
|
||||
export function RoleChangeButton({
|
||||
person,
|
||||
ownAccount = false,
|
||||
}: {
|
||||
person: RolePerson;
|
||||
ownAccount?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [role, setRole] = useState(person.role);
|
||||
const [error, setError] = useState("");
|
||||
const [pending, startTransition] = useTransition();
|
||||
const roles = availableKitaRoles(person);
|
||||
const submit = () => {
|
||||
setError("");
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const result = await changeKitaRoleAction({
|
||||
userId: person.id,
|
||||
role,
|
||||
expectedRole: person.role,
|
||||
});
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
return;
|
||||
}
|
||||
toast.success("Zugriffsrolle gespeichert.");
|
||||
setOpen(false);
|
||||
if (result.ownRoleChanged) router.replace("/dashboard");
|
||||
router.refresh();
|
||||
} catch {
|
||||
setError(
|
||||
"Die Änderung konnte nicht gespeichert werden. Bitte erneut versuchen.",
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label={`Zugriffsrechte für ${person.name}`}
|
||||
disabled={person.role === "SUPERADMIN"}
|
||||
onClick={() => {
|
||||
setRole(person.role);
|
||||
setError("");
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<ShieldCheck className="h-3.5 w-3.5" /> Zugriffsrechte
|
||||
</Button>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
if (!pending) setOpen(next);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Zugriffsrechte für {person.name}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Die Zugriffsrolle bestimmt, welche Bereiche diese Person nutzen
|
||||
und verwalten kann. Ämter wie „Vorstand“ werden separat vergeben.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<fieldset disabled={pending} className="grid gap-2">
|
||||
<legend className="mb-2 text-sm font-semibold text-[var(--ink)]">
|
||||
Zugriffsrolle
|
||||
</legend>
|
||||
{roles.map((value) => (
|
||||
<label
|
||||
key={value}
|
||||
className={`flex cursor-pointer items-start gap-3 rounded-xl border p-3 transition-colors ${role === value ? "border-[var(--accent-mid)] bg-[var(--accent-soft)]" : "border-[var(--hairline)] hover:bg-[var(--surface-2)]"}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`role-${person.id}`}
|
||||
value={value}
|
||||
checked={role === value}
|
||||
onChange={() => setRole(value)}
|
||||
className="mt-1 accent-[var(--accent-deep)]"
|
||||
/>
|
||||
<span>
|
||||
<span className="block text-sm font-semibold text-[var(--ink)]">
|
||||
{kitaRoleLabels[value]}
|
||||
</span>
|
||||
<span className="mt-1 block text-xs leading-5 text-[var(--ink-soft)]">
|
||||
{descriptions[value]}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
<p className="text-xs leading-5 text-[var(--ink-soft)]">
|
||||
{person.hasStaffProfile
|
||||
? "Das Personalprofil mit Arbeitszeiten und Anträgen bleibt erhalten. Mit Kita-Admin-Rechten ist zusätzlich die Personalverwaltung zugänglich."
|
||||
: person.familyId
|
||||
? "Haushalt, Kinder und eigene Dienste bleiben erhalten."
|
||||
: "Dieses Konto hat keinen eigenen Haushalt. Deshalb steht die Elternrolle nicht zur Auswahl."}
|
||||
</p>
|
||||
{ownAccount && role !== person.role ? (
|
||||
<p className="rounded-lg bg-[var(--warn-soft)] p-3 text-sm leading-5 text-[var(--warn)]">
|
||||
Du änderst deine eigenen Rechte. Nach dem Speichern gelangst du
|
||||
zur Übersicht; der Zugang zur Verwaltung richtet sich dann nach
|
||||
der neuen Rolle.
|
||||
</p>
|
||||
) : null}
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm leading-5 text-[var(--danger)]">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={pending}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button disabled={pending || role === person.role} onClick={submit}>
|
||||
{pending ? "Wird gespeichert …" : "Rolle speichern"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user