mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:51:18 +00:00
added attributtion access management for courses, employees and rooms for create, view, update and delete
This commit is contained in:
parent
df09e04d4b
commit
11f5be2d72
24 changed files with 652 additions and 302 deletions
|
|
@ -3,7 +3,7 @@ import CourseCreateView from "@/src/modules/courses/ui/views/course-create-view"
|
|||
import { getMySportTypes } from "@/src/modules/courses/actions/course-actions";
|
||||
|
||||
const CourseCreate = async () => {
|
||||
await requireManagerOrPermission("canCreateCourses");
|
||||
await requireManagerOrPermission((p) => p.courses.create);
|
||||
|
||||
const sportsResult = await getMySportTypes();
|
||||
const customSports = sportsResult.success ? sportsResult.sports : [];
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import CourseEditView from "@/src/modules/courses/ui/views/course-edit-view";
|
|||
import { getMySportTypes } from "@/src/modules/courses/actions/course-actions";
|
||||
|
||||
const CourseEdit = async ({ params }: { params: Promise<{ id: string }> }) => {
|
||||
await requireManagerOrPermission("canCreateCourses");
|
||||
await requireManagerOrPermission((p) => p.courses.edit);
|
||||
const { id } = await params;
|
||||
|
||||
const sportsResult = await getMySportTypes();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import { requireManagerOrPermission } from "@/src/lib/auth/getUser";
|
||||
import { requireManagerOrPermission, isManager, isEmployee } from "@/src/lib/auth/getUser";
|
||||
import CourseView from "@/src/modules/courses/ui/views/course-view";
|
||||
|
||||
const Course = async () => {
|
||||
await requireManagerOrPermission("canCreateCourses");
|
||||
return <CourseView />;
|
||||
const userData = await requireManagerOrPermission((p) => p.courses.view);
|
||||
const canCreate =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.courses.create);
|
||||
const canEdit =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.courses.edit);
|
||||
const canDelete =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.courses.delete);
|
||||
return <CourseView canCreate={canCreate} canEdit={canEdit} canDelete={canDelete} />;
|
||||
};
|
||||
|
||||
export default Course;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import EmployeeCreateView from "@/src/modules/employee/ui/views/employee-create-
|
|||
import { getMyEmployeeRoles } from "@/src/modules/employee/actions/employee-actions";
|
||||
|
||||
const EmployeeCreate = async () => {
|
||||
await requireManagerOrPermission("canCreateEmployees");
|
||||
await requireManagerOrPermission((p) => p.employees.create);
|
||||
|
||||
// Lade alle bereits verwendeten custom Rollen
|
||||
const rolesResult = await getMyEmployeeRoles();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import { requireManagerOrPermission } from "@/src/lib/auth/getUser";
|
||||
import { requireManagerOrPermission, isManager, isEmployee } from "@/src/lib/auth/getUser";
|
||||
import EmployeeView from "@/src/modules/employee/ui/views/employee-view";
|
||||
|
||||
const Employee = async () => {
|
||||
await requireManagerOrPermission("canCreateEmployees");
|
||||
return <EmployeeView />;
|
||||
const userData = await requireManagerOrPermission((p) => p.employees.view);
|
||||
const canCreate =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.employees.create);
|
||||
const canEdit =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.employees.edit);
|
||||
const canDelete =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.employees.delete);
|
||||
return <EmployeeView canCreate={canCreate} canEdit={canEdit} canDelete={canDelete} />;
|
||||
};
|
||||
|
||||
export default Employee;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { requireManagerOrPermission } from "@/src/lib/auth/getUser";
|
||||
import RoomsCreateView from "@/src/modules/rooms/ui/views/rooms-create-view";
|
||||
|
||||
const RoomsCreate = async () => {
|
||||
await requireManager();
|
||||
await requireManagerOrPermission((p) => p.rooms.create);
|
||||
return <RoomsCreateView />;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { requireManagerOrPermission } from "@/src/lib/auth/getUser";
|
||||
import RoomsEditView from "@/src/modules/rooms/ui/views/rooms-edit-view";
|
||||
|
||||
const RoomsEdit = async ({ params }: { params: Promise<{ id: string }> }) => {
|
||||
await requireManager();
|
||||
await requireManagerOrPermission((p) => p.rooms.edit);
|
||||
const { id } = await params;
|
||||
return <RoomsEditView roomId={id} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { requireManagerOrPermission, isManager, isEmployee } from "@/src/lib/auth/getUser";
|
||||
import RoomsView from "@/src/modules/rooms/ui/views/rooms-view";
|
||||
|
||||
const Rooms = async () => {
|
||||
await requireManager();
|
||||
return <RoomsView />;
|
||||
const userData = await requireManagerOrPermission((p) => p.rooms.view);
|
||||
const canCreate =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.rooms.create);
|
||||
const canEdit = isManager(userData) || (isEmployee(userData) && userData.permissions.rooms.edit);
|
||||
const canDelete =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.rooms.delete);
|
||||
return <RoomsView canCreate={canCreate} canEdit={canEdit} canDelete={canDelete} />;
|
||||
};
|
||||
|
||||
export default Rooms;
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ export type EmployeeData = {
|
|||
createdAt: Date | null;
|
||||
role: "employee";
|
||||
permissions: {
|
||||
canCreateCourses: boolean;
|
||||
canCreateEmployees: boolean;
|
||||
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -177,18 +178,61 @@ export const getUserData = cache(async (): Promise<AuthUserData | null> => {
|
|||
"✅ [DB] Employee data fetched:",
|
||||
`${employeeData.firstName} ${employeeData.lastName}`
|
||||
);
|
||||
const rawPermissions = employeeData.permissions as {
|
||||
canCreateCourses?: boolean;
|
||||
canCreateEmployees?: boolean;
|
||||
} | null;
|
||||
const raw = employeeData.permissions as Record<string, unknown> | null;
|
||||
const empty = () => ({ view: false, create: false, edit: false, delete: false });
|
||||
|
||||
let permissions: EmployeeData["permissions"];
|
||||
|
||||
if (raw && ("canCreateCourses" in raw || "canCreateEmployees" in raw)) {
|
||||
// Backward-Kompatibilität: altes Format migrieren
|
||||
const old = raw as { canCreateCourses?: boolean; canCreateEmployees?: boolean };
|
||||
permissions = {
|
||||
employees: {
|
||||
view: old.canCreateEmployees ?? false,
|
||||
create: old.canCreateEmployees ?? false,
|
||||
edit: false,
|
||||
delete: false,
|
||||
},
|
||||
courses: {
|
||||
view: old.canCreateCourses ?? false,
|
||||
create: old.canCreateCourses ?? false,
|
||||
edit: false,
|
||||
delete: false,
|
||||
},
|
||||
rooms: empty(),
|
||||
};
|
||||
} else {
|
||||
const p = raw as {
|
||||
employees?: Partial<EmployeeData["permissions"]["employees"]>;
|
||||
courses?: Partial<EmployeeData["permissions"]["courses"]>;
|
||||
rooms?: Partial<EmployeeData["permissions"]["rooms"]>;
|
||||
} | null;
|
||||
permissions = {
|
||||
employees: {
|
||||
view: p?.employees?.view ?? false,
|
||||
create: p?.employees?.create ?? false,
|
||||
edit: p?.employees?.edit ?? false,
|
||||
delete: p?.employees?.delete ?? false,
|
||||
},
|
||||
courses: {
|
||||
view: p?.courses?.view ?? false,
|
||||
create: p?.courses?.create ?? false,
|
||||
edit: p?.courses?.edit ?? false,
|
||||
delete: p?.courses?.delete ?? false,
|
||||
},
|
||||
rooms: {
|
||||
view: p?.rooms?.view ?? false,
|
||||
create: p?.rooms?.create ?? false,
|
||||
edit: p?.rooms?.edit ?? false,
|
||||
delete: p?.rooms?.delete ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const employeeWithRole: EmployeeData = {
|
||||
...employeeData,
|
||||
role: "employee",
|
||||
permissions: {
|
||||
canCreateCourses: rawPermissions?.canCreateCourses ?? false,
|
||||
canCreateEmployees: rawPermissions?.canCreateEmployees ?? false,
|
||||
},
|
||||
permissions,
|
||||
};
|
||||
return employeeWithRole;
|
||||
}
|
||||
|
|
@ -260,12 +304,14 @@ export function isEmployee(userData: AuthUserData): userData is EmployeeData {
|
|||
* Requires that the authenticated user is a Manager OR an Employee with the given permission.
|
||||
* Redirects to /dashboard if access is denied.
|
||||
*/
|
||||
export async function requireManagerOrPermission(permission: keyof EmployeeData["permissions"]) {
|
||||
export async function requireManagerOrPermission(
|
||||
check: (permissions: EmployeeData["permissions"]) => boolean
|
||||
) {
|
||||
const userData = await requireAuthWithData();
|
||||
|
||||
if (isManager(userData)) return userData;
|
||||
|
||||
if (isEmployee(userData) && userData.permissions[permission]) return userData;
|
||||
if (isEmployee(userData) && check(userData.permissions)) return userData;
|
||||
|
||||
const { redirect } = await import("next/navigation");
|
||||
redirect("/dashboard");
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"use server";
|
||||
|
||||
import { prisma } from "@/src/lib/prisma";
|
||||
import { getUserData, isManager } from "@/src/lib/auth/getUser";
|
||||
import { getUserData, isManager, isEmployee } from "@/src/lib/auth/getUser";
|
||||
import { courseSchema, type CourseFormData } from "../schemas/course-schema";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
|
@ -37,11 +37,13 @@ export async function createCourse(data: CourseFormData, status: "draft" | "publ
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist (nur Manager dürfen Kurse erstellen)
|
||||
if (!isManager(userData)) {
|
||||
// Schritt 3: Manager oder Employee mit Berechtigung
|
||||
const canCreate =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.courses.create);
|
||||
if (!canCreate) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Kurse erstellen",
|
||||
error: "Keine Berechtigung zum Erstellen von Kursen",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -58,13 +60,20 @@ export async function createCourse(data: CourseFormData, status: "draft" | "publ
|
|||
|
||||
const validatedData = validation.data;
|
||||
|
||||
// Schritt 5: Kurs in der Datenbank erstellen
|
||||
// Schritt 5: Manager-ID ermitteln (Employee erbt createdBy vom eigenen Datensatz)
|
||||
let creatorId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
creatorId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 6: Kurs in der Datenbank erstellen
|
||||
const course = await prisma.course.create({
|
||||
data: {
|
||||
name: validatedData.name,
|
||||
sport: validatedData.sport,
|
||||
level: validatedData.level || "any",
|
||||
date: new Date(validatedData.date), // String zu Date konvertieren
|
||||
date: new Date(validatedData.date),
|
||||
timeFrom: validatedData.timeFrom,
|
||||
timeTo: validatedData.timeTo,
|
||||
trainers: validatedData.trainers,
|
||||
|
|
@ -75,8 +84,8 @@ export async function createCourse(data: CourseFormData, status: "draft" | "publ
|
|||
isStandingOrder: validatedData.isStandingOrder,
|
||||
frequency: validatedData.frequency || null,
|
||||
weekdays: validatedData.weekdays || [],
|
||||
status, // "draft" oder "published"
|
||||
createdBy: userData.id, // Manager-ID als Ersteller
|
||||
status,
|
||||
createdBy: creatorId,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -152,8 +161,11 @@ export async function getMyCourses(options?: { dateFrom?: Date; dateTo?: Date })
|
|||
// Step 1: Get current user
|
||||
const userData = await getUserData();
|
||||
|
||||
// Step 2: Check if user is logged in and is a manager
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Step 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.courses.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Unauthorized",
|
||||
|
|
@ -161,9 +173,16 @@ export async function getMyCourses(options?: { dateFrom?: Date; dateTo?: Date })
|
|||
};
|
||||
}
|
||||
|
||||
// Step 3: Build the where clause with optional date filtering
|
||||
// Step 3: Manager-ID ermitteln (Employee erbt createdBy vom eigenen Datensatz)
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Step 4: Build the where clause with optional date filtering
|
||||
const whereClause: Prisma.CourseWhereInput = {
|
||||
createdBy: userData.id, // Filter by manager ID
|
||||
createdBy: managerId,
|
||||
};
|
||||
|
||||
// Add date range filter if provided
|
||||
|
|
@ -222,20 +241,30 @@ export async function getCourseById(courseId: string) {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.courses.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Kurse bearbeiten",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Kurs aus der Datenbank abrufen
|
||||
// Schritt 3: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Kurs aus der Datenbank abrufen
|
||||
const course = await prisma.course.findUnique({
|
||||
where: { id: courseId },
|
||||
});
|
||||
|
||||
// Schritt 4: Überprüfen, ob Kurs existiert
|
||||
// Schritt 5: Überprüfen, ob Kurs existiert
|
||||
if (!course) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -243,8 +272,8 @@ export async function getCourseById(courseId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
if (course.createdBy !== userData.id) {
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
if (course.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Kurse bearbeiten",
|
||||
|
|
@ -293,20 +322,29 @@ export async function updateCourse(
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!isManager(userData)) {
|
||||
// Schritt 3: Manager oder Employee mit Berechtigung
|
||||
const canEdit =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.courses.edit);
|
||||
if (!canEdit) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Kurse bearbeiten",
|
||||
error: "Keine Berechtigung zum Bearbeiten von Kursen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 4: Kurs aus der Datenbank abrufen
|
||||
// Schritt 4: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 5: Kurs aus der Datenbank abrufen
|
||||
const existingCourse = await prisma.course.findUnique({
|
||||
where: { id: courseId },
|
||||
});
|
||||
|
||||
// Schritt 5: Überprüfen, ob Kurs existiert
|
||||
// Schritt 6: Überprüfen, ob Kurs existiert
|
||||
if (!existingCourse) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -314,15 +352,15 @@ export async function updateCourse(
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
if (existingCourse.createdBy !== userData.id) {
|
||||
// Schritt 7: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
if (existingCourse.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Kurse bearbeiten",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 7: Formulardaten mit Zod-Schema validieren
|
||||
// Schritt 8: Formulardaten mit Zod-Schema validieren
|
||||
const validation = courseSchema.safeParse(data);
|
||||
|
||||
if (!validation.success) {
|
||||
|
|
@ -393,20 +431,30 @@ export async function deleteCourse(courseId: string) {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.courses.delete))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Kurse löschen",
|
||||
error: "Keine Berechtigung zum Löschen von Kursen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Kurs aus der Datenbank abrufen
|
||||
// Schritt 3: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Kurs aus der Datenbank abrufen
|
||||
const course = await prisma.course.findUnique({
|
||||
where: { id: courseId },
|
||||
});
|
||||
|
||||
// Schritt 4: Überprüfen, ob Kurs existiert
|
||||
// Schritt 5: Überprüfen, ob Kurs existiert
|
||||
if (!course) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -414,21 +462,20 @@ export async function deleteCourse(courseId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
// (Sicherheit: Manager können nur ihre eigenen Kurse löschen)
|
||||
if (course.createdBy !== userData.id) {
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Kurses ist
|
||||
if (course.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Kurse löschen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 6: Kurs aus der Datenbank löschen
|
||||
// Schritt 7: Kurs aus der Datenbank löschen
|
||||
await prisma.course.delete({
|
||||
where: { id: courseId },
|
||||
});
|
||||
|
||||
// Schritt 7: Next.js Cache invalidieren, damit gelöschter Kurs nicht mehr angezeigt wird
|
||||
// Schritt 8: Next.js Cache invalidieren, damit gelöschter Kurs nicht mehr angezeigt wird
|
||||
revalidatePath("/courses");
|
||||
|
||||
return {
|
||||
|
|
@ -451,12 +498,21 @@ export async function getMySportTypes() {
|
|||
try {
|
||||
const userData = await getUserData();
|
||||
|
||||
if (!userData || !isManager(userData)) {
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.courses.view))
|
||||
) {
|
||||
return { success: false, error: "Unauthorized", sports: [] };
|
||||
}
|
||||
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
const courses = await prisma.course.findMany({
|
||||
where: { createdBy: userData.id },
|
||||
where: { createdBy: managerId },
|
||||
select: { sport: true },
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,13 @@ type Course = {
|
|||
};
|
||||
};
|
||||
|
||||
const CourseListView = () => {
|
||||
interface CourseListViewProps {
|
||||
canCreate: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const CourseListView = ({ canCreate, canEdit, canDelete }: CourseListViewProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [courses, setCourses] = useState<Course[]>([]);
|
||||
|
|
@ -378,16 +384,20 @@ const CourseListView = () => {
|
|||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex flex-col w-full gap-2 mr-2 sm:flex-row sm:justify-end">
|
||||
<div className="flex gap-2 sm:hidden">
|
||||
<Button onClick={() => router.push("/courses/create")} className="flex-1">
|
||||
Kurs anlegen
|
||||
</Button>
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="flex-1"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Kurs löschen"}
|
||||
</Button>
|
||||
{canCreate && (
|
||||
<Button onClick={() => router.push("/courses/create")} className="flex-1">
|
||||
Kurs anlegen
|
||||
</Button>
|
||||
)}
|
||||
{canDelete && (
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="flex-1"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Kurs löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile: Filter button below the two action buttons */}
|
||||
|
|
@ -503,13 +513,15 @@ const CourseListView = () => {
|
|||
)}
|
||||
</Button>
|
||||
</CourseFilter>
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="hidden sm:flex items-center gap-2"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Kurs löschen"}
|
||||
</Button>
|
||||
{canDelete && (
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="hidden sm:flex items-center gap-2"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Kurs löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -540,7 +552,7 @@ const CourseListView = () => {
|
|||
? "Erstellen Sie Ihren ersten Kurs!"
|
||||
: "Ändern Sie die Filter oder setzen Sie sie zurück"}
|
||||
</p>
|
||||
{courses.length === 0 && (
|
||||
{courses.length === 0 && canCreate && (
|
||||
<Button onClick={() => router.push("/courses/create")} className="mt-4">
|
||||
Kurs anlegen
|
||||
</Button>
|
||||
|
|
@ -573,9 +585,11 @@ const CourseListView = () => {
|
|||
trainers={course.trainers}
|
||||
status={course.status}
|
||||
isPast={isPastCourse(course.date)}
|
||||
showDeleteIcon={deleteMode}
|
||||
onDelete={() => handleDeleteClick(course.id, course.name)}
|
||||
onEdit={() => router.push(`/courses/edit/${course.id}`)}
|
||||
showDeleteIcon={deleteMode && canDelete}
|
||||
onDelete={
|
||||
canDelete ? () => handleDeleteClick(course.id, course.name) : undefined
|
||||
}
|
||||
onEdit={canEdit ? () => router.push(`/courses/edit/${course.id}`) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ import { Button } from "@/src/components/ui/button";
|
|||
import { useRouter } from "next/navigation";
|
||||
import CourseListView from "./course-list-view";
|
||||
|
||||
const CourseView = () => {
|
||||
interface CourseViewProps {
|
||||
canCreate: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const CourseView = ({ canCreate, canEdit, canDelete }: CourseViewProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
|
|
@ -13,16 +19,18 @@ const CourseView = () => {
|
|||
<div className="w-full flex flex-col items-left p-2 md:flex-row md:justify-between md:items-center">
|
||||
<h1 className="text-4xl font-bold">Kurse</h1>
|
||||
<div className="flex flex-col gap-4 mt-2 md:flex-row md:mt-0">
|
||||
<Button
|
||||
onClick={() => router.push("/courses/create")}
|
||||
className="hidden sm:flex text-sm tracking-wide font-normal"
|
||||
>
|
||||
Kurs anlegen
|
||||
</Button>
|
||||
{canCreate && (
|
||||
<Button
|
||||
onClick={() => router.push("/courses/create")}
|
||||
className="hidden sm:flex text-sm tracking-wide font-normal"
|
||||
>
|
||||
Kurs anlegen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Suspense>
|
||||
<CourseListView />
|
||||
<CourseListView canCreate={canCreate} canEdit={canEdit} canDelete={canDelete} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type MenuItem = {
|
|||
label: string;
|
||||
href: string;
|
||||
roles?: string[];
|
||||
permission?: keyof EmployeeData["permissions"];
|
||||
permission?: (p: EmployeeData["permissions"]) => boolean;
|
||||
};
|
||||
|
||||
const allMenuItems: MenuItem[] = [
|
||||
|
|
@ -60,7 +60,7 @@ const allMenuItems: MenuItem[] = [
|
|||
label: "Kurse",
|
||||
href: "/courses",
|
||||
roles: ["employee"],
|
||||
permission: "canCreateCourses",
|
||||
permission: (p) => p.courses.view,
|
||||
},
|
||||
{
|
||||
icon: IdCardLanyard,
|
||||
|
|
@ -73,7 +73,7 @@ const allMenuItems: MenuItem[] = [
|
|||
label: "Mitarbeiter",
|
||||
href: "/employee",
|
||||
roles: ["employee"],
|
||||
permission: "canCreateEmployees",
|
||||
permission: (p) => p.employees.view,
|
||||
},
|
||||
{
|
||||
icon: User,
|
||||
|
|
@ -93,6 +93,13 @@ const allMenuItems: MenuItem[] = [
|
|||
href: "/rooms",
|
||||
roles: ["manager"],
|
||||
},
|
||||
{
|
||||
icon: DoorOpen,
|
||||
label: "Räume",
|
||||
href: "/rooms",
|
||||
roles: ["employee"],
|
||||
permission: (p) => p.rooms.view,
|
||||
},
|
||||
{
|
||||
icon: ChartNoAxesCombined,
|
||||
label: "Marketing",
|
||||
|
|
@ -114,7 +121,7 @@ const DashboardSidebar = ({ userData, displayName }: DashboardSidebarProps) => {
|
|||
if (!item.roles?.includes(userRole)) return false;
|
||||
if (userRole === "employee" && item.permission) {
|
||||
const employeeData = userData as EmployeeData;
|
||||
return employeeData.permissions[item.permission] === true;
|
||||
return item.permission(employeeData.permissions);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,14 +44,39 @@ const DashboardView = async () => {
|
|||
) : isEmployee(userData) ? (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-2">Meine Berechtigungen</h2>
|
||||
<ul className="text-gray-600 text-sm space-y-1 mt-2">
|
||||
{userData.permissions.canCreateCourses && <li>• Kurse erstellen</li>}
|
||||
{userData.permissions.canCreateEmployees && <li>• Mitarbeiter anlegen</li>}
|
||||
{!userData.permissions.canCreateCourses &&
|
||||
!userData.permissions.canCreateEmployees && (
|
||||
<li className="text-gray-400">Keine erweiterten Berechtigungen</li>
|
||||
)}
|
||||
</ul>
|
||||
{(() => {
|
||||
const p = userData.permissions;
|
||||
const RESOURCE_LABELS: Record<string, string> = {
|
||||
employees: "Mitarbeiter",
|
||||
courses: "Kurse",
|
||||
rooms: "Räume",
|
||||
};
|
||||
const ACTION_LABELS: Record<string, string> = {
|
||||
view: "ansehen",
|
||||
create: "erstellen",
|
||||
edit: "bearbeiten",
|
||||
delete: "löschen",
|
||||
};
|
||||
const rows = (Object.entries(p) as [string, Record<string, boolean>][])
|
||||
.map(([res, actions]) => {
|
||||
const active = Object.entries(actions)
|
||||
.filter(([, v]) => v)
|
||||
.map(([a]) => ACTION_LABELS[a] ?? a);
|
||||
return active.length > 0
|
||||
? `${RESOURCE_LABELS[res] ?? res}: ${active.join(", ")}`
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
return rows.length > 0 ? (
|
||||
<ul className="text-gray-600 text-sm space-y-1 mt-2">
|
||||
{rows.map((row) => (
|
||||
<li key={row}>• {row}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-400 text-sm mt-2">Keine erweiterten Berechtigungen</p>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export async function createEmployee(data: EmployeeFormData, status: "draft" | "
|
|||
|
||||
// Schritt 3: Manager oder Employee mit Berechtigung
|
||||
const canManageEmployees =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.canCreateEmployees);
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.employees.create);
|
||||
if (!canManageEmployees) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -181,7 +181,7 @@ export async function getMyEmployees() {
|
|||
// Schritt 2: Überprüfen, ob Benutzer ein Manager oder Employee mit Berechtigung ist
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.canCreateEmployees))
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.employees.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -239,7 +239,7 @@ export async function getEmployeeById(employeeId: string) {
|
|||
// Schritt 2: Überprüfen, ob Benutzer ein Manager oder Employee mit Berechtigung ist
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.canCreateEmployees))
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.employees.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -320,7 +320,7 @@ export async function updateEmployee(
|
|||
|
||||
// Schritt 3: Überprüfen, ob Benutzer ein Manager oder Employee mit Berechtigung ist
|
||||
const canManageEmployees =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.canCreateEmployees);
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.employees.edit);
|
||||
if (!canManageEmployees) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -349,11 +349,16 @@ export async function updateEmployee(
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Mitarbeiters ist
|
||||
if (isManager(userData) && existingEmployee.createdBy !== userData.id) {
|
||||
// Schritt 6: Überprüfen, ob der Benutzer der Besitzer des Mitarbeiters ist
|
||||
let ownerManagerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
ownerManagerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
if (existingEmployee.createdBy !== ownerManagerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Mitarbeiter bearbeiten",
|
||||
error: "Sie können nur Mitarbeiter Ihres Managers bearbeiten",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -455,8 +460,11 @@ export async function getMyEmployeeRoles() {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.employees.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Unauthorized",
|
||||
|
|
@ -464,10 +472,17 @@ export async function getMyEmployeeRoles() {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Alle Mitarbeiter des Managers abrufen (nur roles Feld)
|
||||
// Schritt 3: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Alle Mitarbeiter des Managers abrufen (nur roles Feld)
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: {
|
||||
createdBy: userData.id,
|
||||
createdBy: managerId,
|
||||
},
|
||||
select: {
|
||||
roles: true,
|
||||
|
|
@ -517,7 +532,7 @@ export async function deleteEmployee(employeeId: string) {
|
|||
// Schritt 2: Überprüfen, ob Benutzer ein Manager oder Employee mit Berechtigung ist
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.canCreateEmployees))
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.employees.delete))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -546,11 +561,16 @@ export async function deleteEmployee(employeeId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Mitarbeiters ist
|
||||
if (isManager(userData) && employee.createdBy !== userData.id) {
|
||||
// Schritt 5: Überprüfen, ob der Benutzer der Besitzer des Mitarbeiters ist
|
||||
let ownerManagerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
ownerManagerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
if (employee.createdBy !== ownerManagerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Mitarbeiter löschen",
|
||||
error: "Sie können nur Mitarbeiter Ihres Managers löschen",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,24 @@ export const employeeSchema = z
|
|||
|
||||
// Administrationsrechte
|
||||
permissions: z.object({
|
||||
// Berechtigung zum Anzeigen/Verwalten von Kursen
|
||||
canCreateCourses: z.boolean(),
|
||||
// Berechtigung zum Anzeigen/Verwalten von Mitarbeitern
|
||||
canCreateEmployees: z.boolean(),
|
||||
employees: z.object({
|
||||
view: z.boolean(),
|
||||
create: z.boolean(),
|
||||
edit: z.boolean(),
|
||||
delete: z.boolean(),
|
||||
}),
|
||||
courses: z.object({
|
||||
view: z.boolean(),
|
||||
create: z.boolean(),
|
||||
edit: z.boolean(),
|
||||
delete: z.boolean(),
|
||||
}),
|
||||
rooms: z.object({
|
||||
view: z.boolean(),
|
||||
create: z.boolean(),
|
||||
edit: z.boolean(),
|
||||
delete: z.boolean(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.refine(
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ interface EmployeeListItemProps {
|
|||
roles: string[]; // Rollen des Mitarbeiters
|
||||
locations: string[]; // Standorte
|
||||
permissions: {
|
||||
canCreateCourses: boolean;
|
||||
canCreateEmployees: boolean;
|
||||
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
} | null; // Administrationsrechte
|
||||
status: string; // Status ("draft" oder "published")
|
||||
isOnboarded: boolean; // Ob der Mitarbeiter das Onboarding abgeschlossen hat
|
||||
|
|
@ -113,26 +114,49 @@ const EmployeeListItem: React.FC<EmployeeListItemProps> = ({
|
|||
)}
|
||||
|
||||
{/* Administrationsrechte */}
|
||||
{permissions && (permissions.canCreateCourses || permissions.canCreateEmployees) && (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-gray-600 mb-2">
|
||||
<Lock size={16} />
|
||||
<span className="text-sm font-medium">Administrationsrechte</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{permissions.canCreateCourses && (
|
||||
<span className="px-3 py-1 bg-purple-50 text-purple-700 text-sm rounded-full">
|
||||
Kurse anlegen
|
||||
</span>
|
||||
)}
|
||||
{permissions.canCreateEmployees && (
|
||||
<span className="px-3 py-1 bg-purple-50 text-purple-700 text-sm rounded-full">
|
||||
Trainer anlegen
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{permissions &&
|
||||
(() => {
|
||||
const LABELS: Record<string, string> = {
|
||||
"employees.view": "Mitarbeiter ansehen",
|
||||
"employees.create": "Mitarbeiter erstellen",
|
||||
"employees.edit": "Mitarbeiter bearbeiten",
|
||||
"employees.delete": "Mitarbeiter löschen",
|
||||
"courses.view": "Kurse ansehen",
|
||||
"courses.create": "Kurse erstellen",
|
||||
"courses.edit": "Kurse bearbeiten",
|
||||
"courses.delete": "Kurse löschen",
|
||||
"rooms.view": "Räume ansehen",
|
||||
"rooms.create": "Räume erstellen",
|
||||
"rooms.edit": "Räume bearbeiten",
|
||||
"rooms.delete": "Räume löschen",
|
||||
};
|
||||
const active = (
|
||||
Object.entries(permissions) as [string, Record<string, boolean>][]
|
||||
).flatMap(([res, actions]) =>
|
||||
(Object.entries(actions) as [string, boolean][])
|
||||
.filter(([, v]) => v)
|
||||
.map(([action]) => `${res}.${action}`)
|
||||
);
|
||||
if (active.length === 0) return null;
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-gray-600 mb-2">
|
||||
<Lock size={16} />
|
||||
<span className="text-sm font-medium">Administrationsrechte</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{active.map((key) => (
|
||||
<span
|
||||
key={key}
|
||||
className="px-3 py-1 bg-purple-50 text-purple-700 text-sm rounded-full"
|
||||
>
|
||||
{LABELS[key] ?? key}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end items-center pt-3 border-t border-gray-200">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,25 @@ const INITIAL_ROLES = [
|
|||
{ value: "manager", label: "Manager" },
|
||||
];
|
||||
|
||||
const PERMISSION_GROUPS = [
|
||||
{ key: "employees" as const, label: "Mitarbeiter" },
|
||||
{ key: "courses" as const, label: "Kurse" },
|
||||
{ key: "rooms" as const, label: "Räume" },
|
||||
];
|
||||
|
||||
const PERMISSION_ACTIONS = [
|
||||
{ key: "view" as const, label: "Ansehen" },
|
||||
{ key: "create" as const, label: "Erstellen" },
|
||||
{ key: "edit" as const, label: "Bearbeiten" },
|
||||
{ key: "delete" as const, label: "Löschen" },
|
||||
];
|
||||
|
||||
type PermissionsState = {
|
||||
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
};
|
||||
|
||||
interface EmployeeCreateViewProps {
|
||||
customRoles: Array<{ value: string; label: string }>;
|
||||
}
|
||||
|
|
@ -47,8 +66,11 @@ const EmployeeCreateView = ({ customRoles }: EmployeeCreateViewProps) => {
|
|||
|
||||
const [roles, setRoles] = useState(combinedRoles);
|
||||
const [newlyCreatedRoles, setNewlyCreatedRoles] = useState<string[]>([]);
|
||||
const [isPermissionCourseCreation, setIsPermissionCourseCreation] = useState(false);
|
||||
const [isPermissionEmplyoeeCreation, setIsPermissionEmplyoeeCreation] = useState(false);
|
||||
const [permissionsState, setPermissionsState] = useState<PermissionsState>({
|
||||
employees: { view: false, create: false, edit: false, delete: false },
|
||||
courses: { view: false, create: false, edit: false, delete: false },
|
||||
rooms: { view: false, create: false, edit: false, delete: false },
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState<Record<string, string[]>>({});
|
||||
|
||||
|
|
@ -63,80 +85,61 @@ const EmployeeCreateView = ({ customRoles }: EmployeeCreateViewProps) => {
|
|||
setSelectedRoles(selectedRoles.filter((v) => v !== valueToDelete));
|
||||
};
|
||||
|
||||
const handlePermissionChange = (
|
||||
resource: keyof PermissionsState,
|
||||
action: "view" | "create" | "edit" | "delete",
|
||||
value: boolean
|
||||
) => {
|
||||
setPermissionsState((prev) => {
|
||||
const updated = { ...prev, [resource]: { ...prev[resource], [action]: value } };
|
||||
// Andere Aktionen erfordern view
|
||||
if (action !== "view" && value) {
|
||||
updated[resource] = { ...updated[resource], view: true };
|
||||
}
|
||||
// view deaktivieren → alles deaktivieren
|
||||
if (action === "view" && !value) {
|
||||
updated[resource] = { view: false, create: false, edit: false, delete: false };
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const buildData = () => ({
|
||||
email: employeeMail,
|
||||
roles: selectedRoles,
|
||||
isMultipleLocation,
|
||||
locations: isMultipleLocation ? selectedLocations : [],
|
||||
permissions: permissionsState,
|
||||
});
|
||||
|
||||
const handlePublish = async () => {
|
||||
setIsSubmitting(true);
|
||||
setErrors({});
|
||||
|
||||
const data = {
|
||||
email: employeeMail,
|
||||
roles: selectedRoles,
|
||||
isMultipleLocation,
|
||||
locations: isMultipleLocation ? selectedLocations : [],
|
||||
permissions: {
|
||||
canCreateCourses: isPermissionCourseCreation,
|
||||
canCreateEmployees: isPermissionEmplyoeeCreation,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await createEmployee(data, "published");
|
||||
|
||||
const result = await createEmployee(buildData(), "published");
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
router.push("/employee");
|
||||
} else {
|
||||
toast.error(result.error || "Ein Fehler ist aufgetreten");
|
||||
if (result.fieldErrors) {
|
||||
setErrors(result.fieldErrors);
|
||||
}
|
||||
if (result.fieldErrors) setErrors(result.fieldErrors);
|
||||
}
|
||||
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
setIsSubmitting(true);
|
||||
setErrors({});
|
||||
|
||||
const data = {
|
||||
email: employeeMail,
|
||||
roles: selectedRoles,
|
||||
isMultipleLocation,
|
||||
locations: isMultipleLocation ? selectedLocations : [],
|
||||
permissions: {
|
||||
canCreateCourses: isPermissionCourseCreation,
|
||||
canCreateEmployees: isPermissionEmplyoeeCreation,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await createEmployee(data, "draft");
|
||||
|
||||
const result = await createEmployee(buildData(), "draft");
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
router.push("/employee");
|
||||
} else {
|
||||
toast.error(result.error || "Ein Fehler ist aufgetreten");
|
||||
if (result.fieldErrors) {
|
||||
setErrors(result.fieldErrors);
|
||||
}
|
||||
if (result.fieldErrors) setErrors(result.fieldErrors);
|
||||
}
|
||||
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
const permissions = [
|
||||
{
|
||||
id: "course-creation",
|
||||
label: "Kurs anlegen",
|
||||
value: isPermissionCourseCreation,
|
||||
setValue: setIsPermissionCourseCreation,
|
||||
},
|
||||
{
|
||||
id: "employee-creation",
|
||||
label: "Trainer anlegen",
|
||||
value: isPermissionEmplyoeeCreation,
|
||||
setValue: setIsPermissionEmplyoeeCreation,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Mitarbeiter anlegen</h1>
|
||||
|
|
@ -202,25 +205,56 @@ const EmployeeCreateView = ({ customRoles }: EmployeeCreateViewProps) => {
|
|||
)}
|
||||
|
||||
<div>
|
||||
<Label className="p-1 mb-2 text-blue text-lg font-semibold">
|
||||
Welche Administrationsrechte soll der Mitarbeiter erhalten
|
||||
</Label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{permissions.map((p) => (
|
||||
<div key={p.id} className="grid grid-cols-[auto_1fr] items-center gap-3">
|
||||
<Checkbox
|
||||
id={p.id}
|
||||
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
|
||||
checked={p.value}
|
||||
onCheckedChange={(checked) => p.setValue(checked === true)}
|
||||
/>
|
||||
<Label htmlFor={p.id} className="cursor-pointer text-blue">
|
||||
{p.label}
|
||||
</Label>
|
||||
<Label className="p-1 mb-3 text-blue text-lg font-semibold">Administrationsrechte</Label>
|
||||
<div className="grid grid-cols-[auto_1fr] items-center gap-2 mb-4">
|
||||
<Checkbox
|
||||
id="all-permissions"
|
||||
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
|
||||
checked={PERMISSION_GROUPS.every((g) =>
|
||||
PERMISSION_ACTIONS.every((a) => permissionsState[g.key][a.key])
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
const value = checked === true;
|
||||
setPermissionsState({
|
||||
employees: { view: value, create: value, edit: value, delete: value },
|
||||
courses: { view: value, create: value, edit: value, delete: value },
|
||||
rooms: { view: value, create: value, edit: value, delete: value },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="all-permissions" className="cursor-pointer text-blue font-semibold">
|
||||
Alle Berechtigungen
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 mt-2">
|
||||
{PERMISSION_GROUPS.map((group) => (
|
||||
<div key={group.key}>
|
||||
<p className="text-sm font-semibold text-gray-700 mb-2">{group.label}</p>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{PERMISSION_ACTIONS.map((action) => {
|
||||
const id = `${group.key}-${action.key}`;
|
||||
return (
|
||||
<div key={id} className="grid grid-cols-[auto_1fr] items-center gap-2">
|
||||
<Checkbox
|
||||
id={id}
|
||||
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
|
||||
checked={permissionsState[group.key][action.key]}
|
||||
onCheckedChange={(checked) =>
|
||||
handlePermissionChange(group.key, action.key, checked === true)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={id} className="cursor-pointer text-blue text-sm">
|
||||
{action.label}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handlePublish} disabled={isSubmitting}>
|
||||
{isSubmitting ? "Wird veröffentlicht..." : "Veröffentlichen"}
|
||||
|
|
|
|||
|
|
@ -14,15 +14,20 @@ type Employee = {
|
|||
email: string;
|
||||
roles: string[];
|
||||
locations: string[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
permissions: any;
|
||||
permissions: unknown;
|
||||
status: string | null;
|
||||
isOnboarded: boolean;
|
||||
createdAt: Date | null;
|
||||
updatedAt: Date | null;
|
||||
};
|
||||
|
||||
const EmployeeListView = () => {
|
||||
interface EmployeeListViewProps {
|
||||
canCreate: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const EmployeeListView = ({ canCreate, canEdit, canDelete }: EmployeeListViewProps) => {
|
||||
const router = useRouter();
|
||||
const [employees, setEmployees] = useState<Employee[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
|
@ -74,23 +79,29 @@ const EmployeeListView = () => {
|
|||
<div>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex w-full justify-end mr-2 gap-2">
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Mitarbeiter löschen"}
|
||||
</Button>
|
||||
{canDelete && (
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Mitarbeiter löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{employees.length === 0 ? (
|
||||
<div className="text-center py-12 bg-gray-50 rounded-lg">
|
||||
<p className="text-xl text-gray-600">Sie haben noch keinen Mitarbeiter erstellt</p>
|
||||
<p className="text-gray-500 mt-2">Erstellen Sie Ihren ersten Mitarbeiter!</p>
|
||||
<Button onClick={() => router.push("/employee/create")} className="mt-4">
|
||||
Mitarbeiter anlegen
|
||||
</Button>
|
||||
<p className="text-xl text-gray-600">Keine Mitarbeiter vorhanden</p>
|
||||
{canCreate && (
|
||||
<>
|
||||
<p className="text-gray-500 mt-2">Erstellen Sie Ihren ersten Mitarbeiter!</p>
|
||||
<Button onClick={() => router.push("/employee/create")} className="mt-4">
|
||||
Mitarbeiter anlegen
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -104,15 +115,20 @@ const EmployeeListView = () => {
|
|||
locations={employee.locations}
|
||||
permissions={
|
||||
employee.permissions as {
|
||||
canCreateCourses: boolean;
|
||||
canCreateEmployees: boolean;
|
||||
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
|
||||
} | null
|
||||
}
|
||||
status={employee.status || "draft"}
|
||||
isOnboarded={employee.isOnboarded}
|
||||
showDeleteIcon={deleteMode}
|
||||
onDelete={() => handleDeleteClick(employee.id, getEmployeeName(employee))}
|
||||
onEdit={() => router.push(`/employee/edit/${employee.id}`)}
|
||||
showDeleteIcon={deleteMode && canDelete}
|
||||
onDelete={
|
||||
canDelete
|
||||
? () => handleDeleteClick(employee.id, getEmployeeName(employee))
|
||||
: undefined
|
||||
}
|
||||
onEdit={canEdit ? () => router.push(`/employee/edit/${employee.id}`) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import { Button } from "@/src/components/ui/button";
|
|||
import { useRouter } from "next/navigation";
|
||||
import EmployeeListView from "./employee-list-view";
|
||||
|
||||
const EmployeeView = () => {
|
||||
interface EmployeeViewProps {
|
||||
canCreate: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const EmployeeView = ({ canCreate, canEdit, canDelete }: EmployeeViewProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
|
|
@ -12,15 +18,17 @@ const EmployeeView = () => {
|
|||
<div className="w-full flex flex-col items-left p-2 md:flex-row md:justify-between md:items-center">
|
||||
<h1 className="text-4xl font-bold">Deine Mitarbeiter</h1>
|
||||
<div className="flex flex-col gap-4 mt-2 md:flex-row md:mt-0">
|
||||
<Button
|
||||
onClick={() => router.push("/employee/create")}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
Mitarbeiter anlegen
|
||||
</Button>
|
||||
{canCreate && (
|
||||
<Button
|
||||
onClick={() => router.push("/employee/create")}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
Mitarbeiter anlegen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<EmployeeListView />
|
||||
<EmployeeListView canCreate={canCreate} canEdit={canEdit} canDelete={canDelete} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"use server";
|
||||
|
||||
import { prisma } from "@/src/lib/prisma";
|
||||
import { getUserData, isManager } from "@/src/lib/auth/getUser";
|
||||
import { getUserData, isManager, isEmployee } from "@/src/lib/auth/getUser";
|
||||
import { roomSchema, type RoomFormData } from "../schemas/room-schema";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
|
|
@ -35,11 +35,13 @@ export async function createRoom(data: RoomFormData) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist (nur Manager dürfen Räume erstellen)
|
||||
if (!isManager(userData)) {
|
||||
// Schritt 3: Manager oder Employee mit Berechtigung
|
||||
const canCreate =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.rooms.create);
|
||||
if (!canCreate) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Räume erstellen",
|
||||
error: "Keine Berechtigung zum Erstellen von Räumen",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -56,11 +58,18 @@ export async function createRoom(data: RoomFormData) {
|
|||
|
||||
const validatedData = validation.data;
|
||||
|
||||
// Schritt 5: Raum in der Datenbank erstellen
|
||||
// Schritt 5: Manager-ID ermitteln (Employee erbt createdBy vom eigenen Datensatz)
|
||||
let creatorId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
creatorId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 6: Raum in der Datenbank erstellen
|
||||
const room = await prisma.room.create({
|
||||
data: {
|
||||
name: validatedData.name,
|
||||
createdBy: userData.id, // Manager-ID als Ersteller
|
||||
createdBy: creatorId,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -125,8 +134,11 @@ export async function getMyRooms() {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer angemeldet ist und ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.rooms.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Unauthorized",
|
||||
|
|
@ -134,10 +146,17 @@ export async function getMyRooms() {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Räume abrufen, die von diesem Manager erstellt wurden
|
||||
// Schritt 3: Manager-ID ermitteln (Employee erbt createdBy vom eigenen Datensatz)
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Räume abrufen, die von diesem Manager erstellt wurden
|
||||
const rooms = await prisma.room.findMany({
|
||||
where: {
|
||||
createdBy: userData.id, // Filtere nach Manager-ID
|
||||
createdBy: managerId,
|
||||
},
|
||||
orderBy: {
|
||||
name: "asc", // Sortiere nach Name aufsteigend
|
||||
|
|
@ -172,20 +191,30 @@ export async function getRoomById(roomId: string) {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.rooms.view))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Räume bearbeiten",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Raum aus der Datenbank abrufen
|
||||
// Schritt 3: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Raum aus der Datenbank abrufen
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
});
|
||||
|
||||
// Schritt 4: Überprüfen, ob Raum existiert
|
||||
// Schritt 5: Überprüfen, ob Raum existiert
|
||||
if (!room) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -193,8 +222,8 @@ export async function getRoomById(roomId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
if (room.createdBy !== userData.id) {
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
if (room.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Räume bearbeiten",
|
||||
|
|
@ -239,20 +268,29 @@ export async function updateRoom(roomId: string, data: RoomFormData) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!isManager(userData)) {
|
||||
// Schritt 3: Manager oder Employee mit Berechtigung
|
||||
const canEdit =
|
||||
isManager(userData) || (isEmployee(userData) && userData.permissions.rooms.edit);
|
||||
if (!canEdit) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Räume bearbeiten",
|
||||
error: "Keine Berechtigung zum Bearbeiten von Räumen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 4: Raum aus der Datenbank abrufen
|
||||
// Schritt 4: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 5: Raum aus der Datenbank abrufen
|
||||
const existingRoom = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
});
|
||||
|
||||
// Schritt 5: Überprüfen, ob Raum existiert
|
||||
// Schritt 6: Überprüfen, ob Raum existiert
|
||||
if (!existingRoom) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -260,15 +298,15 @@ export async function updateRoom(roomId: string, data: RoomFormData) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
if (existingRoom.createdBy !== userData.id) {
|
||||
// Schritt 7: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
if (existingRoom.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Räume bearbeiten",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 7: Formulardaten mit Zod-Schema validieren
|
||||
// Schritt 8: Formulardaten mit Zod-Schema validieren
|
||||
const validation = roomSchema.safeParse(data);
|
||||
|
||||
if (!validation.success) {
|
||||
|
|
@ -322,20 +360,30 @@ export async function deleteRoom(roomId: string) {
|
|||
// Schritt 1: Aktuellen Benutzer abrufen
|
||||
const userData = await getUserData();
|
||||
|
||||
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
|
||||
if (!userData || !isManager(userData)) {
|
||||
// Schritt 2: Manager oder Employee mit Berechtigung
|
||||
if (
|
||||
!userData ||
|
||||
(!isManager(userData) && !(isEmployee(userData) && userData.permissions.rooms.delete))
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nur Manager können Räume löschen",
|
||||
error: "Keine Berechtigung zum Löschen von Räumen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 3: Raum aus der Datenbank abrufen
|
||||
// Schritt 3: Effektive Manager-ID ermitteln
|
||||
let managerId = userData.id;
|
||||
if (isEmployee(userData)) {
|
||||
const selfRecord = await prisma.employee.findUnique({ where: { email: userData.email } });
|
||||
managerId = selfRecord?.createdBy ?? userData.id;
|
||||
}
|
||||
|
||||
// Schritt 4: Raum aus der Datenbank abrufen
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
});
|
||||
|
||||
// Schritt 4: Überprüfen, ob Raum existiert
|
||||
// Schritt 5: Überprüfen, ob Raum existiert
|
||||
if (!room) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -343,16 +391,15 @@ export async function deleteRoom(roomId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
// (Sicherheit: Manager können nur ihre eigenen Räume löschen)
|
||||
if (room.createdBy !== userData.id) {
|
||||
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Raumes ist
|
||||
if (room.createdBy !== managerId) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Sie können nur Ihre eigenen Räume löschen",
|
||||
};
|
||||
}
|
||||
|
||||
// Schritt 6: Prüfen, ob der Raum noch in zukünftigen Kursen verwendet wird
|
||||
// Schritt 7: Prüfen, ob der Raum noch in zukünftigen Kursen verwendet wird
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
|
|
@ -360,7 +407,7 @@ export async function deleteRoom(roomId: string) {
|
|||
where: {
|
||||
room: roomId,
|
||||
date: { gte: today },
|
||||
createdBy: userData.id,
|
||||
createdBy: managerId,
|
||||
},
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
|
@ -375,12 +422,12 @@ export async function deleteRoom(roomId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
// Schritt 7: Raum aus der Datenbank löschen
|
||||
// Schritt 8: Raum aus der Datenbank löschen
|
||||
await prisma.room.delete({
|
||||
where: { id: roomId },
|
||||
});
|
||||
|
||||
// Schritt 7: Next.js Cache invalidieren, damit gelöschter Raum nicht mehr angezeigt wird
|
||||
// Schritt 9: Next.js Cache invalidieren, damit gelöschter Raum nicht mehr angezeigt wird
|
||||
revalidatePath("/rooms");
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ const RoomsListItem: React.FC<RoomsListItemProps> = ({
|
|||
<Trash2 size={20} className="text-red-600" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={onEdit} className="p-2 rounded-md hover:bg-gray-100 transition">
|
||||
<Pen size={20} className="text-gray-700" />
|
||||
</button>
|
||||
{onEdit && (
|
||||
<button onClick={onEdit} className="p-2 rounded-md hover:bg-gray-100 transition">
|
||||
<Pen size={20} className="text-gray-700" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ type Room = {
|
|||
|
||||
interface RoomsListViewProps {
|
||||
deleteMode: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const RoomsListView = ({ deleteMode }: RoomsListViewProps) => {
|
||||
const RoomsListView = ({ deleteMode, canEdit, canDelete }: RoomsListViewProps) => {
|
||||
const router = useRouter();
|
||||
const [rooms, setRooms] = useState<Room[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
|
@ -82,9 +84,9 @@ const RoomsListView = ({ deleteMode }: RoomsListViewProps) => {
|
|||
<RoomsListItem
|
||||
key={room.id}
|
||||
roomName={room.name}
|
||||
onEdit={() => handleEditClick(room.id)}
|
||||
onDelete={() => handleDeleteClick(room.id, room.name)}
|
||||
showDeleteIcon={deleteMode}
|
||||
onEdit={canEdit ? () => handleEditClick(room.id) : undefined}
|
||||
onDelete={canDelete ? () => handleDeleteClick(room.id, room.name) : undefined}
|
||||
showDeleteIcon={deleteMode && canDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ import { Button } from "@/src/components/ui/button";
|
|||
import { useRouter } from "next/navigation";
|
||||
import RoomsListView from "./rooms-list-view";
|
||||
|
||||
const RoomsView = () => {
|
||||
interface RoomsViewProps {
|
||||
canCreate: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
}
|
||||
|
||||
const RoomsView = ({ canCreate, canEdit, canDelete }: RoomsViewProps) => {
|
||||
const router = useRouter();
|
||||
const [deleteMode, setDeleteMode] = useState(false);
|
||||
|
||||
|
|
@ -14,22 +20,26 @@ const RoomsView = () => {
|
|||
<div className="w-full flex flex-col items-left p-2 md:flex-row md:justify-between md:items-center">
|
||||
<h1 className="text-4xl font-bold">Räume</h1>
|
||||
<div className="flex flex-col gap-2 mt-2 md:flex-row md:mt-0">
|
||||
<Button
|
||||
onClick={() => router.push("/rooms/create")}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
Raum anlegen
|
||||
</Button>
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Raum löschen"}
|
||||
</Button>
|
||||
{canCreate && (
|
||||
<Button
|
||||
onClick={() => router.push("/rooms/create")}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
Raum anlegen
|
||||
</Button>
|
||||
)}
|
||||
{canDelete && (
|
||||
<Button
|
||||
variant={deleteMode ? "destructive" : "outline"}
|
||||
onClick={() => setDeleteMode(!deleteMode)}
|
||||
className="text-sm tracking-wide font-normal"
|
||||
>
|
||||
{deleteMode ? "Abbrechen" : "Raum löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<RoomsListView deleteMode={deleteMode} />
|
||||
<RoomsListView deleteMode={deleteMode && canDelete} canEdit={canEdit} canDelete={canDelete} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue