import { useState, useCallback } from 'react' import { Plus, Save, ScanLine, ChevronLeft, ChevronRight, LayoutDashboard, Clock, EyeOff, Square, Settings, LogOut, Network, Type, PlusCircle, Pencil, Trash2 } from 'lucide-react' import { Logo } from '@/components/ui/Logo' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useCanvasStore } from '@/stores/canvasStore' import { useDesignStore } from '@/stores/designStore' import { useAuthStore } from '@/stores/authStore' import { designsApi } from '@/api/client' import { resolveDesignIcon, DEFAULT_DESIGN_ICON } from '@/utils/designIcons' import { DesignModal, type DesignFormData } from '@/components/modals/DesignModal' import type { Design } from '@/types' import { toast } from 'sonner' import { useLatestRelease } from '@/hooks/useLatestRelease' const STANDALONE = import.meta.env.VITE_STANDALONE === 'true' const PENDING_TRIGGERS: { kind: 'pending' | 'hidden'; icon: typeof ScanLine; label: string }[] = [ { kind: 'pending', icon: ScanLine, label: 'Pending Devices' }, { kind: 'hidden', icon: EyeOff, label: 'Hidden Devices' }, ] interface SidebarProps { onAddNode: () => void onAddGroupRect: () => void onAddText: () => void onScan: () => void onZigbeeImport: () => void onSave: () => void onOpenSettings: () => void onOpenHistory: () => void onOpenPending: (deviceId?: string, status?: 'pending' | 'hidden') => void } export function Sidebar({ onAddNode, onAddGroupRect, onAddText, onScan, onZigbeeImport, onSave, onOpenSettings, onOpenHistory, onOpenPending }: SidebarProps) { const [collapsed, setCollapsed] = useState(false) const logout = useAuthStore((s) => s.logout) const { designs, activeDesignId, setActiveDesign, addDesign, updateDesign, removeDesign } = useDesignStore() const [designSwitcherOpen, setDesignSwitcherOpen] = useState(false) const [designModal, setDesignModal] = useState<{ mode: 'create' | 'edit'; design?: Design } | null>(null) const handleDesignSubmit = useCallback(async (data: DesignFormData) => { if (!designModal) return try { if (designModal.mode === 'create') { const res = await designsApi.create({ name: data.name, icon: data.icon }) addDesign(res.data) } else if (designModal.design) { const res = await designsApi.update(designModal.design.id, { name: data.name, icon: data.icon }) updateDesign(res.data.id, { name: res.data.name, icon: res.data.icon }) } setDesignModal(null) } catch { toast.error(designModal.mode === 'create' ? 'Failed to create canvas' : 'Failed to update canvas') } }, [designModal, addDesign, updateDesign]) const handleDesignDelete = useCallback(async (d: Design) => { if (designs.length <= 1) { toast.error('Cannot delete the only canvas'); return } if (!window.confirm(`Delete canvas "${d.name}"? Its nodes and links will be removed.`)) return try { await designsApi.delete(d.id) removeDesign(d.id) toast.success('Canvas deleted') } catch { toast.error('Failed to delete canvas') } }, [designs.length, removeDesign]) const { nodes, hasUnsavedChanges } = useCanvasStore() const networkNodes = nodes.filter((n) => n.data.type !== 'groupRect' && n.data.type !== 'text') const onlineCount = networkNodes.filter((n) => n.data.status === 'online').length const offlineCount = networkNodes.filter((n) => n.data.status === 'offline').length const handleScan = useCallback(() => { onScan() }, [onScan]) return ( ) } function VersionBadge() { const current = __APP_VERSION__ const { latest, hasUpdate } = useLatestRelease(current) return (
v{current} {hasUpdate && latest && ( ↑ v{latest.version} available )}
) } interface SidebarItemProps { icon: React.ElementType label: string collapsed: boolean active?: boolean badge?: boolean accent?: boolean onClick?: () => void } function SidebarItem({ icon: Icon, label, collapsed, active, badge, accent, onClick }: SidebarItemProps) { const btn = ( ) if (collapsed) { return ( {btn} {label} ) } return btn }