"use client"; import { useEffect, useMemo, useState } from "react"; import type { ExternalProjectObjectsDto } from "../types"; import { getExternalProjectObjects, updateExternalProjectObjects } from "../utils/api"; 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" | "room" | "worksharing">("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); 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 []; const needle = search.trim().toLocaleLowerCase("de-DE"); return data.objects.filter((object) => { if (boardFilter !== "all" && (boardFilter === "none" ? object.distributionBoardId !== null : object.distributionBoardId !== boardFilter)) return false; if (assignmentFilter === "unassigned" && object.circuitDeviceRowId !== null) return false; if (assignmentFilter === "linked" && object.circuitDeviceRowId === null) return false; return !needle || [object.worksharingId, object.familyAndType, object.selectionMarker, object.sourceRoomNumber, object.sourceRoomName, object.planningValues.displayName ?? ""].some((value) => value.toLocaleLowerCase("de-DE").includes(needle)); }).sort((a, b) => { const left = sort === "family" ? a.familyAndType : sort === "room" ? `${a.sourceRoomNumber}\u0000${a.sourceRoomName}` : a.worksharingId; const right = sort === "family" ? b.familyAndType : sort === "room" ? `${b.sourceRoomNumber}\u0000${b.sourceRoomName}` : b.worksharingId; return left.localeCompare(right, "de", { numeric: true }) || a.worksharingId.localeCompare(b.worksharingId); }); }, [data, search, boardFilter, assignmentFilter, sort]); const first = Math.max(0, Math.floor(scrollTop / rowHeight) - 5); const visible = rows.slice(first, first + Math.ceil(viewportHeight / rowHeight) + 10); const selectable = rows.filter((object) => object.circuitDeviceRowId === null); 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); } } 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}
setScrollTop(event.currentTarget.scrollTop)}>
{visible.map((object, index) => { const top = (first + index) * rowHeight; const linked = object.circuitDeviceRowId !== null; 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}
{linked ? "verknüpft" : "frei"}
; })}
{planningField ? : null}
; }