diff --git a/backend/version.py b/backend/version.py index 56fa9dc..4271a2d 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,11 +1,11 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.73" +APP_VERSION = "0.8.74" BUILD_DATE = "2026-05-10" DB_SCHEMA_VERSION = "20260510047" MODULE_VERSIONS = { - "legal_documents": "1.1.0", # Als-Entwurf-kopieren: POST /api/admin/legal-documents/{id}/copy-as-draft + "legal_documents": "1.2.0", # jsPDF-Download auf LegalPage (oeffentlich) + Admin; Abschnitts-Sortierung/-Einfuegen "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 @@ -30,6 +30,14 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.74", + "date": "2026-05-10", + "changes": [ + "Rechtstexte: echtes PDF-Download via jsPDF (pdf.save) statt Browser-Print-Dialog; LegalPage und AdminLegalDocumentsPage", + "Rechtstexte Admin: Abschnitts-Reihenfolge per Pfeil-Buttons aendern; neuen Abschnitt an beliebiger Stelle einfuegen", + ], + }, { "version": "0.8.73", "date": "2026-05-10", diff --git a/frontend/package.json b/frontend/package.json index c04b7e5..eb10a77 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "preview": "vite preview" }, "dependencies": { + "jspdf": "^4.2.1", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/pages/AdminLegalDocumentsPage.jsx b/frontend/src/pages/AdminLegalDocumentsPage.jsx index 7291ee1..cb770e4 100644 --- a/frontend/src/pages/AdminLegalDocumentsPage.jsx +++ b/frontend/src/pages/AdminLegalDocumentsPage.jsx @@ -1,82 +1,112 @@ import { useState, useEffect, useCallback } from 'react' -import { FileText, Plus, Edit2, Archive, CheckCircle, Clock, Copy, Printer } from 'lucide-react' +import { jsPDF } from 'jspdf' +import { FileText, Plus, Edit2, Archive, CheckCircle, Clock, Copy, Download, ChevronUp, ChevronDown } from 'lucide-react' import api from '../utils/api' -function escHtml(str) { - return String(str ?? '') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') -} +// ─── PDF generation ────────────────────────────────────────────────────────── -function printLegalDocument(doc) { - const STATUS_DE = { published: 'Veröffentlicht', draft: 'Entwurf', archived: 'Archiviert' } +function generateLegalPdf(doc) { + const pdf = new jsPDF({ format: 'a4', unit: 'mm' }) + const marginL = 22 + const marginR = 22 + const marginTop = 28 + const pageW = 210 + const contentW = pageW - marginL - marginR + const bottomLimit = 277 // A4 297mm - 20mm bottom margin + let y = marginTop + + const checkBreak = (need) => { + if (y + need > bottomLimit) { + pdf.addPage() + y = marginTop + } + } + + // Title + pdf.setFont('helvetica', 'bold') + pdf.setFontSize(20) + pdf.text(doc.title, marginL, y) + y += 10 + + // Meta line + const STATUS_DE = { published: 'Gültig seit', draft: 'Entwurf — Stand', archived: 'Archiviert — Stand' } const dateStr = doc.published_at ? new Date(doc.published_at).toLocaleDateString('de-DE') : new Date(doc.updated_at || doc.created_at).toLocaleDateString('de-DE') - const metaLine = doc.status === 'published' - ? `Version ${doc.version} | Gültig seit ${dateStr}` - : `${STATUS_DE[doc.status] || doc.status} | Version ${doc.version} | Stand ${dateStr}` + const metaLine = `Version ${doc.version} | ${STATUS_DE[doc.status] || doc.status} ${dateStr}` - const sectionsHtml = (doc.content_sections || []).map(s => ` -

${escHtml(s.heading)}

-

${escHtml(s.content).replace(/\n/g, '
')}

- `).join('') + pdf.setFont('helvetica', 'normal') + pdf.setFontSize(10) + pdf.setTextColor(90, 90, 90) + pdf.text(metaLine, marginL, y) + y += 3 + pdf.setDrawColor(0, 0, 0) + pdf.setLineWidth(0.4) + pdf.line(marginL, y, pageW - marginR, y) + y += 8 + pdf.setTextColor(0, 0, 0) - const win = window.open('', '_blank') - win.document.write(` - - - - ${escHtml(doc.title)} - - - -

${escHtml(doc.title)}

-
${escHtml(metaLine)}
- ${sectionsHtml} - -