'use client' import React, { useState, useEffect, useRef, useMemo } from 'react' import { useRouter } from 'next/navigation' import { useSDK, SDK_STEPS, CommandType, CommandHistory, downloadExport } from '@/lib/sdk' // ============================================================================= // TYPES // ============================================================================= interface Suggestion { id: string type: CommandType label: string description: string shortcut?: string icon: React.ReactNode action: () => void | Promise } // ============================================================================= // ICONS // ============================================================================= const icons = { navigation: ( ), action: ( ), search: ( ), generate: ( ), help: ( ), } // ============================================================================= // COMMAND BAR // ============================================================================= interface CommandBarProps { onClose: () => void } export function CommandBar({ onClose }: CommandBarProps) { const router = useRouter() const { state, dispatch, goToStep } = useSDK() const inputRef = useRef(null) const [query, setQuery] = useState('') const [selectedIndex, setSelectedIndex] = useState(0) const [isLoading, setIsLoading] = useState(false) // Focus input on mount useEffect(() => { inputRef.current?.focus() }, []) // Generate suggestions based on query const suggestions = useMemo((): Suggestion[] => { const results: Suggestion[] = [] const lowerQuery = query.toLowerCase() // Navigation suggestions SDK_STEPS.forEach(step => { const matchesName = step.name.toLowerCase().includes(lowerQuery) || step.nameShort.toLowerCase().includes(lowerQuery) const matchesDescription = step.description.toLowerCase().includes(lowerQuery) if (!query || matchesName || matchesDescription) { results.push({ id: `nav-${step.id}`, type: 'NAVIGATION', label: `Gehe zu ${step.name}`, description: step.description, icon: icons.navigation, action: () => { goToStep(step.id) onClose() }, }) } }) // Action suggestions const actions: Suggestion[] = [ { id: 'action-new-usecase', type: 'ACTION', label: 'Neuen Anwendungsfall erstellen', description: 'Startet die Anwendungsfall-Erfassung', icon: icons.action, action: () => { goToStep('use-case-assessment') onClose() }, }, { id: 'action-export-pdf', type: 'ACTION', label: 'Als PDF exportieren', description: 'Exportiert Compliance-Bericht als PDF', icon: icons.action, action: async () => { setIsLoading(true) try { await downloadExport(state, 'pdf') } catch (error) { console.error('PDF export failed:', error) } finally { setIsLoading(false) onClose() } }, }, { id: 'action-export-zip', type: 'ACTION', label: 'Als ZIP exportieren', description: 'Exportiert alle Daten und Dokumente als ZIP-Archiv', icon: icons.action, action: async () => { setIsLoading(true) try { await downloadExport(state, 'zip') } catch (error) { console.error('ZIP export failed:', error) } finally { setIsLoading(false) onClose() } }, }, { id: 'action-export-json', type: 'ACTION', label: 'Als JSON exportieren', description: 'Exportiert den kompletten State als JSON', icon: icons.action, action: async () => { try { await downloadExport(state, 'json') } catch (error) { console.error('JSON export failed:', error) } finally { onClose() } }, }, { id: 'action-validate', type: 'ACTION', label: 'Checkpoint validieren', description: 'Validiert den aktuellen Schritt', icon: icons.action, action: () => { // Trigger validation onClose() }, }, ] actions.forEach(action => { if (!query || action.label.toLowerCase().includes(lowerQuery)) { results.push(action) } }) // Generate suggestions const generateSuggestions: Suggestion[] = [ { id: 'gen-dsfa', type: 'GENERATE', label: 'DSFA generieren', description: 'Generiert eine Datenschutz-Folgenabschätzung', icon: icons.generate, action: () => { goToStep('dsfa') onClose() }, }, { id: 'gen-tom', type: 'GENERATE', label: 'TOMs generieren', description: 'Generiert technische und organisatorische Maßnahmen', icon: icons.generate, action: () => { goToStep('tom') onClose() }, }, { id: 'gen-vvt', type: 'GENERATE', label: 'VVT generieren', description: 'Generiert das Verarbeitungsverzeichnis', icon: icons.generate, action: () => { goToStep('vvt') onClose() }, }, { id: 'gen-cookie', type: 'GENERATE', label: 'Cookie Banner generieren', description: 'Generiert Cookie-Consent-Banner Code', icon: icons.generate, action: () => { goToStep('cookie-banner') onClose() }, }, ] generateSuggestions.forEach(suggestion => { if (!query || suggestion.label.toLowerCase().includes(lowerQuery)) { results.push(suggestion) } }) // Help suggestions const helpSuggestions: Suggestion[] = [ { id: 'help-docs', type: 'HELP', label: 'Dokumentation öffnen', description: 'Öffnet die SDK-Dokumentation', icon: icons.help, action: () => { window.open('/docs/sdk', '_blank') onClose() }, }, { id: 'help-next', type: 'HELP', label: 'Was muss ich als nächstes tun?', description: 'Zeigt den nächsten empfohlenen Schritt', icon: icons.help, action: () => { // Show contextual help onClose() }, }, ] helpSuggestions.forEach(suggestion => { if (!query || suggestion.label.toLowerCase().includes(lowerQuery)) { results.push(suggestion) } }) return results.slice(0, 10) }, [query, state, goToStep, onClose]) // Keyboard navigation useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault() setSelectedIndex(prev => Math.min(prev + 1, suggestions.length - 1)) break case 'ArrowUp': e.preventDefault() setSelectedIndex(prev => Math.max(prev - 1, 0)) break case 'Enter': e.preventDefault() if (suggestions[selectedIndex]) { executeSuggestion(suggestions[selectedIndex]) } break case 'Escape': onClose() break } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [suggestions, selectedIndex, onClose]) // Reset selected index when query changes useEffect(() => { setSelectedIndex(0) }, [query]) const executeSuggestion = async (suggestion: Suggestion) => { // Add to history const historyEntry: CommandHistory = { id: `${Date.now()}`, query: suggestion.label, type: suggestion.type, timestamp: new Date(), success: true, } dispatch({ type: 'ADD_COMMAND_HISTORY', payload: historyEntry }) try { await suggestion.action() } catch (error) { console.error('Command execution failed:', error) } } const getTypeLabel = (type: CommandType): string => { switch (type) { case 'NAVIGATION': return 'Navigation' case 'ACTION': return 'Aktion' case 'SEARCH': return 'Suche' case 'GENERATE': return 'Generieren' case 'HELP': return 'Hilfe' default: return type } } return (
{/* Backdrop */}
{/* Dialog */}
{/* Search Input */}
setQuery(e.target.value)} placeholder="Suchen oder Befehl eingeben..." className="flex-1 text-lg bg-transparent border-none outline-none placeholder-gray-400" /> {isLoading && ( )} ESC
{/* Suggestions */}
{suggestions.length === 0 ? (
Keine Ergebnisse für "{query}"
) : ( suggestions.map((suggestion, index) => ( )) )}
{/* Footer */}
navigieren auswählen
AI Compliance SDK v1.0
) }