feat: Package 4 Phase 2 — Frontend-Fixes und Backend-Endpoints vervollständigt
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 33s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 17s
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 33s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 17s
- document-generator: STEP_EXPLANATIONS Key 'consent' → 'document-generator'
- Proxy: Content-Type nicht mehr hardcoded; forwarded vom Client (Fix für DOCX-Upload + multipart/arrayBuffer)
- Backend: GET /documents/{id}, DELETE /documents/{id}, GET /versions/{id} ergänzt
- Backend-Tests: 4 neue Tests für die neuen Endpoints
- consent/page.tsx: Create-Modal + handleCreateDocument() + DELETE-Handler verdrahtet
- einwilligungen/page.tsx: odentifier→identifier, ip_address, user_agent, history aus API gemappt; source nullable
- cookie-banner/page.tsx: handleExportCode() + Toast für 'Code exportieren' Button
- workflow/page.tsx: 'Neues Dokument' Button + createDocument() + Modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,7 +67,7 @@ function transformApiDocument(doc: ApiDocument): LegalDocument {
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function DocumentCard({ document }: { document: LegalDocument }) {
|
||||
function DocumentCard({ document, onDelete }: { document: LegalDocument; onDelete: (id: string) => void }) {
|
||||
const typeColors = {
|
||||
'privacy-policy': 'bg-blue-100 text-blue-700',
|
||||
terms: 'bg-green-100 text-green-700',
|
||||
@@ -149,6 +149,12 @@ function DocumentCard({ document }: { document: LegalDocument }) {
|
||||
Veroeffentlichen
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onDelete(document.id)}
|
||||
className="px-3 py-1 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
>
|
||||
Loeschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,6 +171,9 @@ export default function ConsentPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [filter, setFilter] = useState<string>('all')
|
||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||
const [newDocForm, setNewDocForm] = useState({ type: 'privacy_policy', name: '', description: '' })
|
||||
const [creating, setCreating] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
loadDocuments()
|
||||
@@ -192,6 +201,51 @@ export default function ConsentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateDocument() {
|
||||
if (!newDocForm.name.trim()) return
|
||||
setCreating(true)
|
||||
try {
|
||||
const token = localStorage.getItem('bp_admin_token')
|
||||
const res = await fetch('/api/admin/consent/documents', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||
},
|
||||
body: JSON.stringify(newDocForm),
|
||||
})
|
||||
if (res.ok) {
|
||||
setShowCreateModal(false)
|
||||
setNewDocForm({ type: 'privacy_policy', name: '', description: '' })
|
||||
await loadDocuments()
|
||||
} else {
|
||||
setError('Fehler beim Erstellen des Dokuments')
|
||||
}
|
||||
} catch {
|
||||
setError('Verbindungsfehler beim Erstellen')
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteDocument(id: string) {
|
||||
if (!confirm('Dokument wirklich löschen?')) return
|
||||
try {
|
||||
const token = localStorage.getItem('bp_admin_token')
|
||||
const res = await fetch(`/api/admin/consent/documents/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
})
|
||||
if (res.ok || res.status === 204) {
|
||||
setDocuments(prev => prev.filter(d => d.id !== id))
|
||||
} else {
|
||||
setError('Fehler beim Löschen des Dokuments')
|
||||
}
|
||||
} catch {
|
||||
setError('Verbindungsfehler beim Löschen')
|
||||
}
|
||||
}
|
||||
|
||||
const filteredDocuments = filter === 'all'
|
||||
? documents
|
||||
: documents.filter(d => d.type === filter || d.status === filter)
|
||||
@@ -211,7 +265,10 @@ export default function ConsentPage() {
|
||||
explanation={stepInfo.explanation}
|
||||
tips={stepInfo.tips}
|
||||
>
|
||||
<button className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors">
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
@@ -308,7 +365,7 @@ export default function ConsentPage() {
|
||||
{/* Documents List */}
|
||||
<div className="space-y-4">
|
||||
{filteredDocuments.map(document => (
|
||||
<DocumentCard key={document.id} document={document} />
|
||||
<DocumentCard key={document.id} document={document} onDelete={handleDeleteDocument} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -323,6 +380,68 @@ export default function ConsentPage() {
|
||||
<p className="mt-2 text-gray-500">Passen Sie den Filter an oder erstellen Sie ein neues Dokument.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create Document Modal */}
|
||||
{showCreateModal && (
|
||||
<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-md">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-bold text-gray-900">Neues Dokument erstellen</h2>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Dokumenttyp</label>
|
||||
<select
|
||||
value={newDocForm.type}
|
||||
onChange={(e) => setNewDocForm({ ...newDocForm, type: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
>
|
||||
<option value="privacy_policy">Datenschutzerklärung</option>
|
||||
<option value="terms">AGB</option>
|
||||
<option value="cookie_policy">Cookie-Richtlinie</option>
|
||||
<option value="imprint">Impressum</option>
|
||||
<option value="dpa">AVV (Auftragsverarbeitung)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newDocForm.name}
|
||||
onChange={(e) => setNewDocForm({ ...newDocForm, name: e.target.value })}
|
||||
placeholder="z.B. Datenschutzerklärung Website"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung (optional)</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
value={newDocForm.description}
|
||||
onChange={(e) => setNewDocForm({ ...newDocForm, description: e.target.value })}
|
||||
placeholder="Kurze Beschreibung..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 border-t border-gray-200 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setShowCreateModal(false)}
|
||||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateDocument}
|
||||
disabled={creating || !newDocForm.name.trim()}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
{creating ? 'Erstellen...' : 'Erstellen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user