Stempel: Stile + Layout-Customisation (Header, Show-Toggles)

Bilanz-Stempel hat jetzt:
- Custom Header (Default "Nutzflächen", frei editierbar)
- Per-Stempel Show-Toggles: scope, hnf, nnf, nf, vf, ff, ngf, gf, agf,
  count (Anzahl Raeume), separators (Trennlinien)
- Stempel-Stile (Presets) per Doc — separate Storage von Raumstempel-
  Stilen (dossier_stempel_stile)

Backend (elemente.py):
- UserStrings: dossier_stempel_header + dossier_stempel_show_* (11 Flags)
  + dossier_stempel_stil_id
- _make_stempel_text erweitert: header_text, show_scope, show_separators,
  show_count, visibility-dict
- _format_bilanz_lines: visibility + show_separators + show_count Args
- load_stempel_stile / save_stempel_stile (eigener doc.Strings-Key)
- Bridge-Handler SAVE/DELETE/APPLY_STEMPEL_STIL (analog Raum-Stil-Pattern,
  inkl. Bulk-Apply via Selection + applyToIds-Mechanism wie SaveRaum-Stil)
- _sync_stempel_to_source spiegelt Font/Bold/Italic/TextHeight vom Live-
  TextEntity zur UserString-Storage (Oberleiste-Edits ueberleben Regen)
- _update_wall stempel-Branch um header + show-Flags erweitert

Frontend (StempelProperties):
- Stil-Picker-Dropdown analog Raum (Stil wählen / + speichern / 🗑 löschen)
- Header-Input (Inline-Text)
- 11 Show-Toggles als kompakte Grid (Check-Box-Stil)
- Live-Vorschau respektiert Visibility-Flags + bilanz.count
This commit is contained in:
2026-05-27 00:36:31 +02:00
parent ac7b2f2ee5
commit 975071c995
4 changed files with 528 additions and 46 deletions
+125 -15
View File
@@ -12,6 +12,7 @@ import {
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
saveOeffStyle, deleteOeffStyle,
saveRaumStil, deleteRaumStil, applyRaumStil,
saveStempelStil, deleteStempelStil, applyStempelStil,
listLibrary,
} from './lib/rhinoBridge'
@@ -512,7 +513,7 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile }) {
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile, stempelStile }) {
if (!selected) return null
const upd = (p) => updateElement(selected.id, p)
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
@@ -539,6 +540,7 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns,
onUpdate={upd} onDelete={del('Raum')} />
if (selected.kind === 'stempel')
return <StempelProperties stempel={selected} geschosse={geschosse}
stempelStile={stempelStile || []}
onUpdate={upd} onDelete={del('Stempel')} />
if (selected.kind === 'aussparung')
@@ -594,7 +596,8 @@ export default function ElementeApp() {
hatchPatterns={state.hatchPatterns}
fonts={state.fonts || []}
oeffStyles={state.oeffStyles || []}
raumStempelStile={state.raumStempelStile || []} />
raumStempelStile={state.raumStempelStile || []}
stempelStile={state.stempelStile || []} />
</div>
)}
<NeuesElementSection
@@ -1168,21 +1171,76 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
)
}
function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelete }) {
const scope = stempel.scope || 'total'
const bilanz = stempel.bilanz || {}
const stilList = stempelStile || []
const activeStilId = stempel.stilId || ''
const [headerDraft, setHeaderDraft] = useState(stempel.header || 'Nutzflächen')
useEffect(() => {
setHeaderDraft(stempel.header || 'Nutzflächen')
}, [stempel.id, stempel.header])
// Show-Flags vom Backend (default true ausser showCount)
const _show = (k, def = true) =>
(stempel[k] !== undefined ? !!stempel[k] : def)
const handleStilChange = (val) => {
if (val === '__save__') {
const n = (window.prompt('Name für neuen Stempel-Stil:', 'Stil') || '').trim()
if (!n) return
saveStempelStil('', n, {
header: stempel.header || 'Nutzflächen',
showScope: _show('showScope'),
showHnf: _show('showHnf'),
showNnf: _show('showNnf'),
showNf: _show('showNf'),
showVf: _show('showVf'),
showFf: _show('showFf'),
showNgf: _show('showNgf'),
showGf: _show('showGf'),
showAgf: _show('showAgf'),
showCount: _show('showCount', false),
showSeparators: _show('showSeparators'),
font: stempel.font || '',
bold: !!stempel.bold,
italic: !!stempel.italic,
txtH: stempel.txtH,
}, [stempel.id])
return
}
if (val === '__delete__') {
if (activeStilId &&
window.confirm(`Stil "${stilList.find(s => s.id === activeStilId)?.name}" löschen?`))
deleteStempelStil(activeStilId)
return
}
// Empty ids → Backend nutzt aktuell selektierte Stempel
if (val) applyStempelStil(val, [])
}
// Sichtbare Zeilen fuer die Live-Vorschau (nur die mit Werten + Visibility)
const rows = [
{ key: 'hnf', label: 'HNF', val: bilanz.hnf },
{ key: 'nnf', label: 'NNF', val: bilanz.nnf },
{ key: 'nf', label: 'NF', val: bilanz.nf, accent: true,
{ key: 'hnf', label: 'HNF', val: bilanz.hnf, on: _show('showHnf') },
{ key: 'nnf', label: 'NNF', val: bilanz.nnf, on: _show('showNnf') },
{ key: 'nf', label: 'NF', val: bilanz.nf, on: _show('showNf'), accent: true,
hint: 'Nutzfläche = HNF + NNF' },
{ key: 'vf', label: 'VF', val: bilanz.vf },
{ key: 'ff', label: 'FF', val: bilanz.ff },
{ key: 'ngf', label: 'NGF', val: bilanz.ngf,
{ key: 'vf', label: 'VF', val: bilanz.vf, on: _show('showVf') },
{ key: 'ff', label: 'FF', val: bilanz.ff, on: _show('showFf') },
{ key: 'ngf', label: 'NGF', val: bilanz.ngf, on: _show('showNgf'),
hint: 'Nettogeschossfläche = NF + VF + FF' },
{ key: 'gf', label: 'GF', val: bilanz.gf },
{ key: 'agf', label: 'AGF', val: bilanz.agf },
].filter(r => r.val && r.val > 0)
{ key: 'gf', label: 'GF', val: bilanz.gf, on: _show('showGf') },
{ key: 'agf', label: 'AGF', val: bilanz.agf, on: _show('showAgf') },
].filter(r => r.on && r.val && r.val > 0)
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) })}
title={hint || label} />
)
return (
<div style={{
display: 'flex', flexDirection: 'column', gap: 8,
@@ -1202,6 +1260,23 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
</button>
</div>
{/* Stil-Picker — analog Raumstempel-Stile */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Stil</span>
<select value={activeStilId}
onChange={(e) => handleStilChange(e.target.value)}
style={{ flex: 1, fontSize: 11 }}
title="Gespeicherter Stempel-Stil — Klick wendet die Vorlage an">
<option value=""> Stil wählen </option>
{stilList.map(s => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
<option disabled></option>
<option value="__save__">+ Aktuelle Settings als Stil speichern</option>
{activeStilId && <option value="__delete__">🗑 Aktiven Stil löschen</option>}
</select>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Scope</span>
<select value={scope}
@@ -1217,6 +1292,42 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
</select>
</div>
{/* Header-Input — frei editierbar, Default "Nutzflächen" */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Header</span>
<input type="text" value={headerDraft}
onChange={(e) => setHeaderDraft(e.target.value)}
onBlur={() => {
const v = (headerDraft || '').trim()
if (v !== (stempel.header || '')) onUpdate({ header: v })
}}
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
placeholder="Nutzflächen"
style={{ flex: 1, fontSize: 11 }} />
</div>
{/* Show-Toggles — was im Stempel angezeigt wird */}
<div style={{
display: 'flex', flexDirection: 'column', gap: 4,
paddingTop: 6, borderTop: '1px dashed var(--border)',
}}>
<span style={{ ...labelXs, marginBottom: 2 }}>Anzeigen im Stempel</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
<ShowTog field="showScope" label="Scope" hint="Header-Suffix · Total/Geschoss" />
<ShowTog field="showHnf" label="HNF" />
<ShowTog field="showNnf" label="NNF" />
<ShowTog field="showNf" label="NF" hint="Nutzfläche = HNF + NNF" />
<ShowTog field="showVf" label="VF" />
<ShowTog field="showFf" label="FF" />
<ShowTog field="showNgf" label="NGF" hint="Nettogeschossfläche" />
<ShowTog field="showGf" label="GF" />
<ShowTog field="showAgf" label="AGF" />
<ShowTog field="showCount" label="Anzahl" hint="Anzahl klassifizierter Räume" />
<ShowTog field="showSeparators" label="Linien" hint="Trennlinien zwischen Sections" />
</div>
</div>
{/* Live-Vorschau der Bilanz (nur sichtbare Zeilen) */}
<div style={{
paddingTop: 6, borderTop: '1px dashed var(--border)',
fontSize: 10, fontFamily: 'DM Mono, monospace',
@@ -1225,13 +1336,12 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
<div style={{ fontSize: 9, color: 'var(--text-muted)',
letterSpacing: '0.06em', textTransform: 'uppercase',
marginBottom: 4 }}>
Bilanz · {bilanz.count || 0} Räume klassifiziert
Vorschau · {bilanz.count || 0} Räume klassifiziert
</div>
{rows.length === 0 ? (
<div style={{ fontSize: 10, color: 'var(--text-muted)',
fontStyle: 'italic', padding: '4px 0' }}>
Keine klassifizierten Räume im Scope.
Räume mit SIA-Tag (HNF/NNF/VF/FF/GF/AGF) erscheinen hier.
Nichts anzuzeigen Räume mit SIA-Tag fehlen oder Felder deaktiviert.
</div>
) : rows.map(r => (
<div key={r.key} title={r.hint || ''}
+1
View File
@@ -38,6 +38,7 @@ export default function ElementePropertiesApp() {
fonts={state.fonts || []}
oeffStyles={state.oeffStyles || []}
raumStempelStile={state.raumStempelStile || []}
stempelStile={state.stempelStile || []}
/>
) : (
<div style={{
+9
View File
@@ -256,6 +256,15 @@ export function reorderRaumStile(ids) { send('REORDER_RAUM_STILE', { ids }) }
export function duplicateRaumStil(id, newName) {
send('DUPLICATE_RAUM_STIL', { id, newName })
}
// Stempel-Stile (analog Raumstempel-Stile, eigene Storage pro Doc)
export function saveStempelStil(id, name, settings, applyToIds) {
send('SAVE_STEMPEL_STIL', { id, name, settings,
applyToIds: applyToIds || [] })
}
export function deleteStempelStil(id) { send('DELETE_STEMPEL_STIL', { id }) }
export function applyStempelStil(stilId, ids) {
send('APPLY_STEMPEL_STIL', { stilId, ids })
}
export function setSectionStyle(enabled, source, color, pattern, scale, rotation,
opts = {}) {
send('SET_SECTION_STYLE', {