@seamapi/react 1.23.0 → 1.25.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 +5077 -4846
- package/dist/elements.js.map +1 -1
- package/dist/index.css +160 -45
- package/dist/index.css.map +1 -1
- package/dist/index.min.css +1 -1
- package/dist/index.min.css.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -2
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.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 -2
- package/lib/seam/components/DeviceTable/DeviceTable.js +6 -2
- package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.d.ts +5 -0
- package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.js +8 -0
- package/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.d.ts +8 -0
- package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.js +14 -0
- package/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceBrandSection.d.ts +7 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceBrandSection.js +45 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceBrandSection.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js +36 -11
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.d.ts +7 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.js +12 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js +12 -4
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterResultRowProps.d.ts +7 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterResultRowProps.js +12 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterResultRowProps.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +4 -0
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +19 -5
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js +3 -3
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/use-device-provider.d.ts +2 -0
- package/lib/seam/components/SupportedDeviceTable/use-device-provider.js +15 -0
- package/lib/seam/components/SupportedDeviceTable/use-device-provider.js.map +1 -0
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.d.ts +5 -1
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js +9 -2
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -1
- package/lib/seam/devices/use-device-providers.d.ts +5 -0
- package/lib/seam/devices/use-device-providers.js +17 -0
- package/lib/seam/devices/use-device-providers.js.map +1 -0
- 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 +2 -2
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +2 -2
- package/src/lib/seam/components/DeviceDetails/DeviceDetails.element.ts +1 -0
- 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 +16 -4
- package/src/lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.tsx +11 -0
- package/src/lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.tsx +32 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceBrandSection.tsx +96 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +62 -16
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContentRows.tsx +27 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +22 -3
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterResultRowProps.tsx +49 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +52 -20
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.element.ts +1 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceTable.tsx +4 -0
- package/src/lib/seam/components/SupportedDeviceTable/use-device-provider.ts +22 -0
- package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +21 -4
- package/src/lib/seam/devices/use-device-providers.ts +25 -0
- package/src/lib/ui/device/OnlineStatus.tsx +23 -7
- package/src/lib/version.ts +1 -1
- package/src/styles/_access-code-details.scss +1 -1
- package/src/styles/_buttons.scss +3 -3
- package/src/styles/_colors.scss +6 -2
- package/src/styles/_device-table.scss +4 -0
- package/src/styles/_menus.scss +1 -1
- package/src/styles/_supported-device-table.scss +192 -52
- package/src/styles/_tables.scss +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.d.ts +0 -2
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js +0 -12
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.js.map +0 -1
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceHeader.tsx +0 -22
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { OnlineStatusAccountOfflineIcon } from '../../../lib/icons/OnlineStatusAccountOffline.js';
|
|
2
3
|
import { OnlineStatusDeviceOfflineIcon } from '../../../lib/icons/OnlineStatusDeviceOffline.js';
|
|
3
4
|
import { OnlineStatusOnlineIcon } from '../../../lib/icons/OnlineStatusOnline.js';
|
|
4
5
|
export function OnlineStatus(props) {
|
|
5
|
-
const { device
|
|
6
|
-
|
|
6
|
+
const { device } = props;
|
|
7
|
+
const isAccountOffline = device.errors.filter((error) => error.error_code === 'account_disconnected')
|
|
8
|
+
.length > 0;
|
|
9
|
+
if (isAccountOffline) {
|
|
10
|
+
return _jsx(AccountOfflineContent, {});
|
|
11
|
+
}
|
|
12
|
+
return (_jsxs("div", { className: 'seam-online-status', children: [isAccountOffline && _jsx(OnlineStatusAccountOfflineIcon, {}), _jsx(DeviceOnlineContent, { isOnline: device.properties.online })] }));
|
|
13
|
+
}
|
|
14
|
+
function AccountOfflineContent() {
|
|
15
|
+
return (_jsxs("div", { className: 'seam-online-status', children: [_jsx(OnlineStatusAccountOfflineIcon, {}), _jsx("span", { className: 'seam-text-danger', children: t.accountOffline })] }));
|
|
7
16
|
}
|
|
8
|
-
function
|
|
17
|
+
function DeviceOnlineContent(props) {
|
|
9
18
|
const { isOnline } = props;
|
|
10
19
|
if (isOnline) {
|
|
11
20
|
return (_jsxs(_Fragment, { children: [_jsx(OnlineStatusOnlineIcon, {}), _jsx("span", { className: 'seam-status-text', children: t.online })] }));
|
|
@@ -14,6 +23,7 @@ function Content(props) {
|
|
|
14
23
|
}
|
|
15
24
|
const t = {
|
|
16
25
|
online: 'Online',
|
|
26
|
+
accountOffline: 'Account Offline',
|
|
17
27
|
deviceOffline: 'Device Offline',
|
|
18
28
|
};
|
|
19
29
|
//# sourceMappingURL=OnlineStatus.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OnlineStatus.js","sourceRoot":"","sources":["../../../src/lib/ui/device/OnlineStatus.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAMxE,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,
|
|
1
|
+
{"version":3,"file":"OnlineStatus.js","sourceRoot":"","sources":["../../../src/lib/ui/device/OnlineStatus.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAA;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAMxE,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAExB,MAAM,gBAAgB,GACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,sBAAsB,CAAC;SACzE,MAAM,GAAG,CAAC,CAAA;IAEf,IAAI,gBAAgB,EAAE;QACpB,OAAO,KAAC,qBAAqB,KAAG,CAAA;KACjC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,oBAAoB,aAChC,gBAAgB,IAAI,KAAC,8BAA8B,KAAG,EACvD,KAAC,mBAAmB,IAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,GAAI,IACvD,CACP,CAAA;AACH,CAAC;AAED,SAAS,qBAAqB;IAC5B,OAAO,CACL,eAAK,SAAS,EAAC,oBAAoB,aACjC,KAAC,8BAA8B,KAAG,EAClC,eAAM,SAAS,EAAC,kBAAkB,YAAE,CAAC,CAAC,cAAc,GAAQ,IACxD,CACP,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAA4B;IACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAC1B,IAAI,QAAQ,EAAE;QACZ,OAAO,CACL,8BACE,KAAC,sBAAsB,KAAG,EAC1B,eAAM,SAAS,EAAC,kBAAkB,YAAE,CAAC,CAAC,MAAM,GAAQ,IACnD,CACJ,CAAA;KACF;IAED,OAAO,CACL,8BACE,KAAC,6BAA6B,KAAG,EACjC,eAAM,SAAS,EAAC,kBAAkB,YAAE,CAAC,CAAC,aAAa,GAAQ,IAC1D,CACJ,CAAA;AACH,CAAC;AAED,MAAM,CAAC,GAAG;IACR,MAAM,EAAE,QAAQ;IAChB,cAAc,EAAE,iBAAiB;IACjC,aAAa,EAAE,gBAAgB;CAChC,CAAA"}
|
package/lib/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const seamapiReactVersion = "1.
|
|
1
|
+
declare const seamapiReactVersion = "1.25.0";
|
|
2
2
|
export default seamapiReactVersion;
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Seam Components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"@mui/icons-material": "^5.11.16",
|
|
122
122
|
"@mui/material": "^5.12.2",
|
|
123
123
|
"@r2wc/react-to-web-component": "^2.0.2",
|
|
124
|
-
"@seamapi/fake-seam-connect": "^0.
|
|
124
|
+
"@seamapi/fake-seam-connect": "^0.10.0",
|
|
125
125
|
"@storybook/addon-essentials": "^7.0.2",
|
|
126
126
|
"@storybook/addon-interactions": "^7.0.2",
|
|
127
127
|
"@storybook/addon-links": "^7.0.2",
|
|
@@ -32,6 +32,7 @@ import { Title } from 'lib/ui/typography/Title.js'
|
|
|
32
32
|
|
|
33
33
|
export interface AccessCodeTableProps {
|
|
34
34
|
deviceId: string
|
|
35
|
+
disableLockUnlock?: boolean
|
|
35
36
|
accessCodeFilter?: (
|
|
36
37
|
accessCode: AccessCode,
|
|
37
38
|
searchInputValue: string
|
|
@@ -42,7 +43,6 @@ export interface AccessCodeTableProps {
|
|
|
42
43
|
) => number
|
|
43
44
|
onAccessCodeClick?: (accessCodeId: string) => void
|
|
44
45
|
preventDefaultOnAccessCodeClick?: boolean
|
|
45
|
-
disableLockUnlock?: boolean
|
|
46
46
|
onBack?: () => void
|
|
47
47
|
className?: string
|
|
48
48
|
}
|
|
@@ -64,9 +64,9 @@ const defaultAccessCodeFilter = (
|
|
|
64
64
|
|
|
65
65
|
export function AccessCodeTable({
|
|
66
66
|
deviceId,
|
|
67
|
+
disableLockUnlock = false,
|
|
67
68
|
onAccessCodeClick = () => {},
|
|
68
69
|
preventDefaultOnAccessCodeClick = false,
|
|
69
|
-
disableLockUnlock = false,
|
|
70
70
|
onBack,
|
|
71
71
|
accessCodeFilter = defaultAccessCodeFilter,
|
|
72
72
|
accessCodeComparator = compareByCreatedAtDesc,
|
|
@@ -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,11 +27,11 @@ 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
|
|
32
34
|
preventDefaultOnDeviceClick?: boolean
|
|
33
|
-
disableLockUnlock?: boolean
|
|
34
35
|
onBack?: () => void
|
|
35
36
|
className?: string
|
|
36
37
|
}
|
|
@@ -47,9 +48,9 @@ const defaultDeviceFilter = (
|
|
|
47
48
|
export function DeviceTable({
|
|
48
49
|
deviceIds,
|
|
49
50
|
connectedAccountIds,
|
|
51
|
+
disableLockUnlock = false,
|
|
50
52
|
onDeviceClick = () => {},
|
|
51
53
|
preventDefaultOnDeviceClick = false,
|
|
52
|
-
disableLockUnlock = false,
|
|
53
54
|
onBack,
|
|
54
55
|
deviceFilter = defaultDeviceFilter,
|
|
55
56
|
deviceComparator = compareByCreatedAtDesc,
|
|
@@ -127,7 +128,9 @@ function Content(props: {
|
|
|
127
128
|
onDeviceClick: (deviceId: string) => void
|
|
128
129
|
}): JSX.Element {
|
|
129
130
|
const { devices, onDeviceClick } = props
|
|
130
|
-
const [filter, setFilter] = useState<DeviceFilter | null>(
|
|
131
|
+
const [filter, setFilter] = useState<AccountFilter | DeviceFilter | null>(
|
|
132
|
+
null
|
|
133
|
+
)
|
|
131
134
|
|
|
132
135
|
if (devices.length === 0) {
|
|
133
136
|
return <EmptyPlaceholder>{t.noDevicesMessage}</EmptyPlaceholder>
|
|
@@ -138,8 +141,17 @@ function Content(props: {
|
|
|
138
141
|
return true
|
|
139
142
|
}
|
|
140
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
|
+
|
|
141
151
|
if (filter === 'device_issues') {
|
|
142
|
-
return
|
|
152
|
+
return (
|
|
153
|
+
device.errors.filter((error) => 'is_device_error' in error).length > 0
|
|
154
|
+
)
|
|
143
155
|
}
|
|
144
156
|
|
|
145
157
|
return true
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
|
|
2
|
+
|
|
3
|
+
interface ShowAllDevicesButtonProps {
|
|
4
|
+
onClick: () => void
|
|
5
|
+
visible: boolean
|
|
6
|
+
expanded: boolean
|
|
7
|
+
totalDeviceCount: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ShowAllDevicesButton({
|
|
11
|
+
onClick,
|
|
12
|
+
visible,
|
|
13
|
+
expanded,
|
|
14
|
+
totalDeviceCount,
|
|
15
|
+
}: ShowAllDevicesButtonProps) {
|
|
16
|
+
if (!visible) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const label = expanded ? t.showLess : t.showAll(totalDeviceCount)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<button className='show-all-devices-button' onClick={onClick}>
|
|
24
|
+
<ChevronRightIcon /> {label}
|
|
25
|
+
</button>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const t = {
|
|
30
|
+
showLess: 'Show less',
|
|
31
|
+
showAll: (count: number) => `See all ${count} devices`,
|
|
32
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import type { DeviceModel } from 'seamapi'
|
|
3
|
+
|
|
4
|
+
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
|
|
5
|
+
import { HiddenDevicesOverlay } from 'lib/seam/components/SupportedDeviceTable/HiddenDevicesOverlay.js'
|
|
6
|
+
import { ShowAllDevicesButton } from 'lib/seam/components/SupportedDeviceTable/ShowAllDevicesButton.js'
|
|
7
|
+
import { SupportedDeviceRow } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js'
|
|
8
|
+
import { useDeviceProvider } from 'lib/seam/components/SupportedDeviceTable/use-device-provider.js'
|
|
9
|
+
import { useToggle } from 'lib/ui/use-toggle.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How many device models required before we collapse
|
|
13
|
+
* the list, and require the user to click to
|
|
14
|
+
* view all.
|
|
15
|
+
*/
|
|
16
|
+
const maxDevicesBeforeCollapsing = 3
|
|
17
|
+
|
|
18
|
+
export interface SupportedDeviceBrandSectionProps {
|
|
19
|
+
brand: string
|
|
20
|
+
deviceModels: DeviceModel[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function SupportedDeviceBrandSection({
|
|
24
|
+
brand,
|
|
25
|
+
deviceModels,
|
|
26
|
+
}: SupportedDeviceBrandSectionProps): JSX.Element | null {
|
|
27
|
+
const deviceProvider = useDeviceProvider(brand)
|
|
28
|
+
|
|
29
|
+
const [expanded, toggleExpand] = useToggle()
|
|
30
|
+
|
|
31
|
+
const canExpand = deviceModels.length > maxDevicesBeforeCollapsing
|
|
32
|
+
|
|
33
|
+
const visibleDevices = () => {
|
|
34
|
+
if (!canExpand || expanded) {
|
|
35
|
+
return deviceModels
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return deviceModels.filter(
|
|
39
|
+
(_deviceModel, index) => index < maxDevicesBeforeCollapsing
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleHeaderClick = () => {
|
|
44
|
+
if (!canExpand) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toggleExpand()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
className={classNames('seam-brand-section', {
|
|
54
|
+
'can-expand': canExpand,
|
|
55
|
+
expanded,
|
|
56
|
+
})}
|
|
57
|
+
>
|
|
58
|
+
<div className='seam-header' onClick={handleHeaderClick}>
|
|
59
|
+
<img
|
|
60
|
+
src={deviceProvider.image_url}
|
|
61
|
+
alt={brand}
|
|
62
|
+
className='seam-brand-image'
|
|
63
|
+
/>
|
|
64
|
+
<h5 className='seam-brand-name'>
|
|
65
|
+
{deviceProvider.display_name} {t.devices}
|
|
66
|
+
</h5>
|
|
67
|
+
{canExpand && <ChevronRightIcon className='chevron' />}
|
|
68
|
+
</div>
|
|
69
|
+
<div className='seam-supported-device-table-content'>
|
|
70
|
+
{visibleDevices().map((deviceModel, index) => (
|
|
71
|
+
<SupportedDeviceRow
|
|
72
|
+
key={[
|
|
73
|
+
deviceModel.main_category,
|
|
74
|
+
deviceModel.brand,
|
|
75
|
+
deviceModel.model_name,
|
|
76
|
+
deviceModel.manufacturer_model_id,
|
|
77
|
+
index,
|
|
78
|
+
].join(':')}
|
|
79
|
+
deviceModel={deviceModel}
|
|
80
|
+
/>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
<ShowAllDevicesButton
|
|
84
|
+
visible={canExpand}
|
|
85
|
+
onClick={toggleExpand}
|
|
86
|
+
expanded={expanded}
|
|
87
|
+
totalDeviceCount={deviceModels.length}
|
|
88
|
+
/>
|
|
89
|
+
<HiddenDevicesOverlay visible={canExpand && !expanded} />
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const t = {
|
|
95
|
+
devices: 'Devices',
|
|
96
|
+
}
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { DeviceModel } from 'seamapi'
|
|
2
|
+
|
|
3
|
+
import { SupportedDeviceBrandSection } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceBrandSection.js'
|
|
4
|
+
import { SupportedDeviceFilterResultRow } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterResultRowProps.js'
|
|
3
5
|
import {
|
|
4
6
|
type DeviceModelFilters,
|
|
5
7
|
useFilteredDeviceModels,
|
|
6
8
|
} from 'lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js'
|
|
9
|
+
import type { UseDeviceModelsData } from 'lib/seam/device-models/use-device-models.js'
|
|
7
10
|
import { Button } from 'lib/ui/Button.js'
|
|
8
11
|
|
|
9
12
|
interface SupportedDeviceContentProps {
|
|
10
13
|
filterValue: string
|
|
11
14
|
resetFilterValue: () => void
|
|
12
15
|
filters: DeviceModelFilters
|
|
16
|
+
brands: string[]
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export function SupportedDeviceContent({
|
|
16
20
|
resetFilterValue,
|
|
17
21
|
filterValue,
|
|
18
22
|
filters,
|
|
23
|
+
brands,
|
|
19
24
|
}: SupportedDeviceContentProps): JSX.Element | null {
|
|
20
25
|
const { deviceModels, isLoading, isError, refetch } = useFilteredDeviceModels(
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
{
|
|
27
|
+
filterValue,
|
|
28
|
+
filters,
|
|
29
|
+
brands,
|
|
30
|
+
}
|
|
23
31
|
)
|
|
24
32
|
|
|
25
33
|
if (isLoading) {
|
|
@@ -51,12 +59,29 @@ export function SupportedDeviceContent({
|
|
|
51
59
|
return null
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
62
|
+
const isEmpty = deviceModels.length === 0
|
|
63
|
+
if (isEmpty) {
|
|
64
|
+
return (
|
|
65
|
+
<div className='seam-supported-device-table-content'>
|
|
66
|
+
<EmptyResult
|
|
67
|
+
filterValue={filterValue}
|
|
68
|
+
resetFilterValue={resetFilterValue}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If there are no active filters or search, show all the rows without any brand sections.
|
|
75
|
+
const hasFilters =
|
|
76
|
+
filterValue.trim() !== '' ||
|
|
77
|
+
filters.supportedOnly ||
|
|
78
|
+
filters.category !== null ||
|
|
79
|
+
filters.brand !== null
|
|
80
|
+
if (hasFilters) {
|
|
81
|
+
return (
|
|
82
|
+
<div className='seam-supported-device-table-content'>
|
|
58
83
|
{deviceModels.map((deviceModel, index) => (
|
|
59
|
-
<
|
|
84
|
+
<SupportedDeviceFilterResultRow
|
|
60
85
|
key={[
|
|
61
86
|
deviceModel.main_category,
|
|
62
87
|
deviceModel.brand,
|
|
@@ -67,14 +92,22 @@ export function SupportedDeviceContent({
|
|
|
67
92
|
deviceModel={deviceModel}
|
|
68
93
|
/>
|
|
69
94
|
))}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<>
|
|
101
|
+
{Object.entries(groupDeviceModelsByBrand(deviceModels)).map(
|
|
102
|
+
([brand, models]) => (
|
|
103
|
+
<SupportedDeviceBrandSection
|
|
104
|
+
key={brand}
|
|
105
|
+
brand={brand}
|
|
106
|
+
deviceModels={models}
|
|
74
107
|
/>
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
)
|
|
109
|
+
)}
|
|
110
|
+
</>
|
|
78
111
|
)
|
|
79
112
|
}
|
|
80
113
|
|
|
@@ -110,6 +143,19 @@ function EmptyResult({
|
|
|
110
143
|
)
|
|
111
144
|
}
|
|
112
145
|
|
|
146
|
+
function groupDeviceModelsByBrand(
|
|
147
|
+
deviceModels: UseDeviceModelsData
|
|
148
|
+
): Record<string, DeviceModel[]> {
|
|
149
|
+
const result: Record<string, DeviceModel[]> = {}
|
|
150
|
+
|
|
151
|
+
for (const model of deviceModels) {
|
|
152
|
+
const { brand } = model
|
|
153
|
+
const list = result[brand] ?? []
|
|
154
|
+
result[brand] = [...list, model]
|
|
155
|
+
}
|
|
156
|
+
return result
|
|
157
|
+
}
|
|
158
|
+
|
|
113
159
|
const t = {
|
|
114
160
|
loading: 'Loading device models...',
|
|
115
161
|
retry: 'Retry',
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SupportedDeviceRow } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js'
|
|
2
|
+
import type { UseDeviceModelsData } from 'lib/seam/device-models/use-device-models.js'
|
|
3
|
+
|
|
4
|
+
interface SupportedDeviceContentRowsProps {
|
|
5
|
+
deviceModels: UseDeviceModelsData
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function SupportedDeviceContentRows({
|
|
9
|
+
deviceModels,
|
|
10
|
+
}: SupportedDeviceContentRowsProps): JSX.Element | null {
|
|
11
|
+
return (
|
|
12
|
+
<div className='seam-supported-device-table-content'>
|
|
13
|
+
{deviceModels.map((deviceModel, index) => (
|
|
14
|
+
<SupportedDeviceRow
|
|
15
|
+
key={[
|
|
16
|
+
deviceModel.main_category,
|
|
17
|
+
deviceModel.brand,
|
|
18
|
+
deviceModel.model_name,
|
|
19
|
+
deviceModel.manufacturer_model_id,
|
|
20
|
+
index,
|
|
21
|
+
].join(':')}
|
|
22
|
+
deviceModel={deviceModel}
|
|
23
|
+
/>
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|