forked from jappel/leistungsbilanz-ts
764 lines
27 KiB
TypeScript
764 lines
27 KiB
TypeScript
"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,
|
|
createRoom,
|
|
deleteProjectDevice,
|
|
listDistributionBoards,
|
|
listFloors,
|
|
listGlobalDevices,
|
|
listProjectDevices,
|
|
listProjects,
|
|
listRooms,
|
|
updateProjectDevice,
|
|
updateProjectSettings,
|
|
} from "../../../frontend/utils/api";
|
|
import type {
|
|
CreateProjectDeviceInput,
|
|
DistributionBoardDto,
|
|
FloorDto,
|
|
GlobalDeviceDto,
|
|
ProjectDeviceDto,
|
|
ProjectDto,
|
|
RoomDto,
|
|
} from "../../../frontend/types";
|
|
|
|
const emptyProjectDevice: CreateProjectDeviceInput = {
|
|
name: "",
|
|
displayName: "",
|
|
category: "",
|
|
quantity: 1,
|
|
installedPowerPerUnitKw: 0.1,
|
|
demandFactor: 1,
|
|
voltageV: 230,
|
|
phaseCount: 1,
|
|
powerFactor: 1,
|
|
note: "",
|
|
};
|
|
|
|
function toOptionalNumber(value: string) {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
return Number(trimmed);
|
|
}
|
|
|
|
export default function ProjectDetailPage() {
|
|
const params = useParams<{ projectId: string }>();
|
|
const [projectId, setProjectId] = useState("");
|
|
const [project, setProject] = useState<ProjectDto | null>(null);
|
|
const [boards, setBoards] = useState<DistributionBoardDto[]>([]);
|
|
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("");
|
|
const [roomName, setRoomName] = useState("");
|
|
const [roomFloorId, setRoomFloorId] = useState("");
|
|
const [singlePhaseVoltageV, setSinglePhaseVoltageV] = useState("230");
|
|
const [threePhaseVoltageV, setThreePhaseVoltageV] = useState("400");
|
|
const [projectDeviceForm, setProjectDeviceForm] = useState<Record<string, string>>({
|
|
name: "",
|
|
displayName: "",
|
|
category: "",
|
|
quantity: "1",
|
|
installedPowerPerUnitKw: "0.1",
|
|
demandFactor: "1",
|
|
voltageV: "230",
|
|
phaseCount: "1",
|
|
powerFactor: "1",
|
|
note: "",
|
|
});
|
|
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setProjectId(params.projectId);
|
|
}, [params.projectId]);
|
|
|
|
useEffect(() => {
|
|
if (!projectId) {
|
|
return;
|
|
}
|
|
Promise.all([
|
|
listProjects(),
|
|
listDistributionBoards(projectId),
|
|
listFloors(projectId),
|
|
listRooms(projectId),
|
|
listProjectDevices(projectId),
|
|
listGlobalDevices(),
|
|
])
|
|
.then(([projects, distributionBoards, loadedFloors, loadedRooms, loadedProjectDevices, loadedGlobalDevices]) => {
|
|
const currentProject = projects.find((item) => item.id === projectId) ?? null;
|
|
setProject(currentProject);
|
|
if (currentProject) {
|
|
setSinglePhaseVoltageV(String(currentProject.singlePhaseVoltageV));
|
|
setThreePhaseVoltageV(String(currentProject.threePhaseVoltageV));
|
|
}
|
|
setBoards(distributionBoards);
|
|
setFloors(loadedFloors);
|
|
setRooms(loadedRooms);
|
|
setProjectDevices(loadedProjectDevices);
|
|
setGlobalDevices(loadedGlobalDevices);
|
|
setError(null);
|
|
})
|
|
.catch((err: unknown) =>
|
|
setError(err instanceof Error ? err.message : "Projektdaten konnten nicht geladen werden.")
|
|
);
|
|
}, [projectId]);
|
|
|
|
const boardCount = useMemo(() => boards.length, [boards.length]);
|
|
const floorCount = useMemo(() => floors.length, [floors.length]);
|
|
const roomCount = useMemo(() => rooms.length, [rooms.length]);
|
|
const projectDeviceCount = useMemo(() => projectDevices.length, [projectDevices.length]);
|
|
const floorById = useMemo(() => new Map(floors.map((item) => [item.id, item])), [floors]);
|
|
|
|
async function handleCreateBoard(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!projectId || !boardName.trim()) {
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const created = await createDistributionBoard(projectId, boardName.trim());
|
|
setBoards((current) => [...current, created]);
|
|
setBoardName("");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Verteilung konnte nicht erstellt werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleCreateFloor(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!projectId || !floorName.trim()) {
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const created = await createFloor(projectId, { name: floorName.trim() });
|
|
setFloors((current) => [...current, created]);
|
|
setFloorName("");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Etage konnte nicht erstellt werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleCreateRoom(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!projectId || !roomNumber.trim() || !roomName.trim()) {
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const created = await createRoom(projectId, {
|
|
floorId: roomFloorId || undefined,
|
|
roomNumber: roomNumber.trim(),
|
|
roomName: roomName.trim(),
|
|
});
|
|
setRooms((current) => [...current, created]);
|
|
setRoomNumber("");
|
|
setRoomName("");
|
|
setRoomFloorId("");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Raum konnte nicht erstellt werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleSaveProjectSettings() {
|
|
if (!projectId) {
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const updated = await updateProjectSettings(projectId, {
|
|
singlePhaseVoltageV: Number(singlePhaseVoltageV),
|
|
threePhaseVoltageV: Number(threePhaseVoltageV),
|
|
});
|
|
setProject(updated);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Projekteigenschaften konnten nicht gespeichert werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleCreateProjectDevice(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!projectId || !projectDeviceForm.name.trim()) {
|
|
return;
|
|
}
|
|
|
|
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),
|
|
demandFactor: Number(projectDeviceForm.demandFactor),
|
|
voltageV: toOptionalNumber(projectDeviceForm.voltageV),
|
|
phaseCount: projectDeviceForm.phaseCount === "3" ? 3 : 1,
|
|
powerFactor: toOptionalNumber(projectDeviceForm.powerFactor),
|
|
note: projectDeviceForm.note.trim() || undefined,
|
|
};
|
|
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const created = await createProjectDevice(projectId, payload);
|
|
setProjectDevices((current) => [...current, created]);
|
|
setProjectDeviceForm({
|
|
name: emptyProjectDevice.name,
|
|
displayName: emptyProjectDevice.displayName,
|
|
category: emptyProjectDevice.category ?? "",
|
|
quantity: String(emptyProjectDevice.quantity),
|
|
installedPowerPerUnitKw: String(emptyProjectDevice.installedPowerPerUnitKw),
|
|
demandFactor: String(emptyProjectDevice.demandFactor),
|
|
voltageV: String(emptyProjectDevice.voltageV ?? ""),
|
|
phaseCount: String(emptyProjectDevice.phaseCount ?? 1),
|
|
powerFactor: String(emptyProjectDevice.powerFactor ?? ""),
|
|
note: emptyProjectDevice.note ?? "",
|
|
});
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht erstellt werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleDeleteProjectDevice(projectDeviceId: string) {
|
|
if (!projectId) {
|
|
return;
|
|
}
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
await deleteProjectDevice(projectId, projectDeviceId);
|
|
setProjectDevices((current) => current.filter((item) => item.id !== projectDeviceId));
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht gelöscht werden.");
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function handleQuickUpdateProjectDevice(
|
|
device: ProjectDeviceDto,
|
|
key: "name" | "displayName" | "category",
|
|
value: string
|
|
) {
|
|
if (!projectId) {
|
|
return;
|
|
}
|
|
|
|
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,
|
|
demandFactor: device.demandFactor,
|
|
voltageV: device.voltageV ?? undefined,
|
|
phaseCount: device.phaseCount ?? undefined,
|
|
powerFactor: device.powerFactor ?? undefined,
|
|
note: device.note ?? undefined,
|
|
};
|
|
|
|
try {
|
|
const updated = await updateProjectDevice(projectId, device.id, payload);
|
|
setProjectDevices((current) => current.map((item) => (item.id === device.id ? updated : item)));
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Projektgerät konnte nicht aktualisiert werden.");
|
|
}
|
|
}
|
|
|
|
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">
|
|
<div>
|
|
<h1 className="h3 mb-1">{project?.name ?? "Projekt"}</h1>
|
|
<p className="text-secondary mb-0">Verteilerübersicht und Einstieg in die Stromkreislisten</p>
|
|
</div>
|
|
<Link className="btn btn-outline-secondary" href="/projects">
|
|
Zur Projektseite
|
|
</Link>
|
|
</div>
|
|
|
|
{error ? <div className="alert alert-warning">{error}</div> : null}
|
|
|
|
<div className="row g-4">
|
|
<section className="col-12">
|
|
<div className="card shadow-sm">
|
|
<div className="card-header">Projekteigenschaften</div>
|
|
<div className="card-body">
|
|
<div className="row g-3 align-items-end">
|
|
<div className="col-12 col-md-4">
|
|
<label className="form-label">Standardspannung 1-phasig [V]</label>
|
|
<input
|
|
className="form-control"
|
|
type="number"
|
|
min="1"
|
|
value={singlePhaseVoltageV}
|
|
onChange={(event) => setSinglePhaseVoltageV(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="col-12 col-md-4">
|
|
<label className="form-label">Standardspannung 3-phasig [V]</label>
|
|
<input
|
|
className="form-control"
|
|
type="number"
|
|
min="1"
|
|
value={threePhaseVoltageV}
|
|
onChange={(event) => setThreePhaseVoltageV(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="col-12 col-md-4">
|
|
<button
|
|
className="btn btn-primary w-100"
|
|
type="button"
|
|
onClick={handleSaveProjectSettings}
|
|
disabled={isSaving}
|
|
>
|
|
Projekteigenschaften speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="col-12 col-lg-4">
|
|
<div className="card shadow-sm">
|
|
<div className="card-header">Neue Verteilung</div>
|
|
<div className="card-body">
|
|
<form className="vstack gap-3" onSubmit={handleCreateBoard}>
|
|
<input
|
|
className="form-control"
|
|
placeholder="z. B. UV-01"
|
|
value={boardName}
|
|
onChange={(event) => setBoardName(event.target.value)}
|
|
/>
|
|
<button className="btn btn-primary" type="submit" disabled={isSaving}>
|
|
Verteilung erstellen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="col-12 col-lg-8">
|
|
<div className="card shadow-sm">
|
|
<div className="card-header d-flex justify-content-between">
|
|
<span>Alle Verteilungen</span>
|
|
<span className="badge text-bg-secondary">{boardCount}</span>
|
|
</div>
|
|
<div className="table-responsive">
|
|
<table className="table table-hover align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Verteilung</th>
|
|
<th className="text-end">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{boards.map((board) => (
|
|
<tr key={board.id}>
|
|
<td>{board.name}</td>
|
|
<td className="text-end">
|
|
<Link
|
|
className="btn btn-sm btn-outline-primary"
|
|
href={`/projects/${projectId}/circuit-lists?boardId=${board.id}`}
|
|
>
|
|
Stromkreisliste öffnen
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
{!boards.length ? (
|
|
<tr>
|
|
<td colSpan={2} className="text-center text-secondary py-4">
|
|
Noch keine Verteilungen vorhanden.
|
|
</td>
|
|
</tr>
|
|
) : null}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="col-12 col-xl-4">
|
|
<div className="card shadow-sm h-100">
|
|
<div className="card-header d-flex justify-content-between">
|
|
<span>Etagen</span>
|
|
<span className="badge text-bg-secondary">{floorCount}</span>
|
|
</div>
|
|
<div className="card-body border-bottom">
|
|
<form className="vstack gap-2" onSubmit={handleCreateFloor}>
|
|
<input
|
|
className="form-control"
|
|
placeholder="z. B. EG"
|
|
value={floorName}
|
|
onChange={(event) => setFloorName(event.target.value)}
|
|
/>
|
|
<button className="btn btn-primary" type="submit" disabled={isSaving}>
|
|
Etage hinzufügen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<ul className="list-group list-group-flush">
|
|
{floors.map((floor) => (
|
|
<li className="list-group-item" key={floor.id}>
|
|
{floor.name}
|
|
</li>
|
|
))}
|
|
{!floors.length ? (
|
|
<li className="list-group-item text-secondary">Noch keine Etagen vorhanden.</li>
|
|
) : null}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="col-12 col-xl-8">
|
|
<div className="card shadow-sm">
|
|
<div className="card-header d-flex justify-content-between">
|
|
<span>Räume</span>
|
|
<span className="badge text-bg-secondary">{roomCount}</span>
|
|
</div>
|
|
<div className="card-body border-bottom">
|
|
<form className="row g-2" onSubmit={handleCreateRoom}>
|
|
<div className="col-12 col-lg-3">
|
|
<input
|
|
className="form-control"
|
|
placeholder="Raumnummer"
|
|
value={roomNumber}
|
|
onChange={(event) => setRoomNumber(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="col-12 col-lg-4">
|
|
<input
|
|
className="form-control"
|
|
placeholder="Raumname"
|
|
value={roomName}
|
|
onChange={(event) => setRoomName(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="col-12 col-lg-3">
|
|
<select
|
|
className="form-select"
|
|
value={roomFloorId}
|
|
onChange={(event) => setRoomFloorId(event.target.value)}
|
|
>
|
|
<option value="">Ohne Etage</option>
|
|
{floors.map((floor) => (
|
|
<option key={floor.id} value={floor.id}>
|
|
{floor.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="col-12 col-lg-2">
|
|
<button className="btn btn-primary w-100" type="submit" disabled={isSaving}>
|
|
Raum anlegen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div className="table-responsive">
|
|
<table className="table table-hover align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Raumnummer</th>
|
|
<th>Raumname</th>
|
|
<th>Etage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rooms.map((room) => (
|
|
<tr key={room.id}>
|
|
<td>{room.roomNumber}</td>
|
|
<td>{room.roomName}</td>
|
|
<td>{room.floorId ? floorById.get(room.floorId)?.name ?? "-" : "-"}</td>
|
|
</tr>
|
|
))}
|
|
{!rooms.length ? (
|
|
<tr>
|
|
<td colSpan={3} className="text-center text-secondary py-4">
|
|
Noch keine Räume vorhanden.
|
|
</td>
|
|
</tr>
|
|
) : null}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section className="card shadow-sm mt-4">
|
|
<div className="card-header d-flex justify-content-between">
|
|
<span>Projektgeräte / Verbraucher</span>
|
|
<span className="badge text-bg-secondary">{projectDeviceCount}</span>
|
|
</div>
|
|
<div className="card-body border-bottom">
|
|
<form className="row g-2" onSubmit={handleCreateProjectDevice}>
|
|
<div className="col-12 col-md-3">
|
|
<input
|
|
className="form-control"
|
|
placeholder="Interner Name"
|
|
value={projectDeviceForm.name}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({ ...current, name: event.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<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"
|
|
value={projectDeviceForm.category}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({ ...current, category: event.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="col-6 col-md-1">
|
|
<input
|
|
className="form-control"
|
|
type="number"
|
|
min="0"
|
|
value={projectDeviceForm.quantity}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({ ...current, quantity: event.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="col-6 col-md-1">
|
|
<input
|
|
className="form-control"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
value={projectDeviceForm.installedPowerPerUnitKw}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({
|
|
...current,
|
|
installedPowerPerUnitKw: event.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="col-6 col-md-1">
|
|
<input
|
|
className="form-control"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
max="1"
|
|
value={projectDeviceForm.demandFactor}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({ ...current, demandFactor: event.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="col-6 col-md-1">
|
|
<select
|
|
className="form-select"
|
|
value={projectDeviceForm.phaseCount}
|
|
onChange={(event) =>
|
|
setProjectDeviceForm((current) => ({ ...current, phaseCount: event.target.value }))
|
|
}
|
|
>
|
|
<option value="1">1-ph</option>
|
|
<option value="3">3-ph</option>
|
|
</select>
|
|
</div>
|
|
<div className="col-6 col-md-2">
|
|
<button className="btn btn-primary w-100" type="submit" disabled={isSaving}>
|
|
Gerät anlegen
|
|
</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>Interner Name</th>
|
|
<th>Anzeigename</th>
|
|
<th>Kategorie</th>
|
|
<th>Anzahl</th>
|
|
<th>Leistung je Stück [kW]</th>
|
|
<th>GZF</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{projectDevices.map((device) => (
|
|
<tr key={device.id}>
|
|
<td>
|
|
<input
|
|
className="form-control form-control-sm"
|
|
defaultValue={device.name}
|
|
onBlur={(event) =>
|
|
event.target.value !== device.name
|
|
? handleQuickUpdateProjectDevice(device, "name", event.target.value)
|
|
: undefined
|
|
}
|
|
/>
|
|
</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"
|
|
defaultValue={device.category ?? ""}
|
|
onBlur={(event) =>
|
|
event.target.value !== (device.category ?? "")
|
|
? handleQuickUpdateProjectDevice(device, "category", event.target.value)
|
|
: undefined
|
|
}
|
|
/>
|
|
</td>
|
|
<td>{device.quantity}</td>
|
|
<td>{device.installedPowerPerUnitKw}</td>
|
|
<td>{device.demandFactor}</td>
|
|
<td>
|
|
<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={7} className="text-center text-secondary py-4">
|
|
Noch keine Projektgeräte vorhanden.
|
|
</td>
|
|
</tr>
|
|
) : null}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="mt-4">
|
|
<Link className="btn btn-primary" href={`/projects/${projectId}/circuit-lists`}>
|
|
3 parallele Stromkreislisten öffnen
|
|
</Link>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
|