refactor(admin): split remaining components
Split 4 oversized component files (all >500 LOC) into sibling modules: - SDKPipelineSidebar → Icons + Parts siblings (193/264/35 LOC) - SourcesTab → SourceModals sibling (311/243 LOC) - ScopeDecisionTab → ScopeDecisionSections sibling (127/444 LOC) - ComplianceAdvisorWidget → ComplianceAdvisorParts sibling (265/131 LOC) Zero behavior changes; all logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
243
admin-compliance/components/sdk/source-policy/SourceModals.tsx
Normal file
243
admin-compliance/components/sdk/source-policy/SourceModals.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
'use client'
|
||||
|
||||
// =============================================================================
|
||||
// Source Policy - New/Edit Source Modals
|
||||
// =============================================================================
|
||||
|
||||
interface AllowedSource {
|
||||
id: string
|
||||
domain: string
|
||||
name: string
|
||||
description?: string
|
||||
license?: string
|
||||
legal_basis?: string
|
||||
trust_boost: number
|
||||
source_type: string
|
||||
active: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
created_at: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
const LICENSES = [
|
||||
{ value: 'DL-DE-BY-2.0', label: 'Datenlizenz Deutschland' },
|
||||
{ value: 'CC-BY', label: 'Creative Commons BY' },
|
||||
{ value: 'CC-BY-SA', label: 'Creative Commons BY-SA' },
|
||||
{ value: 'CC0', label: 'Public Domain' },
|
||||
{ value: '§5 UrhG', label: 'Amtliche Werke (§5 UrhG)' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// NEW SOURCE MODAL
|
||||
// =============================================================================
|
||||
|
||||
interface NewSourceFormState {
|
||||
domain: string
|
||||
name: string
|
||||
license: string
|
||||
legal_basis: string
|
||||
trust_boost: number
|
||||
active: boolean
|
||||
}
|
||||
|
||||
interface NewSourceModalProps {
|
||||
newSource: NewSourceFormState
|
||||
saving: boolean
|
||||
onClose: () => void
|
||||
onCreate: () => void
|
||||
onChange: (update: Partial<NewSourceFormState>) => void
|
||||
}
|
||||
|
||||
export function NewSourceModal({ newSource, saving, onClose, onCreate, onChange }: NewSourceModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-xl p-6 w-full max-w-lg mx-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900 mb-4">Neue Quelle hinzufuegen</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Domain *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newSource.domain}
|
||||
onChange={(e) => onChange({ domain: e.target.value })}
|
||||
placeholder="z.B. nibis.de"
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newSource.name}
|
||||
onChange={(e) => onChange({ name: e.target.value })}
|
||||
placeholder="z.B. NiBiS Bildungsserver"
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Lizenz *</label>
|
||||
<select
|
||||
value={newSource.license}
|
||||
onChange={(e) => onChange({ license: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
{LICENSES.map((l) => (
|
||||
<option key={l.value} value={l.value}>{l.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Rechtsgrundlage</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newSource.legal_basis}
|
||||
onChange={(e) => onChange({ legal_basis: e.target.value })}
|
||||
placeholder="z.B. §5 UrhG (Amtliche Werke)"
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Trust Boost</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={newSource.trust_boost}
|
||||
onChange={(e) => onChange({ trust_boost: parseFloat(e.target.value) })}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-xs text-slate-500 text-right">
|
||||
{(newSource.trust_boost * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 mt-6 pt-4 border-t border-slate-100">
|
||||
<button onClick={onClose} className="px-4 py-2 text-slate-600 hover:text-slate-700">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onClick={onCreate}
|
||||
disabled={saving || !newSource.domain || !newSource.name}
|
||||
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Speichere...' : 'Erstellen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EDIT SOURCE MODAL
|
||||
// =============================================================================
|
||||
|
||||
interface EditSourceModalProps {
|
||||
source: AllowedSource
|
||||
saving: boolean
|
||||
onClose: () => void
|
||||
onSave: () => void
|
||||
onChange: (update: Partial<AllowedSource>) => void
|
||||
}
|
||||
|
||||
export function EditSourceModal({ source, saving, onClose, onSave, onChange }: EditSourceModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-xl p-6 w-full max-w-lg mx-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900 mb-4">Quelle bearbeiten</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Domain</label>
|
||||
<input
|
||||
type="text"
|
||||
value={source.domain}
|
||||
disabled
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg bg-slate-50 text-slate-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={source.name}
|
||||
onChange={(e) => onChange({ name: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Lizenz *</label>
|
||||
<select
|
||||
value={source.license}
|
||||
onChange={(e) => onChange({ license: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
{LICENSES.map((l) => (
|
||||
<option key={l.value} value={l.value}>{l.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Rechtsgrundlage</label>
|
||||
<input
|
||||
type="text"
|
||||
value={source.legal_basis || ''}
|
||||
onChange={(e) => onChange({ legal_basis: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Trust Boost</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={source.trust_boost}
|
||||
onChange={(e) => onChange({ trust_boost: parseFloat(e.target.value) })}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-xs text-slate-500 text-right">
|
||||
{(source.trust_boost * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="active"
|
||||
checked={source.active}
|
||||
onChange={(e) => onChange({ active: e.target.checked })}
|
||||
className="w-4 h-4 text-purple-600"
|
||||
/>
|
||||
<label htmlFor="active" className="text-sm text-slate-700">Aktiv</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 mt-6 pt-4 border-t border-slate-100">
|
||||
<button onClick={onClose} className="px-4 py-2 text-slate-600 hover:text-slate-700">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onClick={onSave}
|
||||
disabled={saving}
|
||||
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Speichere...' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user