fix: keep approved devices in inventory after bulk approve

Bulk/single approve optimistically stripped approved rows from the local list,
but scanApi.pending() still returns them (on-canvas, with an 'In N canvas'
badge). The list went empty until the modal was reopened. Reload after approve
instead of stripping, so approved rows stay visible with a fresh canvas_count.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-07-07 21:23:28 +02:00
parent 9437a74147
commit b09ebb5cd2
2 changed files with 28 additions and 2 deletions
@@ -287,8 +287,10 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
injectAutoEdges(res.data.edges)
const extra = res.data.edges_created > 0 ? ` (+${res.data.edges_created} link${res.data.edges_created !== 1 ? 's' : ''})` : ''
toast.success(`Approved ${nodeData.label}${extra}`)
setDevices((prev) => prev.filter((d) => d.id !== device.id))
// Keep the row (now on-canvas, shown with an "In N canvas" badge); reload
// for a fresh canvas_count rather than dropping it until reopen.
setSelected(null)
await load()
} catch {
toast.error('Failed to approve device')
}
@@ -353,8 +355,11 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
})
})
injectAutoEdges(res.data.edges)
setDevices((prev) => prev.filter((d) => !ids.includes(d.id)))
// Don't strip approved rows locally: the inventory keeps them with an
// "In N canvas" badge (that's what showOnCanvas toggles). Reload so they
// reappear with a fresh canvas_count instead of vanishing until reopen.
setSelectedIds(new Set())
await load()
const linkExtra = res.data.edges_created > 0 ? ` (+${res.data.edges_created} link${res.data.edges_created !== 1 ? 's' : ''})` : ''
toast.success(`Approved ${res.data.approved} device${res.data.approved !== 1 ? 's' : ''}${linkExtra}`)
} catch {
@@ -263,6 +263,27 @@ describe('PendingDevicesModal', () => {
await waitFor(() => expect(mockBulkApprove).toHaveBeenCalledWith(['dev-a', 'dev-b'], null))
})
it('keeps approved devices listed after bulk approve (reloads, not strips)', async () => {
// After approve, pending() still returns the rows (now on-canvas w/ badge).
mockPending
.mockResolvedValueOnce({ data: [DEVICE_IP, DEVICE_ZIGBEE] })
.mockResolvedValue({ data: [
{ ...DEVICE_IP, canvas_count: 1 },
{ ...DEVICE_ZIGBEE, canvas_count: 1 },
] })
render(<PendingDevicesModal {...baseProps} />)
await waitFor(() => expect(screen.getByTestId('pending-card-dev-a')).toBeInTheDocument())
fireEvent.click(screen.getByRole('button', { name: 'Select mode' }))
fireEvent.click(screen.getByTestId('pending-card-dev-a'))
fireEvent.click(screen.getByTestId('pending-card-dev-b'))
fireEvent.click(screen.getByRole('button', { name: /Approve \(2\)/ }))
await waitFor(() => expect(mockBulkApprove).toHaveBeenCalled())
// Reloaded, so rows remain visible instead of the list going empty.
await waitFor(() => expect(mockPending).toHaveBeenCalledTimes(2))
expect(screen.getByTestId('pending-card-dev-a')).toBeInTheDocument()
expect(screen.getByTestId('pending-card-dev-b')).toBeInTheDocument()
})
it('bulk approve carries the scanned MAC onto the canvas node (#168)', async () => {
render(<PendingDevicesModal {...baseProps} />)
await waitFor(() => expect(screen.getByTestId('pending-card-dev-a')).toBeInTheDocument())