forked from jappel/leistungsbilanz-ts
143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import React, { type FormEvent, useState } from "react";
|
|
import type { BuildingDto, CreateRoomInput, RoomDto } from "../types";
|
|
import { FloorSelectOptions, type FloorOption } from "./floor-select-options";
|
|
import { FormModal } from "./form-modal";
|
|
import {
|
|
formatOptionalRoomAreaInput,
|
|
parseRoomAreaInput,
|
|
} from "../utils/room-area";
|
|
|
|
interface RoomModalProps {
|
|
/** When given, the floor select groups floors by building. */
|
|
buildings?: ReadonlyArray<Pick<BuildingDto, "id" | "name">>;
|
|
floors: ReadonlyArray<FloorOption>;
|
|
initialRoom?: RoomDto;
|
|
initialValues?: Partial<CreateRoomInput>;
|
|
isSaving: boolean;
|
|
onClose: () => void;
|
|
onDelete?: () => void;
|
|
onSave: (input: CreateRoomInput) => Promise<void>;
|
|
}
|
|
|
|
export function RoomModal({
|
|
buildings,
|
|
floors,
|
|
initialRoom,
|
|
initialValues,
|
|
isSaving,
|
|
onClose,
|
|
onDelete,
|
|
onSave,
|
|
}: RoomModalProps) {
|
|
const [roomNumber, setRoomNumber] = useState(
|
|
initialRoom?.roomNumber ?? initialValues?.roomNumber ?? ""
|
|
);
|
|
const [roomName, setRoomName] = useState(
|
|
initialRoom?.roomName ?? initialValues?.roomName ?? ""
|
|
);
|
|
const [floorId, setFloorId] = useState(
|
|
initialRoom?.floorId ?? initialValues?.floorId ?? ""
|
|
);
|
|
const [areaText, setAreaText] = useState(() =>
|
|
formatOptionalRoomAreaInput(
|
|
initialRoom ? initialRoom.areaM2 : initialValues?.areaM2
|
|
)
|
|
);
|
|
const parsedArea = parseRoomAreaInput(areaText);
|
|
|
|
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!roomNumber.trim() || !roomName.trim() || !parsedArea.ok) return;
|
|
await onSave({
|
|
floorId: floorId || undefined,
|
|
roomNumber: roomNumber.trim(),
|
|
roomName: roomName.trim(),
|
|
areaM2: parsedArea.value,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<FormModal
|
|
isSaving={isSaving}
|
|
onClose={onClose}
|
|
onSubmit={handleSubmit}
|
|
submitDisabled={!roomNumber.trim() || !roomName.trim() || !parsedArea.ok}
|
|
submitLabel={initialRoom ? "Änderungen speichern" : "Raum anlegen"}
|
|
title={initialRoom ? "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) => setFloorId(event.target.value)}
|
|
value={floorId}
|
|
>
|
|
<option value="">Ohne Etage</option>
|
|
<FloorSelectOptions buildings={buildings} floors={floors} />
|
|
</select>
|
|
</div>
|
|
<div className="col-12 col-md-6">
|
|
<label className="form-label" htmlFor="room-area">
|
|
Fläche [m²]
|
|
</label>
|
|
<input
|
|
aria-describedby="room-area-help"
|
|
aria-invalid={!parsedArea.ok}
|
|
className={`form-control${parsedArea.ok ? "" : " is-invalid"}`}
|
|
id="room-area"
|
|
inputMode="decimal"
|
|
onChange={(event) => setAreaText(event.target.value)}
|
|
placeholder="z. B. 23,45"
|
|
type="text"
|
|
value={areaText}
|
|
/>
|
|
<div className={parsedArea.ok ? "form-text" : "invalid-feedback"} id="room-area-help">
|
|
{parsedArea.ok
|
|
? "Optionale Netto-Raumfläche; Eingabe mit Dezimalkomma."
|
|
: parsedArea.error}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{initialRoom && onDelete ? (
|
|
<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={onDelete}
|
|
type="button"
|
|
>
|
|
Raum löschen
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
</FormModal>
|
|
);
|
|
}
|