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:
+125
-15
@@ -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 || ''}
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function ElementePropertiesApp() {
|
||||
fonts={state.fonts || []}
|
||||
oeffStyles={state.oeffStyles || []}
|
||||
raumStempelStile={state.raumStempelStile || []}
|
||||
stempelStile={state.stempelStile || []}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
|
||||
@@ -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', {
|
||||
|
||||
Reference in New Issue
Block a user