Merge pull request #53 from Since-3/refactor/added-deletion-process-in-edit

added deletion process in edit and abstracted deletion process
This commit is contained in:
Rajpreet0 2026-03-05 15:47:17 +01:00 • committed by GitHub
commit 885cf3c20e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 312 additions and 134 deletions

View file

@ -127,10 +127,7 @@ const ResetPasswordView = () => {
<div className="flex flex-col sm:flex-row gap-2 items-center mt-8 text-sm">
<p>Zurück zum</p>
<p
className="text-yellow hover:underline cursor-pointer"
onClick={() => router.push("/login")}
>
<p className="text-blue underline cursor-pointer" onClick={() => router.push("/login")}>
Login
</p>
</div>

View file

@ -0,0 +1,48 @@
"use client";
import { useState } from "react";
import { deleteCourse } from "../actions/course-actions";
import { toast } from "sonner";
interface UseDeleteCourseOptions {
onSuccess?: (deletedId: string) => void;
}
export function useDeleteCourse({ onSuccess }: UseDeleteCourseOptions = {}) {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [courseToDelete, setCourseToDelete] = useState<{ id: string; name: string } | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const handleDeleteClick = (id: string, name: string) => {
setCourseToDelete({ id, name });
setDeleteDialogOpen(true);
};
const handleDeleteConfirm = async () => {
if (!courseToDelete) return;
setIsDeleting(true);
const result = await deleteCourse(courseToDelete.id);
setIsDeleting(false);
setDeleteDialogOpen(false);
if (result.success) {
toast.success(result.message);
const deletedId = courseToDelete.id;
setCourseToDelete(null);
onSuccess?.(deletedId);
} else {
setCourseToDelete(null);
toast.error(result.error || "Fehler beim Löschen des Kurses");
}
};
return {
deleteDialogOpen,
setDeleteDialogOpen,
courseToDelete,
setCourseToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
};
}

View file

@ -12,6 +12,9 @@ import { createCourse, updateCourse } from "../../actions/course-actions";
import { getMyRooms } from "@/src/modules/rooms/actions/room-actions";
import { toast } from "sonner";
import { Course } from "../../types/course.types";
import { Trash2 } from "lucide-react";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteCourse } from "../../hooks/useDeleteCourse";
const TRAINERS = [
{ value: "trainer1", label: "Max Mustermann" },
@ -82,6 +85,18 @@ const CourseCreateView = ({
const [rooms, setRooms] = useState<Array<{ value: string; label: string }>>([]);
const [isLoadingRooms, setIsLoadingRooms] = useState(true);
const {
deleteDialogOpen,
setDeleteDialogOpen,
courseToDelete,
setCourseToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteCourse({
onSuccess: () => router.push("/courses"),
});
// Load rooms from database
useEffect(() => {
const loadRooms = async () => {
@ -246,12 +261,23 @@ const CourseCreateView = ({
return (
<div>
<h1 className="text-3xl font-bold">{mode === "edit" ? "Kurs bearbeiten" : "Kurs anlegen"}</h1>
<p className="text-xl mt-2">
{mode === "edit"
? "Ändern Sie die Kursdetails und speichern Sie Ihre Änderungen"
: "Alle Kurse können nach dem Speichern angepasst oder gelöscht werden"}
</p>
<div className="w-full flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">
{mode === "edit" ? "Kurs bearbeiten" : "Kurs anlegen"}
</h1>
<p className="text-xl mt-2">
{mode === "edit"
? "Ändern Sie die Kursdetails und speichern Sie Ihre Änderungen"
: "Alle Kurse können nach dem Speichern angepasst oder gelöscht werden"}
</p>
</div>
{mode === "edit" && (
<Button variant="destructive" onClick={() => handleDeleteClick(courseId!, courseName)}>
<Trash2 className="mb-1" /> Löschen
</Button>
)}
</div>
<div className="mt-6 flex flex-col gap-4 max-w-xl">
<div>
<InputComponent
@ -473,6 +499,18 @@ const CourseCreateView = ({
{mode === "edit" ? "Als Entwurf speichern" : "Entwurf speichern"}
</button>
</div>
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setCourseToDelete(null);
}}
itemName={courseToDelete?.name}
topicName="Kurs"
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
/>
</div>
);
};

View file

@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { getMyCourses, deleteCourse } from "../../actions/course-actions";
import { getMyCourses } from "../../actions/course-actions";
import { useDeleteCourse } from "../../hooks/useDeleteCourse";
import { getAllRooms } from "@/src/modules/rooms/actions/room-actions";
import { Button } from "@/src/components/ui/button";
import { toast } from "sonner";
@ -52,11 +53,24 @@ const CourseListView = () => {
const [priceMin, setPriceMin] = useState<number | null>(null);
const [priceMax, setPriceMax] = useState<number | null>(null);
const [deleteMode, setDeleteMode] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [courseToDelete, setCourseToDelete] = useState<{ id: string; name: string } | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const [roomsMap, setRoomsMap] = useState<Record<string, string>>({});
const {
deleteDialogOpen,
setDeleteDialogOpen,
courseToDelete,
setCourseToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteCourse({
onSuccess: async () => {
if (loadedDateFrom && loadedDateTo) {
await loadCourses({ dateFrom: loadedDateFrom, dateTo: loadedDateTo }, "initial");
}
},
});
// Date range state for infinite scroll
const [loadedDateFrom, setLoadedDateFrom] = useState<Date | null>(null);
const [loadedDateTo, setLoadedDateTo] = useState<Date | null>(null);
@ -228,33 +242,6 @@ const CourseListView = () => {
setIsLoadingNewer(false);
};
const requestDelete = (courseId: string, courseName: string) => {
setCourseToDelete({ id: courseId, name: courseName });
setDeleteDialogOpen(true);
};
const confirmDelete = async () => {
if (!courseToDelete) return;
setIsDeleting(true);
const result = await deleteCourse(courseToDelete.id);
setIsDeleting(false);
setDeleteDialogOpen(false);
setCourseToDelete(null);
if (result.success) {
toast.success(result.message);
// Reload courses with the current date range
if (loadedDateFrom && loadedDateTo) {
await loadCourses({ dateFrom: loadedDateFrom, dateTo: loadedDateTo }, "initial");
}
} else {
toast.error(result.error || "Fehler beim Löschen");
}
};
const formatDate = (date: Date) => {
return new Date(date).toLocaleDateString("de-DE", {
weekday: "long",
@ -515,7 +502,7 @@ const CourseListView = () => {
status={course.status}
isPast={isPastCourse(course.date)}
showDeleteIcon={deleteMode}
onDelete={() => requestDelete(course.id, course.name)}
onDelete={() => handleDeleteClick(course.id, course.name)}
onEdit={() => router.push(`/courses/edit/${course.id}`)}
/>
))}
@ -543,10 +530,13 @@ const CourseListView = () => {
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setCourseToDelete(null);
}}
itemName={courseToDelete?.name}
topicName="Kurs"
onConfirm={confirmDelete}
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
/>
</div>

View file

@ -0,0 +1,50 @@
"use client";
import { useState } from "react";
import { deleteEmployee } from "../actions/employee-actions";
import { toast } from "sonner";
interface UseDeleteEmployeeOptions {
onSuccess?: (deletedId: string) => void;
}
export function useDeleteEmployee({ onSuccess }: UseDeleteEmployeeOptions = {}) {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [employeeToDelete, setEmployeeToDelete] = useState<{ id: string; name: string } | null>(
null
);
const [isDeleting, setIsDeleting] = useState(false);
const handleDeleteClick = (id: string, name: string) => {
setEmployeeToDelete({ id, name });
setDeleteDialogOpen(true);
};
const handleDeleteConfirm = async () => {
if (!employeeToDelete) return;
setIsDeleting(true);
const result = await deleteEmployee(employeeToDelete.id);
setIsDeleting(false);
setDeleteDialogOpen(false);
if (result.success) {
toast.success(result.message);
const deletedId = employeeToDelete.id;
setEmployeeToDelete(null);
onSuccess?.(deletedId);
} else {
setEmployeeToDelete(null);
toast.error(result.error || "Fehler beim Löschen des Mitarbeiters");
}
};
return {
deleteDialogOpen,
setDeleteDialogOpen,
employeeToDelete,
setEmployeeToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
};
}

View file

@ -1,10 +1,11 @@
import { Button } from "@/src/components/ui/button";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { getMyEmployees, deleteEmployee } from "../../actions/employee-actions";
import { getMyEmployees } from "../../actions/employee-actions";
import EmployeeListItem from "../components/EmployeeListItem";
import { toast } from "sonner";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteEmployee } from "../../hooks/useDeleteEmployee";
type Employee = {
id: string;
@ -26,11 +27,18 @@ const EmployeeListView = () => {
const [employees, setEmployees] = useState<Employee[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [deleteMode, setDeleteMode] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [employeeToDelete, setEmployeeToDelete] = useState<{ id: string; name: string } | null>(
null
);
const [isDeleting, setIsDeleting] = useState(false);
const {
deleteDialogOpen,
setDeleteDialogOpen,
employeeToDelete,
setEmployeeToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteEmployee({
onSuccess: (deletedId) => setEmployees((prev) => prev.filter((e) => e.id !== deletedId)),
});
useEffect(() => {
const loadEmployees = async () => {
@ -47,34 +55,6 @@ const EmployeeListView = () => {
loadEmployees();
}, []);
const requestDelete = (employeeId: string, employeeName: string) => {
setEmployeeToDelete({ id: employeeId, name: employeeName });
setDeleteDialogOpen(true);
};
const confirmDelete = async () => {
if (!employeeToDelete) return;
setIsDeleting(true);
const result = await deleteEmployee(employeeToDelete.id);
setIsDeleting(false);
setDeleteDialogOpen(false);
setEmployeeToDelete(null);
if (result.success) {
toast.success(result.message);
// Reload employees
const reloadResult = await getMyEmployees();
if (reloadResult.success) {
setEmployees(reloadResult.employees);
}
} else {
toast.error(result.error || "Fehler beim Löschen");
}
};
const getEmployeeName = (employee: Employee) => {
if (employee.firstName && employee.lastName) {
return `${employee.firstName} ${employee.lastName}`;
@ -131,7 +111,7 @@ const EmployeeListView = () => {
status={employee.status || "draft"}
isOnboarded={employee.isOnboarded}
showDeleteIcon={deleteMode}
onDelete={() => requestDelete(employee.id, getEmployeeName(employee))}
onDelete={() => handleDeleteClick(employee.id, getEmployeeName(employee))}
onEdit={() => router.push(`/employee/edit/${employee.id}`)}
/>
))}
@ -140,10 +120,13 @@ const EmployeeListView = () => {
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setEmployeeToDelete(null);
}}
itemName={employeeToDelete?.name}
topicName="Mitarbeiter"
onConfirm={confirmDelete}
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
/>
</div>

View file

@ -0,0 +1,67 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { deleteRoom } from "../actions/room-actions";
import { toast } from "sonner";
interface UseDeleteRoomOptions {
onSuccess?: (deletedId: string) => void;
}
export function useDeleteRoom({ onSuccess }: UseDeleteRoomOptions = {}) {
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [roomToDelete, setRoomToDelete] = useState<{ id: string; name: string } | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const handleDeleteClick = (id: string, name: string) => {
setRoomToDelete({ id, name });
setDeleteDialogOpen(true);
};
const handleDeleteConfirm = async () => {
if (!roomToDelete) return;
setIsDeleting(true);
const result = await deleteRoom(roomToDelete.id);
if (result.success) {
toast.success(result.message);
setDeleteDialogOpen(false);
const deletedId = roomToDelete.id;
setRoomToDelete(null);
onSuccess?.(deletedId);
} else if (result.hasActiveCourses) {
setDeleteDialogOpen(false);
setRoomToDelete(null);
toast.error(result.error, {
duration: 8000,
style: { maxWidth: "680px" },
action: {
label: "Kurse anzeigen",
onClick: () => router.push(`/courses?room=${encodeURIComponent(result.roomName)}`),
},
actionButtonStyle: {
backgroundColor: "#F4C00C",
color: "#1E293B",
fontWeight: "600",
padding: "8px 14px",
borderRadius: "6px",
},
});
} else {
toast.error(result.error || "Fehler beim Löschen des Raumes");
}
setIsDeleting(false);
};
return {
deleteDialogOpen,
setDeleteDialogOpen,
roomToDelete,
setRoomToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
};
}

View file

@ -6,6 +6,9 @@ import { Button } from "@/src/components/ui/button";
import { createRoom, updateRoom } from "../../actions/room-actions";
import { toast } from "sonner";
import { Room } from "../../types/room.types";
import { Trash2 } from "lucide-react";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteRoom } from "../../hooks/useDeleteRoom";
interface RoomsCreateViewProps {
mode?: "create" | "edit";
@ -19,6 +22,18 @@ const RoomsCreateView = ({ mode = "create", roomId, initialData }: RoomsCreateVi
const [errors, setErrors] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const {
deleteDialogOpen,
setDeleteDialogOpen,
roomToDelete,
setRoomToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteRoom({
onSuccess: () => router.push("/rooms"),
});
// Load initial data when in edit mode
useEffect(() => {
if (mode === "edit" && initialData) {
@ -80,12 +95,23 @@ const RoomsCreateView = ({ mode = "create", roomId, initialData }: RoomsCreateVi
return (
<div>
<h1 className="text-3xl font-bold">{mode === "edit" ? "Raum bearbeiten" : "Raum anlegen"}</h1>
<p className="text-xl mt-2">
{mode === "edit"
? "Ändern Sie den Raumnamen und speichern Sie Ihre Änderungen"
: "Alle Räume können nach dem Speichern angepasst oder gelöscht werden"}
</p>
<div className="flex w-full items-center justify-between">
<div>
<h1 className="text-3xl font-bold">
{mode === "edit" ? "Raum bearbeiten" : "Raum anlegen"}
</h1>
<p className="text-xl mt-2">
{mode === "edit"
? "Ändern Sie den Raumnamen und speichern Sie Ihre Änderungen"
: "Alle Räume können nach dem Speichern angepasst oder gelöscht werden"}
</p>
</div>
{mode === "edit" && (
<Button variant="destructive" onClick={() => handleDeleteClick(roomId!, roomName)}>
<Trash2 size={20} className="mb-1" /> Löschen
</Button>
)}
</div>
<div className="mt-6 flex flex-col gap-4 max-w-xl">
<div>
<InputComponent
@ -111,6 +137,18 @@ const RoomsCreateView = ({ mode = "create", roomId, initialData }: RoomsCreateVi
</Button>
</div>
</div>
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setRoomToDelete(null);
}}
itemName={roomToDelete?.name}
topicName="Raum"
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
/>
</div>
);
};

View file

@ -1,10 +1,11 @@
"use client";
import { useEffect, useState } from "react";
import { getMyRooms, deleteRoom } from "../../actions/room-actions";
import { getMyRooms } from "../../actions/room-actions";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import RoomsListItem from "../components/RoomsListItem";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import { useDeleteRoom } from "../../hooks/useDeleteRoom";
type Room = {
id: string;
@ -22,9 +23,18 @@ const RoomsListView = ({ deleteMode }: RoomsListViewProps) => {
const router = useRouter();
const [rooms, setRooms] = useState<Room[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [roomToDelete, setRoomToDelete] = useState<{ id: string; name: string } | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const {
deleteDialogOpen,
setDeleteDialogOpen,
roomToDelete,
setRoomToDelete,
isDeleting,
handleDeleteClick,
handleDeleteConfirm,
} = useDeleteRoom({
onSuccess: (deletedId) => setRooms((prev) => prev.filter((room) => room.id !== deletedId)),
});
// Load rooms
const loadRooms = async () => {
@ -46,49 +56,6 @@ const RoomsListView = ({ deleteMode }: RoomsListViewProps) => {
fetchRooms();
}, []);
// Handle delete click
const handleDeleteClick = (id: string, name: string) => {
setRoomToDelete({ id, name });
setDeleteDialogOpen(true);
};
// Confirm delete
const handleDeleteConfirm = async () => {
if (!roomToDelete) return;
setIsDeleting(true);
const result = await deleteRoom(roomToDelete.id);
if (result.success) {
toast.success(result.message);
// Remove the deleted room from the list
setRooms((prev) => prev.filter((room) => room.id !== roomToDelete.id));
setDeleteDialogOpen(false);
setRoomToDelete(null);
} else if (result.hasActiveCourses) {
setDeleteDialogOpen(false);
setRoomToDelete(null);
toast.error(result.error, {
duration: 8000,
style: { maxWidth: "680px" },
action: {
label: "Kurse anzeigen",
onClick: () => router.push(`/courses?room=${encodeURIComponent(result.roomName)}`),
},
actionButtonStyle: {
backgroundColor: "#F4C00C",
color: "#1E293B",
fontWeight: "600",
padding: "8px 14px",
borderRadius: "6px",
},
});
} else {
toast.error(result.error || "Fehler beim Löschen des Raumes");
}
setIsDeleting(false);
};
// Handle edit click
const handleEditClick = (id: string) => {
router.push(`/rooms/edit/${id}`);