Projects with several buildings could not be structured: floors and rooms hung directly off the project. Floors can now belong to a building (migration 0009: table buildings, nullable floors.building_id). Rooms and distribution boards keep their floor; their building follows from it. Existing floors stay without a building. Buildings use the location-structure command store with project-building.insert/.update/.delete, inverses and persistent Undo/Redo; deleting a building with assigned floors is rejected. Floor commands carry buildingId since command version 2. Version 1 floor commands from existing history predate buildings and are upgraded with buildingId = null before they are executed and persisted, so forward and inverse keep one version. Project snapshot schema 7 stores buildings and floor.buildingId; versions 1-6 are upgraded without buildings. Restore and portable transfer include buildings, and the transfer remaps building ids. Buildings, floors and rooms move from the project overview to their own page /projects/:id/struktur with three columns (Gebäude -> Etagen -> Räume) that filter each other, and a sidebar entry. Floor selects in the board and room dialogs are grouped by building. Unchanged dialogs close without a command. Details in docs/building-structure.md; see issue #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
1.2 KiB
TypeScript
49 lines
1.2 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import type { BuildingDto, FloorDto } from "../types";
|
|
import { groupFloorsByBuilding } from "../utils/building-structure";
|
|
|
|
export type FloorOption = Pick<FloorDto, "id" | "name"> & {
|
|
buildingId?: string | null;
|
|
};
|
|
|
|
interface FloorSelectOptionsProps {
|
|
buildings?: ReadonlyArray<Pick<BuildingDto, "id" | "name">>;
|
|
floors: ReadonlyArray<FloorOption>;
|
|
}
|
|
|
|
/**
|
|
* Floor <option>s for a <select>, grouped by building via <optgroup> as soon
|
|
* as the project has buildings. The caller renders its own empty option
|
|
* ("Ohne Etage") before these.
|
|
*/
|
|
export function FloorSelectOptions({
|
|
buildings = [],
|
|
floors,
|
|
}: FloorSelectOptionsProps) {
|
|
if (!buildings.length) {
|
|
return (
|
|
<>
|
|
{floors.map((floor) => (
|
|
<option key={floor.id} value={floor.id}>
|
|
{floor.name}
|
|
</option>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
return (
|
|
<>
|
|
{groupFloorsByBuilding(floors, buildings).map((group) => (
|
|
<optgroup key={group.buildingId ?? "unassigned"} label={group.label}>
|
|
{group.floors.map((floor) => (
|
|
<option key={floor.id} value={floor.id}>
|
|
{floor.name}
|
|
</option>
|
|
))}
|
|
</optgroup>
|
|
))}
|
|
</>
|
|
);
|
|
}
|