import { useState, useEffect, useCallback } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import * as api from '../api'; import type { Site, Room, Rack } from '../types'; interface TreeRoom extends Room { racks: Rack[]; expanded: boolean; } interface TreeSite extends Site { rooms: TreeRoom[]; expanded: boolean; } export default function Sidebar() { const [tree, setTree] = useState([]); const [search, setSearch] = useState(''); const [searchResults, setSearchResults] = useState> | null>(null); const [loading, setLoading] = useState(true); const [addSiteName, setAddSiteName] = useState(''); const [showAddSite, setShowAddSite] = useState(false); const navigate = useNavigate(); const location = useLocation(); const loadTree = useCallback(async () => { const sites = await api.getSites(); setTree(sites.map(s => ({ ...s, rooms: [], expanded: false }))); setLoading(false); }, []); useEffect(() => { loadTree(); }, [loadTree]); const expandSite = async (siteId: string) => { setTree(prev => prev.map(s => { if (s.id !== siteId) return s; if (s.expanded) return { ...s, expanded: false }; return { ...s, expanded: true }; })); // Load rooms if not loaded setTree(prev => { const site = prev.find(s => s.id === siteId); if (site && site.rooms.length === 0) { api.getSite(siteId).then(full => { setTree(p => p.map(s => s.id === siteId ? { ...s, rooms: (full.rooms ?? []).map(r => ({ ...r, racks: [], expanded: false })) } : s )); }); } return prev; }); }; const expandRoom = async (siteId: string, roomId: string) => { setTree(prev => prev.map(s => s.id !== siteId ? s : { ...s, rooms: s.rooms.map(r => { if (r.id !== roomId) return r; if (r.expanded) return { ...r, expanded: false }; // Load racks if (r.racks.length === 0) { api.getRacks(roomId).then(racks => { setTree(p => p.map(ss => ss.id !== siteId ? ss : { ...ss, rooms: ss.rooms.map(rr => rr.id === roomId ? { ...rr, racks } : rr), })); }); } return { ...r, expanded: true }; }), })); }; const handleAddSite = async (e: React.FormEvent) => { e.preventDefault(); if (!addSiteName.trim()) return; const site = await api.createSite({ name: addSiteName.trim() }); setAddSiteName(''); setShowAddSite(false); await loadTree(); navigate(`/sites/${site.id}`); }; useEffect(() => { if (!search.trim()) { setSearchResults(null); return; } const t = setTimeout(async () => { const r = await api.search(search); setSearchResults(r); }, 300); return () => clearTimeout(t); }, [search]); const isActive = (path: string) => location.pathname === path; return ( ); }