Add Revit catalog creation flow

This commit is contained in:
Julian Appel 2026-08-02 18:08:18 +02:00
parent ab2449419b
commit bc2bc7ece5
8 changed files with 435 additions and 133 deletions

View file

@ -35,6 +35,7 @@ import {
} from "../../../frontend/utils/api";
import type {
CircuitListDto,
CreateRoomInput,
CreateProjectDeviceInput,
DistributionBoardDto,
FloorDto,
@ -60,6 +61,7 @@ import {
import { FormModal } from "../../../frontend/components/form-modal";
import { ProjectDeviceModal } from "../../../frontend/components/project-device-modal";
import { RevitCsvModal } from "../../../frontend/components/revit-csv-modal";
import { RoomModal } from "../../../frontend/components/room-modal";
const projectDeviceSyncFieldLabels: Record<ProjectDeviceSyncField, string> = {
name: "Technischer Name",
@ -101,9 +103,6 @@ export default function ProjectDetailPage() {
const [editingBoardCopyName, setEditingBoardCopyName] = useState("");
const [floorName, setFloorName] = useState("");
const [editingFloor, setEditingFloor] = useState<FloorDto | null>(null);
const [roomNumber, setRoomNumber] = useState("");
const [roomName, setRoomName] = useState("");
const [roomFloorId, setRoomFloorId] = useState("");
const [editingRoom, setEditingRoom] = useState<RoomDto | null>(null);
const [isProjectSettingsOpen, setIsProjectSettingsOpen] = useState(false);
const [isRevitCsvOpen, setIsRevitCsvOpen] = useState(false);
@ -285,24 +284,11 @@ export default function ProjectDetailPage() {
}
}
async function handleSaveRoom(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (
!projectId ||
!project ||
!roomNumber.trim() ||
!roomName.trim()
) {
return;
}
async function handleSaveRoom(input: CreateRoomInput) {
if (!projectId || !project) return;
setIsSaving(true);
setError(null);
try {
const input = {
floorId: roomFloorId || undefined,
roomNumber: roomNumber.trim(),
roomName: roomName.trim(),
};
const result = editingRoom
? await updateRoom(
projectId,
@ -319,9 +305,6 @@ export default function ProjectDetailPage() {
: [...current, result.room]
);
applyProjectRevision(result.history.currentRevision);
setRoomNumber("");
setRoomName("");
setRoomFloorId("");
setEditingRoom(null);
setStructureModal(null);
} catch (err) {
@ -500,6 +483,22 @@ 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;
}
function openCreateFloor() {
setEditingFloor(null);
setFloorName("");
@ -514,17 +513,11 @@ export default function ProjectDetailPage() {
function openCreateRoom() {
setEditingRoom(null);
setRoomNumber("");
setRoomName("");
setRoomFloorId("");
setStructureModal("room");
}
function openEditRoom(room: RoomDto) {
setEditingRoom(room);
setRoomNumber(room.roomNumber);
setRoomName(room.roomName);
setRoomFloorId(room.floorId ?? "");
setStructureModal("room");
}
@ -557,9 +550,6 @@ export default function ProjectDetailPage() {
setRooms((current) => current.filter((room) => room.id !== editingRoom.id));
applyProjectRevision(result.history.currentRevision);
setEditingRoom(null);
setRoomNumber("");
setRoomName("");
setRoomFloorId("");
setStructureModal(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Raum konnte nicht gelöscht werden.");
@ -1585,73 +1575,17 @@ export default function ProjectDetailPage() {
) : null}
{structureModal === "room" ? (
<FormModal
<RoomModal
floors={floors}
initialRoom={editingRoom ?? undefined}
isSaving={isSaving}
onClose={() => {
setStructureModal(null);
setEditingRoom(null);
}}
onSubmit={handleSaveRoom}
submitDisabled={!roomNumber.trim() || !roomName.trim()}
submitLabel={editingRoom ? "Änderungen speichern" : "Raum anlegen"}
title={editingRoom ? "Raum bearbeiten" : "Raum hinzufügen"}
>
<div className="row g-3">
<div className="col-12 col-md-4">
<label className="form-label" htmlFor="room-number">
Raumnummer
</label>
<input
autoFocus
className="form-control"
id="room-number"
onChange={(event) => setRoomNumber(event.target.value)}
required
value={roomNumber}
/>
</div>
<div className="col-12 col-md-8">
<label className="form-label" htmlFor="room-name">
Raumname
</label>
<input
className="form-control"
id="room-name"
onChange={(event) => setRoomName(event.target.value)}
required
value={roomName}
/>
</div>
<div className="col-12">
<label className="form-label" htmlFor="room-floor">
Etage
</label>
<select
className="form-select"
id="room-floor"
onChange={(event) => setRoomFloorId(event.target.value)}
value={roomFloorId}
>
<option value="">Ohne Etage</option>
{floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.name}
</option>
))}
</select>
</div>
</div>
{editingRoom ? (
<div className="border-top mt-4 pt-3">
<p className="text-secondary small mb-2">
In Stromkreisen verwendete Räume können nicht gelöscht werden.
</p>
<button className="btn btn-outline-danger" disabled={isSaving} onClick={() => void handleDeleteRoom()} type="button">
Raum löschen
</button>
</div>
) : null}
</FormModal>
onDelete={editingRoom ? () => void handleDeleteRoom() : undefined}
onSave={handleSaveRoom}
/>
) : null}
{isProjectDeviceModalOpen ? (
@ -1692,6 +1626,8 @@ export default function ProjectDetailPage() {
<RevitCsvModal
currentRevision={project.currentRevision}
onClose={() => setIsRevitCsvOpen(false)}
onCreateProjectDevice={handleCreateRevitProjectDevice}
onCreateRoom={handleCreateRevitRoom}
onRevisionChange={applyProjectRevision}
projectId={projectId}
/>