Design: Rechtstexte als Abschnitte mit Haarlinien, Aufklappzeile als Button

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-06 14:41:40 +00:00
1 parent 38beaead99
commit 8f5013aafc
4 files changed
+37 -33

No files matched your search

+16 -11
View File
@@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { Badge, Button, Card } from '../ui' import { Badge, Card } from '../ui'
import { currentVersion, formatDate } from '../../lib/legal' import { currentVersion, formatDate } from '../../lib/legal'
import VersionForm from './VersionForm' import VersionForm from './VersionForm'
import { FileLinks } from './legalUi' import { FileLinks } from './legalUi'
@@ -21,8 +21,8 @@ export default function LegalTextsTab({ documents, versions, fileNames, addVersi
} }
return ( return (
<div className="legal-grid"> <div className="legal-docs">
{actionError && <div className="legal-error">{actionError}</div>} {actionError && <div className="msg err legal-msg" role="alert">{actionError}</div>}
{documents.map((doc) => { {documents.map((doc) => {
const own = versions.filter((v) => v.documentKey === doc.key).sort(byNewest) const own = versions.filter((v) => v.documentKey === doc.key).sort(byNewest)
const current = currentVersion(own) const current = currentVersion(own)
@@ -33,9 +33,14 @@ export default function LegalTextsTab({ documents, versions, fileNames, addVersi
key={doc.$id} key={doc.$id}
className="legal-doc" className="legal-doc"
title={doc.title} title={doc.title}
actions={<Button size="sm" onClick={() => setFormFor(doc)}>Neue Version</Button>} actions={<button type="button" className="link" onClick={() => setFormFor(doc)}>Neue Version</button>}
> >
<div className="legal-doc-summary" onClick={() => setOpen(expanded ? null : doc.key)} role="button"> <button
type="button"
className="legal-doc-summary"
aria-expanded={expanded}
onClick={() => setOpen(expanded ? null : doc.key)}
>
{current ? ( {current ? (
<Badge tone="ok">{current.version} · gilt seit {formatDate(current.validFrom)}</Badge> <Badge tone="ok">{current.version} · gilt seit {formatDate(current.validFrom)}</Badge>
) : ( ) : (
@@ -44,23 +49,23 @@ export default function LegalTextsTab({ documents, versions, fileNames, addVersi
{drafts.map((d) => ( {drafts.map((d) => (
<Badge key={d.$id} tone="muted">Entwurf {d.version}</Badge> <Badge key={d.$id} tone="muted">Entwurf {d.version}</Badge>
))} ))}
<span className="faint legal-toggle">{expanded ? 'Verlauf ausblenden' : `Verlauf (${own.length})`}</span> <span className="legal-toggle">{expanded ? 'Verlauf ausblenden' : `Verlauf (${own.length})`}</span>
</div> </button>
{doc.liveUrl && ( {doc.liveUrl && (
<a href={doc.liveUrl} target="_blank" rel="noreferrer" className="legal-live">Live-Seite öffnen</a> <a href={doc.liveUrl} target="_blank" rel="noreferrer" className="link legal-live">Live-Seite öffnen</a>
)} )}
{expanded && ( {expanded && (
<ol className="legal-history"> <ol className="legal-history">
{own.length === 0 && <li className="faint">Noch keine Versionen eingetragen.</li>} {own.length === 0 && <li className="faint">Noch keine Versionen eingetragen.</li>}
{own.map((v) => ( {own.map((v) => (
<li key={v.$id} className={`legal-version legal-version-${v.status}`}> <li key={v.$id} className={v.status === 'superseded' ? 'legal-version is-superseded' : 'legal-version'}>
<div className="legal-version-head"> <div className="legal-version-head">
<strong>{v.version}</strong> <strong>{v.version}</strong>
<span>{formatDate(v.validFrom)} – {v.status === 'current' ? 'heute' : formatDate(v.validTo)}</span> <span className="muted">{formatDate(v.validFrom)} – {v.status === 'current' ? 'heute' : formatDate(v.validTo)}</span>
{v.status === 'current' && <Badge tone="ok">gültig</Badge>} {v.status === 'current' && <Badge tone="ok">gültig</Badge>}
{v.status === 'draft' && <Badge tone="muted">Entwurf</Badge>} {v.status === 'draft' && <Badge tone="muted">Entwurf</Badge>}
{v.status === 'draft' && ( {v.status === 'draft' && (
<Button size="sm" onClick={() => promote(v)}>Als gültig markieren</Button> <button type="button" className="link" onClick={() => promote(v)}>Als gültig markieren</button>
)} )}
</div> </div>
{v.changeNote && <div className="legal-note">{v.changeNote}</div>} {v.changeNote && <div className="legal-note">{v.changeNote}</div>}
+2 -2
View File
@@ -11,9 +11,9 @@ export function FileLinks({ fileIds = [], fileNames = {} }) {
<span className="legal-files"> <span className="legal-files">
{fileIds.map((id) => ( {fileIds.map((id) => (
<span key={id}> <span key={id}>
<a href={fileUrl(id)} target="_blank" rel="noreferrer">{fileNames[id] || 'Datei'}</a> <a href={fileUrl(id)} target="_blank" rel="noreferrer" className="link">{fileNames[id] || 'Datei'}</a>
{' '} {' '}
<a href={fileDownloadUrl(id)} className="faint">(herunterladen)</a> <a href={fileDownloadUrl(id)} className="link legal-dl">(herunterladen)</a>
</span> </span>
))} ))}
</span> </span>
+2 -2
View File
@@ -16,7 +16,7 @@ export default function LegalPage() {
const active = tab === 'texte' ? texts : contracts const active = tab === 'texte' ? texts : contracts
let body let body
if (active.loading) body = <div className="faint">Lädt …</div> if (active.loading) body = <div className="loading">Lädt …</div>
else if (active.missingSetup) { else if (active.missingSetup) {
body = ( body = (
<EmptyState <EmptyState
@@ -31,7 +31,7 @@ export default function LegalPage() {
<div className="page"> <div className="page">
<PageHeader title="Rechtliches" subtitle="Rechtstexte mit Versionsgeschichte und Verträge mit Anbietern" /> <PageHeader title="Rechtliches" subtitle="Rechtstexte mit Versionsgeschichte und Verträge mit Anbietern" />
<Tabs tabs={TABS} active={tab} onChange={setTab} /> <Tabs tabs={TABS} active={tab} onChange={setTab} />
{active.error && <div className="legal-error">{active.error}</div>} {active.error && <div className="msg err" role="alert">{active.error}</div>}
{body} {body}
</div> </div>
) )
+17 -18
View File
@@ -601,20 +601,20 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
} }
/* --- Rechtliches --- */ /* --- Rechtliches --- */
.legal-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr)); gap: var(--sp-4); align-items: start; margin-top: var(--sp-4); } .legal-doc-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; padding: 12px 0; text-align: left; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.legal-grid > .legal-error { grid-column: 1 / -1; } .legal-toggle { margin-left: auto; font-size: 13px; color: var(--text-2); }
.legal-doc-summary { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; cursor: pointer; } .legal-live { display: inline-block; margin-top: 8px; font-size: 13px; }
.legal-toggle { margin-left: auto; font-size: 13px; } .legal-history { list-style: none; margin: 0; padding: 0; }
.legal-live { display: inline-block; margin-top: 8px; font-size: 13px; color: var(--accent); } .legal-version { padding: 10px 0; border-bottom: 1px solid var(--line); }
.legal-history { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; } .legal-version.is-superseded { color: var(--text-2); }
.legal-version { border: 1px solid var(--border); border-radius: var(--r-sm); padding: 8px 10px; font-size: 14px; } .legal-version-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.legal-version-current { border-color: var(--ok); background: rgba(16, 185, 129, 0.08); } .legal-version-head .link { margin-left: auto; }
.legal-version-draft { border-style: dashed; } .legal-note { white-space: pre-wrap; margin: 4px 0; font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; }
.legal-version-superseded { color: var(--text-muted); } .legal-files { display: inline-flex; flex-wrap: wrap; gap: 4px 12px; font-size: 13px; overflow-wrap: anywhere; }
.legal-version-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } .legal-files .link { font-weight: 400; }
.legal-note { white-space: pre-wrap; margin: 4px 0; color: var(--text-muted); font-size: 13px; } .legal-files .legal-dl { color: var(--text-2); }
.legal-files { display: inline-flex; flex-wrap: wrap; gap: 4px 12px; font-size: 13px; } .legal-msg { margin: 0 0 12px; }
.legal-files a { color: var(--accent); } .legal-doc h2.section .link { white-space: nowrap; } /* "Neue Version" bleibt am Handy eine Zeile */
.legal-error { background: var(--danger); color: #fff; padding: 8px 12px; border-radius: var(--r-sm); margin: 8px 0; } .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-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.legal-contracts { margin-top: var(--sp-2); } .legal-contracts { margin-top: var(--sp-2); }
@@ -628,7 +628,6 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
.legal-detail td { background: var(--surface-2); cursor: default; } .legal-detail td { background: var(--surface-2); cursor: default; }
.legal-actions { margin-top: 8px; flex-wrap: wrap; } .legal-actions { margin-top: 8px; flex-wrap: wrap; }
@media (max-width: 700px) { @media (max-width: 700px) {
.legal-grid { grid-template-columns: 1fr; }
.legal-form-row { grid-template-columns: 1fr; } .legal-form-row { grid-template-columns: 1fr; }
.legal-contracts { display: block; overflow-x: auto; } .legal-contracts { display: block; overflow-x: auto; }
} }
@@ -1266,13 +1265,13 @@ div.row { display: flex; flex-wrap: wrap; margin: 0 -8px; }
.page .page { margin: 0; padding: 0; max-width: none; } .page .page { margin: 0; padding: 0; max-width: none; }
/* Links in Bestandsklassen, die bisher --accent als Farbe nutzten: feiner Unterstrich */ /* Links in Bestandsklassen, die bisher --accent als Farbe nutzten: feiner Unterstrich */
.fin-link, .legal-files a { .fin-link {
text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px; text-decoration-thickness: 1px; text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px; text-decoration-thickness: 1px;
} }
.fin-link:hover, .legal-files a:hover { text-decoration-color: currentColor; } .fin-link:hover { text-decoration-color: currentColor; }
/* Abschnitte (Card = <section>) als Spalten nebeneinander: der Abstand .page section + section gilt nur fuer untereinander stehende Abschnitte */ /* Abschnitte (Card = <section>) als Spalten nebeneinander: der Abstand .page section + section gilt nur fuer untereinander stehende Abschnitte */
.legal-grid > section + section, .fin-cols > section + section, .fin-cols > section + section,
.page div[style*="display: grid"] > section + section { margin-top: 0; } .page div[style*="display: grid"] > section + section { margin-top: 0; }
/* Altformulare mit <Field> (Kunde, Projekt zuweisen, Neuer Lead): Zeilenabstand wie frueher; neue Container bestimmen ihren Abstand selbst */ /* Altformulare mit <Field> (Kunde, Projekt zuweisen, Neuer Lead): Zeilenabstand wie frueher; neue Container bestimmen ihren Abstand selbst */