"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ReactNode } from "react"; import { Download, Eye, FileText, ImageIcon, Plus, Trash2, Upload, X, ZoomIn, ZoomOut, } from "lucide-react"; import ConfirmDialog from "@/components/common/ConfirmDialog"; import { useToast } from "@/components/common/ToastProvider"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button, buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { api } from "@/lib/api"; import type { RepairDocument, RepairDocumentType, RepairDocumentVisibility } from "@/types/repair"; const documentTypeLabels: Record = { device_photo: "Gerätefoto", fault_photo: "Fehlerbild", measurement: "Messbild", estimate: "Kostenvoranschlag", repair_report: "Reparaturbericht", shipping: "Versandbeleg", other: "Sonstiges", }; const documentTypes = Object.entries(documentTypeLabels) as Array<[RepairDocumentType, string]>; 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 formatSize(value: number) { if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`; return `${(value / 1024 / 1024).toFixed(1)} MB`; } function isImage(document: RepairDocument) { return document.mime_type.startsWith("image/"); } function isPdf(document: RepairDocument) { return document.mime_type === "application/pdf"; } function downloadUrl(repairId: number, documentId: number, disposition: "inline" | "attachment" = "inline") { return `/api/repairs/${repairId}/documents/${documentId}/download?disposition=${disposition}`; } type Props = { repairId: number; canUpdate: boolean; }; export default function RepairDocumentsSection({ repairId, canUpdate }: Props) { const { showToast } = useToast(); const fileInputRef = useRef(null); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [uploadOpen, setUploadOpen] = useState(false); const [title, setTitle] = useState(""); const [documentType, setDocumentType] = useState("device_photo"); const [visibility, setVisibility] = useState("internal"); const [note, setNote] = useState(""); const [selectedFile, setSelectedFile] = useState(null); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const [previewDocument, setPreviewDocument] = useState(null); const [zoom, setZoom] = useState(1); const loadDocuments = useCallback(async () => { setLoading(true); setError(""); try { const response = await api.get(`/repairs/${repairId}/documents`); setDocuments(response.data); } catch (err) { setError(getErrorMessage(err)); } finally { setLoading(false); } }, [repairId]); useEffect(() => { queueMicrotask(() => { void loadDocuments(); }); }, [loadDocuments]); const imageDocuments = useMemo(() => documents.filter(isImage), [documents]); useEffect(() => { if (!previewDocument) return; function handleKeyDown(event: KeyboardEvent) { if (event.key === "Escape") { setPreviewDocument(null); } } window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [previewDocument]); function resetUploadForm() { setTitle(""); setDocumentType("device_photo"); setVisibility("internal"); setNote(""); setSelectedFile(null); setUploadProgress(0); if (fileInputRef.current) { fileInputRef.current.value = ""; } } async function uploadDocument() { if (!selectedFile) { showToast({ type: "error", title: "Keine Datei ausgewählt" }); return; } const formData = new FormData(); formData.append("file", selectedFile); formData.append("title", title.trim() || selectedFile.name); formData.append("document_type", documentType); formData.append("visibility", visibility); formData.append("note", note.trim()); setUploading(true); setUploadProgress(0); try { await api.post(`/repairs/${repairId}/documents/upload`, formData, { onUploadProgress: (event) => { if (event.total) { setUploadProgress(Math.round((event.loaded / event.total) * 100)); } }, }); await loadDocuments(); resetUploadForm(); setUploadOpen(false); showToast({ type: "success", title: "Dokument hochgeladen" }); } catch (err) { showToast({ type: "error", title: "Upload fehlgeschlagen", description: getErrorMessage(err), }); } finally { setUploading(false); } } async function deleteDocument() { if (!deleteTarget) return; setDeleting(true); try { await api.delete(`/repairs/${repairId}/documents/${deleteTarget.id}`); setDocuments((current) => current.filter((item) => item.id !== deleteTarget.id)); setDeleteTarget(null); showToast({ type: "success", title: "Dokument gelöscht" }); } catch (err) { showToast({ type: "error", title: "Dokument konnte nicht gelöscht werden", description: getErrorMessage(err), }); } finally { setDeleting(false); } } function openDocument(document: RepairDocument) { if (isImage(document)) { setPreviewDocument(document); setZoom(1); return; } window.open(downloadUrl(repairId, document.id, "inline"), "_blank", "noopener,noreferrer"); } if (loading) { return
Dokumente werden geladen...
; } if (error) { return
{error}
; } return (

{documents.length} Datei{documents.length === 1 ? "" : "en"} gespeichert

{canUpdate && ( )}
{documents.length === 0 ? (

Noch keine Dokumente oder Bilder vorhanden.

Gerätefotos, Messbilder, PDF-Berichte und Belege werden sicher im Olympus Storage gespeichert.

{canUpdate && (
)}
) : (
{documents.map((document) => (
{isImage(document) ? : }

{document.title}

{document.original_filename}

{document.note &&

{document.note}

}
Download {canUpdate && ( )}
))}
)} { setUploadOpen(open); if (!open && !uploading) resetUploadForm(); }}> Dokument hochladen JPG, PNG, WEBP und PDF werden im Reparatur-Storage abgelegt.
{ const file = event.target.files?.[0] ?? null; setSelectedFile(file); if (file && !title) setTitle(file.name); }} /> setTitle(event.target.value)} />