'use client' import { useState, useEffect } from 'react' import { useDrag } from 'react-dnd' import { getEmptyImage } from 'react-dnd-html5-backend' import { Input } from '@/components/ui/input' import { ScrollArea } from '@/components/ui/scroll-area' import { Search, Flame, Droplets, AlertTriangle, Car, Users, Truck, Building, Target, Upload, Loader2, X, LayoutGrid, ChevronLeft, ChevronRight, Map, ClipboardList, PanelRightClose, PanelRightOpen, Shield, Wrench, Radio, MoreHorizontal, Heart, } from 'lucide-react' interface DisplaySymbol { id: string name: string imageUrl: string } interface DisplayCategory { id: string name: string symbols: DisplaySymbol[] } interface RightSidebarProps { onSymbolDrop: (iconId: string, coordinates: [number, number], imageUrl?: string) => void canEdit: boolean isOpen?: boolean onToggle?: () => void activeTab?: 'map' | 'journal' onTabChange?: (tab: 'map' | 'journal') => void isCollapsed?: boolean onToggleCollapse?: () => void tenantId?: string | null } const categoryIcons: Record = { 'Feuer / Brand': Flame, 'Wasser': Droplets, 'Gefahren / Stoffe': AlertTriangle, 'Rettung / Personen': Heart, 'Leitern / Geräte': Wrench, 'Gebäude / Schäden': Building, 'Einsatzführung': Radio, 'Organisationen': Shield, 'Entwicklung / Taktik': Target, 'Verschiedenes': MoreHorizontal, 'Eigene': Upload, } function DraggableSymbol({ symbol, canEdit }: { symbol: DisplaySymbol canEdit: boolean }) { const [{ isDragging }, drag, preview] = useDrag(() => ({ type: 'SYMBOL', item: { iconId: symbol.id, imageUrl: symbol.imageUrl }, canDrag: canEdit, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }), [symbol.id, symbol.imageUrl, canEdit]) // Suppress native HTML5 drag ghost — we use CustomDragLayer instead useEffect(() => { preview(getEmptyImage(), { captureDraggingState: true }) }, [preview]) return (
} className={` flex flex-col items-center gap-1 p-1.5 md:p-2 lg:p-2.5 rounded-lg border-2 transition-all border-transparent hover:border-border hover:bg-accent cursor-grab active:cursor-grabbing active:scale-95 ${isDragging ? 'opacity-0' : ''} ${!canEdit ? 'opacity-50 cursor-not-allowed' : ''} `} title={symbol.name} >
{symbol.name}
{symbol.name}
) } export function RightSidebar({ onSymbolDrop, canEdit, isOpen, onToggle, activeTab, onTabChange, isCollapsed, onToggleCollapse, tenantId }: RightSidebarProps) { const [searchQuery, setSearchQuery] = useState('') const [activeCategory, setActiveCategory] = useState('') const [categories, setCategories] = useState([]) const [tenantIcons, setTenantIcons] = useState([]) const [isLoading, setIsLoading] = useState(true) const [showTenantSection, setShowTenantSection] = useState(true) const [showLibrarySection, setShowLibrarySection] = useState(true) useEffect(() => { async function fetchIcons() { setIsLoading(true) try { const res = await fetch('/api/icons', { cache: 'no-store' }) if (res.ok) { const data = await res.json() const allCats: DisplayCategory[] = (data.categories || []) .filter((cat: any) => cat.icons && cat.icons.length > 0) .map((cat: any) => ({ id: cat.id, name: cat.name, symbols: cat.icons.map((icon: any) => ({ id: icon.id, name: icon.name, imageUrl: icon.url || `/api/icons/${icon.id}/image`, })), })) // Separate tenant-specific icons ("Eigene" category) from global library const eigene = allCats.find(c => c.name === 'Eigene') const globalCats = allCats.filter(c => c.name !== 'Eigene') // Merge: mySymbols (custom collection) + legacy "Eigene" category uploads const mySymbols: DisplaySymbol[] = (data.mySymbols || []).map((s: any) => ({ id: s.id, name: s.name, imageUrl: s.url || `/api/icons/${s.id}/image`, })) const legacyOwn = eigene?.symbols || [] // Deduplicate: mySymbols takes priority over legacy const mySymbolIds = new Set(mySymbols.map(s => s.id)) const mergedTenant = [...mySymbols, ...legacyOwn.filter(s => !mySymbolIds.has(s.id))] setTenantIcons(mergedTenant) setCategories(globalCats) if (globalCats.length > 0) setActiveCategory(globalCats[0].id) // Auto-collapse library if tenant has own symbols if (mergedTenant.length > 0) { setShowLibrarySection(false) } } } catch (err) { console.error('Failed to load icons:', err) } finally { setIsLoading(false) } } fetchIcons() }, [tenantId]) const filteredCategories = categories.map((cat) => ({ ...cat, symbols: cat.symbols.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()) ), })) const filteredTenantIcons = tenantIcons.filter(s => s.name.toLowerCase().includes(searchQuery.toLowerCase()) ) const currentCategory = filteredCategories.find((c) => c.id === activeCategory) const totalSymbols = categories.reduce((sum, c) => sum + c.symbols.length, 0) + tenantIcons.length return ( <> {/* Mobile toggle button */} {!isOpen && onToggle && ( )} {/* Backdrop on mobile */} {isOpen && onToggle && (
)} {/* Collapsed state: thin strip with toggle + tab icons */} {isCollapsed && ( )} ) }