207 lines
8.6 KiB
TypeScript
207 lines
8.6 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { useAuth } from "@/lib/auth-context"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Trash2, Plus, Upload } from "lucide-react"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog"
|
|
import type { Room } from "@/lib/types"
|
|
|
|
export function RoomManagement() {
|
|
const { rooms, authHeader } = useAuth()
|
|
const [localRooms, setLocalRooms] = useState<Room[]>([])
|
|
const [newRoomName, setNewRoomName] = useState("")
|
|
const [isCreateOpen, setIsCreateOpen] = useState(false)
|
|
const [csvFile, setCsvFile] = useState<File | null>(null)
|
|
const [uploading, setUploading] = useState(false)
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL + "/api"
|
|
|
|
useEffect(() => {
|
|
setLocalRooms(rooms)
|
|
}, [rooms])
|
|
|
|
const refreshRooms = async () => {
|
|
// Force reload (window reload is simplest for now to update Context, or expose fetchRooms in Context)
|
|
// Ideally Context should expose a 'refreshRooms' method.
|
|
// For now, let's update local list manually after actions or rely on hard refresh if needed.
|
|
// Actually, fetching from API here is better.
|
|
if (!authHeader) return
|
|
const res = await fetch(`${API_URL}/rooms`, { headers: { Authorization: authHeader } })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setLocalRooms(data)
|
|
}
|
|
}
|
|
|
|
const handleCreate = async () => {
|
|
if (!newRoomName.trim() || !authHeader) return
|
|
try {
|
|
const res = await fetch(`${API_URL}/rooms`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": authHeader,
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({ name: newRoomName })
|
|
})
|
|
if (res.ok) {
|
|
setNewRoomName("")
|
|
setIsCreateOpen(false)
|
|
refreshRooms()
|
|
}
|
|
} catch (e) { console.error(e) }
|
|
}
|
|
|
|
const handleDelete = async (id: number) => {
|
|
if (!confirm("Raum wirklich löschen? Dies könnte fehlschlagen, wenn Tickets existieren.") || !authHeader) return
|
|
try {
|
|
const res = await fetch(`${API_URL}/rooms/${id}`, {
|
|
method: "DELETE",
|
|
headers: { "Authorization": authHeader }
|
|
})
|
|
if (res.ok) refreshRooms()
|
|
else alert("Löschen fehlgeschlagen (evtl. existieren Tickets für diesen Raum)")
|
|
} catch (e) { console.error(e) }
|
|
}
|
|
|
|
const handleUpdate = async (id: number, newName: string) => {
|
|
if (!authHeader) return
|
|
try {
|
|
const res = await fetch(`${API_URL}/rooms/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Authorization": authHeader,
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({ name: newName })
|
|
})
|
|
if (res.ok) refreshRooms()
|
|
} catch (e) { console.error(e) }
|
|
}
|
|
|
|
const handleFileUpload = async () => {
|
|
if (!csvFile || !authHeader) return
|
|
setUploading(true)
|
|
const formData = new FormData()
|
|
formData.append("file", csvFile)
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/rooms/import`, {
|
|
method: "POST",
|
|
headers: { "Authorization": authHeader },
|
|
body: formData
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
alert(data.message)
|
|
setCsvFile(null)
|
|
refreshRooms()
|
|
}
|
|
} catch (e) { console.error(e) }
|
|
finally { setUploading(false) }
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>Räume verwalten</CardTitle>
|
|
<CardDescription>Erstellen, Bearbeiten, Löschen und CSV-Import</CardDescription>
|
|
</div>
|
|
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button size="sm"><Plus className="mr-2 h-4 w-4" /> Neuer Raum</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Neuen Raum erstellen</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="flex gap-2 mt-4">
|
|
<Input
|
|
placeholder="Raum Name (z.B. A-101)"
|
|
value={newRoomName}
|
|
onChange={(e) => setNewRoomName(e.target.value)}
|
|
/>
|
|
<Button onClick={handleCreate}>Erstellen</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
|
|
{/* CSV Import Section */}
|
|
<div className="flex items-center gap-4 p-4 border border-dashed rounded-lg bg-muted/30">
|
|
<div className="grid gap-1.5 flex-1">
|
|
<span className="font-semibold text-sm">Raum-Import (CSV)</span>
|
|
<span className="text-xs text-muted-foreground">Datei mit Raumnamen pro Zeile</span>
|
|
</div>
|
|
<Input
|
|
type="file"
|
|
accept=".csv,.txt"
|
|
onChange={(e) => setCsvFile(e.target.files?.[0] || null)}
|
|
className="w-full max-w-xs"
|
|
/>
|
|
<Button
|
|
variant="secondary"
|
|
disabled={!csvFile || uploading}
|
|
onClick={handleFileUpload}
|
|
>
|
|
<Upload className="mr-2 h-4 w-4" /> Importieren
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>ID</TableHead>
|
|
<TableHead className="w-full">Name</TableHead>
|
|
<TableHead className="text-right">Aktionen</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{localRooms.map((room) => (
|
|
<TableRow key={room.id}>
|
|
<TableCell>{room.id}</TableCell>
|
|
<TableCell>
|
|
<Input
|
|
defaultValue={room.name}
|
|
className="h-8 w-40"
|
|
onBlur={(e) => {
|
|
if (e.target.value !== room.name) {
|
|
handleUpdate(room.id, e.target.value)
|
|
}
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-destructive hover:text-destructive"
|
|
onClick={() => handleDelete(room.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|