All first todos completed

This commit is contained in:
2026-05-01 17:58:14 +02:00
parent 65819900b1
commit 18a4fdd893
29 changed files with 1263 additions and 160 deletions
+120 -16
View File
@@ -1,9 +1,11 @@
"use client";
"use client";
import Link from "next/link";
import { useParams } from "next/navigation";
import { FormEvent, useEffect, useMemo, useState } from "react";
import {
copyGlobalDeviceToProject,
copyProjectDeviceToGlobal,
createDistributionBoard,
createFloor,
createProjectDevice,
@@ -11,6 +13,7 @@ import {
deleteProjectDevice,
listDistributionBoards,
listFloors,
listGlobalDevices,
listProjectDevices,
listProjects,
listRooms,
@@ -21,6 +24,7 @@ import type {
CreateProjectDeviceInput,
DistributionBoardDto,
FloorDto,
GlobalDeviceDto,
ProjectDeviceDto,
ProjectDto,
RoomDto,
@@ -28,6 +32,7 @@ import type {
const emptyProjectDevice: CreateProjectDeviceInput = {
name: "",
displayName: "",
category: "",
quantity: 1,
installedPowerPerUnitKw: 0.1,
@@ -54,6 +59,7 @@ export default function ProjectDetailPage() {
const [floors, setFloors] = useState<FloorDto[]>([]);
const [rooms, setRooms] = useState<RoomDto[]>([]);
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
const [globalDevices, setGlobalDevices] = useState<GlobalDeviceDto[]>([]);
const [boardName, setBoardName] = useState("");
const [floorName, setFloorName] = useState("");
const [roomNumber, setRoomNumber] = useState("");
@@ -63,6 +69,7 @@ export default function ProjectDetailPage() {
const [threePhaseVoltageV, setThreePhaseVoltageV] = useState("400");
const [projectDeviceForm, setProjectDeviceForm] = useState<Record<string, string>>({
name: "",
displayName: "",
category: "",
quantity: "1",
installedPowerPerUnitKw: "0.1",
@@ -72,6 +79,7 @@ export default function ProjectDetailPage() {
powerFactor: "1",
note: "",
});
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
const [error, setError] = useState<string | null>(null);
const [isSaving, setIsSaving] = useState(false);
@@ -89,8 +97,9 @@ export default function ProjectDetailPage() {
listFloors(projectId),
listRooms(projectId),
listProjectDevices(projectId),
listGlobalDevices(),
])
.then(([projects, distributionBoards, loadedFloors, loadedRooms, loadedProjectDevices]) => {
.then(([projects, distributionBoards, loadedFloors, loadedRooms, loadedProjectDevices, loadedGlobalDevices]) => {
const currentProject = projects.find((item) => item.id === projectId) ?? null;
setProject(currentProject);
if (currentProject) {
@@ -101,6 +110,7 @@ export default function ProjectDetailPage() {
setFloors(loadedFloors);
setRooms(loadedRooms);
setProjectDevices(loadedProjectDevices);
setGlobalDevices(loadedGlobalDevices);
setError(null);
})
.catch((err: unknown) =>
@@ -201,6 +211,7 @@ export default function ProjectDetailPage() {
const payload: CreateProjectDeviceInput = {
name: projectDeviceForm.name.trim(),
displayName: projectDeviceForm.displayName.trim() || projectDeviceForm.name.trim(),
category: projectDeviceForm.category.trim() || undefined,
quantity: Number(projectDeviceForm.quantity),
installedPowerPerUnitKw: Number(projectDeviceForm.installedPowerPerUnitKw),
@@ -218,6 +229,7 @@ export default function ProjectDetailPage() {
setProjectDevices((current) => [...current, created]);
setProjectDeviceForm({
name: emptyProjectDevice.name,
displayName: emptyProjectDevice.displayName,
category: emptyProjectDevice.category ?? "",
quantity: String(emptyProjectDevice.quantity),
installedPowerPerUnitKw: String(emptyProjectDevice.installedPowerPerUnitKw),
@@ -252,7 +264,7 @@ export default function ProjectDetailPage() {
async function handleQuickUpdateProjectDevice(
device: ProjectDeviceDto,
key: "name" | "category",
key: "name" | "displayName" | "category",
value: string
) {
if (!projectId) {
@@ -261,6 +273,7 @@ export default function ProjectDetailPage() {
const payload: CreateProjectDeviceInput = {
name: key === "name" ? value : device.name,
displayName: key === "displayName" ? value : device.displayName,
category: key === "category" ? value : device.category ?? undefined,
quantity: device.quantity,
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
@@ -279,6 +292,40 @@ export default function ProjectDetailPage() {
}
}
async function handleCopyGlobalToProject() {
if (!projectId || !selectedGlobalDeviceId) {
return;
}
setIsSaving(true);
setError(null);
try {
const created = await copyGlobalDeviceToProject(projectId, selectedGlobalDeviceId);
setProjectDevices((current) => [...current, created]);
} catch (err) {
setError(err instanceof Error ? err.message : "Globales Gerät konnte nicht ins Projekt kopiert werden.");
} finally {
setIsSaving(false);
}
}
async function handleCopyProjectToGlobal(projectDeviceId: string) {
if (!projectId) {
return;
}
setIsSaving(true);
setError(null);
try {
const created = await copyProjectDeviceToGlobal(projectId, projectDeviceId);
setGlobalDevices((current) => [...current, created]);
} catch (err) {
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht global kopiert werden.");
} finally {
setIsSaving(false);
}
}
return (
<main className="container py-4">
<div className="d-flex justify-content-between align-items-center mb-3">
@@ -512,14 +559,24 @@ export default function ProjectDetailPage() {
<div className="col-12 col-md-3">
<input
className="form-control"
placeholder="Bezeichnung"
placeholder="Interner Name"
value={projectDeviceForm.name}
onChange={(event) =>
setProjectDeviceForm((current) => ({ ...current, name: event.target.value }))
}
/>
</div>
<div className="col-6 col-md-2">
<div className="col-12 col-md-3">
<input
className="form-control"
placeholder="Anzeigename"
value={projectDeviceForm.displayName}
onChange={(event) =>
setProjectDeviceForm((current) => ({ ...current, displayName: event.target.value }))
}
/>
</div>
<div className="col-6 col-md-1">
<input
className="form-control"
placeholder="Kategorie"
@@ -540,7 +597,7 @@ export default function ProjectDetailPage() {
}
/>
</div>
<div className="col-6 col-md-2">
<div className="col-6 col-md-1">
<input
className="form-control"
type="number"
@@ -586,17 +643,40 @@ export default function ProjectDetailPage() {
</button>
</div>
</form>
<div className="input-group mt-3">
<select
className="form-select"
value={selectedGlobalDeviceId}
onChange={(event) => setSelectedGlobalDeviceId(event.target.value)}
>
<option value="">Globales Gerät auswählen</option>
{globalDevices.map((device) => (
<option key={device.id} value={device.id}>
{device.displayName}
</option>
))}
</select>
<button
className="btn btn-outline-primary"
type="button"
onClick={handleCopyGlobalToProject}
disabled={!selectedGlobalDeviceId || isSaving}
>
Nach Projekt kopieren
</button>
</div>
</div>
<div className="table-responsive">
<table className="table table-sm table-striped align-middle mb-0">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Interner Name</th>
<th>Anzeigename</th>
<th>Kategorie</th>
<th>Anzahl</th>
<th>Leistung je Stück [kW]</th>
<th>GZF</th>
<th>Aktion</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
@@ -613,6 +693,17 @@ export default function ProjectDetailPage() {
}
/>
</td>
<td>
<input
className="form-control form-control-sm"
defaultValue={device.displayName}
onBlur={(event) =>
event.target.value !== device.displayName
? handleQuickUpdateProjectDevice(device, "displayName", event.target.value)
: undefined
}
/>
</td>
<td>
<input
className="form-control form-control-sm"
@@ -628,19 +719,30 @@ export default function ProjectDetailPage() {
<td>{device.installedPowerPerUnitKw}</td>
<td>{device.demandFactor}</td>
<td>
<button
className="btn btn-sm btn-outline-danger"
type="button"
onClick={() => handleDeleteProjectDevice(device.id)}
>
Löschen
</button>
<div className="btn-group btn-group-sm">
<button
className="btn btn-outline-secondary"
type="button"
onClick={() => handleCopyProjectToGlobal(device.id)}
disabled={isSaving}
>
Nach global kopieren
</button>
<button
className="btn btn-outline-danger"
type="button"
onClick={() => handleDeleteProjectDevice(device.id)}
disabled={isSaving}
>
Löschen
</button>
</div>
</td>
</tr>
))}
{!projectDevices.length ? (
<tr>
<td colSpan={6} className="text-center text-secondary py-4">
<td colSpan={7} className="text-center text-secondary py-4">
Noch keine Projektgeräte vorhanden.
</td>
</tr>
@@ -658,3 +760,5 @@ export default function ProjectDetailPage() {
</main>
);
}