From 9b8f15bec30fbf058b7970052cdfb53954ed25ce Mon Sep 17 00:00:00 2001 From: Pouzor Date: Fri, 26 Jun 2026 13:10:12 +0200 Subject: [PATCH] feat: show Z-Wave scan runs in Scan History Scan History now recognises kind=zwave: dedicated Z-Wave filter chip, badge (RadioTower, orange) and import-done toast, instead of falling back to the generic IP type. ha-relevant: yes --- .../components/modals/ScanHistoryModal.tsx | 35 +++++++++++++------ .../__tests__/ScanHistoryModal.test.tsx | 21 +++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/modals/ScanHistoryModal.tsx b/frontend/src/components/modals/ScanHistoryModal.tsx index b76e919..dcbb348 100644 --- a/frontend/src/components/modals/ScanHistoryModal.tsx +++ b/frontend/src/components/modals/ScanHistoryModal.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback, useRef } from 'react' -import { RefreshCw, X, Loader2, StopCircle, Clock, ScanLine, Network, Inbox } from 'lucide-react' +import { RefreshCw, X, Loader2, StopCircle, Clock, ScanLine, Network, RadioTower, Inbox } from 'lucide-react' import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { scanApi } from '@/api/client' @@ -22,7 +22,18 @@ interface ScanHistoryModalProps { onClose: () => void } -type KindFilter = 'all' | 'ip' | 'zigbee' +type KindFilter = 'all' | 'ip' | 'zigbee' | 'zwave' + +/** Normalise a ScanRun.kind into one of the known display kinds. */ +function runKind(kind: string | undefined): 'ip' | 'zigbee' | 'zwave' { + return kind === 'zigbee' ? 'zigbee' : kind === 'zwave' ? 'zwave' : 'ip' +} + +const KIND_META = { + ip: { label: 'IP', color: '#a855f7' }, + zigbee: { label: 'Zigbee', color: '#00d4ff' }, + zwave: { label: 'Z-Wave', color: '#ff6e00' }, +} as const type StatusFilter = 'all' | 'running' | 'done' | 'error' | 'cancelled' const STATUS_FILTERS: { key: StatusFilter; label: string }[] = [ @@ -37,6 +48,7 @@ const KIND_FILTERS: { key: KindFilter; label: string }[] = [ { key: 'all', label: 'All' }, { key: 'ip', label: 'IP' }, { key: 'zigbee', label: 'Zigbee' }, + { key: 'zwave', label: 'Z-Wave' }, ] function statusColor(s: string): string { @@ -90,8 +102,9 @@ export function ScanHistoryModal({ open, onClose }: ScanHistoryModalProps) { toast.error(`Scan failed: ${run.error ?? 'unknown error'}`) } if (prev?.status === 'running' && run.status === 'done') { - if (run.kind === 'zigbee') { - toast.success(`Zigbee import done — ${run.devices_found} device${run.devices_found !== 1 ? 's' : ''}`) + if (run.kind === 'zigbee' || run.kind === 'zwave') { + const label = run.kind === 'zwave' ? 'Z-Wave' : 'Zigbee' + toast.success(`${label} import done — ${run.devices_found} device${run.devices_found !== 1 ? 's' : ''}`) } useCanvasStore.getState().notifyScanDeviceFound() } @@ -143,7 +156,7 @@ export function ScanHistoryModal({ open, onClose }: ScanHistoryModalProps) { } const filtered = runs.filter((r) => { - const k = r.kind === 'zigbee' ? 'zigbee' : 'ip' + const k = runKind(r.kind) if (kindFilter !== 'all' && k !== kindFilter) return false if (statusFilter !== 'all' && r.status !== statusFilter) return false return true @@ -216,7 +229,9 @@ export function ScanHistoryModal({ open, onClose }: ScanHistoryModalProps) { )} {filtered.map((r) => { - const isZigbee = r.kind === 'zigbee' + const kind = runKind(r.kind) + const meta = KIND_META[kind] + const KindIcon = kind === 'zigbee' ? Network : kind === 'zwave' ? RadioTower : ScanLine return (
@@ -225,12 +240,10 @@ export function ScanHistoryModal({ open, onClose }: ScanHistoryModalProps) { {r.status === 'running' && } - {isZigbee ? : } - {isZigbee ? 'Zigbee' : 'IP'} + + {meta.label} {r.devices_found} found diff --git a/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx b/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx index 764c2d9..229eea7 100644 --- a/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx +++ b/frontend/src/components/modals/__tests__/ScanHistoryModal.test.tsx @@ -61,6 +61,17 @@ const ZIGBEE_RUN = { error: null, } +const ZWAVE_RUN = { + id: 'run-5', + status: 'done', + kind: 'zwave', + ranges: [], + devices_found: 5, + started_at: new Date().toISOString(), + finished_at: new Date().toISOString(), + error: null, +} + function renderModal() { return render( @@ -155,4 +166,14 @@ describe('ScanHistoryModal', () => { expect(screen.getByText('7 found')).toBeDefined() expect(screen.queryByText('3 found')).toBeNull() }) + + it('filters by zwave kind', async () => { + vi.mocked(scanApi.runs).mockResolvedValue({ data: [DONE_RUN, ZWAVE_RUN] } as never) + renderModal() + await waitFor(() => expect(screen.getAllByText('done').length).toBe(2)) + fireEvent.click(screen.getByRole('button', { name: 'Z-Wave' })) + // Only the zwave run (5 found) remains + expect(screen.getByText('5 found')).toBeDefined() + expect(screen.queryByText('3 found')).toBeNull() + }) })