Add Revit XLSX settings page

This commit is contained in:
Julian Appel 2026-09-30 09:02:52 +02:00
parent 1cfeb06ec7
commit c633e78cd4
15 changed files with 259 additions and 1025 deletions

View file

@ -5,7 +5,7 @@ import { useParams } from "next/navigation";
import { FormEvent, useEffect, useMemo, useState } from "react";
import {
ArrowLeft,
FileUp,
FileSpreadsheet,
Library,
Link2,
ListTree,
@ -51,7 +51,6 @@ import {
copyProjectDeviceToGlobal,
createDistributionBoard,
createProjectDevice,
createRoom,
deleteProjectDevice,
deleteDistributionBoard,
disconnectProjectDeviceRows,
@ -83,7 +82,6 @@ import type {
BuildingDto,
CircuitListDto,
CircuitTreeResponseDto,
CreateRoomInput,
CreateProjectDeviceInput,
DistributionBoardDto,
FloorDto,
@ -115,7 +113,6 @@ import {
} from "../../../frontend/components/project-settings-modal";
import { FormModal } from "../../../frontend/components/form-modal";
import { ProjectDeviceModal } from "../../../frontend/components/project-device-modal";
import { RevitCsvModal } from "../../../frontend/components/revit-csv-modal";
import { FloorSelectOptions } from "../../../frontend/components/floor-select-options";
const projectDeviceSyncFieldLabels: Record<ProjectDeviceSyncField, string> = {
@ -200,7 +197,6 @@ export default function ProjectDetailPage() {
})
);
const [isProjectSettingsOpen, setIsProjectSettingsOpen] = useState(false);
const [isRevitCsvOpen, setIsRevitCsvOpen] = useState(false);
const [structureModal, setStructureModal] = useState<"board" | null>(null);
const [isProjectDeviceModalOpen, setIsProjectDeviceModalOpen] =
useState(false);
@ -679,22 +675,6 @@ export default function ProjectDetailPage() {
}
}
async function handleCreateRevitRoom(input: CreateRoomInput) {
if (!projectId || !project) throw new Error("Projekt ist nicht geladen.");
const result = await createRoom(projectId, input, project.currentRevision);
setRooms((current) => [...current, result.room]);
applyProjectRevision(result.history.currentRevision);
return result;
}
async function handleCreateRevitProjectDevice(input: CreateProjectDeviceInput) {
if (!projectId || !project) throw new Error("Projekt ist nicht geladen.");
const result = await createProjectDevice(projectId, input, project.currentRevision);
setProjectDevices((current) => [...current, result.projectDevice]);
applyProjectRevision(result.history.currentRevision);
return result;
}
async function handleCopyBoard() {
if (
!projectId ||
@ -997,15 +977,13 @@ export default function ProjectDetailPage() {
<Printer aria-hidden="true" size={16} />
Seitenansicht
</Link>
<button
<Link
className="btn btn-outline-primary btn-with-icon"
disabled={!project}
onClick={() => setIsRevitCsvOpen(true)}
type="button"
href={`/projects/${projectId}/revit`}
>
<FileUp aria-hidden="true" size={16} strokeWidth={2} />
Revit-CSV
</button>
<FileSpreadsheet aria-hidden="true" size={16} strokeWidth={2} />
Revit
</Link>
<button
className="btn btn-outline-primary btn-with-icon"
disabled={!project}
@ -1897,16 +1875,6 @@ export default function ProjectDetailPage() {
usedDistributionBoardSupplyTypes={usedBoardSupplyTypes}
/>
) : null}
{project && isRevitCsvOpen ? (
<RevitCsvModal
currentRevision={project.currentRevision}
onClose={() => setIsRevitCsvOpen(false)}
onCreateProjectDevice={handleCreateRevitProjectDevice}
onCreateRoom={handleCreateRevitRoom}
onRevisionChange={applyProjectRevision}
projectId={projectId}
/>
) : null}
</main>
);
}

View file

@ -0,0 +1,34 @@
"use client";
import { ArrowLeft, FileSpreadsheet, Settings } from "lucide-react";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
import { IconLink } from "../../../../frontend/components/icon-link";
import { RevitXlsxSettingsModal } from "../../../../frontend/components/revit-xlsx-settings-modal";
import type { ExternalModelConfiguration } from "../../../../external-model/configuration/external-model-configuration";
import { detectExternalModelWorkbook, getExternalModelConfiguration, getProject, updateExternalModelConfiguration } from "../../../../frontend/utils/api";
import { createDefaultRevitXlsxConfiguration } from "../../../../frontend/utils/revit-xlsx-settings";
import type { ExternalModelConfigurationSnapshotDto, ProjectDto } from "../../../../frontend/types";
export default function RevitPage() {
const { projectId } = useParams<{ projectId: string }>();
const [project, setProject] = useState<ProjectDto | null>(null);
const [snapshot, setSnapshot] = useState<ExternalModelConfigurationSnapshotDto | null>(null);
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => { Promise.all([getProject(projectId), getExternalModelConfiguration(projectId)]).then(([loadedProject, result]) => { setProject(loadedProject); setSnapshot(result.configuration); }).catch((value: unknown) => setError(value instanceof Error ? value.message : "Revit-Daten konnten nicht geladen werden.")); }, [projectId]);
async function save(configuration: ExternalModelConfiguration) {
if (!project) return;
setSaving(true); setError(null);
try { const result = await updateExternalModelConfiguration(projectId, project.currentRevision, configuration); setSnapshot(result.configuration); setProject({ ...project, currentRevision: result.history.currentRevision }); setOpen(false); }
catch (value) { setError(value instanceof Error ? value.message : "Einstellungen konnten nicht gespeichert werden."); }
finally { setSaving(false); }
}
return <main className="container py-4">
<div className="page-header"><div><div className="kicker">Projekt · Revit</div><h1>{project?.name ?? "Revit-Anbindung"}</h1><p className="text-secondary mb-0">Linear-XLSX austauschen und Revit-Objekte verwalten</p></div><div className="d-flex gap-2"><button className="btn btn-primary btn-with-icon" disabled={!project} onClick={() => setOpen(true)} type="button"><Settings size={16} />Einstellungen</button><IconLink href={`/projects/${projectId}`} icon={ArrowLeft} label="Zum Projekt" showLabel /></div></div>
{error ? <div className="alert alert-danger">{error}</div> : null}
<div className="row g-3 mt-1"><div className="col-md-6"><section className="card h-100"><div className="card-body"><FileSpreadsheet className="mb-3" size={28} /><h2 className="h5">XLSX-Konfiguration</h2><p className="text-secondary">{snapshot ? "Die Linear-Spalten und das Tabellenlayout sind konfiguriert." : "Noch keine XLSX-Konfiguration gespeichert."}</p><span className={`badge ${snapshot ? "text-bg-success" : "text-bg-secondary"}`}>{snapshot ? "Konfiguriert" : "Offen"}</span></div></section></div><div className="col-md-6"><section className="card h-100"><div className="card-body"><h2 className="h5">Importstatus</h2><p className="text-secondary mb-0">Noch kein Linear-XLSX-Import vorhanden. Der Erstimport folgt in R5.</p></div></section></div></div>
{open ? <RevitXlsxSettingsModal configuration={snapshot?.configuration ?? createDefaultRevitXlsxConfiguration()} isSaving={saving} onClose={() => setOpen(false)} onInspectSample={async (file, configuration) => { const result = await detectExternalModelWorkbook(projectId, file, configuration); return `Muster erkannt: ${result.sheetName}, ${result.columnCount} Spalten, ${result.objectCount} Objekte.`; }} onSave={save} /> : null}
</main>;
}