Merge pull request #84 from Since-3/feature/employee-deletion

fallback on courses if employee does not exist anymore but is display…
This commit is contained in:
Rajpreet0 2026-04-17 20:30:45 +02:00 • committed by GitHub
commit d3791060d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 126 additions and 10 deletions

View file

@ -17,6 +17,7 @@ interface DeleteDialogProps {
topicName?: string;
onConfirm: () => void;
isLoading?: boolean;
warningContent?: React.ReactNode;
}
const DeleteDialog: React.FC<DeleteDialogProps> = ({
@ -26,6 +27,7 @@ const DeleteDialog: React.FC<DeleteDialogProps> = ({
topicName,
onConfirm,
isLoading,
warningContent,
}) => {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@ -39,6 +41,11 @@ const DeleteDialog: React.FC<DeleteDialogProps> = ({
dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.
</DialogDescription>
</DialogHeader>
{warningContent && (
<div className="rounded-lg border border-yellow-300 bg-yellow-50 p-3 text-sm text-yellow-800">
{warningContent}
</div>
)}
<DialogFooter>
<DialogClose className="mt-6 flex gap-2" asChild>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View file

@ -94,7 +94,7 @@ const CourseListItem: React.FC<CourseListItemProps> = ({
<div className="flex items-center w-full">
{trainers.map((trainerId, index) => {
const trainerInfo = trainersMap[trainerId];
const displayName = trainerInfo?.label ?? trainerId;
const displayName = trainerInfo?.label ?? "Ehemaliger Trainer";
const avatarSrc = trainerInfo?.pbSrc;
return (
<AbstractTooltip key={`${trainerId}-${index}`} tooltipText={displayName}>

View file

@ -50,7 +50,7 @@ const CourseListView = ({ canCreate, canEdit, canDelete }: CourseListViewProps)
const [isLoading, setIsLoading] = useState(true);
const [filterStatus, setFilterStatus] = useState<"all" | "draft" | "published">("all");
const [filterSport, setFilterSport] = useState<string>("all");
const [filterTrainer, setFilterTrainer] = useState<string>("all");
const [filterTrainer, setFilterTrainer] = useState<string>(searchParams.get("trainer") ?? "all");
const [filterRoom, setFilterRoom] = useState<string>(searchParams.get("room") ?? "all");
const [filterLevel, setFilterLevel] = useState<string>("all");
const [dateFrom, setDateFrom] = useState("");

View file

@ -708,6 +708,38 @@ export async function deleteEmployee(employeeId: string) {
}
}
/**
* Prüft, ob ein Trainer noch in aktiven (zukünftigen) Kursen eingetragen ist.
* Gibt die Anzahl der betroffenen Kurse zurück.
*/
export async function getActiveCoursesCountByTrainer(employeeId: string) {
try {
const userData = await getUserData();
if (
!userData ||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.employees.delete))
) {
return { success: false, error: "Unauthorized", count: 0 };
}
const today = new Date();
today.setHours(0, 0, 0, 0);
const count = await prisma.course.count({
where: {
trainers: { has: employeeId },
date: { gte: today },
},
});
return { success: true, count };
} catch (error) {
console.error("Error checking active courses for trainer:", error);
return { success: false, error: "Fehler beim Prüfen der Kurse", count: 0 };
}
}
/**
* Ruft einen Mitarbeiter anhand des Onboarding-Tokens ab (öffentlich, kein Login nötig)
*/

View file

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { deleteEmployee } from "../actions/employee-actions";
import { deleteEmployee, getActiveCoursesCountByTrainer } from "../actions/employee-actions";
import { toast } from "sonner";
interface UseDeleteEmployeeOptions {
@ -13,9 +13,12 @@ export function useDeleteEmployee({ onSuccess }: UseDeleteEmployeeOptions = {})
null
);
const [isDeleting, setIsDeleting] = useState(false);
const [activeCourseCount, setActiveCourseCount] = useState(0);
const handleDeleteClick = (id: string, name: string) => {
const handleDeleteClick = async (id: string, name: string) => {
setEmployeeToDelete({ id, name });
const result = await getActiveCoursesCountByTrainer(id);
setActiveCourseCount(result.success ? result.count : 0);
setDeleteDialogOpen(true);
};
@ -31,9 +34,11 @@ export function useDeleteEmployee({ onSuccess }: UseDeleteEmployeeOptions = {})
toast.success(result.message);
const deletedId = employeeToDelete.id;
setEmployeeToDelete(null);
setActiveCourseCount(0);
onSuccess?.(deletedId);
} else {
setEmployeeToDelete(null);
setActiveCourseCount(0);
toast.error(result.error || "Fehler beim Löschen des Mitarbeiters");
}
};
@ -44,6 +49,7 @@ export function useDeleteEmployee({ onSuccess }: UseDeleteEmployeeOptions = {})
employeeToDelete,
setEmployeeToDelete,
isDeleting,
activeCourseCount,
handleDeleteClick,
handleDeleteConfirm,
};

View file

@ -9,6 +9,9 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
import { createEmployee, updateEmployee } from "../../actions/employee-actions";
import { toast } from "sonner";
import { Trash2 } from "lucide-react";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteEmployee } from "../../hooks/useDeleteEmployee";
const LOCATION = [
{ value: "hainburg", label: "Hainburg" },
@ -115,6 +118,19 @@ const EmployeeCreateView = ({
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string[]>>({});
const {
deleteDialogOpen,
setDeleteDialogOpen,
employeeToDelete,
setEmployeeToDelete,
isDeleting,
activeCourseCount,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteEmployee({
onSuccess: () => router.push("/employee"),
});
const handleCreateRole = (newRole: { value: string; label: string }) => {
setRoles([...roles, newRole]);
setNewlyCreatedRoles([...newlyCreatedRoles, newRole.value]);
@ -182,12 +198,24 @@ const EmployeeCreateView = ({
return (
<div>
<h1 className="text-3xl font-bold">
{mode === "edit" ? "Mitarbeiter bearbeiten" : "Mitarbeiter anlegen"}
</h1>
<p className="text-xl mt-2">
Alle Daten können nach dem Speichern geändert oder gelöscht werden.
</p>
<div className="flex w-full items-center justify-between">
<div>
<h1 className="text-3xl font-bold">
{mode === "edit" ? "Mitarbeiter bearbeiten" : "Mitarbeiter anlegen"}
</h1>
<p className="text-xl mt-2">
Alle Daten können nach dem Speichern geändert oder gelöscht werden.
</p>
</div>
{mode === "edit" && employeeId && (
<Button
variant="destructive"
onClick={() => handleDeleteClick(employeeId, initialData?.email ?? employeeId)}
>
<Trash2 size={20} className="mb-1" /> Löschen
</Button>
)}
</div>
<div className="mt-6 flex flex-col gap-4 max-w-xl">
<div>
<InputComponent
@ -343,6 +371,29 @@ const EmployeeCreateView = ({
{isSubmitting ? "Wird gespeichert..." : "Entwurf speichern"}
</button>
</div>
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setEmployeeToDelete(null);
}}
itemName={employeeToDelete?.name}
topicName="Mitarbeiter"
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
warningContent={
activeCourseCount > 0 && employeeToDelete ? (
<p>
Dieser Trainer ist noch in{" "}
<strong>
{activeCourseCount} aktiven Kurs{activeCourseCount !== 1 ? "en" : ""}
</strong>{" "}
eingetragen. Nach dem Löschen wird er in diesen Kursen nicht mehr angezeigt.
</p>
) : undefined
}
/>
</div>
);
};

View file

@ -6,6 +6,7 @@ import EmployeeListItem from "../components/EmployeeListItem";
import { toast } from "sonner";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteEmployee } from "../../hooks/useDeleteEmployee";
import Link from "next/link";
type Employee = {
id: string;
@ -39,6 +40,7 @@ const EmployeeListView = ({ canCreate, canEdit, canDelete }: EmployeeListViewPro
employeeToDelete,
setEmployeeToDelete,
isDeleting,
activeCourseCount,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteEmployee({
@ -144,6 +146,24 @@ const EmployeeListView = ({ canCreate, canEdit, canDelete }: EmployeeListViewPro
topicName="Mitarbeiter"
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
warningContent={
activeCourseCount > 0 && employeeToDelete ? (
<p>
Dieser Trainer ist noch in{" "}
<strong>
{activeCourseCount} aktiven Kurs{activeCourseCount !== 1 ? "en" : ""}
</strong>{" "}
eingetragen.{" "}
<Link
href={`/courses?trainer=${employeeToDelete.id}`}
className="underline font-semibold"
onClick={() => setDeleteDialogOpen(false)}
>
Kurse anzeigen
</Link>
</p>
) : undefined
}
/>
</div>
);