diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 635c149..54dbfb5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -44,7 +44,7 @@ export default function App() { const [themeModalOpen, setThemeModalOpen] = useState(false) const [searchOpen, setSearchOpen] = useState(false) - const [sidebarForceView, setSidebarForceView] = useState<'pending' | undefined>(undefined) + const [sidebarForceView, setSidebarForceView] = useState<'pending' | 'history' | undefined>(undefined) const [highlightPendingId, setHighlightPendingId] = useState(undefined) const [shortcutsOpen, setShortcutsOpen] = useState(false) const [addNodeOpen, setAddNodeOpen] = useState(false) @@ -454,7 +454,11 @@ export default function App() { setScanConfigOpen(false)} - onScanNow={() => toast.success('Scan triggered')} + onScanNow={() => { + toast.success('Network scan started — check Scan History for results') + setSidebarForceView(undefined) + setTimeout(() => setSidebarForceView('history'), 0) + }} /> )} diff --git a/frontend/src/components/modals/ScanConfigModal.tsx b/frontend/src/components/modals/ScanConfigModal.tsx index bc184fb..f1d3742 100644 --- a/frontend/src/components/modals/ScanConfigModal.tsx +++ b/frontend/src/components/modals/ScanConfigModal.tsx @@ -24,29 +24,22 @@ export function ScanConfigModal({ open, onClose, onScanNow }: ScanConfigModalPro .catch(() => {/* use defaults */}) }, [open]) - const handleSave = async () => { + const handleScanNow = async () => { const cleaned = ranges.map((r) => r.trim()).filter(Boolean) if (cleaned.length === 0) { toast.error('Add at least one IP range'); return } setSaving(true) try { await scanApi.saveConfig({ ranges: cleaned }) - toast.success('Scan config saved') + await scanApi.trigger() + onScanNow() onClose() } catch { - toast.error('Failed to save config') + toast.error('Failed to start scan') } finally { setSaving(false) } } - const handleScanNow = async () => { - const cleaned = ranges.map((r) => r.trim()).filter(Boolean) - if (cleaned.length === 0) { toast.error('Add at least one IP range'); return } - await handleSave() - onScanNow() - onClose() - } - return ( !v && onClose()}> diff --git a/frontend/src/components/modals/__tests__/ScanConfigModal.test.tsx b/frontend/src/components/modals/__tests__/ScanConfigModal.test.tsx index 2eb86a4..988be69 100644 --- a/frontend/src/components/modals/__tests__/ScanConfigModal.test.tsx +++ b/frontend/src/components/modals/__tests__/ScanConfigModal.test.tsx @@ -6,6 +6,7 @@ vi.mock('@/api/client', () => ({ scanApi: { getConfig: vi.fn(), saveConfig: vi.fn(), + trigger: vi.fn(), }, })) vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) @@ -20,6 +21,8 @@ describe('ScanConfigModal', () => { vi.mocked(scanApi.getConfig).mockResolvedValue(defaultConfig as never) vi.mocked(scanApi.saveConfig).mockReset() vi.mocked(scanApi.saveConfig).mockResolvedValue({} as never) + vi.mocked(scanApi.trigger).mockReset() + vi.mocked(scanApi.trigger).mockResolvedValue({} as never) vi.mocked(toast.success).mockReset() vi.mocked(toast.error).mockReset() }) @@ -72,7 +75,7 @@ describe('ScanConfigModal', () => { expect(scanApi.saveConfig).not.toHaveBeenCalled() }) - it('calls onScanNow after saving on "Scan Now" click', async () => { + it('saves config, triggers scan, calls onScanNow and closes on "Scan Now" click', async () => { const onScanNow = vi.fn() const onClose = vi.fn() render() @@ -80,7 +83,9 @@ describe('ScanConfigModal', () => { fireEvent.click(screen.getByRole('button', { name: 'Scan Now' })) await waitFor(() => { expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['192.168.1.0/24'] }) + expect(scanApi.trigger).toHaveBeenCalledOnce() expect(onScanNow).toHaveBeenCalledOnce() + expect(onClose).toHaveBeenCalledOnce() }) }) diff --git a/frontend/src/components/panels/Sidebar.tsx b/frontend/src/components/panels/Sidebar.tsx index e0f2b9c..22e63dd 100644 --- a/frontend/src/components/panels/Sidebar.tsx +++ b/frontend/src/components/panels/Sidebar.tsx @@ -53,15 +53,8 @@ export function Sidebar({ onAddNode, onAddGroupRect, onScan, onSave, onNodeAppro const onlineCount = networkNodes.filter((n) => n.data.status === 'online').length const offlineCount = networkNodes.filter((n) => n.data.status === 'offline').length - const handleScan = useCallback(async () => { - try { - await scanApi.trigger() - toast.success('Network scan started — check Scan History for results') - setActiveView('history') - onScan() - } catch { - toast.error('Failed to trigger scan') - } + const handleScan = useCallback(() => { + onScan() }, [onScan]) return ( diff --git a/frontend/src/components/panels/__tests__/Sidebar.test.tsx b/frontend/src/components/panels/__tests__/Sidebar.test.tsx index 79ce5eb..ddfbbbb 100644 --- a/frontend/src/components/panels/__tests__/Sidebar.test.tsx +++ b/frontend/src/components/panels/__tests__/Sidebar.test.tsx @@ -222,20 +222,12 @@ describe('Sidebar', () => { // ── Scan action ──────────────────────────────────────────────────────────── - it('calls scanApi.trigger and onScan prop when Scan Network is clicked', async () => { - const { scanApi } = await import('@/api/client') + it('calls onScan prop when Scan Network is clicked (scan trigger moved to ScanConfigModal)', () => { render() fireEvent.click(screen.getByText('Scan Network')) - await waitFor(() => expect(scanApi.trigger).toHaveBeenCalledOnce()) expect(defaultProps.onScan).toHaveBeenCalledOnce() }) - it('switches to Scan History view after scan is triggered', async () => { - render() - fireEvent.click(screen.getByText('Scan Network')) - await waitFor(() => expect(screen.getByText('History')).toBeInTheDocument()) - }) - // ── Navigation ───────────────────────────────────────────────────────────── it('shows Pending panel when Pending Devices nav item is clicked', async () => {