// forms.jsx — modal forms for CRUD on Stammdaten (Properties, Units, Tenants, Transactions) // Lightweight reusable Modal + form components, registered on window. function Modal({ open, onClose, title, children, footer, wide }) { // Esc closes the modal — preserves the keyboard escape route after we removed // backdrop-click-to-close (which dismissed forms accidentally mid-typing). React.useEffect(() => { if (!open || !onClose) return; const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); if (!open) return null; return (

{title}

{onClose && }
{children}
{footer &&
{footer}
}
); } // Wrap a form to suppress Enter-submit from single-line inputs. // Without this, hitting Enter in any text/number input submits the form, // which closes the modal before the user finishes filling out other fields. // Textareas still get Enter for line breaks. const noEnterSubmit = (e) => { if (e.key === 'Enter' && e.target.tagName === 'INPUT' && e.target.type !== 'submit') { e.preventDefault(); } }; function FormField({ label, children, hint, required }) { return (
{children} {hint &&
{hint}
}
); } const PROPERTY_TYPES = ['Mehrfamilienhaus', 'Doppelhaushälfte', 'Einfamilienhaus', 'Eigentumswohnung', 'Gewerbeobjekt', 'Sonstiges']; function PropertyForm({ open, onClose, store, initial }) { const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(null); const initForm = (i) => ({ name: i?.name || '', address: i?.address || '', type: i?.type || 'Mehrfamilienhaus', yearBuilt: i?.yearBuilt || '', purchaseDate: i?.purchaseDate || '', purchasePrice: i?.purchasePrice || '', notes: i?.notes || '', legalOwnerName: i?.legalOwnerName || '', legalOwnerAddress: i?.legalOwnerAddress || '', legalOwnerEmail: i?.legalOwnerEmail || '', ownerIban: i?.ownerIban || '', ownerUstId: i?.ownerUstId || '', }); const [form, setForm] = React.useState(() => initForm(initial)); // Auto-expand the override section if any field is filled const hasOverrides = (i) => !!(i?.legalOwnerName || i?.legalOwnerAddress || i?.legalOwnerEmail || i?.ownerIban || i?.ownerUstId); const [showOverride, setShowOverride] = React.useState(() => hasOverrides(initial)); React.useEffect(() => { if (open) { setForm(initForm(initial)); setShowOverride(hasOverrides(initial)); setErr(null); } }, [open, initial]); const update = (k, v) => setForm(f => ({ ...f, [k]: v })); const submit = async (e) => { e.preventDefault(); if (!form.name.trim()) return; setBusy(true); setErr(null); try { const body = { name: form.name.trim(), address: form.address.trim() || null, type: form.type || null, yearBuilt: form.yearBuilt ? Number(form.yearBuilt) : null, purchaseDate: form.purchaseDate || null, purchasePrice: form.purchasePrice ? Number(form.purchasePrice) : null, notes: form.notes.trim() || null, // Per-property overrides — null means "use global settings" legalOwnerName: form.legalOwnerName.trim() || null, legalOwnerAddress: form.legalOwnerAddress.trim() || null, legalOwnerEmail: form.legalOwnerEmail.trim() || null, ownerIban: form.ownerIban.trim() || null, ownerUstId: form.ownerUstId.trim() || null, }; if (initial?.id) await store.updateProperty(initial.id, body); else await store.addProperty(body); onClose(); } catch (e) { setErr(e.message); } finally { setBusy(false); } }; return ( }>
update('name', e.target.value)} placeholder="z.B. Lindenstraße 42" autoFocus /> update('address', e.target.value)} placeholder="Straße, PLZ, Ort" />
update('yearBuilt', e.target.value)} placeholder="z.B. 1972" />
update('purchaseDate', e.target.value)} /> update('purchasePrice', e.target.value)} placeholder="z.B. 450000" step="1000" />