From b8466f4077867e67390ce91e8050a19e9f15fa88 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 6 Jun 2023 12:32:46 -0700 Subject: [PATCH 1/4] feat: Add deviceFilter and deviceComparator to DeviceTable --- .../DeviceTable/DeviceTable.element.ts | 2 + .../components/DeviceTable/DeviceTable.tsx | 46 +++++++++++-------- src/lib/seam/devices/use-devices.ts | 4 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/lib/seam/components/DeviceTable/DeviceTable.element.ts b/src/lib/seam/components/DeviceTable/DeviceTable.element.ts index 95cedc9be..e1ada8213 100644 --- a/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +++ b/src/lib/seam/components/DeviceTable/DeviceTable.element.ts @@ -5,6 +5,8 @@ import type { DeviceTableProps } from './DeviceTable.js' export const name = 'seam-device-table' export const props: ElementProps = { + deviceFilter: 'function', + deviceComparator: 'function', onBack: 'function', className: 'string', } diff --git a/src/lib/seam/components/DeviceTable/DeviceTable.tsx b/src/lib/seam/components/DeviceTable/DeviceTable.tsx index a41a96073..4c8679d24 100644 --- a/src/lib/seam/components/DeviceTable/DeviceTable.tsx +++ b/src/lib/seam/components/DeviceTable/DeviceTable.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames' -import { useState } from 'react' +import { useMemo, useState } from 'react' +import { compareByCreatedAtDesc } from 'lib/dates.js' import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js' import { type DeviceFilter, @@ -19,23 +20,44 @@ import { TableTitle } from 'lib/ui/Table/TableTitle.js' import { SearchTextField } from 'lib/ui/TextField/SearchTextField.js' import { Caption } from 'lib/ui/typography/Caption.js' +type Device = UseDevicesData[number] + export interface DeviceTableProps { deviceIds?: string[] + deviceFilter?: (device: Device, searchInputValue: string) => boolean + deviceComparator?: (deviceA: Device, deviceB: Device) => number onBack?: () => void className?: string } +const defaultDeviceFilter = (device: Device, searchInputValue: string) => { + if (searchInputValue === '') return true + return new RegExp(searchInputValue, 'i').test(device.properties.name ?? '') +} + export function DeviceTable({ deviceIds, onBack, + deviceFilter = defaultDeviceFilter, + deviceComparator = compareByCreatedAtDesc, className, -}: DeviceTableProps = {}): JSX.Element | null { +}: DeviceTableProps = {}): JSX.Element { const { devices, isLoading, isError, error } = useDevices({ device_ids: deviceIds, }) const [selectedDeviceId, selectDevice] = useState(null) - const [searchTerm, setSearchTerm] = useState('') + const [searchInputValue, setSearchInputValue] = useState('') + + const filteredDevices = useMemo( + () => + devices + ?.filter((device) => deviceFilter(device, searchInputValue)) + ?.sort(deviceComparator) ?? [], + [devices, searchInputValue, deviceFilter, deviceComparator] + ) + + const deviceCount = devices?.length ?? 0 if (selectedDeviceId != null) { return ( @@ -57,20 +79,6 @@ export function DeviceTable({ return

{error?.message}

} - if (devices == null) { - return null - } - - const deviceCount = devices.length - - const filteredDevices = devices.filter((device) => { - if (searchTerm === '') { - return true - } - - return new RegExp(searchTerm, 'i').test(device.properties.name) - }) - return (
@@ -79,8 +87,8 @@ export function DeviceTable({ {t.devices} ({deviceCount}) diff --git a/src/lib/seam/devices/use-devices.ts b/src/lib/seam/devices/use-devices.ts index 5b10e0678..5c88bf4db 100644 --- a/src/lib/seam/devices/use-devices.ts +++ b/src/lib/seam/devices/use-devices.ts @@ -7,7 +7,6 @@ import type { SeamError, } from 'seamapi' -import { compareByCreatedAtDesc } from 'lib/dates.js' import { useSeamClient } from 'lib/seam/use-seam-client.js' import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' @@ -26,8 +25,7 @@ export function useDevices( queryKey: ['devices', 'list', params], queryFn: async () => { if (client == null) return [] - const devices = await client?.devices.list(params) - return devices.sort(compareByCreatedAtDesc) + return await client?.devices.list(params) }, onSuccess: (devices) => { // Prime cache for each device. From 3b9836de102670fe61ea38c09ed3a199204e8d58 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 6 Jun 2023 12:53:30 -0700 Subject: [PATCH 2/4] feat: Add accessCodeFilter and accessCodeComparator props to AccessCodeTable --- src/lib/seam/access-codes/use-access-codes.ts | 4 +- .../AccessCodeTable.element.ts | 2 + .../AccessCodeTable/AccessCodeTable.tsx | 61 ++++++++++++------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/lib/seam/access-codes/use-access-codes.ts b/src/lib/seam/access-codes/use-access-codes.ts index 82b9560de..5cdaff790 100644 --- a/src/lib/seam/access-codes/use-access-codes.ts +++ b/src/lib/seam/access-codes/use-access-codes.ts @@ -6,7 +6,6 @@ import type { SeamError, } from 'seamapi' -import { compareByCreatedAtDesc } from 'lib/dates.js' import { useSeamClient } from 'lib/seam/use-seam-client.js' import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' @@ -28,8 +27,7 @@ export function useAccessCodes( queryKey: ['access_codes', 'list', normalizedParams], queryFn: async () => { if (client == null) return [] - const accessCodes = await client?.accessCodes.list(normalizedParams) - return accessCodes.sort(compareByCreatedAtDesc) + return await client?.accessCodes.list(normalizedParams) }, }) diff --git a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts index 087a48106..181019dee 100644 --- a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +++ b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts @@ -6,6 +6,8 @@ export const name = 'seam-access-code-table' export const props: ElementProps = { deviceId: 'string', + accessCodeFilter: 'function', + accessCodeComparator: 'function', onBack: 'function', className: 'string', } diff --git a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx index ed4957203..130eecaa8 100644 --- a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +++ b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' -import { useState } from 'react' -import type { AccessCode } from 'seamapi' +import { useMemo, useState } from 'react' +import { compareByCreatedAtDesc } from 'lib/dates.js' import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js' import { CopyIcon } from 'lib/icons/Copy.js' import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js' @@ -27,15 +27,35 @@ import { Title } from 'lib/ui/typography/Title.js' export interface AccessCodeTableProps { deviceId: string + accessCodeFilter?: ( + accessCode: AccessCode, + searchInputValue: string + ) => boolean + accessCodeComparator?: ( + accessCodeA: AccessCode, + accessCodeB: AccessCode + ) => number onBack?: () => void className?: string } +type AccessCode = UseAccessCodesData[number] + +const defaultAccessCodeFilter = ( + accessCode: AccessCode, + searchInputValue: string +) => { + if (searchInputValue === '') return true + return new RegExp(searchInputValue, 'i').test(accessCode.name ?? '') +} + export function AccessCodeTable({ deviceId, onBack, + accessCodeFilter = defaultAccessCodeFilter, + accessCodeComparator = compareByCreatedAtDesc, className, -}: AccessCodeTableProps): JSX.Element | null { +}: AccessCodeTableProps): JSX.Element { const { accessCodes } = useAccessCodes({ device_id: deviceId, }) @@ -43,7 +63,17 @@ export function AccessCodeTable({ const [selectedAccessCode, selectAccessCode] = useState( null ) - const [searchTerm, setSearchTerm] = useState('') + const [searchInputValue, setSearchInputValue] = useState('') + + const filteredAccessCodes = useMemo( + () => + accessCodes + ?.filter((accessCode) => accessCodeFilter(accessCode, searchInputValue)) + ?.sort(accessCodeComparator) ?? [], + [accessCodes, searchInputValue, accessCodeFilter, accessCodeComparator] + ) + + const accessCodeCount = accessCodes?.length ?? 0 if (selectedAccessCode != null) { return ( @@ -57,20 +87,6 @@ export function AccessCodeTable({ ) } - if (accessCodes == null) { - return null - } - - const filteredCodes = accessCodes.filter((accessCode) => { - if (searchTerm === '') { - return true - } - - return new RegExp(searchTerm, 'i').test(accessCode.name ?? '') - }) - - const accessCodeCount = accessCodes.length - return (
@@ -79,13 +95,16 @@ export function AccessCodeTable({ {t.accessCodes} ({accessCodeCount}) - +
) From a318abf53347a082113a13a2e7e9c5215dedb08f Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 6 Jun 2023 14:28:04 -0700 Subject: [PATCH 3/4] Trim searchInputValue in default filters --- src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx | 5 +++-- src/lib/seam/components/DeviceTable/DeviceTable.tsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx index 130eecaa8..9fe727d17 100644 --- a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +++ b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx @@ -45,8 +45,9 @@ const defaultAccessCodeFilter = ( accessCode: AccessCode, searchInputValue: string ) => { - if (searchInputValue === '') return true - return new RegExp(searchInputValue, 'i').test(accessCode.name ?? '') + const value = searchInputValue.trim() + if (value === '') return true + return new RegExp(value, 'i').test(accessCode.name ?? '') } export function AccessCodeTable({ diff --git a/src/lib/seam/components/DeviceTable/DeviceTable.tsx b/src/lib/seam/components/DeviceTable/DeviceTable.tsx index 4c8679d24..384fbd5c0 100644 --- a/src/lib/seam/components/DeviceTable/DeviceTable.tsx +++ b/src/lib/seam/components/DeviceTable/DeviceTable.tsx @@ -31,8 +31,9 @@ export interface DeviceTableProps { } const defaultDeviceFilter = (device: Device, searchInputValue: string) => { - if (searchInputValue === '') return true - return new RegExp(searchInputValue, 'i').test(device.properties.name ?? '') + const value = searchInputValue.trim() + if (value === '') return true + return new RegExp(value, 'i').test(device.properties.name ?? '') } export function DeviceTable({ From 2fdef95f0c3b6e0fe4e8fc19523fc39e8b9f11b6 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 6 Jun 2023 15:35:41 -0700 Subject: [PATCH 4/4] fix: Count in table should match filtered result count --- src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx | 6 ++---- src/lib/seam/components/DeviceTable/DeviceTable.tsx | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx index 9fe727d17..4ed70532d 100644 --- a/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +++ b/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx @@ -74,8 +74,6 @@ export function AccessCodeTable({ [accessCodes, searchInputValue, accessCodeFilter, accessCodeComparator] ) - const accessCodeCount = accessCodes?.length ?? 0 - if (selectedAccessCode != null) { return ( - {t.accessCodes} ({accessCodeCount}) + {t.accessCodes} ({filteredAccessCodes.length}) diff --git a/src/lib/seam/components/DeviceTable/DeviceTable.tsx b/src/lib/seam/components/DeviceTable/DeviceTable.tsx index 384fbd5c0..b023ee217 100644 --- a/src/lib/seam/components/DeviceTable/DeviceTable.tsx +++ b/src/lib/seam/components/DeviceTable/DeviceTable.tsx @@ -58,8 +58,6 @@ export function DeviceTable({ [devices, searchInputValue, deviceFilter, deviceComparator] ) - const deviceCount = devices?.length ?? 0 - if (selectedDeviceId != null) { return ( - {t.devices} ({deviceCount}) + {t.devices} ({filteredDevices.length})