Personen-Belegung pro Raum + Stempel-Aggregation

Architekten-Workflow: bei Schulen/Buero/Versammlungsstaetten muss die
Personenzahl pro Raum erfasst werden (SIA: m²/Person als Indikator).

Backend:
- UserString dossier_raum_personen (int)
- _attach_meta + _read_meta + state-emit
- _update_wall raum-Branch akzeptiert "personen" im Patch
- compute_sia_bilanz aggregiert personen-Summe ueber Scope
- Stempel: neue Show-Flag stempel_show_personen (default false) +
  Bilanz-Renderer-Zeile "N Personen"
- Stempel-Stil-Field showPersonen mit dabei

Frontend (RaumProperties):
- Personen-Input (number, min 0) zwischen Funktion + Layout-Builder
- Nur sichtbar bei normalen Raeumen (nicht GF/AGF)
- Live-Anzeige "m²/Person" Suffix wenn area + personen > 0
  → User sieht sofort ob die Belegung sinnvoll ist (SIA-Vergleich)

Frontend (StempelProperties):
- Neuer Show-Toggle "Personen" (default off)
- Live-Vorschau zeigt Personen-Summe wenn aktiv + > 0
- _OFF_BY_DEFAULT Set generalisiert Default-Handling (showCount, showPersonen)
This commit is contained in:
2026-05-27 01:08:58 +02:00
parent 1c3b0f3919
commit e2d66a5d64
2 changed files with 84 additions and 11 deletions
+47 -5
View File
@@ -937,6 +937,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
const [name, setName] = useState(raum.name || 'Raum')
const [nummer, setNummer] = useState(raum.nummer || '')
const [funktion, setFunktion] = useState(raum.funktion || '')
const [personenStr, setPersonenStr] = useState(String(raum.personen || 0))
// Texthoehe (raum.txtH) + Ausrichtung (raum.align) werden via Oberleiste
// gesetzt — kein Local-State noetig, Stil-Speichern liest direkt aus raum.
const txtModus = raum.txtModus || 'fix'
@@ -948,7 +949,8 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
setName(raum.name || 'Raum')
setNummer(raum.nummer || '')
setFunktion(raum.funktion || '')
}, [raum.id, raum.name, raum.nummer, raum.funktion])
setPersonenStr(String(raum.personen || 0))
}, [raum.id, raum.name, raum.nummer, raum.funktion, raum.personen])
// Aktueller Wert von raum_fuellung: "" | "Solid" | "Hatch1" | … | "ByLayer"
const fuell = raum.fuellung || ''
@@ -1133,6 +1135,33 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
</div>
)}
{/* Personen-Belegung (SIA: Schulen, Bueros, Versammlungen) —
aggregiert im Bilanz-Stempel als "Personen"-Zeile wenn aktiv. */}
{!isFlaeche && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Personen</span>
<input type="number" min="0" step="1" value={personenStr}
onChange={(e) => setPersonenStr(e.target.value)}
onBlur={() => {
const n = parseInt(personenStr, 10)
const v = Number.isFinite(n) && n >= 0 ? n : 0
if (v !== (raum.personen || 0)) onUpdate({ personen: v })
setPersonenStr(String(v))
}}
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
placeholder="0"
title="Anzahl Personen — wird im Bilanz-Stempel summiert (m²/Person nach SIA)"
style={{ width: 64, fontSize: 11, textAlign: 'right',
fontFamily: 'DM Mono, monospace' }} />
{raum.area > 0 && raum.personen > 0 && (
<span style={{ fontSize: 9, color: 'var(--text-muted)',
fontFamily: 'DM Mono, monospace' }}>
{(raum.area / raum.personen).toFixed(1)} /Person
</span>
)}
</div>
)}
{/* Stempel-Layout — Drag-and-Drop. Jede Row ist eine Textzeile,
Felder innerhalb einer Row landen in derselben Zeile. Drag
zwischen Rows um umzuordnen. Klick auf Field oben fügt es in
@@ -1185,7 +1214,8 @@ function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelet
setHeaderDraft(stempel.header || 'Nutzflächen')
}, [stempel.id, stempel.header])
// Show-Flags vom Backend (default true ausser showCount)
// Show-Flags vom Backend default true ausser folgende:
const _OFF_BY_DEFAULT = new Set(['showCount', 'showPersonen'])
const _show = (k, def = true) =>
(stempel[k] !== undefined ? !!stempel[k] : def)
@@ -1205,6 +1235,7 @@ function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelet
showGf: _show('showGf'),
showAgf: _show('showAgf'),
showCount: _show('showCount', false),
showPersonen: _show('showPersonen', false),
showSeparators: _show('showSeparators'),
font: stempel.font || '',
bold: !!stempel.bold,
@@ -1239,9 +1270,9 @@ function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelet
const ShowTog = ({ field, label, hint }) => (
<BarToggle label={label}
icon={_show(field, field === 'showCount' ? false : true) ? 'check_box' : 'check_box_outline_blank'}
active={_show(field, field === 'showCount' ? false : true)}
onClick={() => onUpdate({ [field]: !_show(field, field === 'showCount' ? false : true) })}
icon={_show(field, _OFF_BY_DEFAULT.has(field) ? false : true) ? 'check_box' : 'check_box_outline_blank'}
active={_show(field, _OFF_BY_DEFAULT.has(field) ? false : true)}
onClick={() => onUpdate({ [field]: !_show(field, _OFF_BY_DEFAULT.has(field) ? false : true) })}
title={hint || label} />
)
@@ -1327,6 +1358,7 @@ function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelet
<ShowTog field="showGf" label="GF" />
<ShowTog field="showAgf" label="AGF" />
<ShowTog field="showCount" label="Anzahl" hint="Anzahl klassifizierter Räume" />
<ShowTog field="showPersonen" label="Personen" hint="Summe der Personen-Belegung (SIA)" />
<ShowTog field="showSeparators" label="Linien" hint="Trennlinien zwischen Sections" />
</div>
</div>
@@ -1357,6 +1389,16 @@ function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelet
<span>{r.val.toFixed(1)} </span>
</div>
))}
{_show('showPersonen', false) && (bilanz.personen || 0) > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between',
padding: '2px 0',
borderTop: '1px dashed var(--border-light)',
marginTop: 2 }}
title="Summe Personen über alle Räume im Scope">
<span>Personen</span>
<span>{bilanz.personen}</span>
</div>
)}
</div>
<div style={{