Projektdaten in Project-Settings + Swisstopo-Adress-Prefill

Schema-Erweiterung:
- _PROJECT_SETTINGS_DEFAULTS hat jetzt 'project'-Block mit
  name / number / address / bauherr / architekt / notes / projectZeroMum
- _normalize_project_meta stripped Strings + clampt mum als float
- load/save_project_settings handeln das 'project'-feld
- save_project_settings spiegelt projectZeroMum auch in den Legacy-Key
  dossier_project_zero_mum (fuer Geschoss-Settings-Dialog)
- load_project_settings liest Legacy-Key als Fallback wenn neuer Wert
  noch nicht gesetzt

UI:
- InlineTextField + TextareaField Helpers (Pill-Stil)
- Projektdaten-Section in Voreinstellungen-Tab:
  Name, Projekt-Nr., Adresse, Bauherrschaft, Architekt:in,
  EG-Nullpunkt m.ü.M (mit Hinweis auf Swisstopo-Nutzung), Notizen

Swisstopo:
- _cmd_open_swisstopo_dialog laedt Projekt-Adresse + sendet projectAddress
  im SWISSTOPO_STATE
- SwisstopoApp: vorbelegt searchText mit projectAddress wenn Feld leer
  ist (User-Input wird nicht ueberschrieben)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:21:19 +02:00
parent a597b58c93
commit 8f691e37c4
4 changed files with 164 additions and 22 deletions
+75 -1
View File
@@ -25,6 +25,46 @@ function Field({ label, hint, children, style }) {
)
}
/* InlineTextField — Label links, Text-Input rechts (kompakt) */
function InlineTextField({ label, value, onChange, placeholder, width = 240 }) {
return (
<div style={{ padding: '5px 0',
display: 'flex', alignItems: 'center', gap: 10 }}>
<span style={{ flex: 1, fontSize: 11, color: 'var(--text-primary)' }}>
{label}
</span>
<input type="text" value={value || ''}
placeholder={placeholder || ''}
onChange={(ev) => onChange(ev.target.value)}
style={{ width, height: BAR_H, padding: '0 12px',
fontSize: 11 }} />
</div>
)
}
/* TextareaField — Label oben, mehrzeiliges Input darunter (full-width) */
function TextareaField({ label, value, onChange, rows = 3, placeholder }) {
return (
<div style={{ padding: '6px 0' }}>
<div style={{ fontSize: 11, color: 'var(--text-primary)',
marginBottom: 4 }}>{label}</div>
<textarea value={value || ''}
placeholder={placeholder || ''}
rows={rows}
onChange={(ev) => onChange(ev.target.value)}
style={{ width: '100%', boxSizing: 'border-box',
padding: '6px 12px',
fontSize: 11, fontFamily: 'var(--font)',
background: 'var(--bg-input)',
color: 'var(--text-primary)',
border: '1px solid var(--border)',
borderRadius: 12,
resize: 'vertical', outline: 'none',
lineHeight: 1.5 }} />
</div>
)
}
/* InlineNumberField — Label links, schmales Number-Input rechts (kompakt) */
function InlineNumberField({ label, hint, value, onChange, step, min, max, suffix }) {
return (
@@ -579,7 +619,10 @@ export default function ProjectSettingsDialog({
const [draft, setDraft] = useState(() => ({
defaults: { ...(initial.defaults || {}) },
materials: [...(initial.materials || [])],
project: { ...(initial.project || {}) },
}))
const setProject = (k, v) =>
setDraft(d => ({ ...d, project: { ...(d.project || {}), [k]: v } }))
const [selMat, setSelMat] = useState(() => {
// Default-Auswahl: erstes Builtin wenn vorhanden, sonst erstes Local
const b = initial.builtinMaterials || []
@@ -704,7 +747,38 @@ export default function ProjectSettingsDialog({
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto',
padding: '8px 14px' }}>
{tab === 'defaults' && (
<div style={{ maxWidth: 520 }}>
<div style={{ maxWidth: 560 }}>
<DetailSection title="Projektdaten">
<InlineTextField label="Projektname"
value={draft.project?.name}
placeholder="z.B. Wohnhaus Müller"
onChange={(v) => setProject('name', v)} />
<InlineTextField label="Projekt-Nr."
value={draft.project?.number}
placeholder="z.B. 2026-014"
width={140}
onChange={(v) => setProject('number', v)} />
<TextareaField label="Adresse"
value={draft.project?.address}
placeholder="Strasse, PLZ Ort"
rows={2}
onChange={(v) => setProject('address', v)} />
<InlineTextField label="Bauherrschaft"
value={draft.project?.bauherr}
onChange={(v) => setProject('bauherr', v)} />
<InlineTextField label="Architekt:in"
value={draft.project?.architekt}
onChange={(v) => setProject('architekt', v)} />
<InlineNumberField label="EG-Nullpunkt m.ü.M"
value={draft.project?.projectZeroMum ?? 0.0}
step={0.01} suffix="m"
onChange={(v) => setProject('projectZeroMum', v || 0.0)}
hint="Höhe von Rhino z=0 (= EG OKFF) über Meeresspiegel. Wird vom Swisstopo-Terrain-Import zur Kalibrierung verwendet." />
<TextareaField label="Notizen"
value={draft.project?.notes}
rows={2}
onChange={(v) => setProject('notes', v)} />
</DetailSection>
<div style={{ fontSize: 10, color: 'var(--text-muted)',
padding: '6px 0 10px', lineHeight: 1.5 }}>
Voreinstellungen fuer neue Elemente. Pro-Element editierte