diff --git a/backend/version.py b/backend/version.py index 576abb2..5a32e9d 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,11 +1,11 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.95" +APP_VERSION = "0.8.96" BUILD_DATE = "2026-05-12" DB_SCHEMA_VERSION = "20260511053" MODULE_VERSIONS = { - "legal_documents": "1.3.0", # P-01: Ausgabe §-Nummerierung pro Abschnitt; Markdown im Fließtext + PDF; gem. legalPdfExport + "legal_documents": "1.4.0", # Admin: Live-Vorschau pro Abschnitt + modale Vollvorschau (Editor + Dokumentenliste) "auth": "1.2.3", # P-05b: reset-password min_length=8 via Pydantic PasswordResetConfirm "profiles": "1.7.0", # exercise_list_prefs JSONB (Standard Übungsfilter); Patch via ProfileUpdate + Json() "tenant_context": "1.0.5", # Plattform-Admin: effective_club ohne Header aus Profil active_club_id wenn Verein existiert @@ -34,6 +34,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.96", + "date": "2026-05-12", + "changes": [ + "P-01 Admin Rechtstexte: Live-Vorschau je Abschnitt (Markdown) neben dem Editor; modale „Vollständige Vorschau“ aus dem Formular; Augen-Symbol in der Dokumentenliste für die gerenderte Ansicht (API-Laden).", + ], + }, { "version": "0.8.95", "date": "2026-05-12", diff --git a/frontend/src/app.css b/frontend/src/app.css index 1b9c9f1..9379619 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -368,6 +368,82 @@ ul > li.card + li.card, margin: 0.85em 0; } +/* Admin: Rechtstext Editor — Live-Vorschau neben Textarea */ +.legal-section-split { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 4px; +} +@media (min-width: 720px) { + .legal-section-split { + flex-direction: row; + align-items: flex-start; + } + .legal-section-split__editor { + flex: 1; + min-width: 0; + } + .legal-section-split__preview { + flex: 1; + min-width: 0; + } +} +.legal-section-preview-box { + padding: 12px; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 8px; + min-height: 100px; +} +.legal-section-preview-box h4 { + font-size: 0.95rem; + font-weight: 700; + margin: 0 0 0.45rem; + color: var(--text1); +} + +/* Modales Vorschau-Overlay (Rechtstexte Admin) */ +.legal-preview-modal-backdrop { + position: fixed; + inset: 0; + z-index: 2000; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: flex-start; + justify-content: center; + padding: max(16px, env(safe-area-inset-top, 0px)) 16px 24px; + overflow-y: auto; +} +.legal-preview-modal { + width: 100%; + max-width: 760px; + margin-top: 4vh; + margin-bottom: 4vh; + background: var(--surface); + border-radius: 12px; + border: 1px solid var(--border); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + max-height: min(92vh, 960px); + display: flex; + flex-direction: column; +} +.legal-preview-modal__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 14px 16px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.legal-preview-modal__body { + padding: 16px 18px 20px; + overflow-y: auto; + min-height: 0; + flex: 1; +} + .form-input { width: 100%; min-width: 0; diff --git a/frontend/src/components/LegalDocumentPreview.jsx b/frontend/src/components/LegalDocumentPreview.jsx new file mode 100644 index 0000000..c7e80ad --- /dev/null +++ b/frontend/src/components/LegalDocumentPreview.jsx @@ -0,0 +1,111 @@ +import { useEffect } from 'react' +import LegalDocumentBody from './LegalDocumentBody' +import { legalSectionNumber } from '../utils/legalPdfExport' + +/** Inhalt wie auf der öffentlichen Rechtstextseite (inkl. §-Nummerierung). */ +export function LegalDocumentPublicPreviewContent({ + title, + sections, + showDraftNotice = true, + metaLine, +}) { + const safeTitle = (title || '').trim() || 'Ohne Titel' + + return ( +
+ {showDraftNotice && ( +
+ Vorschau +

+ So erscheint der Text für Besucher nach Veröffentlichung (Markdown wird gerendert, §-Nummern wie online). +

+
+ )} + {metaLine && ( +

{metaLine}

+ )} +

{safeTitle}

+ {(sections || []).map((section, i) => ( +
+

+ {section.heading?.trim() + ? `${legalSectionNumber(i)} ${section.heading}` + : legalSectionNumber(i)} +

+ +
+ ))} + {sections?.length === 0 && ( +

Noch keine Abschnitte.

+ )} +
+ ) +} + +/** + * Modal: gerenderte Rechtstext-Vorschau (Editor oder gespeicherte Version). + */ +export function LegalPreviewModal({ + open, + onClose, + title, + sections, + metaLine, + loading, + showDraftNotice = true, +}) { + useEffect(() => { + if (!open) 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 ( +
+
e.stopPropagation()} + > +
+ + +
+
+ {loading ? ( +
+ ) : ( + + )} +
+
+
+ ) +} diff --git a/frontend/src/pages/AdminLegalDocumentsPage.jsx b/frontend/src/pages/AdminLegalDocumentsPage.jsx index af6c928..85558f3 100644 --- a/frontend/src/pages/AdminLegalDocumentsPage.jsx +++ b/frontend/src/pages/AdminLegalDocumentsPage.jsx @@ -1,7 +1,9 @@ import { useState, useEffect, useCallback } from 'react' -import { FileText, Plus, Edit2, Archive, CheckCircle, Clock, Copy, Download, ChevronUp, ChevronDown } from 'lucide-react' +import { FileText, Plus, Edit2, Archive, CheckCircle, Clock, Copy, Download, ChevronUp, ChevronDown, Eye } from 'lucide-react' import api from '../utils/api' -import { generateLegalPdf } from '../utils/legalPdfExport' +import { generateLegalPdf, legalSectionNumber } from '../utils/legalPdfExport' +import LegalDocumentBody from '../components/LegalDocumentBody' +import { LegalPreviewModal } from '../components/LegalDocumentPreview' const PDF_STATUS_META = { published: 'Gültig seit', draft: 'Entwurf — Stand', archived: 'Archiviert — Stand' } @@ -109,18 +111,35 @@ function SectionEditor({ sections, onChange }) { placeholder="Abschnittsüberschrift" />
-
-