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:
Sharang Parnerkar
2026-04-17 12:41:29 +02:00
parent d32ad81094
commit b00fe6cb73
9 changed files with 1236 additions and 1299 deletions

View File

@@ -0,0 +1,131 @@
'use client'
// =============================================================================
// ComplianceAdvisorWidget — shared constants and sub-components
// =============================================================================
// =============================================================================
// EXAMPLE QUESTIONS
// =============================================================================
export const EXAMPLE_QUESTIONS: Record<string, string[]> = {
vvt: [
'Was ist ein Verarbeitungsverzeichnis?',
'Welche Informationen muss ich erfassen?',
'Wie dokumentiere ich die Rechtsgrundlage?',
],
'compliance-scope': [
'Was bedeutet L3?',
'Wann brauche ich eine DSFA?',
'Was ist der Unterschied zwischen L2 und L3?',
],
tom: [
'Was sind TOM?',
'Welche Massnahmen sind erforderlich?',
'Wie dokumentiere ich Verschluesselung?',
],
dsfa: [
'Was ist eine DSFA?',
'Wann ist eine DSFA verpflichtend?',
'Wie bewerte ich Risiken?',
],
loeschfristen: [
'Wie definiere ich Loeschfristen?',
'Was ist der Unterschied zwischen Loeschpflicht und Aufbewahrungspflicht?',
'Wann muss ich Daten loeschen?',
],
default: [
'Wie starte ich mit dem SDK?',
'Was ist der erste Schritt?',
'Welche Compliance-Anforderungen gelten fuer KI-Systeme?',
],
}
// =============================================================================
// TYPES
// =============================================================================
export interface Message {
id: string
role: 'user' | 'agent'
content: string
timestamp: Date
}
// =============================================================================
// EmptyState — shown when no messages yet
// =============================================================================
interface EmptyStateProps {
exampleQuestions: string[]
onExampleClick: (question: string) => void
}
export function AdvisorEmptyState({ exampleQuestions, onExampleClick }: EmptyStateProps) {
return (
<div className="text-center py-8">
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
</div>
<h3 className="text-sm font-medium text-gray-900 mb-2">Willkommen beim Compliance Advisor</h3>
<p className="text-xs text-gray-500 mb-4">Stellen Sie Fragen zu DSGVO, KI-Verordnung und mehr.</p>
<div className="text-left space-y-2">
<p className="text-xs font-medium text-gray-700 mb-2">Beispielfragen:</p>
{exampleQuestions.map((question, idx) => (
<button
key={idx}
onClick={() => onExampleClick(question)}
className="w-full text-left px-3 py-2 text-xs bg-white hover:bg-purple-50 border border-gray-200 rounded-lg transition-colors text-gray-700"
>
{question}
</button>
))}
</div>
</div>
)
}
// =============================================================================
// MessageList — renders messages + typing indicator
// =============================================================================
interface MessageListProps {
messages: Message[]
isTyping: boolean
messagesEndRef: React.RefObject<HTMLDivElement | null>
}
export function AdvisorMessageList({ messages, isTyping, messagesEndRef }: MessageListProps) {
return (
<>
{messages.map((message) => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] rounded-lg px-3 py-2 ${message.role === 'user' ? 'bg-indigo-600 text-white' : 'bg-white border border-gray-200 text-gray-800'}`}>
<p className={`text-sm ${message.role === 'agent' ? 'whitespace-pre-wrap' : ''}`}>
{message.content || (message.role === 'agent' && isTyping ? '' : message.content)}
</p>
<p className={`text-xs mt-1 ${message.role === 'user' ? 'text-indigo-200' : 'text-gray-400'}`}>
{message.timestamp.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
</div>
))}
{isTyping && (
<div className="flex justify-start">
<div className="bg-white border border-gray-200 rounded-lg px-3 py-2">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</>
)
}