diff --git a/src/App.jsx b/src/App.jsx index 6f61cd7..25b813d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,6 +11,7 @@ import CustomersPage from './pages/CustomersPage' import LeadsPage from './pages/LeadsPage' import CustomerDetailPage from './pages/CustomerDetailPage' import FinancePage from './pages/FinancePage' +import LegalPage from './pages/LegalPage' function ProtectedRoute({ children }) { const { user, loading } = useAuth() @@ -43,6 +44,7 @@ function AppRoutes() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index fff4ddb..f4589a1 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -6,6 +6,7 @@ import { IconUsers, IconTargetArrow, IconReportMoney, + IconScale, IconFolders, IconCalendar, IconSettings, @@ -25,7 +26,7 @@ export default function Sidebar() { navigate('/login') } - // Eine flache Liste: fuer ein kleines Team reichen sieben Eintraege ohne Gruppen + // Eine flache Liste: fuer ein kleines Team reichen acht Eintraege ohne Gruppen const links = [ { label: 'Tickets', href: '/tickets', icon: }, { label: 'Planboard', href: '/planboard', icon: }, @@ -33,6 +34,7 @@ export default function Sidebar() { { label: 'Kunden', href: '/customers', icon: }, { label: 'Leads', href: '/leads', icon: }, { label: 'Finanzen', href: '/finance', icon: }, + { label: 'Rechtliches', href: '/rechtliches', icon: }, ...(isAdmin ? [{ label: 'Admin', href: '/admin', icon: }] : []), ] diff --git a/src/components/legal/ContractForm.jsx b/src/components/legal/ContractForm.jsx new file mode 100644 index 0000000..cc42ba8 --- /dev/null +++ b/src/components/legal/ContractForm.jsx @@ -0,0 +1,106 @@ +import { useState } from 'react' +import { FaTimes } from 'react-icons/fa' +import { Button } from '../ui' + +const FIELDS = ['provider', 'purpose', 'cost', 'costInterval', 'startDate', 'termMonths', 'renewalMonths', 'noticeValue', 'noticeUnit', 'note'] +const NUMBERS = ['termMonths', 'renewalMonths', 'noticeValue'] + +function toForm(contract) { + const form = { costInterval: 'month', noticeUnit: 'months' } + for (const key of FIELDS) { + if (contract?.[key] !== null && contract?.[key] !== undefined) form[key] = String(contract[key]) + } + return form +} + +/** Leere Felder werden null, damit die Fristenlogik "Fristen fehlen" erkennt. */ +function toDoc(form) { + const doc = {} + for (const key of FIELDS) { + const raw = (form[key] ?? '').trim() + if (raw === '') doc[key] = null + else if (NUMBERS.includes(key)) doc[key] = parseInt(raw, 10) + else if (key === 'cost') doc[key] = parseFloat(raw.replace(',', '.')) + else doc[key] = raw + } + return doc +} + +export default function ContractForm({ contract, onSave, onClose }) { + const [form, setForm] = useState(() => toForm(contract)) + const [files, setFiles] = useState([]) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + const set = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value })) + + const submit = async (e) => { + e.preventDefault() + setSaving(true) + setError(null) + try { + await onSave(toDoc(form), files) + onClose() + } catch (err) { + setError(err.message) + setSaving(false) + } + } + + const input = (key, label, props = {}) => ( +
+ + +
+ ) + + return ( +
+ +
+

{contract ? 'Vertrag bearbeiten' : 'Neuer Vertrag'}

+ {error &&
{error}
} +
+ {input('provider', 'Anbieter', { required: true })} + {input('purpose', 'Wofür')} +
+ {input('cost', 'Kosten (€)', { inputMode: 'decimal' })} +
+ + +
+
+ {input('startDate', 'Vertragsbeginn', { type: 'date' })} +
+ {input('termMonths', 'Laufzeit (Monate, 0 = jederzeit kündbar)', { type: 'number', min: 0 })} + {input('renewalMonths', 'Verlängert sich um (Monate, 0 = endet)', { type: 'number', min: 0 })} +
+
+ {input('noticeValue', 'Kündigungsfrist', { type: 'number', min: 0 })} +
+ + +
+
+
+ +