import { useState, useCallback, useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import GraphView from './components/GraphView'; import Sidebar from './components/Sidebar'; import NodePanel from './components/NodePanel'; import AddNodeModal from './components/AddNodeModal'; import QueryBar from './components/QueryBar'; import LinkModal from './components/LinkModal'; import Toast from './components/Toast'; import MaintenancePanel from './components/MaintenancePanel'; import PromptPanel from './components/PromptPanel'; export default function App() { const [selectedId, setSelectedId] = useState(null); const [showAddNode, setShowAddNode] = useState(false); const [linkFromId, setLinkFromId] = useState(null); const [toast, setToast] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const [showQuery, setShowQuery] = useState(false); const [showMaintenance, setShowMaintenance] = useState(false); const [showPrompt, setShowPrompt] = useState(false); const qc = useQueryClient(); const refresh = useCallback(() => { qc.invalidateQueries({ queryKey: ['graph'] }); qc.invalidateQueries({ queryKey: ['nodes'] }); }, [qc]); const notify = useCallback((msg: string) => { setToast(msg); setTimeout(() => setToast(null), 3000); }, []); const selectNode = useCallback((id: string) => { setSelectedId(id); setDrawerOpen(false); }, []); useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (selectedId) setSelectedId(null); else if (showPrompt) setShowPrompt(false); else if (showQuery) setShowQuery(false); else if (drawerOpen) setDrawerOpen(false); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [selectedId, drawerOpen, showQuery, showPrompt]); return (
{/* Full-screen graph */}
{/* Floating action buttons — bottom-left */}
{/* Sidebar drawer — slides in from left */} {drawerOpen && (
setDrawerOpen(false)}>
e.stopPropagation()} > { setDrawerOpen(false); setShowAddNode(true); }} onClose={() => setDrawerOpen(false)} />
)} {/* Node detail panel — slides in from right */} {selectedId && (
setSelectedId(null)} />
e.stopPropagation()} > setSelectedId(null)} onLink={(id) => setLinkFromId(id)} onRefresh={refresh} onNotify={notify} />
)} {/* Query panel — slides up from bottom */} {showQuery && (
setShowQuery(false)}>
e.stopPropagation()} > { setShowQuery(false); selectNode(id); }} onClose={() => setShowQuery(false)} />
)} {/* Prompt panel — slides up from bottom */} {showPrompt && (
setShowPrompt(false)}>
e.stopPropagation()} > setShowPrompt(false)} onDone={() => { refresh(); notify('Prompt executed'); }} />
)} {showAddNode && ( setShowAddNode(false)} onCreated={() => { refresh(); notify('Node created'); }} /> )} {linkFromId && ( setLinkFromId(null)} onCreated={() => { refresh(); notify('Edge created'); }} /> )} {showMaintenance && (
setShowMaintenance(false)} onNotify={(msg) => { notify(msg); refresh(); }} />
)} {toast && }
); }