"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { Download, Edit, Eye, Plus, Trash2 } 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 DocumentTypeBadge from "@/components/knowledge/DocumentTypeBadge"; import { DeviceFormDialog, DocumentUploadDialog, ManufacturerFormDialog } from "@/components/knowledge/KnowledgeForms"; import TagList from "@/components/knowledge/TagList"; import { Button, buttonVariants } from "@/components/ui/button"; import { api } from "@/lib/api"; import type { DevicePayload, DocumentPayload, DocumentType, KnowledgeDevice, KnowledgeDocument, KnowledgeManufacturer, ManufacturerPayload } from "@/types/knowledge"; function errorMessage(error: unknown) { if (typeof error === "object" && error !== null && "response" in error) { return (error as { response?: { data?: { detail?: string } } }).response?.data?.detail ?? "Aktion fehlgeschlagen"; } return "Aktion fehlgeschlagen"; } function fileSize(value: number) { if (!value) return "-"; if (value < 1024 * 1024) return `${Math.round(value / 1024)} KB`; return `${(value / 1024 / 1024).toFixed(1)} MB`; } export default function KnowledgeDocumentsPage() { const { showToast } = useToast(); const [documents, setDocuments] = useState([]); const [manufacturers, setManufacturers] = useState([]); const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [formError, setFormError] = useState(""); const [pending, setPending] = useState(false); const [search, setSearch] = useState(""); const [manufacturerFilter, setManufacturerFilter] = useState("all"); const [typeFilter, setTypeFilter] = useState("all"); const [sortKey, setSortKey] = useState("created_at"); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc"); const [formOpen, setFormOpen] = useState(false); const [manufacturerFormOpen, setManufacturerFormOpen] = useState(false); const [deviceFormOpen, setDeviceFormOpen] = useState(false); const [deviceInitialManufacturerId, setDeviceInitialManufacturerId] = useState(null); const [selected, setSelected] = useState(null); const [deleteItem, setDeleteItem] = useState(null); const loadItems = useCallback(async () => { setLoading(true); try { const [manufacturerResponse, deviceResponse, documentResponse] = await Promise.all([ api.get("/knowledge/manufacturers?limit=100"), api.get("/knowledge/devices?limit=100"), api.get("/knowledge/documents?limit=100"), ]); setManufacturers(manufacturerResponse.data); setDevices(deviceResponse.data); setDocuments(documentResponse.data); setError(""); } catch (err) { setError(errorMessage(err)); } finally { setLoading(false); } }, []); useEffect(() => { queueMicrotask(() => { void loadItems(); }); }, [loadItems]); const rows = useMemo(() => documents .filter((item) => manufacturerFilter === "all" || item.manufacturer_id === Number(manufacturerFilter)) .filter((item) => typeFilter === "all" || item.document_type === typeFilter) .filter((item) => [item.title, item.manufacturer.name, item.device?.name ?? "", ...item.tags].some((value) => value.toLowerCase().includes(search.toLowerCase()))) .sort((a, b) => { const av = sortKey === "manufacturer" ? a.manufacturer.name : String(a[sortKey as keyof KnowledgeDocument] ?? ""); const bv = sortKey === "manufacturer" ? b.manufacturer.name : String(b[sortKey as keyof KnowledgeDocument] ?? ""); const result = av.toLowerCase() > bv.toLowerCase() ? 1 : av.toLowerCase() < bv.toLowerCase() ? -1 : 0; return sortDirection === "asc" ? result : -result; }), [documents, manufacturerFilter, search, sortDirection, sortKey, typeFilter]); const selectedManufacturerId = manufacturerFilter === "all" ? devices[0]?.manufacturer_id ?? manufacturers[0]?.id ?? null : Number(manufacturerFilter); const selectedManufacturer = manufacturers.find((manufacturer) => manufacturer.id === selectedManufacturerId) ?? null; const selectedManufacturerDevices = selectedManufacturerId ? devices.filter((device) => device.manufacturer_id === selectedManufacturerId) : []; const canUpload = manufacturers.length > 0 && devices.length > 0 && (manufacturerFilter === "all" || selectedManufacturerDevices.length > 0); const columns: DataTableColumn[] = [ { key: "title", label: "Titel", sortable: true, render: (item) => {item.title} }, { key: "manufacturer", label: "Hersteller", sortable: true, render: (item) => item.manufacturer.name }, { key: "device", label: "Gerät", render: (item) => item.device?.name ?? "-" }, { key: "document_type", label: "Typ", sortable: true, render: (item) => }, { key: "language", label: "Sprache", sortable: true, render: (item) => item.language }, { key: "file_size", label: "Größe", render: (item) => fileSize(item.file_size) }, { key: "tags", label: "Tags", render: (item) => }, { key: "actions", label: "Aktionen", className: "px-4 py-3 text-right", render: (item) => (
{item.file_name && }
), }, ]; async function save(payload: DocumentPayload, file: File | null) { if (!payload.manufacturer_id || !payload.device_id || (!selected && !file)) { const message = "Die Datei konnte nicht hochgeladen werden, weil die Zuordnung unvollständig ist."; setFormError(message); showToast({ type: "error", title: "Dokument kann nicht gespeichert werden", description: message }); return; } setPending(true); setFormError(""); try { if (selected) { await api.put(`/knowledge/documents/${selected.id}`, payload); showToast({ type: "success", title: "Dokument aktualisiert", description: payload.title }); } else { if (!file) return; const formData = new FormData(); Object.entries(payload).forEach(([key, value]) => { if (key === "tags") formData.append(key, (value as string[]).join(",")); else if (value !== null && value !== undefined) formData.append(key, String(value)); }); formData.append("file", file); await api.post("/knowledge/documents/upload", formData); showToast({ type: "success", title: "Dokument hochgeladen", description: payload.title }); } setFormOpen(false); await loadItems(); } catch (err) { const message = errorMessage(err); setFormError(message); showToast({ type: "error", title: "Dokument konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } async function saveManufacturer(payload: ManufacturerPayload) { setPending(true); setFormError(""); try { const response = await api.post("/knowledge/manufacturers", payload); showToast({ type: "success", title: "Hersteller erstellt", description: response.data.name }); setManufacturerFilter(String(response.data.id)); setDeviceInitialManufacturerId(response.data.id); setManufacturerFormOpen(false); await loadItems(); } catch (err) { const message = errorMessage(err); setFormError(message); showToast({ type: "error", title: "Hersteller konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } async function saveDevice(payload: DevicePayload) { setPending(true); setFormError(""); try { const response = await api.post("/knowledge/devices", payload); showToast({ type: "success", title: "Gerät erstellt", description: response.data.name }); setManufacturerFilter(String(response.data.manufacturer_id)); setDeviceFormOpen(false); await loadItems(); } catch (err) { const message = errorMessage(err); setFormError(message); showToast({ type: "error", title: "Gerät konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } function openDeviceCreate(manufacturerId: number | null = selectedManufacturerId) { setDeviceInitialManufacturerId(manufacturerId); setFormError(""); setDeviceFormOpen(true); } async function confirmDelete() { if (!deleteItem) return; setPending(true); try { await api.delete(`/knowledge/documents/${deleteItem.id}`); showToast({ type: "success", title: "Dokument gelöscht", description: deleteItem.title }); setDeleteItem(null); await loadItems(); } catch (err) { showToast({ type: "error", title: "Dokument konnte nicht gelöscht werden", description: errorMessage(err) }); } finally { setPending(false); } } return (

Dokumente

{rows.length} Unterlagen

{!loading && !error && manufacturers.length === 0 ? ( { setFormError(""); setManufacturerFormOpen(true); }}>Hersteller anlegen} /> ) : !loading && !error && devices.length === 0 ? ( openDeviceCreate()}>Gerät anlegen} /> ) : !loading && !error && selectedManufacturer && manufacturerFilter !== "all" && selectedManufacturerDevices.length === 0 ? ( openDeviceCreate(selectedManufacturer.id)}>Gerät für diesen Hersteller anlegen} /> ) : !loading && !error && documents.length === 0 ? ( { setSelected(null); setFormError(""); setFormOpen(true); }} disabled={!canUpload}>Dokument hochladen} /> ) : !loading && !error && rows.length === 0 ? ( { setSelected(null); setFormError(""); setFormOpen(true); }} disabled={!canUpload}>Dokument hochladen} /> ) : ( item.id} sortKey={sortKey} sortDirection={sortDirection} loading={loading} error={error} emptyTitle="Keine Dokumente" emptyDescription="Lade das erste Service-Dokument hoch." onSort={(key) => { setSortKey(key); setSortDirection((current) => sortKey === key && current === "asc" ? "desc" : "asc"); }} /> )} showToast({ type: "error", title: "Upload unvollständig", description: message })} /> !open && setDeleteItem(null)} onConfirm={confirmDelete}> {deleteItem &&

{deleteItem.title}

}
); }