Translate remaining frontend

This commit is contained in:
2026-07-22 23:25:53 +02:00
parent 7257deacdc
commit e4fd7e0df6
11 changed files with 79 additions and 53 deletions
@@ -25,7 +25,7 @@ export default function CircuitTreeEditPage() {
Zum Projekt
</Link>
<Link href={`/projects/${params.projectId}/circuit-lists`} className="btn btn-outline-secondary btn-sm">
Legacy-Editor
Bisherige Listenansicht
</Link>
</div>
</div>
@@ -13,22 +13,22 @@ export default function CircuitTreePreviewPage() {
<main className="container py-4">
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
<h1 className="h4 mb-1">Circuit Tree Preview</h1>
<h1 className="h4 mb-1">Vorschau der Stromkreisstruktur</h1>
<p className="text-secondary mb-0">
Read-only preview of section blocks, circuits and device rows.
Schreibgeschützte Vorschau der Bereiche, Stromkreise und Gerätezeilen.
</p>
</div>
<Link
href={`/projects/${projectId}/circuit-lists/${circuitListId}/tree-edit`}
className="btn btn-outline-primary btn-sm"
>
Open editor
Editor öffnen
</Link>
<Link
href={`/projects/${projectId}/circuit-lists`}
className="btn btn-outline-secondary btn-sm"
>
Back to legacy editor
Zurück zur bisherigen Listenansicht
</Link>
</div>
@@ -128,7 +128,7 @@ const allColumns: Array<{ key: ColumnKey; label: string }> = [
{ key: "description", label: "Beschreibung" },
{ key: "name", label: "Verbraucher" },
{ key: "projectDevice", label: "Projektgerät" },
{ key: "deviceLink", label: "Link" },
{ key: "deviceLink", label: "Verknüpfung" },
{ key: "room", label: "Raum" },
{ key: "floor", label: "Etage" },
{ key: "category", label: "Kategorie" },
@@ -646,7 +646,7 @@ export default function CircuitListsPage() {
await reloadConsumers();
setBulkEditForms((current) => ({ ...current, [slotIndex]: { ...defaultBulkEditState } }));
} catch (err) {
setError(err instanceof Error ? err.message : "Bulk-Änderung fehlgeschlagen.");
setError(err instanceof Error ? err.message : "Sammeländerung fehlgeschlagen.");
} finally {
setIsSaving(false);
}
+2 -2
View File
@@ -611,7 +611,7 @@ export default function ProjectDetailPage() {
className="btn btn-sm btn-outline-secondary"
href={`/projects/${projectId}/circuit-lists?boardId=${board.id}`}
>
Legacy
Bisherige Ansicht
</Link>
</div>
</td>
@@ -1084,7 +1084,7 @@ export default function ProjectDetailPage() {
{syncPreview.rows.length === 0 ? (
<tr>
<td colSpan={4} className="text-center text-secondary py-3">
Keine verknüpften Circuit-First-Gerätezeilen vorhanden.
Keine verknüpften Gerätezeilen im neuen Stromkreismodell vorhanden.
</td>
</tr>
) : null}
@@ -142,12 +142,22 @@ function normalizeUiError(err: unknown): string {
if (message.includes("Duplicate equipmentIdentifier")) {
return "Das Betriebsmittelkennzeichen ist in dieser Stromkreisliste bereits vorhanden.";
}
if (message.includes("Invalid number")) {
if (message.includes("Invalid number") || message.includes("Ungültiger Zahlenwert")) {
return "Bitte einen gültigen Zahlenwert eingeben.";
}
return message;
}
function formatPhaseTypeLabel(value: string): string {
if (value === "three_phase") {
return "Dreiphasig";
}
if (value === "single_phase") {
return "Einphasig";
}
return value;
}
function isPrintableKey(event: KeyboardEvent<HTMLElement>) {
return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
}
@@ -2587,7 +2597,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
>
<strong>{device.displayName || device.name}</strong>
<span>Name: {device.name}</span>
<span>Phase: {device.phaseType}</span>
<span>Phasenart: {formatPhaseTypeLabel(device.phaseType)}</span>
<span>Anzahl: {device.quantity}</span>
<span>Leistung/Gerät: {device.powerPerUnit}</span>
<span>Gleichzeitigkeit: {device.simultaneityFactor}</span>
@@ -15,12 +15,22 @@ function formatNumber(value: number | undefined, digits = 2) {
}).format(value);
}
function formatPhaseType(value: string | undefined) {
if (value === "three_phase") {
return "Dreiphasig";
}
if (value === "single_phase") {
return "Einphasig";
}
return value ?? "-";
}
function renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) {
if (circuit.displayName?.trim()) {
return circuit.displayName;
}
if (circuit.deviceRows.length > 1) {
return `${circuit.deviceRows.length} devices`;
return `${circuit.deviceRows.length} Geräte`;
}
return "Reserve";
}
@@ -37,13 +47,13 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st
getCircuitTree(projectId, circuitListId)
.then(setData)
.catch((err: unknown) =>
setError(err instanceof Error ? err.message : "Circuit tree could not be loaded.")
setError(err instanceof Error ? err.message : "Die Stromkreisstruktur konnte nicht geladen werden.")
)
.finally(() => setIsLoading(false));
}, [projectId, circuitListId]);
if (isLoading) {
return <div className="alert alert-info">Loading circuit tree...</div>;
return <div className="alert alert-info">Stromkreisstruktur wird geladen </div>;
}
if (error) {
@@ -51,12 +61,12 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st
}
if (!data || !data.sections.length) {
return <div className="alert alert-secondary">No sections or circuits available.</div>;
return <div className="alert alert-secondary">Keine Bereiche oder Stromkreise vorhanden.</div>;
}
const hasAnyCircuits = data.sections.some((section) => section.circuits.length > 0);
if (!hasAnyCircuits) {
return <div className="alert alert-secondary">Sections exist, but no circuits were found yet.</div>;
return <div className="alert alert-secondary">Bereiche sind vorhanden, enthalten aber noch keine Stromkreise.</div>;
}
return (
@@ -66,28 +76,28 @@ export function CircuitTreePreview(props: { projectId: string; circuitListId: st
<table className="table table-sm align-middle circuit-tree-table">
<thead>
<tr>
<th>Equipment identifier</th>
<th>Display name</th>
<th>Phase type</th>
<th>Connection kind</th>
<th>Cost group</th>
<th>Category</th>
<th>Level</th>
<th>Room number</th>
<th>Room name</th>
<th className="text-end">Quantity</th>
<th className="text-end">Power / unit</th>
<th className="text-end">Simultaneity</th>
<th className="text-end">cosPhi</th>
<th className="text-end">Row total</th>
<th className="text-end">Circuit total</th>
<th>Protection type</th>
<th className="text-end">Protection current</th>
<th>Protection characteristic</th>
<th>Cable type</th>
<th>Cable cross-section</th>
<th className="text-end">Cable length</th>
<th>Remark</th>
<th>Betriebsmittelkennzeichen</th>
<th>Anzeigename</th>
<th>Phasenart</th>
<th>Anschlussart</th>
<th>Kostengruppe</th>
<th>Kategorie</th>
<th>Ebene</th>
<th>Raumnummer</th>
<th>Raumname</th>
<th className="text-end">Anzahl</th>
<th className="text-end">Leistung / Gerät</th>
<th className="text-end">Gleichzeitigkeit</th>
<th className="text-end">cos φ</th>
<th className="text-end">Zeilensumme</th>
<th className="text-end">Stromkreissumme</th>
<th>Schutzart</th>
<th className="text-end">Bemessungsstrom</th>
<th>Charakteristik</th>
<th>Kabeltyp</th>
<th>Kabelquerschnitt</th>
<th className="text-end">Kabellänge</th>
<th>Bemerkung</th>
</tr>
</thead>
<tbody>
@@ -136,7 +146,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
<tr key={circuit.id} className={circuit.isReserve ? "reserve-row" : ""}>
<td>{circuit.equipmentIdentifier}</td>
<td>{row.displayName || row.name}</td>
<td>{row.phaseType ?? "-"}</td>
<td>{formatPhaseType(row.phaseType)}</td>
<td>{row.connectionKind ?? "-"}</td>
<td>{row.costGroup ?? "-"}</td>
<td>{row.category ?? "-"}</td>
@@ -179,7 +189,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
<tr key={row.id} className="device-row">
<td className="text-muted"> </td>
<td className="indented-cell">{row.displayName || row.name}</td>
<td>{row.phaseType ?? "-"}</td>
<td>{formatPhaseType(row.phaseType)}</td>
<td>{row.connectionKind ?? "-"}</td>
<td>{row.costGroup ?? "-"}</td>
<td>{row.category ?? "-"}</td>
@@ -206,7 +216,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
})}
<tr className="placeholder-row">
<td>-frei-</td>
<td colSpan={21}>free placeholder</td>
<td colSpan={21}>Freie Zeile</td>
</tr>
</>
);
@@ -321,7 +321,7 @@ export function PowerBalanceWorkspace() {
setEditConsumerForm(null);
}
} catch (err) {
setError(err instanceof Error ? err.message : "Verbraucher konnte nicht geloescht werden.");
setError(err instanceof Error ? err.message : "Verbraucher konnte nicht gelöscht werden.");
} finally {
setIsSaving(false);
}
@@ -424,7 +424,7 @@ export function PowerBalanceWorkspace() {
<form className="consumerForm" onSubmit={handleCreateConsumer}>
<label>
Verbraucher
<input value={consumerForm.name} onChange={(event) => updateConsumerForm("name", event.target.value)} placeholder="z. B. Steckdosen Buero" />
<input value={consumerForm.name} onChange={(event) => updateConsumerForm("name", event.target.value)} placeholder="z. B. Steckdosen Büro" />
</label>
<label>
Kategorie
@@ -435,7 +435,7 @@ export function PowerBalanceWorkspace() {
<input min="0" type="number" value={consumerForm.quantity} onChange={(event) => updateConsumerForm("quantity", event.target.value)} />
</label>
<label>
Leistung je Stueck [kW]
Leistung je Stück [kW]
<input min="0" step="0.01" type="number" value={consumerForm.installedPowerPerUnitKw} onChange={(event) => updateConsumerForm("installedPowerPerUnitKw", event.target.value)} />
</label>
<label>
@@ -463,7 +463,7 @@ export function PowerBalanceWorkspace() {
</label>
<button className="primaryButton" type="submit" disabled={!selectedProjectId || isSaving}>
<Plus size={17} />
Hinzufuegen
Hinzufügen
</button>
</form>
</section>
@@ -472,12 +472,12 @@ export function PowerBalanceWorkspace() {
<div className="tableHeader">
<div>
<p className="eyebrow">Aktuelles Projekt</p>
<h2>{selectedProject?.name || "Noch kein Projekt ausgewaehlt"}</h2>
<h2>{selectedProject?.name || "Noch kein Projekt ausgewählt"}</h2>
<p className="subline">{selectedBoard ? `Verteilung: ${selectedBoard.name}` : "Alle Verteilungen"}</p>
</div>
<div className="statusPill">
<Activity size={16} />
{isLoading ? "Laedt" : "Bereit"}
{isLoading ? "Lädt" : "Bereit"}
</div>
</div>
@@ -505,7 +505,7 @@ export function PowerBalanceWorkspace() {
{!totalsByBoard.length ? (
<tr>
<td colSpan={4} className="emptyState">
Noch keine Verbraucher fuer eine Summenbildung vorhanden.
Noch keine Verbraucher für eine Summenbildung vorhanden.
</td>
</tr>
) : null}
@@ -528,7 +528,7 @@ export function PowerBalanceWorkspace() {
<th>Verteilung</th>
<th>Kategorie</th>
<th>Anzahl</th>
<th>Leistung je Stueck [kW]</th>
<th>Leistung je Stück [kW]</th>
<th>Installierte Leistung [kW]</th>
<th>GZF</th>
<th>Berechnete Leistung [kW]</th>
@@ -642,7 +642,7 @@ export function PowerBalanceWorkspace() {
<Pencil size={14} />
</button>
)}
<button className="iconButton small danger" type="button" title="Loeschen" onClick={() => handleDeleteConsumer(consumer.id)} disabled={isSaving}>
<button className="iconButton small danger" type="button" title="Löschen" onClick={() => handleDeleteConsumer(consumer.id)} disabled={isSaving}>
<Trash2 size={14} />
</button>
</div>
+1 -1
View File
@@ -37,7 +37,7 @@ async function request<T>(url: string, init?: RequestInit): Promise<T> {
if (!response.ok) {
const details = await response.text();
throw new Error(details || `Request failed with ${response.status}`);
throw new Error(details || `Anfrage fehlgeschlagen (Status ${response.status})`);
}
if (response.status === 204) {
+1 -1
View File
@@ -184,7 +184,7 @@ export function parseNumeric(cellKey: CellKey, draft: string): number | undefine
}
const parsed = Number(trimmed);
if (Number.isNaN(parsed)) {
throw new Error(`Invalid number in ${cellKey}`);
throw new Error(`Ungültiger Zahlenwert in ${cellKey}`);
}
return parsed;
}