diff --git a/src/components/legal/ProviderContractsTab.jsx b/src/components/legal/ProviderContractsTab.jsx index 920fd92..ab7c043 100644 --- a/src/components/legal/ProviderContractsTab.jsx +++ b/src/components/legal/ProviderContractsTab.jsx @@ -1,39 +1,12 @@ import { Fragment, useState } from 'react' -import { Button, EmptyState } from '../ui' +import { Badge, Button, EmptyState } from '../ui' import { deadlineTone, formatDate, nextCancellationDate } from '../../lib/legal' +import { costLabel, termLabel, noticeLabel, deadlineView } from '../../lib/legalView' +import { isRowActivation } from '../../lib/rowClick' import ContractForm from './ContractForm' import { FileLinks, todayIso } from './legalUi' const TONE_ORDER = { red: 0, orange: 1, normal: 2, missing: 3, grey: 4 } -const euro = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }) - -function costLabel(c) { - if (c.cost === null || c.cost === undefined) return '–' - return `${euro.format(c.cost)}/${c.costInterval === 'year' ? 'Jahr' : 'Monat'}` -} - -function termLabel(c) { - if (c.termMonths === null || c.termMonths === undefined) return '–' - if (c.termMonths === 0) return 'jederzeit kündbar' - const renew = c.renewalMonths ? `, dann je ${c.renewalMonths} Monate` : ', endet danach' - return `${c.termMonths} Monate${renew}` -} - -function noticeLabel(c) { - if (c.noticeValue === null || c.noticeValue === undefined) return '–' - return `${c.noticeValue} ${c.noticeUnit === 'days' ? 'Tage' : 'Monate'}` -} - -function deadlineLabel(r) { - switch (r.state) { - case 'active': return `kündbar bis ${formatDate(r.noticeBy)} (Periode endet ${formatDate(r.periodEnd)})` - case 'anytime': return 'jederzeit kündbar' - case 'ends': return `endet am ${formatDate(r.periodEnd)}` - case 'ended': return `beendet seit ${formatDate(r.periodEnd)}` - case 'cancelled': return r.periodEnd ? `gekündigt zum ${formatDate(r.periodEnd)}` : 'gekündigt' - default: return 'Fristen fehlen' - } -} export default function ProviderContractsTab({ contracts, fileNames, saveContract, deleteContract }) { const [open, setOpen] = useState(null) @@ -77,55 +50,83 @@ export default function ProviderContractsTab({ contracts, fileNames, saveContrac
- {actionError &&
{actionError}
} + {actionError &&
{actionError}
} {rows.length === 0 ? ( ) : ( - +
- + + + + + + - {rows.map(({ c, result, tone }) => ( - - setOpen(open === c.$id ? null : c.$id)}> - - - - - - - - {open === c.$id && ( - - { if (isRowActivation(e)) toggle() }}> + + + + + + - )} - - ))} + {open === c.$id && ( + + + + )} + + ) + })}
AnbieterWofürKostenLaufzeitKündigungsfristNächster TerminAnbieterWofürKostenLaufzeitKündigungsfristNächster Termin
{c.provider}{c.purpose || '–'}{costLabel(c)}{termLabel(c)}{noticeLabel(c)}{deadlineLabel(result)}
- {c.startDate &&
Beginn: {formatDate(c.startDate)}
} - {c.note &&
{c.note}
} -
-
- - {!c.cancelledAt && cancelling !== c.$id && ( - - )} - {cancelling === c.$id && ( - - - setEndsAt(e.target.value)} /> - - - - )} - + {rows.map(({ c, result }) => { + const view = deadlineView(result, today) + const toggle = () => setOpen(open === c.$id ? null : c.$id) + return ( + +
+
+
{c.purpose || '–'}{costLabel(c)}{termLabel(c)}{noticeLabel(c)} + {view.badge ? ( + <> + {view.lead} + {view.rest} + + ) : ( + {view.text} + )} +
+
+
Wofür
{c.purpose || '–'}
+
Kosten
{costLabel(c)}
+
Laufzeit
{termLabel(c)}
+
Kündigungsfrist
{noticeLabel(c)}
+
+ {c.startDate &&
Beginn: {formatDate(c.startDate)}
} + {c.note &&
{c.note}
} +
+
+ + {!c.cancelledAt && cancelling !== c.$id && ( + + )} + {cancelling === c.$id && ( + + + setEndsAt(e.target.value)} /> + + + + )} + +
+
)} diff --git a/src/styles/global.css b/src/styles/global.css index 25a86a0..65ea61f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -618,18 +618,15 @@ a.list-row:hover .cell-title { color: var(--accent-ink); } .legal-error { background: var(--danger); color: #fff; padding: 8px 12px; border-radius: var(--r-sm); margin: 8px 0; } .legal-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); } .legal-contracts { margin-top: var(--sp-2); } -.legal-contracts tbody tr { cursor: pointer; } -.legal-tone-red td { background: rgba(239, 68, 68, 0.10); } -.legal-tone-red .legal-deadline { color: var(--danger); font-weight: 700; } -.legal-tone-orange td { background: rgba(245, 158, 11, 0.10); } -.legal-tone-orange .legal-deadline { color: var(--orange-fg); font-weight: 600; } -.legal-tone-missing .legal-deadline { color: var(--text-faint); font-style: italic; } -.legal-tone-grey td { color: var(--text-faint); } -.legal-detail td { background: var(--surface-2); cursor: default; } -.legal-actions { margin-top: 8px; flex-wrap: wrap; } -@media (max-width: 700px) { - .legal-form-row { grid-template-columns: 1fr; } - .legal-contracts { display: block; overflow-x: auto; } +.legal-contracts .cell-title .link { text-align: left; } /* Knopf zentriert sonst lange Anbieternamen */ +.legal-contracts tr.row:has(+ .legal-detail) td { border-bottom: 0; } /* offene Zeile und Detail bilden einen Block, die Haarlinie steht darunter */ +.legal-detail td { padding: 0 0 16px; } +.legal-facts { display: none; margin-bottom: 8px; } +.legal-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin-top: 8px; } +.legal-cancel { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } +.legal-cancel .input { width: 160px; } +@media (max-width: 860px) { + .legal-facts { display: grid; } } /* ===== FINANZEN (P7) ===== */