@seamapi/react 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/elements.js +4998 -4842
- package/dist/elements.js.map +1 -1
- package/dist/index.css +3 -0
- package/dist/index.css.map +1 -1
- package/dist/index.min.css +1 -1
- package/dist/index.min.css.map +1 -1
- package/lib/icons/TriangleWarningOutline.d.ts +2 -0
- package/lib/icons/TriangleWarningOutline.js +3 -0
- package/lib/icons/TriangleWarningOutline.js.map +1 -0
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +2 -1
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +3 -3
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
- package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.d.ts +2 -1
- package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js +8 -6
- package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.d.ts +10 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js +29 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js.map +1 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +30 -10
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
- package/lib/seam/components/DeviceDetails/DeviceDetails.d.ts +2 -1
- package/lib/seam/components/DeviceDetails/DeviceDetails.js +2 -2
- package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
- package/lib/seam/components/DeviceDetails/LockDeviceDetails.d.ts +1 -0
- package/lib/seam/components/DeviceDetails/LockDeviceDetails.js +4 -4
- package/lib/seam/components/DeviceDetails/LockDeviceDetails.js.map +1 -1
- package/lib/seam/components/DeviceTable/DeviceHealthBar.d.ts +3 -2
- package/lib/seam/components/DeviceTable/DeviceHealthBar.js +23 -11
- package/lib/seam/components/DeviceTable/DeviceHealthBar.js.map +1 -1
- package/lib/seam/components/DeviceTable/DeviceRow.js +19 -1
- package/lib/seam/components/DeviceTable/DeviceRow.js.map +1 -1
- package/lib/seam/components/DeviceTable/DeviceTable.d.ts +2 -1
- package/lib/seam/components/DeviceTable/DeviceTable.js +7 -3
- package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -1
- package/lib/ui/device/OnlineStatus.js +14 -4
- package/lib/ui/device/OnlineStatus.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/lib/icons/TriangleWarningOutline.tsx +18 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +4 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx +23 -10
- package/src/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.tsx +68 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +47 -6
- package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +1 -0
- package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +3 -0
- package/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx +14 -9
- package/src/lib/seam/components/DeviceTable/DeviceHealthBar.tsx +49 -19
- package/src/lib/seam/components/DeviceTable/DeviceRow.tsx +48 -9
- package/src/lib/seam/components/DeviceTable/DeviceTable.element.ts +1 -0
- package/src/lib/seam/components/DeviceTable/DeviceTable.tsx +17 -2
- package/src/lib/ui/device/OnlineStatus.tsx +23 -7
- package/src/lib/version.ts +1 -1
- package/src/styles/_device-table.scss +4 -0
|
@@ -13,12 +13,14 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
|
|
|
13
13
|
import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
|
|
14
14
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
15
15
|
import { useToggle } from 'lib/ui/use-toggle.js'
|
|
16
|
+
|
|
16
17
|
export function LockDeviceDetails(props: {
|
|
17
18
|
device: LockDevice
|
|
19
|
+
disableLockUnlock: boolean
|
|
18
20
|
onBack?: () => void
|
|
19
21
|
className?: string
|
|
20
22
|
}): JSX.Element | null {
|
|
21
|
-
const { device, onBack, className } = props
|
|
23
|
+
const { device, disableLockUnlock, onBack, className } = props
|
|
22
24
|
|
|
23
25
|
const [accessCodesOpen, toggleAccessCodesOpen] = useToggle()
|
|
24
26
|
const toggleLock = useToggleLock(device)
|
|
@@ -41,6 +43,7 @@ export function LockDeviceDetails(props: {
|
|
|
41
43
|
className={className}
|
|
42
44
|
deviceId={device.device_id}
|
|
43
45
|
onBack={toggleAccessCodesOpen}
|
|
46
|
+
disableLockUnlock={disableLockUnlock}
|
|
44
47
|
/>
|
|
45
48
|
)
|
|
46
49
|
}
|
|
@@ -98,14 +101,16 @@ export function LockDeviceDetails(props: {
|
|
|
98
101
|
<span className='seam-value'>{lockStatus}</span>
|
|
99
102
|
</div>
|
|
100
103
|
<div className='seam-right'>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
{!disableLockUnlock && (
|
|
105
|
+
<Button
|
|
106
|
+
size='small'
|
|
107
|
+
onClick={() => {
|
|
108
|
+
toggleLock.mutate()
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{toggleLockLabel}
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
109
114
|
</div>
|
|
110
115
|
</div>
|
|
111
116
|
<AccessCodeLength
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { CheckIcon } from 'lib/icons/Check.js'
|
|
2
2
|
import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
|
|
3
|
+
import { OnlineStatusAccountOfflineIcon } from 'lib/icons/OnlineStatusAccountOffline.js'
|
|
3
4
|
import type { UseDevicesData } from 'lib/seam/devices/use-devices.js'
|
|
4
5
|
import { TableFilterBar } from 'lib/ui/Table/TableFilterBar/TableFilterBar.js'
|
|
5
6
|
import { TableFilterItem } from 'lib/ui/Table/TableFilterBar/TableFilterItem.js'
|
|
6
7
|
|
|
8
|
+
export type AccountFilter = 'account_issues'
|
|
7
9
|
export type DeviceFilter = 'device_issues'
|
|
8
10
|
|
|
9
11
|
interface DeviceHealthBarProps {
|
|
10
12
|
devices: Array<UseDevicesData[number]>
|
|
11
|
-
filter: DeviceFilter | null
|
|
12
|
-
onFilterSelect: (filter: DeviceFilter | null) => void
|
|
13
|
+
filter: AccountFilter | DeviceFilter | null
|
|
14
|
+
onFilterSelect: (filter: AccountFilter | DeviceFilter | null) => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function DeviceHealthBar({
|
|
@@ -17,25 +19,40 @@ export function DeviceHealthBar({
|
|
|
17
19
|
filter,
|
|
18
20
|
onFilterSelect,
|
|
19
21
|
}: DeviceHealthBarProps): JSX.Element {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
+
const erroredAccounts = devices.filter((device) => {
|
|
23
|
+
return (
|
|
24
|
+
device.errors.filter((error) => 'is_connected_account_error' in error)
|
|
25
|
+
.length > 0
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
const erroredDevices = devices.filter((device) => {
|
|
29
|
+
return (
|
|
30
|
+
device.errors.filter((error) => 'is_device_error' in error).length > 0
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
const accountIssueCount = erroredAccounts.length
|
|
34
|
+
const deviceIssueCount = erroredDevices.length
|
|
22
35
|
|
|
23
|
-
const toggle = (target: DeviceFilter) => () => {
|
|
36
|
+
const toggle = (target: AccountFilter | DeviceFilter) => () => {
|
|
24
37
|
const newFilter = target === filter ? null : target
|
|
25
38
|
onFilterSelect(newFilter)
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
const deviceLabel =
|
|
42
|
+
deviceIssueCount > 0
|
|
43
|
+
? `${deviceIssueCount} ${t.deviceIssues}`
|
|
44
|
+
: `${deviceIssueCount} ${t.deviceIssue}`
|
|
45
|
+
const accountLabel =
|
|
46
|
+
accountIssueCount > 0
|
|
47
|
+
? `${accountIssueCount} ${t.accountIssues}`
|
|
48
|
+
: `${accountIssueCount} ${t.accountIssue}`
|
|
32
49
|
|
|
33
|
-
if (
|
|
50
|
+
if (accountIssueCount === 0 && deviceIssueCount === 0) {
|
|
34
51
|
return (
|
|
35
52
|
<TableFilterBar filterCleared>
|
|
36
53
|
<TableFilterItem>
|
|
37
54
|
<CheckIcon />
|
|
38
|
-
{t.
|
|
55
|
+
{t.devicesAndAccountsOk}
|
|
39
56
|
</TableFilterItem>
|
|
40
57
|
</TableFilterBar>
|
|
41
58
|
)
|
|
@@ -48,19 +65,32 @@ export function DeviceHealthBar({
|
|
|
48
65
|
onFilterSelect(null)
|
|
49
66
|
}}
|
|
50
67
|
>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
{deviceIssueCount > 0 && (
|
|
69
|
+
<TableFilterItem
|
|
70
|
+
onClick={toggle('device_issues')}
|
|
71
|
+
selected={filter === 'device_issues'}
|
|
72
|
+
>
|
|
73
|
+
<ExclamationCircleOutlineIcon />
|
|
74
|
+
{deviceLabel}
|
|
75
|
+
</TableFilterItem>
|
|
76
|
+
)}
|
|
77
|
+
{accountIssueCount > 0 && (
|
|
78
|
+
<TableFilterItem
|
|
79
|
+
onClick={toggle('account_issues')}
|
|
80
|
+
selected={filter === 'account_issues'}
|
|
81
|
+
>
|
|
82
|
+
<OnlineStatusAccountOfflineIcon />
|
|
83
|
+
{accountLabel}
|
|
84
|
+
</TableFilterItem>
|
|
85
|
+
)}
|
|
58
86
|
</TableFilterBar>
|
|
59
87
|
)
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
const t = {
|
|
63
|
-
|
|
91
|
+
devicesAndAccountsOk: 'Devices & accounts OK',
|
|
92
|
+
accountIssue: 'account issue',
|
|
93
|
+
accountIssues: 'account issues',
|
|
64
94
|
deviceIssue: 'device issue',
|
|
65
95
|
deviceIssues: 'device issues',
|
|
66
96
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
|
|
1
3
|
import type {
|
|
2
4
|
UseDevicesData,
|
|
3
5
|
UseDevicesParams,
|
|
@@ -25,24 +27,61 @@ export function DeviceRow({
|
|
|
25
27
|
device,
|
|
26
28
|
onClick,
|
|
27
29
|
}: DeviceRowProps): JSX.Element | null {
|
|
30
|
+
const opacityClass =
|
|
31
|
+
isAccountOffline(device) || isDeviceOffline(device)
|
|
32
|
+
? 'seam-offline-fade'
|
|
33
|
+
: undefined
|
|
28
34
|
return (
|
|
29
|
-
<TableRow
|
|
30
|
-
<TableCell className='seam-image-cell'>
|
|
35
|
+
<TableRow onClick={onClick}>
|
|
36
|
+
<TableCell className={classNames('seam-image-cell', opacityClass)}>
|
|
31
37
|
<DeviceImage device={device} />
|
|
32
38
|
</TableCell>
|
|
33
39
|
<TableCell className='seam-body-cell'>
|
|
34
|
-
<Title className='seam-truncated-text'
|
|
40
|
+
<Title className={classNames('seam-truncated-text', opacityClass)}>
|
|
41
|
+
{device.properties.name}
|
|
42
|
+
</Title>
|
|
35
43
|
<div className='seam-bottom'>
|
|
36
|
-
<span className='seam-device-model'>
|
|
44
|
+
<span className={classNames('seam-device-model', opacityClass)}>
|
|
37
45
|
{device.properties.model.display_name}
|
|
38
46
|
</span>
|
|
39
|
-
<
|
|
40
|
-
<OnlineStatus device={device} />
|
|
41
|
-
<BatteryStatus device={device} />
|
|
42
|
-
<LockStatus device={device} />
|
|
43
|
-
</div>
|
|
47
|
+
<DeviceStatuses device={device} />
|
|
44
48
|
</div>
|
|
45
49
|
</TableCell>
|
|
46
50
|
</TableRow>
|
|
47
51
|
)
|
|
48
52
|
}
|
|
53
|
+
|
|
54
|
+
interface DeviceStatusesProps {
|
|
55
|
+
device: UseDevicesData[number]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function DeviceStatuses({ device }: DeviceStatusesProps): JSX.Element | null {
|
|
59
|
+
if (isAccountOffline(device) || isDeviceOffline(device)) {
|
|
60
|
+
return (
|
|
61
|
+
<div className='seam-device-statuses'>
|
|
62
|
+
<OnlineStatus device={device} />
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
<div className='seam-device-statuses'>
|
|
68
|
+
<OnlineStatus device={device} />
|
|
69
|
+
<BatteryStatus device={device} />
|
|
70
|
+
<LockStatus device={device} />
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isAccountOffline = (device: UseDevicesData[number]): boolean => {
|
|
76
|
+
return (
|
|
77
|
+
device.errors.filter((error) => error.error_code === 'account_disconnected')
|
|
78
|
+
.length > 0
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const isDeviceOffline = (device: UseDevicesData[number]): boolean => {
|
|
83
|
+
return (
|
|
84
|
+
device.errors.filter((error) => error.error_code === 'device_disconnected')
|
|
85
|
+
.length > 0
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -7,6 +7,7 @@ export const name = 'seam-device-table'
|
|
|
7
7
|
export const props: ElementProps<DeviceTableProps> = {
|
|
8
8
|
deviceIds: 'json',
|
|
9
9
|
connectedAccountIds: 'json',
|
|
10
|
+
disableLockUnlock: 'boolean',
|
|
10
11
|
deviceFilter: 'function',
|
|
11
12
|
deviceComparator: 'function',
|
|
12
13
|
onDeviceClick: 'function',
|
|
@@ -5,6 +5,7 @@ import { isLockDevice } from 'seamapi'
|
|
|
5
5
|
import { compareByCreatedAtDesc } from 'lib/dates.js'
|
|
6
6
|
import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
|
|
7
7
|
import {
|
|
8
|
+
type AccountFilter,
|
|
8
9
|
type DeviceFilter,
|
|
9
10
|
DeviceHealthBar,
|
|
10
11
|
} from 'lib/seam/components/DeviceTable/DeviceHealthBar.js'
|
|
@@ -26,6 +27,7 @@ type Device = UseDevicesData[number]
|
|
|
26
27
|
export interface DeviceTableProps {
|
|
27
28
|
deviceIds?: string[]
|
|
28
29
|
connectedAccountIds?: string[]
|
|
30
|
+
disableLockUnlock?: boolean
|
|
29
31
|
deviceFilter?: (device: Device, searchInputValue: string) => boolean
|
|
30
32
|
deviceComparator?: (deviceA: Device, deviceB: Device) => number
|
|
31
33
|
onDeviceClick?: (deviceId: string) => void
|
|
@@ -46,6 +48,7 @@ const defaultDeviceFilter = (
|
|
|
46
48
|
export function DeviceTable({
|
|
47
49
|
deviceIds,
|
|
48
50
|
connectedAccountIds,
|
|
51
|
+
disableLockUnlock = false,
|
|
49
52
|
onDeviceClick = () => {},
|
|
50
53
|
preventDefaultOnDeviceClick = false,
|
|
51
54
|
onBack,
|
|
@@ -87,6 +90,7 @@ export function DeviceTable({
|
|
|
87
90
|
onBack={() => {
|
|
88
91
|
setSelectedDeviceId(null)
|
|
89
92
|
}}
|
|
93
|
+
disableLockUnlock={disableLockUnlock}
|
|
90
94
|
/>
|
|
91
95
|
)
|
|
92
96
|
}
|
|
@@ -124,7 +128,9 @@ function Content(props: {
|
|
|
124
128
|
onDeviceClick: (deviceId: string) => void
|
|
125
129
|
}): JSX.Element {
|
|
126
130
|
const { devices, onDeviceClick } = props
|
|
127
|
-
const [filter, setFilter] = useState<DeviceFilter | null>(
|
|
131
|
+
const [filter, setFilter] = useState<AccountFilter | DeviceFilter | null>(
|
|
132
|
+
null
|
|
133
|
+
)
|
|
128
134
|
|
|
129
135
|
if (devices.length === 0) {
|
|
130
136
|
return <EmptyPlaceholder>{t.noDevicesMessage}</EmptyPlaceholder>
|
|
@@ -135,8 +141,17 @@ function Content(props: {
|
|
|
135
141
|
return true
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
if (filter === 'account_issues') {
|
|
145
|
+
return (
|
|
146
|
+
device.errors.filter((error) => 'is_connected_account_error' in error)
|
|
147
|
+
.length > 0
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
if (filter === 'device_issues') {
|
|
139
|
-
return
|
|
152
|
+
return (
|
|
153
|
+
device.errors.filter((error) => 'is_device_error' in error).length > 0
|
|
154
|
+
)
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
return true
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CommonDevice } from 'seamapi'
|
|
2
2
|
|
|
3
|
+
import { OnlineStatusAccountOfflineIcon } from 'lib/icons/OnlineStatusAccountOffline.js'
|
|
3
4
|
import { OnlineStatusDeviceOfflineIcon } from 'lib/icons/OnlineStatusDeviceOffline.js'
|
|
4
5
|
import { OnlineStatusOnlineIcon } from 'lib/icons/OnlineStatusOnline.js'
|
|
5
6
|
|
|
@@ -8,20 +9,34 @@ interface OnlineStatusProps {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export function OnlineStatus(props: OnlineStatusProps): JSX.Element {
|
|
11
|
-
const {
|
|
12
|
-
device: {
|
|
13
|
-
properties: { online },
|
|
14
|
-
},
|
|
15
|
-
} = props
|
|
12
|
+
const { device } = props
|
|
16
13
|
|
|
14
|
+
const isAccountOffline =
|
|
15
|
+
device.errors.filter((error) => error.error_code === 'account_disconnected')
|
|
16
|
+
.length > 0
|
|
17
|
+
|
|
18
|
+
if (isAccountOffline) {
|
|
19
|
+
return <AccountOfflineContent />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className='seam-online-status'>
|
|
24
|
+
{isAccountOffline && <OnlineStatusAccountOfflineIcon />}
|
|
25
|
+
<DeviceOnlineContent isOnline={device.properties.online} />
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function AccountOfflineContent() {
|
|
17
31
|
return (
|
|
18
32
|
<div className='seam-online-status'>
|
|
19
|
-
<
|
|
33
|
+
<OnlineStatusAccountOfflineIcon />
|
|
34
|
+
<span className='seam-text-danger'>{t.accountOffline}</span>
|
|
20
35
|
</div>
|
|
21
36
|
)
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
function
|
|
39
|
+
function DeviceOnlineContent(props: { isOnline: boolean }): JSX.Element {
|
|
25
40
|
const { isOnline } = props
|
|
26
41
|
if (isOnline) {
|
|
27
42
|
return (
|
|
@@ -42,5 +57,6 @@ function Content(props: { isOnline: boolean }): JSX.Element {
|
|
|
42
57
|
|
|
43
58
|
const t = {
|
|
44
59
|
online: 'Online',
|
|
60
|
+
accountOffline: 'Account Offline',
|
|
45
61
|
deviceOffline: 'Device Offline',
|
|
46
62
|
}
|
package/src/lib/version.ts
CHANGED