Add optional room areas
This commit is contained in:
parent
a6500e2b26
commit
dce31ee143
26 changed files with 3669 additions and 78 deletions
|
|
@ -48,13 +48,17 @@ import {
|
|||
filterRooms,
|
||||
groupFloorsByBuilding,
|
||||
hasFloorsWithoutBuilding,
|
||||
hasRoomsWithoutFloor,
|
||||
normalizeBuildingSelection,
|
||||
normalizeFloorSelection,
|
||||
showsRoomsWithoutFloorEntry,
|
||||
summarizeRoomAreas,
|
||||
type RoomAreaSummary,
|
||||
type StructureSelection,
|
||||
} from "../../../../frontend/utils/building-structure";
|
||||
import { FormModal } from "../../../../frontend/components/form-modal";
|
||||
import { RoomModal } from "../../../../frontend/components/room-modal";
|
||||
import { formatRoomArea } from "../../../../frontend/utils/room-area";
|
||||
|
||||
const BUILDING_HAS_FLOORS_MESSAGE =
|
||||
"Das Gebäude kann nicht gelöscht werden, solange Etagen zugeordnet sind.";
|
||||
|
|
@ -68,6 +72,23 @@ function formatCount(count: number, singular: string, plural: string) {
|
|||
return `${count} ${count === 1 ? singular : plural}`;
|
||||
}
|
||||
|
||||
function formatAreaSummary(summary: RoomAreaSummary) {
|
||||
return `${formatRoomArea(summary.areaM2)} m² · ${formatCount(
|
||||
summary.missingAreaCount,
|
||||
"Raum ohne Fläche",
|
||||
"Räume ohne Fläche"
|
||||
)}`;
|
||||
}
|
||||
|
||||
function formatStructureMeta(
|
||||
count: number,
|
||||
singular: string,
|
||||
plural: string,
|
||||
summary: RoomAreaSummary
|
||||
) {
|
||||
return `${formatCount(count, singular, plural)} · ${formatAreaSummary(summary)}`;
|
||||
}
|
||||
|
||||
function compareRooms(left: RoomDto, right: RoomDto) {
|
||||
return (
|
||||
left.roomNumber.localeCompare(right.roomNumber, "de", { numeric: true }) ||
|
||||
|
|
@ -307,7 +328,25 @@ export default function BuildingStructurePage() {
|
|||
() => countRoomsByFloor(rooms, floors),
|
||||
[floors, rooms]
|
||||
);
|
||||
const showUnassignedBuilding = hasFloorsWithoutBuilding(floors, buildings);
|
||||
const areaSummaries = useMemo(
|
||||
() => summarizeRoomAreas(rooms, floors, buildings),
|
||||
[buildings, floors, rooms]
|
||||
);
|
||||
const emptyAreaSummary: RoomAreaSummary = {
|
||||
areaM2: 0,
|
||||
roomCount: 0,
|
||||
missingAreaCount: 0,
|
||||
};
|
||||
const selectedBuildingAreaSummary =
|
||||
effectiveBuildingSelection === ALL_SELECTION
|
||||
? areaSummaries.total
|
||||
: areaSummaries.byBuilding.get(
|
||||
effectiveBuildingSelection === UNASSIGNED_SELECTION
|
||||
? null
|
||||
: effectiveBuildingSelection
|
||||
) ?? emptyAreaSummary;
|
||||
const showUnassignedBuilding =
|
||||
hasFloorsWithoutBuilding(floors, buildings) || hasRoomsWithoutFloor(rooms, floors);
|
||||
const showUnassignedFloor = showsRoomsWithoutFloorEntry(
|
||||
rooms,
|
||||
floors,
|
||||
|
|
@ -435,7 +474,8 @@ export default function BuildingStructurePage() {
|
|||
editingRoom &&
|
||||
editingRoom.roomNumber === input.roomNumber.trim() &&
|
||||
editingRoom.roomName === input.roomName.trim() &&
|
||||
editingRoom.floorId === (input.floorId || null)
|
||||
editingRoom.floorId === (input.floorId || null) &&
|
||||
editingRoom.areaM2 === input.areaM2
|
||||
) {
|
||||
setDialog(null);
|
||||
return;
|
||||
|
|
@ -536,7 +576,12 @@ export default function BuildingStructurePage() {
|
|||
<StructureEntry
|
||||
isSelected={effectiveBuildingSelection === ALL_SELECTION}
|
||||
label="Alle"
|
||||
meta={formatCount(floors.length, "Etage", "Etagen")}
|
||||
meta={formatStructureMeta(
|
||||
floors.length,
|
||||
"Etage",
|
||||
"Etagen",
|
||||
areaSummaries.total
|
||||
)}
|
||||
onSelect={() => selectBuilding(ALL_SELECTION)}
|
||||
/>
|
||||
{buildings.map((building) => (
|
||||
|
|
@ -553,10 +598,11 @@ export default function BuildingStructurePage() {
|
|||
isSelected={effectiveBuildingSelection === building.id}
|
||||
key={building.id}
|
||||
label={building.name}
|
||||
meta={formatCount(
|
||||
meta={formatStructureMeta(
|
||||
floorCountByBuilding.get(building.id) ?? 0,
|
||||
"Etage",
|
||||
"Etagen"
|
||||
"Etagen",
|
||||
areaSummaries.byBuilding.get(building.id) ?? emptyAreaSummary
|
||||
)}
|
||||
onSelect={() => selectBuilding(building.id)}
|
||||
/>
|
||||
|
|
@ -565,10 +611,11 @@ export default function BuildingStructurePage() {
|
|||
<StructureEntry
|
||||
isSelected={effectiveBuildingSelection === UNASSIGNED_SELECTION}
|
||||
label={<em>{UNASSIGNED_BUILDING_LABEL}</em>}
|
||||
meta={formatCount(
|
||||
meta={formatStructureMeta(
|
||||
floorCountByBuilding.get(null) ?? 0,
|
||||
"Etage",
|
||||
"Etagen"
|
||||
"Etagen",
|
||||
areaSummaries.byBuilding.get(null) ?? emptyAreaSummary
|
||||
)}
|
||||
onSelect={() => selectBuilding(UNASSIGNED_SELECTION)}
|
||||
/>
|
||||
|
|
@ -591,7 +638,12 @@ export default function BuildingStructurePage() {
|
|||
<StructureEntry
|
||||
isSelected={effectiveFloorSelection === ALL_SELECTION}
|
||||
label="Alle"
|
||||
meta={formatCount(roomsInBuildingSelection, "Raum", "Räume")}
|
||||
meta={formatStructureMeta(
|
||||
roomsInBuildingSelection,
|
||||
"Raum",
|
||||
"Räume",
|
||||
selectedBuildingAreaSummary
|
||||
)}
|
||||
onSelect={() => setFloorSelection(ALL_SELECTION)}
|
||||
/>
|
||||
{visibleFloorGroups.map((group) => (
|
||||
|
|
@ -613,10 +665,11 @@ export default function BuildingStructurePage() {
|
|||
isSelected={effectiveFloorSelection === floor.id}
|
||||
key={floor.id}
|
||||
label={floor.name}
|
||||
meta={formatCount(
|
||||
meta={formatStructureMeta(
|
||||
roomCountByFloor.get(floor.id) ?? 0,
|
||||
"Raum",
|
||||
"Räume"
|
||||
"Räume",
|
||||
areaSummaries.byFloor.get(floor.id) ?? emptyAreaSummary
|
||||
)}
|
||||
onSelect={() => setFloorSelection(floor.id)}
|
||||
/>
|
||||
|
|
@ -627,7 +680,12 @@ export default function BuildingStructurePage() {
|
|||
<StructureEntry
|
||||
isSelected={effectiveFloorSelection === UNASSIGNED_SELECTION}
|
||||
label={<em>{UNASSIGNED_FLOOR_LABEL}</em>}
|
||||
meta={formatCount(roomCountByFloor.get(null) ?? 0, "Raum", "Räume")}
|
||||
meta={formatStructureMeta(
|
||||
roomCountByFloor.get(null) ?? 0,
|
||||
"Raum",
|
||||
"Räume",
|
||||
areaSummaries.byFloor.get(null) ?? emptyAreaSummary
|
||||
)}
|
||||
onSelect={() => setFloorSelection(UNASSIGNED_SELECTION)}
|
||||
/>
|
||||
) : null}
|
||||
|
|
@ -671,6 +729,11 @@ export default function BuildingStructurePage() {
|
|||
{room.roomName}
|
||||
</>
|
||||
}
|
||||
meta={
|
||||
room.areaM2 === null
|
||||
? "Fläche fehlt"
|
||||
: `${formatRoomArea(room.areaM2)} m²`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!visibleRooms.length && !isLoading ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue