"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { Edit, Eye, Plus, Trash2, Wrench } from "lucide-react"; import ConfirmDialog from "@/components/common/ConfirmDialog"; import DataTable, { type DataTableColumn } from "@/components/common/DataTable"; import EmptyState from "@/components/common/EmptyState"; import SearchInput from "@/components/common/SearchInput"; import { useToast } from "@/components/common/ToastProvider"; import RepairFormDialog from "@/components/repairs/RepairFormDialog"; import { RepairPriorityBadge, RepairStatusBadge, priorityLabels, statusLabels } from "@/components/repairs/RepairStatusBadge"; import RepairStatusDialog from "@/components/repairs/RepairStatusDialog"; import { Button, buttonVariants } from "@/components/ui/button"; import { api } from "@/lib/api"; import { hasPermission } from "@/lib/permissions"; import type { CurrentUser } from "@/types/rbac"; import type { Repair, RepairListResponse, RepairPayload, RepairPriority, RepairSource, RepairStatus } from "@/types/repair"; const pageSize = 10; function getErrorMessage(error: unknown) { if (typeof error === "object" && error !== null && "response" in error) { const response = (error as { response?: { data?: { detail?: string; message?: string } } }).response; return response?.data?.detail ?? response?.data?.message ?? "Aktion konnte nicht abgeschlossen werden"; } return "Aktion konnte nicht abgeschlossen werden"; } function formatDate(value: string) { return new Intl.DateTimeFormat("de-DE", { dateStyle: "short", timeStyle: "short" }).format(new Date(value)); } function deviceLabel(repair: Repair) { return `${repair.device_manufacturer} ${repair.device_model}`.trim(); } export default function RepairsPage() { const { showToast } = useToast(); const [repairs, setRepairs] = useState([]); const [total, setTotal] = useState(0); const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [formError, setFormError] = useState(""); const [statusError, setStatusError] = useState(""); const [cancelError, setCancelError] = useState(""); const [pending, setPending] = useState(false); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [sourceFilter, setSourceFilter] = useState("all"); const [priorityFilter, setPriorityFilter] = useState("all"); const [page, setPage] = useState(1); const [sortKey, setSortKey] = useState("created_at"); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc"); const [formOpen, setFormOpen] = useState(false); const [statusOpen, setStatusOpen] = useState(false); const [selectedRepair, setSelectedRepair] = useState(null); const [cancelRepair, setCancelRepair] = useState(null); const loadRepairs = useCallback(async () => { setLoading(true); setError(""); try { const params = new URLSearchParams(); params.set("limit", String(pageSize)); params.set("offset", String((page - 1) * pageSize)); if (search.trim()) params.set("q", search.trim()); if (statusFilter !== "all") params.set("status", statusFilter); if (sourceFilter !== "all") params.set("source", sourceFilter); if (priorityFilter !== "all") params.set("priority", priorityFilter); const [repairResponse, meResponse] = await Promise.all([ api.get(`/repairs?${params.toString()}`), api.get("/me"), ]); setRepairs(repairResponse.data.items); setTotal(repairResponse.data.total); setCurrentUser(meResponse.data); } catch (err) { setError(getErrorMessage(err)); } finally { setLoading(false); } }, [page, priorityFilter, search, sourceFilter, statusFilter]); useEffect(() => { queueMicrotask(() => { void loadRepairs(); }); }, [loadRepairs]); const sortedRepairs = useMemo(() => [...repairs].sort((first, second) => { const firstValue = String(first[sortKey as keyof Repair] ?? "").toLowerCase(); const secondValue = String(second[sortKey as keyof Repair] ?? "").toLowerCase(); if (firstValue === secondValue) return 0; const result = firstValue > secondValue ? 1 : -1; return sortDirection === "asc" ? result : -result; }), [repairs, sortDirection, sortKey]); const pageCount = Math.max(1, Math.ceil(total / pageSize)); const currentPage = Math.min(page, pageCount); const canCreate = hasPermission(currentUser, "repairs.create"); const canUpdate = hasPermission(currentUser, "repairs.update"); const canUpdateStatus = hasPermission(currentUser, "repairs.status.update"); const canDelete = hasPermission(currentUser, "repairs.delete"); const columns: DataTableColumn[] = [ { key: "repair_number", label: "Reparaturnummer", sortable: true, render: (repair) => {repair.repair_number} }, { key: "status", label: "Status", sortable: true, render: (repair) => }, { key: "priority", label: "Priorität", sortable: true, render: (repair) => }, { key: "customer_name", label: "Kunde", sortable: true, render: (repair) => repair.customer_name }, { key: "device_model", label: "Gerät", sortable: true, render: (repair) => deviceLabel(repair) }, { key: "fault_description", label: "Fehler", render: (repair) => {repair.fault_description.slice(0, 80)}{repair.fault_description.length > 80 ? "..." : ""} }, { key: "source", label: "Quelle", sortable: true, render: (repair) => repair.source }, { key: "created_at", label: "Erstellt", sortable: true, render: (repair) => formatDate(repair.created_at) }, { key: "actions", label: "Aktionen", className: "px-4 py-3 text-right", render: (repair) => (
{canUpdate && } {canUpdateStatus && } {canDelete && }
), }, ]; function handleSort(key: string) { if (sortKey === key) { setSortDirection((current) => (current === "asc" ? "desc" : "asc")); return; } setSortKey(key); setSortDirection("asc"); } async function saveRepair(payload: RepairPayload) { setPending(true); setFormError(""); try { if (selectedRepair) { const response = await api.put(`/repairs/${selectedRepair.id}`, payload); setRepairs((current) => current.map((repair) => repair.id === selectedRepair.id ? response.data : repair)); showToast({ type: "success", title: "Reparatur aktualisiert", description: response.data.repair_number }); } else { const response = await api.post("/repairs", payload); setRepairs((current) => [response.data, ...current].slice(0, pageSize)); setTotal((current) => current + 1); showToast({ type: "success", title: "Reparatur angelegt", description: response.data.repair_number }); } setFormOpen(false); } catch (err) { const message = getErrorMessage(err); setFormError(message); showToast({ type: "error", title: "Reparatur konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } async function saveStatus(status: RepairStatus, note: string) { if (!selectedRepair) return; setPending(true); setStatusError(""); try { const response = await api.put(`/repairs/${selectedRepair.id}/status`, { status, note }); setRepairs((current) => current.map((repair) => repair.id === selectedRepair.id ? response.data : repair)); showToast({ type: "success", title: "Status aktualisiert", description: response.data.repair_number }); setStatusOpen(false); } catch (err) { const message = getErrorMessage(err); setStatusError(message); showToast({ type: "error", title: "Status konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } async function confirmCancel() { if (!cancelRepair) return; setPending(true); setCancelError(""); try { const response = await api.delete(`/repairs/${cancelRepair.id}`); setRepairs((current) => current.map((repair) => repair.id === cancelRepair.id ? response.data : repair)); showToast({ type: "success", title: "Reparatur storniert", description: cancelRepair.repair_number }); setCancelRepair(null); } catch (err) { const message = getErrorMessage(err); setCancelError(message); showToast({ type: "error", title: "Reparatur konnte nicht storniert werden", description: message }); } finally { setPending(false); } } return (

Reparaturen

{total} Werkstattvorgänge

{canCreate && }
{ setSearch(value); setPage(1); }} placeholder="Reparaturen suchen" />
{!loading && !error && total === 0 && !search && statusFilter === "all" && sourceFilter === "all" && priorityFilter === "all" ? ( setFormOpen(true)}>Reparatur anlegen : undefined} /> ) : ( repair.id} sortKey={sortKey} sortDirection={sortDirection} loading={loading} error={error} emptyTitle="Noch keine Reparaturen vorhanden." emptyDescription="Passe Suche oder Filter an oder lege eine Reparatur an." onSort={handleSort} /> )}
Seite {currentPage} von {pageCount}
{ if (!open) { setCancelRepair(null); setCancelError(""); } }} onConfirm={confirmCancel} > {cancelRepair && (

{cancelRepair.repair_number}

{cancelRepair.customer_name} · {deviceLabel(cancelRepair)}

{cancelError &&

{cancelError}

}
)}
); }