"use client"; import { useEffect, useMemo, useState } from "react"; import type { ExternalProjectObjectsDto, ExternalReturnScopeDto } from "../types"; import { createExternalReturnWorkbook, deleteMissingExternalProjectObjects, getExternalProjectObjects, updateExternalProjectObjects } from "../utils/api"; import { createVirtualRevitObjectWindow, filterAndSortRevitObjects, type RevitObjectSort } from "./revit-object-table-model"; const rowHeight = 44; const viewportHeight = 520; export function RevitObjectTable({ projectId, onRevision }: { projectId: string; onRevision: (revision: number) => void }) { const [data, setData] = useState(null); const [search, setSearch] = useState(""); const [boardFilter, setBoardFilter] = useState("all"); const [assignmentFilter, setAssignmentFilter] = useState("all"); const [sort, setSort] = useState("family"); const [selected, setSelected] = useState>(new Set()); const [scrollTop, setScrollTop] = useState(0); const [bulkBoard, setBulkBoard] = useState(""); const [bulkDevice, setBulkDevice] = useState(""); const [power, setPower] = useState(""); const [gzf, setGzf] = useState(""); const [planningField, setPlanningField] = useState(""); const [planningValue, setPlanningValue] = useState(""); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [returnScope, setReturnScope] = useState("all"); const [exporting, setExporting] = useState(false); const [exportWarnings, setExportWarnings] = useState([]); const [exportMessage, setExportMessage] = useState(null); async function load() { try { const loaded = await getExternalProjectObjects(projectId); setData(loaded); onRevision(loaded.currentRevision); } catch (value) { setError(value instanceof Error ? value.message : "Revit-Objekte konnten nicht geladen werden."); } } useEffect(() => { void load(); }, [projectId]); // eslint-disable-line react-hooks/exhaustive-deps const rows = useMemo(() => { if (!data) return []; return filterAndSortRevitObjects(data.objects, { search, boardFilter, assignmentFilter, sort }); }, [data, search, boardFilter, assignmentFilter, sort]); const { first, rows: visible } = createVirtualRevitObjectWindow(rows, scrollTop, rowHeight, viewportHeight); const selectable = rows.filter((object) => object.circuitDeviceRowId === null); const returnSelectable = rows.filter((object) => object.presenceStatus === "present"); const selectedHasLinkedObject = data?.objects.some((object) => selected.has(object.id) && object.circuitDeviceRowId !== null) ?? false; const selectedMissing = data?.objects.filter((object) => selected.has(object.id) && object.presenceStatus === "missing") ?? []; const selectionContainsPresentObject = data?.objects.some((object) => selected.has(object.id) && object.presenceStatus === "present") ?? false; async function applyBulk() { if (!data || selected.size === 0) return; const patch: Record = {}; if (bulkBoard !== "") patch.distributionBoardId = bulkBoard === "none" ? null : bulkBoard; if (bulkDevice !== "") patch.linkedProjectDeviceId = bulkDevice === "none" ? null : bulkDevice; if (power !== "") patch.powerPerUnitW = Number(power.replace(",", ".")); if (gzf !== "") patch.simultaneityFactor = Number(gzf.replace(",", ".")); if (planningField) { patch[planningField] = ["effectiveQuantity", "cosPhi"].includes(planningField) ? Number(planningValue.replace(",", ".")) : planningValue === "" ? null : planningValue; } if (Object.keys(patch).length === 0) { setError("Mindestens eine Sammeländerung auswählen."); return; } setSaving(true); setError(null); try { const result = await updateExternalProjectObjects(projectId, data.currentRevision, [...selected], patch); onRevision(result.history.currentRevision); setSelected(new Set()); await load(); } catch (value) { setError(value instanceof Error ? value.message : "Sammeländerung fehlgeschlagen."); } finally { setSaving(false); } } async function exportReturnWorkbook() { if (!data || (returnScope === "selected" && selected.size === 0)) return; setExporting(true); setError(null); setExportWarnings([]); setExportMessage(null); try { const result = await createExternalReturnWorkbook(projectId, data.currentRevision, returnScope, [...selected]); downloadBase64(result.contentBase64, result.fileName); setExportWarnings(result.warnings); setExportMessage(`${result.objectCount} Revit-Objekte wurden in ${result.fileName} geschrieben.`); } catch (value) { setError(value instanceof Error ? value.message : "Rückgabe-Excel konnte nicht erstellt werden."); } finally { setExporting(false); } } async function deleteMissing() { if (!data || selectedMissing.length === 0 || selectionContainsPresentObject) return; if (!window.confirm(`${selectedMissing.length} fehlende Revit-Objekte wirklich löschen? Verknüpfte Mengen werden angepasst. Undo stellt Objekte, Links und Mengen wieder her.`)) return; setSaving(true); setError(null); try { const result = await deleteMissingExternalProjectObjects(projectId, data.currentRevision, selectedMissing.map((object) => object.id)); onRevision(result.history.currentRevision); setSelected(new Set()); await load(); } catch (value) { setError(value instanceof Error ? value.message : "Fehlende Revit-Objekte konnten nicht gelöscht werden."); } finally { setSaving(false); } } if (!data) return
Revit-Objekte werden geladen …
; return

Revit-Objekte

{rows.length} von {data.objects.length} · {selected.size} ausgewählt
setSearch(event.target.value)} />
{error ?
{error}
: null}
{exportMessage ?
{exportMessage}
: null}{exportWarnings.length ?
Hinweise zur Rückgabe:
    {exportWarnings.map((warning) =>
  • {warning}
  • )}
: null}
setScrollTop(event.currentTarget.scrollTop)}>
{visible.map((object, index) => { const top = (first + index) * rowHeight; const linked = object.circuitDeviceRowId !== null; const missing = object.presenceStatus === "missing"; return
setSelected((current) => { const next = new Set(current); if (next.has(object.id)) next.delete(object.id); else next.add(object.id); return next; })} />
{object.planningValues.displayName ?? object.familyAndType}
{object.familyAndType}
{object.sourceRoomNumber} {object.sourceRoomName}
{data.distributionBoards.find((board) => board.id === object.distributionBoardId)?.name ?? "Ohne Verteiler"}
{object.selectionMarker || object.worksharingId}
{missing ? "fehlend" : linked ? "verknüpft" : "frei"}
; })}
{planningField ? : null}
; } function downloadBase64(contentBase64: string, fileName: string) { const binary = atob(contentBase64); const bytes = new Uint8Array(binary.length); for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); const url = URL.createObjectURL(new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" })); const anchor = document.createElement("a"); anchor.href = url; anchor.download = fileName; anchor.click(); URL.revokeObjectURL(url); }