added crud feature for rooms

This commit is contained in:
Rajpreet0 2025-12-17 17:59:12 +01:00
parent eab6b672bc
commit a61ba54d08
23 changed files with 1018 additions and 5 deletions

View file

@ -0,0 +1,5 @@
{
"permissions": {
"allow": ["Bash(npx prisma migrate:*)"]
}
}

View file

@ -9,5 +9,6 @@ export default defineConfig({
engine: "classic",
datasource: {
url: env("DATABASE_URL"),
directUrl: env("DIRECT_URL"),
},
});

View file

@ -0,0 +1,137 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT,
"complaints" TEXT,
"birthday" TIMESTAMP(3),
"plz" TEXT,
"city" TEXT,
"street" TEXT,
"houseNumber" TEXT,
"email" TEXT NOT NULL,
"tel" TEXT,
"gender" TEXT,
"is2FA" BOOLEAN NOT NULL DEFAULT false,
"isSendingMail" BOOLEAN NOT NULL DEFAULT false,
"isNewsletter" BOOLEAN NOT NULL DEFAULT false,
"pbSrc" TEXT,
"isValid" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Employee" (
"id" TEXT NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"email" TEXT NOT NULL,
"is2FA" BOOLEAN NOT NULL DEFAULT false,
"isSendingMail" BOOLEAN NOT NULL DEFAULT false,
"pbSrc" TEXT,
"status" TEXT,
"affiliation" TEXT,
"label" TEXT,
"qualification" TEXT,
"authorization" JSONB,
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),
CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserTraining" (
"userId" TEXT NOT NULL,
"employeeId" TEXT NOT NULL,
CONSTRAINT "UserTraining_pkey" PRIMARY KEY ("userId","employeeId")
);
-- CreateTable
CREATE TABLE "Manager" (
"id" TEXT NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"tel" TEXT,
"email" TEXT NOT NULL,
"is2FA" BOOLEAN NOT NULL DEFAULT false,
"isSendingMail" BOOLEAN NOT NULL DEFAULT false,
"pbSrc" TEXT,
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),
CONSTRAINT "Manager_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Business" (
"id" TEXT NOT NULL,
"managerId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"address" TEXT NOT NULL,
"email" TEXT NOT NULL,
"title" TEXT NOT NULL,
"ustId" TEXT,
"banking" TEXT,
"plan" TEXT,
"subscription" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Business_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Course" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"sport" TEXT NOT NULL,
"date" TIMESTAMP(3) NOT NULL,
"time" TEXT NOT NULL,
"trainers" TEXT[],
"room" TEXT NOT NULL,
"description" TEXT NOT NULL,
"maxParticipants" INTEGER NOT NULL,
"isStandingOrder" BOOLEAN NOT NULL DEFAULT false,
"frequency" TEXT,
"weekdays" TEXT[],
"status" TEXT NOT NULL DEFAULT 'draft',
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Room" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'draft',
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Room_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Employee_email_key" ON "Employee"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Manager_email_key" ON "Manager"("email");
-- AddForeignKey
ALTER TABLE "UserTraining" ADD CONSTRAINT "UserTraining_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserTraining" ADD CONSTRAINT "UserTraining_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Business" ADD CONSTRAINT "Business_managerId_fkey" FOREIGN KEY ("managerId") REFERENCES "Manager"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `status` on the `Room` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Room" DROP COLUMN "status";

View file

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View file

@ -110,3 +110,11 @@ model Course {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Room {
id String @id @default(uuid())
name String
createdBy String // Manager ID who created the room
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View file

@ -1,6 +1,8 @@
import { requireManager } from "@/src/lib/auth/getUser";
import CourseCreateView from "@/src/modules/courses/ui/views/course-create-view";
const CourseCreate = () => {
const CourseCreate = async () => {
await requireManager();
return <CourseCreateView />;
};

View file

@ -1,6 +1,8 @@
import { requireManager } from "@/src/lib/auth/getUser";
import CourseEditView from "@/src/modules/courses/ui/views/course-edit-view";
const CourseEdit = async ({ params }: { params: Promise<{ id: string }> }) => {
await requireManager();
const { id } = await params;
return <CourseEditView courseId={id} />;
};

View file

@ -1,6 +1,8 @@
import { requireManager } from "@/src/lib/auth/getUser";
import CourseView from "@/src/modules/courses/ui/views/course-view";
const Course = () => {
const Course = async () => {
await requireManager();
return <CourseView />;
};

View file

@ -0,0 +1,9 @@
import { requireManager } from "@/src/lib/auth/getUser";
import RoomsCreateView from "@/src/modules/rooms/ui/views/rooms-create-view";
const RoomsCreate = async () => {
await requireManager();
return <RoomsCreateView />;
};
export default RoomsCreate;

View file

@ -0,0 +1,10 @@
import { requireManager } 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();
const { id } = await params;
return <RoomsEditView roomId={id} />;
};
export default RoomsEdit;

View file

@ -0,0 +1,9 @@
import { requireManager } from "@/src/lib/auth/getUser";
import RoomsView from "@/src/modules/rooms/ui/views/rooms-view";
const Rooms = async () => {
await requireManager();
return <RoomsView />;
};
export default Rooms;

View file

@ -147,9 +147,16 @@ export async function requireAuth() {
}
export async function requireAuthWithData() {
const user = await getUser();
if (!user) {
const { redirect } = await import("next/navigation");
redirect("/login");
}
const userData = await getUserData();
if (!userData) {
throw new Error("User or Manager not found");
const { redirect } = await import("next/navigation");
redirect("/login");
}
return userData;
}
@ -162,6 +169,22 @@ export async function requireGuest() {
}
}
/**
* Requires that the authenticated user is a Manager.
* Redirects to /dashboard if user is not a manager.
* Redirects to /login if not authenticated.
*/
export async function requireManager() {
const userData = await requireAuthWithData();
if (!isManager(userData)) {
const { redirect } = await import("next/navigation");
redirect("/dashboard");
}
return userData;
}
// Helper function to check if user is a manager
export function isManager(userData: AuthUserData): userData is ManagerData {
return userData.role === "manager";

View file

@ -9,13 +9,46 @@ const menuTitles: Record<string, string> = {
"/dashboard": "Dashboard",
"/courses": "Kurs Übersicht",
"/courses/create": "Erstelle einen Kurs",
"/courses/edit": "Bearbeite den Kurs",
"/rooms": "Räume",
"/rooms/create": "Erstelle einen Raum",
"/rooms/edit": "Bearbeite den Raum",
};
const dynamicRoutes: Array<{
prefix: string;
titleKey: string;
}> = [
{
prefix: "/courses/edit",
titleKey: "/courses/edit",
},
{
prefix: "/rooms/edit",
titleKey: "/rooms/edit",
},
];
const getPageTitle = (pathname: string) => {
if (menuTitles[pathname]) {
return menuTitles[pathname];
}
for (const route of dynamicRoutes) {
if (pathname.startsWith(route.prefix)) {
return menuTitles[route.titleKey];
}
}
// Fallback
return "Dashboard";
};
const DashboardNavbar = () => {
const { state, toggleSidebar, isMobile } = useSidebar();
const pathname = usePathname();
const pageTitle = menuTitles[pathname] || "Dashboard";
const pageTitle = getPageTitle(pathname);
return (
<div>

View file

@ -69,7 +69,7 @@ const allMenuItems = [
{
icon: DoorOpen,
label: "Räume",
href: "/raume",
href: "/rooms",
roles: ["manager"],
},
{

View file

@ -0,0 +1,374 @@
/**
* Room Actions - Server-seitige Aktionen für Raumverwaltung
*
* Diese Datei enthält alle Server Actions für das Raum-Management-System.
* Funktionen umfassen: Räume erstellen, abrufen, aktualisieren und löschen.
* Alle Funktionen sind mit Authentifizierung und Autorisierung geschützt.
*/
"use server";
import { prisma } from "@/src/lib/prisma";
import { getUserData, isManager } from "@/src/lib/auth/getUser";
import { roomSchema, type RoomFormData } from "../schemas/room-schema";
import { revalidatePath } from "next/cache";
/**
* Erstellt einen neuen Raum in der Datenbank
*
* Diese Funktion validiert die Eingabedaten, überprüft die Berechtigungen des Benutzers
* und erstellt einen neuen Raum. Nur Manager können Räume erstellen.
*
* @param data - Die Raumdaten aus dem Formular
* @returns Ein Objekt mit success-Flag, optionaler roomId und Nachricht oder Fehler
*/
export async function createRoom(data: RoomFormData) {
try {
// Schritt 1: Aktuellen Benutzer abrufen
const userData = await getUserData();
// Schritt 2: Überprüfen, ob Benutzer angemeldet ist
if (!userData) {
return {
success: false,
error: "Sie müssen angemeldet sein, um einen Raum zu erstellen",
};
}
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist (nur Manager dürfen Räume erstellen)
if (!isManager(userData)) {
return {
success: false,
error: "Nur Manager können Räume erstellen",
};
}
// Schritt 4: Formulardaten mit Zod-Schema validieren
const validation = roomSchema.safeParse(data);
if (!validation.success) {
return {
success: false,
error: "Validierungsfehler",
fieldErrors: validation.error.flatten().fieldErrors,
};
}
const validatedData = validation.data;
// Schritt 5: Raum in der Datenbank erstellen
const room = await prisma.room.create({
data: {
name: validatedData.name,
createdBy: userData.id, // Manager-ID als Ersteller
},
});
// Schritt 6: Next.js Cache für /rooms Seite invalidieren, damit neue Daten angezeigt werden
revalidatePath("/rooms");
return {
success: true,
roomId: room.id,
message: "Raum erfolgreich erstellt",
};
} catch (error) {
console.error("Error creating room:", error);
return {
success: false,
error: "Ein Fehler ist beim Erstellen des Raumes aufgetreten",
};
}
}
/**
* Ruft alle Räume ab
*
* Diese Funktion gibt alle Räume zurück.
* Die Räume werden nach Namen aufsteigend sortiert.
*
* @returns Ein Objekt mit success-Flag und einem Array von Räumen
*/
export async function getAllRooms() {
try {
// Alle Räume aus der Datenbank abrufen
// Sortiert nach Namen aufsteigend
const rooms = await prisma.room.findMany({
orderBy: {
name: "asc", // Aufsteigende Sortierung nach Name
},
});
return {
success: true,
rooms,
};
} catch (error) {
console.error("Error fetching rooms:", error);
return {
success: false,
error: "Fehler beim Laden der Räume",
rooms: [],
};
}
}
/**
* Ruft alle Räume ab, die vom eingeloggten Manager erstellt wurden
*
* Diese Funktion gibt alle Räume zurück, die vom aktuell eingeloggten Manager erstellt wurden.
*
* @returns Ein Objekt mit success-Flag und einem Array der Räume des Managers
*/
export async function getMyRooms() {
try {
// 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)) {
return {
success: false,
error: "Unauthorized",
rooms: [],
};
}
// Schritt 3: Räume abrufen, die von diesem Manager erstellt wurden
const rooms = await prisma.room.findMany({
where: {
createdBy: userData.id, // Filtere nach Manager-ID
},
orderBy: {
name: "asc", // Sortiere nach Name aufsteigend
},
});
return {
success: true,
rooms,
};
} catch (error) {
console.error("Error fetching my rooms:", error);
return {
success: false,
error: "Fehler beim Laden der Räume",
rooms: [],
};
}
}
/**
* Ruft einen einzelnen Raum nach ID ab
*
* Diese Funktion gibt einen Raum zurück, wenn der eingeloggte Manager
* der Ersteller des Raumes ist.
*
* @param roomId - Die eindeutige ID des Raumes
* @returns Ein Objekt mit success-Flag und dem Raum oder Fehler
*/
export async function getRoomById(roomId: string) {
try {
// Schritt 1: Aktuellen Benutzer abrufen
const userData = await getUserData();
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
if (!userData || !isManager(userData)) {
return {
success: false,
error: "Nur Manager können Räume bearbeiten",
};
}
// Schritt 3: Raum aus der Datenbank abrufen
const room = await prisma.room.findUnique({
where: { id: roomId },
});
// Schritt 4: Überprüfen, ob Raum existiert
if (!room) {
return {
success: false,
error: "Raum nicht gefunden",
};
}
// Schritt 5: Überprüfen, ob der Manager der Besitzer des Raumes ist
if (room.createdBy !== userData.id) {
return {
success: false,
error: "Sie können nur Ihre eigenen Räume bearbeiten",
};
}
return {
success: true,
room,
};
} catch (error) {
console.error("Error fetching room:", error);
return {
success: false,
error: "Fehler beim Laden des Raumes",
};
}
}
/**
* Aktualisiert einen bestehenden Raum
*
* Diese Funktion validiert die Eingabedaten, überprüft die Berechtigungen des Benutzers
* und aktualisiert einen bestehenden Raum. Nur der Manager, der den Raum erstellt hat,
* kann ihn bearbeiten.
*
* @param roomId - Die eindeutige ID des zu aktualisierenden Raumes
* @param data - Die aktualisierten Raumdaten aus dem Formular
* @param status - Der Status des Raumes ("draft" für Entwurf oder "published" für veröffentlicht)
* @returns Ein Objekt mit success-Flag und Nachricht oder Fehler
*/
export async function updateRoom(roomId: string, data: RoomFormData) {
try {
// Schritt 1: Aktuellen Benutzer abrufen
const userData = await getUserData();
// Schritt 2: Überprüfen, ob Benutzer angemeldet ist
if (!userData) {
return {
success: false,
error: "Sie müssen angemeldet sein, um einen Raum zu bearbeiten",
};
}
// Schritt 3: Überprüfen, ob Benutzer ein Manager ist
if (!isManager(userData)) {
return {
success: false,
error: "Nur Manager können Räume bearbeiten",
};
}
// Schritt 4: Raum aus der Datenbank abrufen
const existingRoom = await prisma.room.findUnique({
where: { id: roomId },
});
// Schritt 5: Überprüfen, ob Raum existiert
if (!existingRoom) {
return {
success: false,
error: "Raum nicht gefunden",
};
}
// Schritt 6: Überprüfen, ob der Manager der Besitzer des Raumes ist
if (existingRoom.createdBy !== userData.id) {
return {
success: false,
error: "Sie können nur Ihre eigenen Räume bearbeiten",
};
}
// Schritt 7: Formulardaten mit Zod-Schema validieren
const validation = roomSchema.safeParse(data);
if (!validation.success) {
return {
success: false,
error: "Validierungsfehler",
fieldErrors: validation.error.flatten().fieldErrors,
};
}
const validatedData = validation.data;
// Schritt 8: Raum in der Datenbank aktualisieren
await prisma.room.update({
where: { id: roomId },
data: {
name: validatedData.name,
},
});
// Schritt 9: Next.js Cache für /rooms Seite invalidieren
revalidatePath("/rooms");
revalidatePath(`/rooms/edit/${roomId}`);
return {
success: true,
message: "Raum erfolgreich aktualisiert",
};
} catch (error) {
console.error("Error updating room:", error);
return {
success: false,
error: "Ein Fehler ist beim Aktualisieren des Raumes aufgetreten",
};
}
}
/**
* Löscht einen Raum aus der Datenbank
*
* Diese Funktion löscht einen Raum, überprüft aber vorher:
* - Ob der Benutzer ein Manager ist
* - Ob der Raum existiert
* - Ob der Manager der Besitzer des Raumes ist (Manager können nur eigene Räume löschen)
*
* @param roomId - Die eindeutige ID des zu löschenden Raumes
* @returns Ein Objekt mit success-Flag und Nachricht oder Fehler
*/
export async function deleteRoom(roomId: string) {
try {
// Schritt 1: Aktuellen Benutzer abrufen
const userData = await getUserData();
// Schritt 2: Überprüfen, ob Benutzer ein Manager ist
if (!userData || !isManager(userData)) {
return {
success: false,
error: "Nur Manager können Räume löschen",
};
}
// Schritt 3: Raum aus der Datenbank abrufen
const room = await prisma.room.findUnique({
where: { id: roomId },
});
// Schritt 4: Überprüfen, ob Raum existiert
if (!room) {
return {
success: false,
error: "Raum nicht gefunden",
};
}
// 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) {
return {
success: false,
error: "Sie können nur Ihre eigenen Räume löschen",
};
}
// Schritt 6: 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
revalidatePath("/rooms");
return {
success: true,
message: "Raum erfolgreich gelöscht",
};
} catch (error) {
console.error("Error deleting room:", error);
return {
success: false,
error: "Fehler beim Löschen des Raumes",
};
}
}

View file

@ -0,0 +1,7 @@
import { z } from "zod";
export const roomSchema = z.object({
name: z.string().min(1, "Name ist erforderlich"),
});
export type RoomFormData = z.infer<typeof roomSchema>;

View file

@ -0,0 +1,7 @@
export type Room = {
id: string;
name: string;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};

View file

@ -0,0 +1,40 @@
import { Pen, Trash2 } from "lucide-react";
/**
* Props für die RoomListItem Komponente
*/
interface RoomsListItemProps {
roomName: string; // Name des Raumes
onEdit?: () => void; // Callback-Funktion beim Klick auf Bearbeiten-Button
onDelete?: () => void; // Callback-Funktion beim Klick auf Löschen-Button
showDeleteIcon?: boolean; // Ob das Löschen-Icon angezeigt werden soll
}
const RoomsListItem: React.FC<RoomsListItemProps> = ({
roomName,
onEdit,
onDelete,
showDeleteIcon,
}) => {
return (
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4 w-full max-w-xs justify-self-center">
<div className="w-full flex items-center justify-between">
<div className="flex-1">
<h2 className="text-xl text-gray-900">{roomName}</h2>
</div>
<div className="flex gap-2">
{showDeleteIcon && (
<button onClick={onDelete} className="p-2 rounded-md hover:bg-red-100 transition">
<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>
</div>
</div>
</div>
);
};
export default RoomsListItem;

View file

@ -0,0 +1,118 @@
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import InputComponent from "@/src/components/layout/InputComponent";
import { Button } from "@/src/components/ui/button";
import { createRoom, updateRoom } from "../../actions/room-actions";
import { toast } from "sonner";
import { Room } from "../../types/room.types";
interface RoomsCreateViewProps {
mode?: "create" | "edit";
roomId?: string;
initialData?: Room;
}
const RoomsCreateView = ({ mode = "create", roomId, initialData }: RoomsCreateViewProps = {}) => {
const router = useRouter();
const [roomName, setRoomName] = useState("");
const [errors, setErrors] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
// Load initial data when in edit mode
useEffect(() => {
if (mode === "edit" && initialData) {
setRoomName(initialData.name || "");
}
}, [mode, initialData]);
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!roomName.trim()) newErrors.name = "Raumname ist erforderlich";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async () => {
if (!validateForm()) {
toast.error("Bitte füllen Sie alle erforderlichen Felder aus");
return;
}
setIsSubmitting(true);
try {
const roomData = {
name: roomName,
};
if (mode === "edit" && !roomId) {
throw new Error("Missing roomId in edit mode");
}
const result =
mode === "edit" ? await updateRoom(roomId!, roomData) : await createRoom(roomData);
if (result.success) {
toast.success(result.message);
router.push("/rooms");
} else {
toast.error(result.error || "Ein Fehler ist aufgetreten");
if (result.fieldErrors) {
const fieldErrors: Record<string, string> = {};
Object.entries(result.fieldErrors).forEach(([key, value]) => {
if (Array.isArray(value) && value.length > 0) {
fieldErrors[key] = value[0];
}
});
setErrors(fieldErrors);
}
}
} catch (error) {
toast.error("Ein unerwarteter Fehler ist aufgetreten");
console.error(error);
} finally {
setIsSubmitting(false);
}
};
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="mt-6 flex flex-col gap-4 max-w-xl">
<div>
<InputComponent
isLabel
label="Name"
type="text"
id="room-create-name"
value={roomName}
onChange={(e) => setRoomName(e.target.value)}
/>
{errors.name && <p className="text-red-500 text-sm mt-1">{errors.name}</p>}
</div>
<div className="flex gap-2">
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting
? "Wird gespeichert..."
: mode === "edit"
? "Änderungen speichern"
: "Raum erstellen"}
</Button>
<Button variant="outline" onClick={() => router.push("/rooms")} disabled={isSubmitting}>
Abbrechen
</Button>
</div>
</div>
</div>
);
};
export default RoomsCreateView;

View file

@ -0,0 +1,55 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { getRoomById } from "../../actions/room-actions";
import { toast } from "sonner";
import RoomsCreateView from "./rooms-create-view";
import { Room } from "../../types/room.types";
interface RoomsEditViewProps {
roomId: string;
}
const RoomsEditView = ({ roomId }: RoomsEditViewProps) => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(true);
const [roomData, setRoomData] = useState<Room | null>(null);
useEffect(() => {
let alive = true;
const fetchRoom = async () => {
if (alive) setIsLoading(true);
const result = await getRoomById(roomId);
if (result.success && result.room) {
if (alive) setRoomData(result.room);
} else {
toast.error(result.error || "Fehler beim Laden des Raumes");
router.push("/rooms");
}
if (alive) setIsLoading(false);
};
fetchRoom();
return () => {
alive = false;
};
}, [roomId, router]);
if (isLoading) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<p className="text-xl">Raum wird geladen...</p>
</div>
);
}
if (!roomData) {
return null;
}
return <RoomsCreateView mode="edit" roomId={roomId} initialData={roomData} />;
};
export default RoomsEditView;

View file

@ -0,0 +1,123 @@
"use client";
import { useEffect, useState } from "react";
import { getMyRooms, deleteRoom } 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";
type Room = {
id: string;
name: string;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};
interface RoomsListViewProps {
deleteMode: boolean;
}
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);
// Load rooms
const loadRooms = async () => {
const result = await getMyRooms();
if (result.success) {
setRooms(result.rooms);
} else {
toast.error(result.error || "Fehler beim Laden der Räume");
}
};
// Initial load
useEffect(() => {
const fetchRooms = async () => {
setIsLoading(true);
await loadRooms();
setIsLoading(false);
};
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 {
toast.error(result.error || "Fehler beim Löschen des Raumes");
}
setIsDeleting(false);
};
// Handle edit click
const handleEditClick = (id: string) => {
router.push(`/rooms/edit/${id}`);
};
if (isLoading) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<p className="text-xl">Räume werden geladen...</p>
</div>
);
}
return (
<div className="mt-6">
{/* Rooms List */}
{rooms.length === 0 ? (
<div className="text-center py-12">
<p className="text-gray-500 text-lg">Keine Räume gefunden</p>
</div>
) : (
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 justify-center">
{rooms.map((room) => (
<RoomsListItem
key={room.id}
roomName={room.name}
onEdit={() => handleEditClick(room.id)}
onDelete={() => handleDeleteClick(room.id, room.name)}
showDeleteIcon={deleteMode}
/>
))}
</div>
)}
{/* Delete Confirmation Dialog */}
<DeleteDialog
open={deleteDialogOpen}
onOpenChange={(open) => {
setDeleteDialogOpen(open);
if (!open) setRoomToDelete(null);
}}
courseName={roomToDelete?.name}
onConfirm={handleDeleteConfirm}
isLoading={isDeleting}
/>
</div>
);
};
export default RoomsListView;

View file

@ -0,0 +1,37 @@
"use client";
import { useState } from "react";
import { Button } from "@/src/components/ui/button";
import { useRouter } from "next/navigation";
import RoomsListView from "./rooms-list-view";
const RoomsView = () => {
const router = useRouter();
const [deleteMode, setDeleteMode] = useState(false);
return (
<div>
<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>
</div>
</div>
<RoomsListView deleteMode={deleteMode} />
</div>
);
};
export default RoomsView;