Compact circuit editor header, pin grid headers, scope sidebar to project

The circuit editor page now fills the viewport so only the grid scrolls
and its sticky column headers stay visible. The large page title is
replaced by a compact title bar with back link, board number/name, the
feeding reference, the toolbar and a condensed power summary.

Inside a project the sidebar shows only the active project with its
sections, an expandable board list ordered by logical feed (current board
highlighted), Revit and the print view. The feed ordering is shared with
the print view through orderDistributionBoardsByFeed.

Refs #26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-10-30 07:37:41 +01:00
parent 898be43ebc
commit 07bc52a134
7 changed files with 444 additions and 156 deletions

View file

@ -1,10 +1,11 @@
"use client";
import Link from "next/link";
import { DragEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
import { DragEvent, KeyboardEvent, type ReactNode, useEffect, useMemo, useRef, useState } from "react";
import {
ArrowDown,
ArrowDown01,
ArrowLeft,
ArrowUp,
Calculator,
Check,
@ -27,6 +28,7 @@ import {
Undo2,
} from "lucide-react";
import { IconButton, IconButtonSeparator } from "./icon-button";
import { IconLink } from "./icon-link";
import {
inferProjectDeviceSectionKey,
isProjectDevicePlacementValid,
@ -652,6 +654,66 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
);
}
// Compact title block at the start of the toolbar: back link, board
// number/name and the optional feeding reference in one line.
function renderEditorHeading() {
const feedingReference = data?.feedingReference;
return (
<div className="editor-heading">
<IconLink
href={`/projects/${projectId}`}
icon={ArrowLeft}
label="Zurück zum Projekt"
/>
<div className="editor-heading-text">
<span className="editor-heading-kicker">
Stromkreisliste
{feedingReference ? (
<>
{" · gespeist von "}
<Link
href={`/projects/${projectId}/circuit-lists/${feedingReference.circuitListId}/tree-edit`}
title={
feedingReference.circuitId
? undefined
: "Speisender Stromkreis noch nicht festgelegt"
}
>
{feedingReference.distributionBoardNumber ??
feedingReference.distributionBoardName}
{feedingReference.circuitEquipmentIdentifier ?? ""}
</Link>
{feedingReference.circuitId ? null : " (Stromkreis offen)"}
</>
) : null}
</span>
<h1 className="editor-heading-title">
{data ? (
<>
{data.distributionBoardNumber ? (
<strong>{data.distributionBoardNumber}</strong>
) : null}
<span>{data.distributionBoardName}</span>
</>
) : (
"Verteiler"
)}
</h1>
</div>
</div>
);
}
// Loading/error states keep the title bar so the back link is always there.
function renderEditorStatus(notice: ReactNode) {
return (
<div className="tree-editor-shell">
<div className="editor-toolbar">{renderEditorHeading()}</div>
{notice}
</div>
);
}
function renderDistributionPowerSummary() {
if (!data) {
return null;
@ -661,8 +723,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
className="distribution-power-summary"
aria-label="Leistungszusammenfassung des Verteilers"
>
<div>
<span>Gesamtleistung Verteiler</span>
<div title="Gesamtleistung Verteiler">
<span>Gesamtleistung</span>
<strong>
{formatValue(
data.distributionBoardTotalPower,
@ -671,8 +733,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
kW
</strong>
</div>
<div>
<span>Verteilerweiter Gleichzeitigkeitsfaktor</span>
<div title="Verteilerweiter Gleichzeitigkeitsfaktor">
<span>GZF Verteiler</span>
<strong>
{formatValue(
data.distributionBoardSimultaneityFactor,
@ -680,11 +742,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
)}
</strong>
</div>
<div>
<span>
Gesamtleistung Verteiler unter Berücksichtigung des
Gleichzeitigkeitsfaktors
</span>
<div title="Gesamtleistung Verteiler unter Berücksichtigung des Gleichzeitigkeitsfaktors">
<span>Leistung mit GZF</span>
<strong>
{formatValue(
data.distributionBoardTotalPowerWithSimultaneityFactor,
@ -3217,18 +3276,24 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
if (isLoading && !data) {
return <div className="notice info">Stromkreislisteneditor wird geladen …</div>;
return renderEditorStatus(
<div className="notice info">Stromkreislisteneditor wird geladen …</div>
);
}
if (error && !data) {
return <div className="notice error">{error}</div>;
return renderEditorStatus(<div className="notice error">{error}</div>);
}
if (!data || data.sections.length === 0) {
return <div className="notice muted">Keine Bereiche oder Stromkreise vorhanden.</div>;
return renderEditorStatus(
<div className="notice muted">Keine Bereiche oder Stromkreise vorhanden.</div>
);
}
if (filteredSortedSections.length === 0) {
return (
<div className="tree-editor-shell">
<div className="editor-toolbar">
{renderEditorHeading()}
<IconButtonSeparator />
<IconButton
disabled={!historyState || historyState.undoDepth === 0 || historyBusy || isSaving}
icon={Undo2}
@ -3258,10 +3323,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
onClick={clearSortAndFilters}
showLabel
/>
{renderDistributionPowerSummary()}
{renderColumnSettingsMenu("col-empty")}
</div>
{renderActiveViewSummary()}
{renderDistributionPowerSummary()}
{error ? (
<div className="notice error editor-error-notice" role="alert">
<span>{error}</span>
@ -3396,24 +3461,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
threePhaseVoltageV={data.threePhaseVoltageV}
/>
) : null}
{data.feedingReference ? (
<div className="alert alert-info py-2" role="status">
Gespeist von{" "}
<Link
href={`/projects/${projectId}/circuit-lists/${
data.feedingReference.circuitListId
}/tree-edit`}
>
{data.feedingReference.distributionBoardNumber ??
data.feedingReference.distributionBoardName}
{data.feedingReference.circuitEquipmentIdentifier ?? ""}
</Link>
{data.feedingReference.circuitId
? null
: " (speisender Stromkreis noch nicht festgelegt)"}
</div>
) : null}
<div className="editor-toolbar">
{renderEditorHeading()}
<IconButtonSeparator />
<IconButton
disabled={!historyState || historyState.undoDepth === 0 || historyBusy || isSaving}
icon={Undo2}
@ -3507,10 +3557,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
showLabel
/>
{renderDistributionPowerSummary()}
{renderColumnSettingsMenu("col")}
</div>
{renderActiveViewSummary()}
{renderDistributionPowerSummary()}
{hasActiveSortOrFilter ? (
<div className="notice muted">Sortierung und Filter verändern nur die Ansicht. Die Neunummerierung ist währenddessen deaktiviert.</div>
) : null}