- Refactoring: Error Boundaries, apiFetch Wrapper, Socket Status-Tracking - Refactoring: UI Kontrast (theme-aware colors), unused imports bereinigt - Symbol-Verwaltung: Neues Split-Panel (Meine Symbole + Bibliothek) - Symbol-Verwaltung: Umbenennen (TLF rot/blau), Duplikate erlaubt - Symbol-Verwaltung: Karten-Sidebar zeigt eigene Symbole bevorzugt - Schlauch-Labels: Groessere Schrift (13px/10px), verschiebbar (Drag) - Schema: TenantSymbol customName, sortOrder, unique constraint entfernt - Open Source Referenz entfernt (kostenloses Projekt)
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { useToast } from '@/components/ui/use-toast'
|
|
import { BookOpen, Plus, X } from 'lucide-react'
|
|
import { apiFetch, ApiError } from '@/lib/api'
|
|
|
|
interface DictWord {
|
|
id: string
|
|
word: string
|
|
scope: string
|
|
}
|
|
|
|
export function DictionaryTab() {
|
|
const { toast } = useToast()
|
|
const [globalDictWords, setGlobalDictWords] = useState<DictWord[]>([])
|
|
const [newGlobalWord, setNewGlobalWord] = useState('')
|
|
const [dictLoading, setDictLoading] = useState(false)
|
|
|
|
const fetchGlobalDict = async () => {
|
|
try {
|
|
const data = await apiFetch<{ words: DictWord[] }>('/api/dictionary?scope=GLOBAL', { silent: true })
|
|
if (data?.words) setGlobalDictWords(data.words)
|
|
} catch {}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchGlobalDict()
|
|
}, [])
|
|
|
|
const handleAdd = async () => {
|
|
if (!newGlobalWord.trim()) return
|
|
setDictLoading(true)
|
|
try {
|
|
await apiFetch('/api/dictionary', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ word: newGlobalWord.trim(), scope: 'GLOBAL' }),
|
|
})
|
|
setNewGlobalWord('')
|
|
fetchGlobalDict()
|
|
toast({ title: 'Begriff hinzugefügt' })
|
|
} catch (err) {
|
|
toast({ title: 'Fehler', description: err instanceof ApiError ? err.message : 'Fehler', variant: 'destructive' })
|
|
} finally { setDictLoading(false) }
|
|
}
|
|
|
|
return (
|
|
<div className="border rounded-lg p-6">
|
|
<h3 className="font-semibold text-lg mb-2 flex items-center gap-2">
|
|
<BookOpen className="w-5 h-5" />
|
|
Globales Wörterbuch
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Globale Begriffe, die allen Mandanten als Journal-Vorschläge zur Verfügung stehen.
|
|
Mandanten können zusätzlich eigene Begriffe über ihre Wörterliste hinzufügen.
|
|
</p>
|
|
|
|
{/* Add new word */}
|
|
<div className="flex gap-2 mb-4">
|
|
<Input
|
|
placeholder="Neuer globaler Begriff, z.B. 'Leitung aufbauen'..."
|
|
value={newGlobalWord}
|
|
onChange={(e) => setNewGlobalWord(e.target.value)}
|
|
onKeyDown={async (e) => {
|
|
if (e.key === 'Enter' && newGlobalWord.trim()) handleAdd()
|
|
}}
|
|
className="flex-1"
|
|
disabled={dictLoading}
|
|
/>
|
|
<Button
|
|
onClick={handleAdd}
|
|
disabled={!newGlobalWord.trim() || dictLoading}
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
Hinzufügen
|
|
</Button>
|
|
</div>
|
|
|
|
{/* List of global words */}
|
|
{globalDictWords.length === 0 ? (
|
|
<div className="text-center text-muted-foreground py-8 text-sm border-2 border-dashed rounded-lg">
|
|
Noch keine globalen Begriffe hinterlegt.
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-wrap gap-2">
|
|
{globalDictWords.map((w) => (
|
|
<span key={w.id} className="inline-flex items-center gap-1 px-3 py-1.5 bg-green-50 dark:bg-green-950/30 text-green-700 dark:text-green-300 rounded-full text-sm border border-green-200 dark:border-green-800">
|
|
{w.word}
|
|
<button
|
|
onClick={async () => {
|
|
try {
|
|
await apiFetch(`/api/dictionary/${w.id}`, { method: 'DELETE' })
|
|
fetchGlobalDict()
|
|
toast({ title: 'Entfernt' })
|
|
} catch { toast({ title: 'Fehler', variant: 'destructive' }) }
|
|
}}
|
|
className="ml-1 hover:text-red-500 transition-colors"
|
|
>
|
|
<X className="w-3.5 h-3.5" />
|
|
</button>
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<p className="text-xs text-muted-foreground mt-4">
|
|
{globalDictWords.length} globale(r) Begriff(e). Diese erscheinen bei allen Mandanten als Vorschläge im Journal.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|