feat: Package 4 Phase 3 — Finale Fixes + Dokumentation (MkDocs, SDK Flow, StepHeader)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 40s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 40s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
Finale Fixes (5 Bugs): - workflow/page.tsx: Array-Format-Fix fuer loadVersions — Array.isArray statt data.versions - einwilligungen_routes.py: ip_address + user_agent in GET /consents Response ergaenzt - consent/page.tsx: Bearbeiten/Vorschau/Veroeffentlichen + Quick Actions verdrahtet (useRouter + Preview Modal) - cookie-banner/page.tsx: BannerTexts State + Controlled Inputs + DB-Persistenz (banner_texts) - embed-code/route.ts: In-Memory configStorage → DB-fetch aus Backend, embed_code Key korrigiert Dokumentation: - docs-src/services/sdk-modules/rechtliche-texte.md: Neue MkDocs-Seite fuer Paket 4 (Einwilligungen, Rechtliche Vorlagen, Cookie Banner, Document Workflow) - mkdocs.yml: Nav-Eintrag 'Rechtliche Texte (Paket 4)' ergaenzt - dokumentations-module.md: Datenfluss-Diagramm um Paket-4-Module erweitert - flow-data.ts: Paket-4-Steps mit korrekten dbTables/dbMode und aktualisierten Beschreibungen - StepHeader.tsx: cookie-banner + workflow STEP_EXPLANATIONS auf Persistenz und Funktionsumfang aktualisiert Tests: 24/24 bestanden (test_einwilligungen_routes.py) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
||||
|
||||
@@ -67,7 +68,17 @@ function transformApiDocument(doc: ApiDocument): LegalDocument {
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelete: (id: string) => void }) {
|
||||
function DocumentCard({
|
||||
document,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onPreview,
|
||||
}: {
|
||||
document: LegalDocument
|
||||
onDelete: (id: string) => void
|
||||
onEdit: (id: string) => void
|
||||
onPreview: (doc: LegalDocument) => void
|
||||
}) {
|
||||
const typeColors = {
|
||||
'privacy-policy': 'bg-blue-100 text-blue-700',
|
||||
terms: 'bg-green-100 text-green-700',
|
||||
@@ -138,14 +149,23 @@ function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelet
|
||||
<span>Aktualisiert: {document.lastUpdated.toLocaleDateString('de-DE')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="px-3 py-1 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onEdit(document.id)}
|
||||
className="px-3 py-1 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button className="px-3 py-1 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onPreview(document)}
|
||||
className="px-3 py-1 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
Vorschau
|
||||
</button>
|
||||
{document.status === 'draft' && (
|
||||
<button className="px-3 py-1 bg-green-50 text-green-700 hover:bg-green-100 rounded-lg transition-colors">
|
||||
<button
|
||||
onClick={() => onEdit(document.id)}
|
||||
className="px-3 py-1 bg-green-50 text-green-700 hover:bg-green-100 rounded-lg transition-colors"
|
||||
>
|
||||
Veroeffentlichen
|
||||
</button>
|
||||
)}
|
||||
@@ -167,6 +187,7 @@ function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelet
|
||||
|
||||
export default function ConsentPage() {
|
||||
const { state } = useSDK()
|
||||
const router = useRouter()
|
||||
const [documents, setDocuments] = useState<LegalDocument[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -174,6 +195,7 @@ export default function ConsentPage() {
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
const [newDocForm, setNewDocForm] = useState({ type: 'privacy_policy', name: '', description: '' })
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [previewDoc, setPreviewDoc] = useState<{ name: string; content: string } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadDocuments()
|
||||
@@ -246,6 +268,30 @@ export default function ConsentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit(id: string) {
|
||||
router.push('/sdk/workflow')
|
||||
}
|
||||
|
||||
async function handlePreview(doc: LegalDocument) {
|
||||
try {
|
||||
const token = localStorage.getItem('bp_admin_token')
|
||||
const res = await fetch(`/api/admin/consent/documents/${doc.id}/versions`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const versions = Array.isArray(data) ? data : (data.versions || [])
|
||||
const published = versions.find((v: { status: string; content?: string }) => v.status === 'published')
|
||||
const latest = published || versions[0]
|
||||
setPreviewDoc({ name: doc.name, content: latest?.content || '<p>Kein Inhalt verfügbar.</p>' })
|
||||
} else {
|
||||
setPreviewDoc({ name: doc.name, content: '<p>Vorschau nicht verfügbar.</p>' })
|
||||
}
|
||||
} catch {
|
||||
setPreviewDoc({ name: doc.name, content: '<p>Fehler beim Laden der Vorschau.</p>' })
|
||||
}
|
||||
}
|
||||
|
||||
const filteredDocuments = filter === 'all'
|
||||
? documents
|
||||
: documents.filter(d => d.type === filter || d.status === filter)
|
||||
@@ -312,25 +358,37 @@ export default function ConsentPage() {
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 rounded-xl border border-purple-200 p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Schnellaktionen</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-blue-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">Datenschutz generieren</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-green-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">AGB generieren</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-yellow-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium">Cookie-Richtlinie</span>
|
||||
</button>
|
||||
<button className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center">
|
||||
<button
|
||||
onClick={() => router.push('/sdk/document-generator')}
|
||||
className="p-4 bg-white rounded-lg border border-gray-200 hover:border-purple-300 hover:shadow transition-all text-center"
|
||||
>
|
||||
<svg className="w-8 h-8 mx-auto text-purple-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
@@ -365,7 +423,13 @@ export default function ConsentPage() {
|
||||
{/* Documents List */}
|
||||
<div className="space-y-4">
|
||||
{filteredDocuments.map(document => (
|
||||
<DocumentCard key={document.id} document={document} onDelete={handleDeleteDocument} />
|
||||
<DocumentCard
|
||||
key={document.id}
|
||||
document={document}
|
||||
onDelete={handleDeleteDocument}
|
||||
onEdit={handleEdit}
|
||||
onPreview={handlePreview}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -381,6 +445,29 @@ export default function ConsentPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Preview Modal */}
|
||||
{previewDoc && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[80vh] flex flex-col">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h2 className="text-lg font-bold text-gray-900">{previewDoc.name}</h2>
|
||||
<button
|
||||
onClick={() => setPreviewDoc(null)}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="p-6 overflow-y-auto prose max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: previewDoc.content }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create Document Modal */}
|
||||
{showCreateModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
|
||||
Reference in New Issue
Block a user