Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/lib/seam/access-codes/use-access-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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)
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const name = 'seam-access-code-table'

export const props: ElementProps<AccessCodeTableProps> = {
deviceId: 'string',
accessCodeFilter: 'function',
accessCodeComparator: 'function',
onAccessCodeClick: 'function',
preventDefaultOnAccessCodeClick: 'boolean',
onBack: 'function',
Expand Down
60 changes: 38 additions & 22 deletions src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import classNames from 'classnames'
import { useCallback, useState } from 'react'
import { useCallback, 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'
Expand All @@ -26,19 +27,40 @@ 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
onAccessCodeClick?: (accessCodeId: string) => void
preventDefaultOnAccessCodeClick?: boolean
onBack?: () => void
className?: string
}

type AccessCode = UseAccessCodesData[number]

const defaultAccessCodeFilter = (
accessCode: AccessCode,
searchInputValue: string
) => {
const value = searchInputValue.trim()
if (value === '') return true
return new RegExp(value, 'i').test(accessCode.name ?? '')
}

export function AccessCodeTable({
deviceId,
onAccessCodeClick = () => {},
preventDefaultOnAccessCodeClick = false,
onBack,
accessCodeFilter = defaultAccessCodeFilter,
accessCodeComparator = compareByCreatedAtDesc,
className,
}: AccessCodeTableProps): JSX.Element | null {
}: AccessCodeTableProps): JSX.Element {
const { accessCodes } = useAccessCodes({
device_id: deviceId,
})
Expand All @@ -47,7 +69,15 @@ export function AccessCodeTable({
string | null
>(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 handleAccessCodeClick = useCallback(
(accessCodeId: string): void => {
Expand All @@ -74,36 +104,22 @@ 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 (
<div className={classNames('seam-access-code-table', className)}>
<ContentHeader onBack={onBack} />
<TableHeader>
<TableTitle>
{t.accessCodes} <Caption>({accessCodeCount})</Caption>
{t.accessCodes} <Caption>({filteredAccessCodes.length})</Caption>
</TableTitle>
<SearchTextField
value={searchTerm}
onChange={setSearchTerm}
disabled={accessCodeCount === 0}
value={searchInputValue}
onChange={setSearchInputValue}
disabled={(accessCodes?.length ?? 0) === 0}
/>
</TableHeader>
<TableBody>
<Body
accessCodes={filteredCodes}
accessCodes={filteredAccessCodes}
onAccessCodeClick={handleAccessCodeClick}
/>
</TableBody>
Expand Down
2 changes: 2 additions & 0 deletions src/lib/seam/components/DeviceTable/DeviceTable.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const name = 'seam-device-table'

export const props: ElementProps<DeviceTableProps> = {
deviceIds: 'json',
deviceFilter: 'function',
deviceComparator: 'function',
onDeviceClick: 'function',
preventDefaultOnDeviceClick: 'boolean',
onBack: 'function',
Expand Down
50 changes: 28 additions & 22 deletions src/lib/seam/components/DeviceTable/DeviceTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import classNames from 'classnames'
import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'

import { compareByCreatedAtDesc } from 'lib/dates.js'
import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
import {
type DeviceFilter,
Expand All @@ -19,28 +20,47 @@ 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
onDeviceClick?: (deviceId: string) => void
preventDefaultOnDeviceClick?: boolean
onBack?: () => void
className?: string
}

const defaultDeviceFilter = (device: Device, searchInputValue: string) => {
const value = searchInputValue.trim()
if (value === '') return true
return new RegExp(value, 'i').test(device.properties.name ?? '')
}

export function DeviceTable({
deviceIds,
onDeviceClick = () => {},
preventDefaultOnDeviceClick = false,
onBack,
deviceFilter = defaultDeviceFilter,
deviceComparator = compareByCreatedAtDesc,
className,
}: DeviceTableProps = {}): JSX.Element | null {
}: DeviceTableProps = {}): JSX.Element {
const { devices, isLoading, isError, error } = useDevices({
device_ids: deviceIds,
})

const [selectedDeviceId, setSelectedDeviceId] = useState<string | null>(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 handleDeviceClick = useCallback(
(deviceId: string): void => {
Expand Down Expand Up @@ -71,31 +91,17 @@ export function DeviceTable({
return <p className={className}>{error?.message}</p>
}

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 (
<div className={classNames('seam-device-table', className)}>
<ContentHeader onBack={onBack} />
<TableHeader>
<TableTitle>
{t.devices} <Caption>({deviceCount})</Caption>
{t.devices} <Caption>({filteredDevices.length})</Caption>
</TableTitle>
<SearchTextField
value={searchTerm}
onChange={setSearchTerm}
disabled={deviceCount === 0}
value={searchInputValue}
onChange={setSearchInputValue}
disabled={(devices?.length ?? 0) === 0}
/>
</TableHeader>
<TableBody>
Expand Down
4 changes: 1 addition & 3 deletions src/lib/seam/devices/use-devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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.
Expand Down