"use client"; import Link from "next/link"; import { FormEvent, useEffect, useState } from "react"; import { createGlobalDevice, createProject, deleteGlobalDevice, importProjectTransferAsNew, listGlobalDevices, updateGlobalDevice, } from "../../frontend/utils/api"; import type { CreateGlobalDeviceInput, GlobalDeviceDto, } from "../../frontend/types"; import { GzfPickerModal } from "../../frontend/components/gzf-picker-modal"; const PROJECTS_CHANGED_EVENT = "leistungsbilanz:projects-changed"; const emptyGlobalDevice: CreateGlobalDeviceInput = { name: "", displayName: "", category: "", quantity: 1, installedPowerPerUnitKw: 0.1, demandFactor: 1, phaseCount: 1, powerFactor: 1, note: "", }; function toOptionalNumber(value: string) { const trimmed = value.trim(); if (!trimmed) { return undefined; } return Number(trimmed); } export default function ProjectsPage() { const [globalDevices, setGlobalDevices] = useState([]); const [projectName, setProjectName] = useState(""); const [globalDeviceForm, setGlobalDeviceForm] = useState>({ name: "", displayName: "", category: "", quantity: "1", installedPowerPerUnitKw: "0.1", demandFactor: "1", phaseCount: "1", powerFactor: "1", note: "", }); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const [projectImport, setProjectImport] = useState(null); const [projectImportFilename, setProjectImportFilename] = useState(""); const [projectImportInputKey, setProjectImportInputKey] = useState(0); const [projectImportFileError, setProjectImportFileError] = useState< string | null >(null); const [importedProject, setImportedProject] = useState<{ projectId: string; name: string; } | null>(null); const [createdProject, setCreatedProject] = useState<{ id: string; name: string; } | null>(null); const [isGzfPickerOpen, setIsGzfPickerOpen] = useState(false); async function loadData() { setError(null); setGlobalDevices(await listGlobalDevices()); } useEffect(() => { loadData().catch((err: unknown) => setError(err instanceof Error ? err.message : "Daten konnten nicht geladen werden.") ); }, []); async function handleCreateProject(event: FormEvent) { event.preventDefault(); if (!projectName.trim()) { return; } setIsSaving(true); setError(null); setCreatedProject(null); try { const created = await createProject(projectName.trim()); setCreatedProject({ id: created.id, name: created.name }); setProjectName(""); window.dispatchEvent(new Event(PROJECTS_CHANGED_EVENT)); } catch (err) { setError(err instanceof Error ? err.message : "Projekt konnte nicht erstellt werden."); } finally { setIsSaving(false); } } async function handleProjectImportFile(file: File | undefined) { setProjectImport(null); setProjectImportFilename(""); setProjectImportFileError(null); setImportedProject(null); if (!file) { return; } try { setProjectImport(JSON.parse(await file.text()) as unknown); setProjectImportFilename(file.name); } catch { setProjectImportFileError( "Die ausgewählte Datei enthält kein gültiges JSON." ); } } async function handleImportProject() { if (projectImport === null) { return; } setIsSaving(true); setError(null); setImportedProject(null); try { const imported = await importProjectTransferAsNew(projectImport); setImportedProject(imported); setProjectImport(null); setProjectImportFilename(""); setProjectImportInputKey((current) => current + 1); window.dispatchEvent(new Event(PROJECTS_CHANGED_EVENT)); } catch (err) { setError( err instanceof Error ? err.message : "Projektdatei konnte nicht importiert werden." ); } finally { setIsSaving(false); } } async function handleCreateGlobalDevice(event: FormEvent) { event.preventDefault(); if (!globalDeviceForm.name.trim()) { return; } const payload: CreateGlobalDeviceInput = { name: globalDeviceForm.name.trim(), displayName: globalDeviceForm.displayName.trim() || globalDeviceForm.name.trim(), category: globalDeviceForm.category.trim() || undefined, quantity: Number(globalDeviceForm.quantity), installedPowerPerUnitKw: Number(globalDeviceForm.installedPowerPerUnitKw), demandFactor: Number(globalDeviceForm.demandFactor), phaseCount: globalDeviceForm.phaseCount === "3" ? 3 : 1, powerFactor: toOptionalNumber(globalDeviceForm.powerFactor), note: globalDeviceForm.note.trim() || undefined, }; setIsSaving(true); setError(null); try { const created = await createGlobalDevice(payload); setGlobalDevices((current) => [...current, created]); setGlobalDeviceForm({ name: emptyGlobalDevice.name, displayName: emptyGlobalDevice.displayName, category: emptyGlobalDevice.category ?? "", quantity: String(emptyGlobalDevice.quantity), installedPowerPerUnitKw: String(emptyGlobalDevice.installedPowerPerUnitKw), demandFactor: String(emptyGlobalDevice.demandFactor), phaseCount: String(emptyGlobalDevice.phaseCount ?? 1), powerFactor: String(emptyGlobalDevice.powerFactor ?? ""), note: emptyGlobalDevice.note ?? "", }); } catch (err) { setError(err instanceof Error ? err.message : "Globales Gerät konnte nicht erstellt werden."); } finally { setIsSaving(false); } } async function handleDeleteGlobalDevice(globalDeviceId: string) { setIsSaving(true); setError(null); try { await deleteGlobalDevice(globalDeviceId); setGlobalDevices((current) => current.filter((item) => item.id !== globalDeviceId)); } catch (err) { setError(err instanceof Error ? err.message : "Globales Gerät konnte nicht gelöscht werden."); } finally { setIsSaving(false); } } async function handleQuickUpdateGlobalDevice( device: GlobalDeviceDto, key: "name" | "displayName" | "category" | "phaseCount", value: string ) { const payload: CreateGlobalDeviceInput = { 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, phaseCount: key === "phaseCount" ? value === "3" ? 3 : 1 : device.phaseCount ?? 1, powerFactor: device.powerFactor ?? undefined, note: device.note ?? undefined, }; try { const updated = await updateGlobalDevice(device.id, payload); setGlobalDevices((current) => current.map((item) => (item.id === device.id ? updated : item))); } catch (err) { setError(err instanceof Error ? err.message : "Globales Gerät konnte nicht aktualisiert werden."); } } return (
Leistungsbilanz

Projekte

Projektübersicht und globale Geräteverwaltung

{error ?
{error}
: null}
Neues Projekt
setProjectName(event.target.value)} placeholder="z. B. Neubau Schule" />
{createdProject ? (
„{createdProject.name}“ wurde angelegt.
Projekt öffnen
) : null}
Projekt importieren

Eine exportierte JSON-Projektdatei wird als eigenständige Kopie mit neuen internen IDs angelegt.

void handleProjectImportFile(event.target.files?.[0]) } type="file" /> {projectImportFilename ? (
{projectImportFilename} ist bereit.
) : null} {projectImportFileError ? (
{projectImportFileError}
) : null} {importedProject ? (
„{importedProject.name}“ wurde angelegt.
Importiertes Projekt öffnen
) : null}
Globale Geräte / Verbraucher
setGlobalDeviceForm((current) => ({ ...current, name: event.target.value }))} />
setGlobalDeviceForm((current) => ({ ...current, displayName: event.target.value }))} />
setGlobalDeviceForm((current) => ({ ...current, category: event.target.value })) } />
setGlobalDeviceForm((current) => ({ ...current, quantity: event.target.value })) } />
setGlobalDeviceForm((current) => ({ ...current, installedPowerPerUnitKw: event.target.value, })) } />
setGlobalDeviceForm((current) => ({ ...current, demandFactor: event.target.value })) } />
{globalDevices.map((device) => ( ))} {!globalDevices.length ? ( ) : null}
Interner Name Anzeigename Kategorie Anzahl Leistung je Stück [kW] GZF Phasenart Aktion
event.target.value !== device.name ? handleQuickUpdateGlobalDevice(device, "name", event.target.value) : undefined } /> event.target.value !== device.displayName ? handleQuickUpdateGlobalDevice(device, "displayName", event.target.value) : undefined } /> event.target.value !== (device.category ?? "") ? handleQuickUpdateGlobalDevice(device, "category", event.target.value) : undefined } /> {device.quantity} {device.installedPowerPerUnitKw} {device.demandFactor}
Noch keine globalen Geräte vorhanden.
{isGzfPickerOpen ? ( setIsGzfPickerOpen(false)} onSelect={(value) => { setGlobalDeviceForm((current) => ({ ...current, demandFactor: String(value) })); setIsGzfPickerOpen(false); }} /> ) : null}
); }