@seamapi/react 1.22.0 → 1.23.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/dist/elements.js +3607 -3516
- package/dist/elements.js.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/DeviceTable.d.ts +2 -1
- package/lib/seam/components/DeviceTable/DeviceTable.js +2 -2
- package/lib/seam/components/DeviceTable/DeviceTable.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.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.tsx +47 -6
- 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/DeviceTable.tsx +3 -0
- package/src/lib/version.ts +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CheckIcon } from 'lib/icons/Check.js'
|
|
2
|
+
import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
|
|
3
|
+
import type { UseAccessCodesData } from 'lib/seam/access-codes/use-access-codes.js'
|
|
4
|
+
import { TableFilterBar } from 'lib/ui/Table/TableFilterBar/TableFilterBar.js'
|
|
5
|
+
import { TableFilterItem } from 'lib/ui/Table/TableFilterBar/TableFilterItem.js'
|
|
6
|
+
|
|
7
|
+
export type AccessCodeFilter = 'access_code_issues'
|
|
8
|
+
|
|
9
|
+
interface AccessCodeHealthBarProps {
|
|
10
|
+
accessCodes: Array<UseAccessCodesData[number]>
|
|
11
|
+
filter: AccessCodeFilter | null
|
|
12
|
+
onFilterSelect: (filter: AccessCodeFilter | null) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AccessCodeHealthBar({
|
|
16
|
+
accessCodes,
|
|
17
|
+
filter,
|
|
18
|
+
onFilterSelect,
|
|
19
|
+
}: AccessCodeHealthBarProps): JSX.Element {
|
|
20
|
+
const codesWithIssues = accessCodes.filter(
|
|
21
|
+
({ errors, warnings }) => errors.length > 0 || warnings.length > 0
|
|
22
|
+
)
|
|
23
|
+
const issueCount = codesWithIssues.length
|
|
24
|
+
|
|
25
|
+
const toggle = (target: AccessCodeFilter) => () => {
|
|
26
|
+
const newFilter = target === filter ? null : target
|
|
27
|
+
onFilterSelect(newFilter)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isPlural = issueCount === 0 || issueCount > 1
|
|
31
|
+
const label = isPlural
|
|
32
|
+
? `${issueCount} ${t.accessCodeIssues}`
|
|
33
|
+
: `${issueCount} ${t.accessCodeIssue}`
|
|
34
|
+
|
|
35
|
+
if (issueCount === 0) {
|
|
36
|
+
return (
|
|
37
|
+
<TableFilterBar filterCleared>
|
|
38
|
+
<TableFilterItem>
|
|
39
|
+
<CheckIcon />
|
|
40
|
+
{t.accessCodesOk}
|
|
41
|
+
</TableFilterItem>
|
|
42
|
+
</TableFilterBar>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TableFilterBar
|
|
48
|
+
filterCleared={filter == null}
|
|
49
|
+
onFilterClear={() => {
|
|
50
|
+
onFilterSelect(null)
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<TableFilterItem
|
|
54
|
+
onClick={toggle('access_code_issues')}
|
|
55
|
+
selected={filter === 'access_code_issues'}
|
|
56
|
+
>
|
|
57
|
+
<ExclamationCircleOutlineIcon />
|
|
58
|
+
{label}
|
|
59
|
+
</TableFilterItem>
|
|
60
|
+
</TableFilterBar>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const t = {
|
|
65
|
+
accessCodesOk: 'Access codes OK',
|
|
66
|
+
accessCodeIssue: 'access code issue',
|
|
67
|
+
accessCodeIssues: 'access code issues',
|
|
68
|
+
}
|
|
@@ -5,11 +5,16 @@ import { compareByCreatedAtDesc } from 'lib/dates.js'
|
|
|
5
5
|
import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
|
|
6
6
|
import { CopyIcon } from 'lib/icons/Copy.js'
|
|
7
7
|
import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
|
|
8
|
+
import { TriangleWarningOutlineIcon } from 'lib/icons/TriangleWarningOutline.js'
|
|
8
9
|
import {
|
|
9
10
|
useAccessCodes,
|
|
10
11
|
type UseAccessCodesData,
|
|
11
12
|
} from 'lib/seam/access-codes/use-access-codes.js'
|
|
12
13
|
import { AccessCodeDetails } from 'lib/seam/components/AccessCodeDetails/AccessCodeDetails.js'
|
|
14
|
+
import {
|
|
15
|
+
type AccessCodeFilter,
|
|
16
|
+
AccessCodeHealthBar,
|
|
17
|
+
} from 'lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js'
|
|
13
18
|
import { CodeDetails } from 'lib/seam/components/AccessCodeTable/CodeDetails.js'
|
|
14
19
|
import { copyToClipboard } from 'lib/ui/clipboard.js'
|
|
15
20
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
@@ -37,6 +42,7 @@ export interface AccessCodeTableProps {
|
|
|
37
42
|
) => number
|
|
38
43
|
onAccessCodeClick?: (accessCodeId: string) => void
|
|
39
44
|
preventDefaultOnAccessCodeClick?: boolean
|
|
45
|
+
disableLockUnlock?: boolean
|
|
40
46
|
onBack?: () => void
|
|
41
47
|
className?: string
|
|
42
48
|
}
|
|
@@ -49,13 +55,18 @@ const defaultAccessCodeFilter = (
|
|
|
49
55
|
): boolean => {
|
|
50
56
|
const value = searchInputValue.trim()
|
|
51
57
|
if (value === '') return true
|
|
52
|
-
|
|
58
|
+
const searchRegex = new RegExp(value, 'i')
|
|
59
|
+
return (
|
|
60
|
+
searchRegex.test(accessCode?.name ?? '') ||
|
|
61
|
+
searchRegex.test(accessCode?.code ?? '')
|
|
62
|
+
)
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
export function AccessCodeTable({
|
|
56
66
|
deviceId,
|
|
57
67
|
onAccessCodeClick = () => {},
|
|
58
68
|
preventDefaultOnAccessCodeClick = false,
|
|
69
|
+
disableLockUnlock = false,
|
|
59
70
|
onBack,
|
|
60
71
|
accessCodeFilter = defaultAccessCodeFilter,
|
|
61
72
|
accessCodeComparator = compareByCreatedAtDesc,
|
|
@@ -100,6 +111,7 @@ export function AccessCodeTable({
|
|
|
100
111
|
onBack={() => {
|
|
101
112
|
setSelectedAccessCodeId(null)
|
|
102
113
|
}}
|
|
114
|
+
disableLockUnlock={disableLockUnlock}
|
|
103
115
|
/>
|
|
104
116
|
)
|
|
105
117
|
}
|
|
@@ -132,6 +144,21 @@ function Content(props: {
|
|
|
132
144
|
onAccessCodeClick: (accessCodeId: string) => void
|
|
133
145
|
}): JSX.Element {
|
|
134
146
|
const { accessCodes, onAccessCodeClick } = props
|
|
147
|
+
const [filter, setFilter] = useState<AccessCodeFilter | null>(null)
|
|
148
|
+
|
|
149
|
+
const filteredAccessCodes = useMemo(() => {
|
|
150
|
+
if (filter === null) {
|
|
151
|
+
return accessCodes
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (filter === 'access_code_issues') {
|
|
155
|
+
return accessCodes.filter((accessCode) => {
|
|
156
|
+
return accessCode.errors.length > 0 || accessCode.warnings.length > 0
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return accessCodes
|
|
161
|
+
}, [accessCodes, filter])
|
|
135
162
|
|
|
136
163
|
if (accessCodes.length === 0) {
|
|
137
164
|
return <EmptyPlaceholder>{t.noAccessCodesMessage}</EmptyPlaceholder>
|
|
@@ -139,7 +166,12 @@ function Content(props: {
|
|
|
139
166
|
|
|
140
167
|
return (
|
|
141
168
|
<>
|
|
142
|
-
|
|
169
|
+
<AccessCodeHealthBar
|
|
170
|
+
accessCodes={accessCodes}
|
|
171
|
+
filter={filter}
|
|
172
|
+
onFilterSelect={setFilter}
|
|
173
|
+
/>
|
|
174
|
+
{filteredAccessCodes.map((accessCode) => (
|
|
143
175
|
<AccessCodeRow
|
|
144
176
|
key={accessCode.access_code_id}
|
|
145
177
|
accessCode={accessCode}
|
|
@@ -158,14 +190,18 @@ function AccessCodeRow(props: {
|
|
|
158
190
|
}): JSX.Element {
|
|
159
191
|
const { onClick, accessCode } = props
|
|
160
192
|
|
|
161
|
-
const errorCount = accessCode.errors
|
|
193
|
+
const errorCount = accessCode.errors.length
|
|
194
|
+
const warningCount = accessCode.warnings.length
|
|
162
195
|
const isPlural = errorCount === 0 || errorCount > 1
|
|
163
|
-
const
|
|
196
|
+
const errorIconTitle = isPlural
|
|
164
197
|
? `${errorCount} ${t.codeIssues}`
|
|
165
198
|
: `${errorCount} ${t.codeIssue}`
|
|
199
|
+
const warningIconTitle = isPlural
|
|
200
|
+
? `${warningCount} ${t.codeIssues}`
|
|
201
|
+
: `${warningCount} ${t.codeIssue}`
|
|
166
202
|
|
|
167
203
|
return (
|
|
168
|
-
<TableRow
|
|
204
|
+
<TableRow onClick={onClick}>
|
|
169
205
|
<TableCell className='seam-icon-cell'>
|
|
170
206
|
<div>
|
|
171
207
|
<AccessCodeKeyIcon />
|
|
@@ -177,10 +213,15 @@ function AccessCodeRow(props: {
|
|
|
177
213
|
</TableCell>
|
|
178
214
|
<TableCell className='seam-action-cell'>
|
|
179
215
|
{errorCount > 0 && (
|
|
180
|
-
<div className='seam-code-issue-icon-wrap' title={
|
|
216
|
+
<div className='seam-code-issue-icon-wrap' title={errorIconTitle}>
|
|
181
217
|
<ExclamationCircleOutlineIcon />
|
|
182
218
|
</div>
|
|
183
219
|
)}
|
|
220
|
+
{errorCount === 0 && warningCount > 0 && (
|
|
221
|
+
<div className='seam-code-issue-icon-wrap' title={warningIconTitle}>
|
|
222
|
+
<TriangleWarningOutlineIcon />
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
184
225
|
<MoreActionsMenu
|
|
185
226
|
menuProps={{
|
|
186
227
|
backgroundProps: {
|
|
@@ -5,12 +5,14 @@ import { useDevice } from 'lib/seam/devices/use-device.js'
|
|
|
5
5
|
|
|
6
6
|
export interface DeviceDetailsProps {
|
|
7
7
|
deviceId: string
|
|
8
|
+
disableLockUnlock?: boolean
|
|
8
9
|
onBack?: () => void
|
|
9
10
|
className?: string
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function DeviceDetails({
|
|
13
14
|
deviceId,
|
|
15
|
+
disableLockUnlock = false,
|
|
14
16
|
onBack,
|
|
15
17
|
className,
|
|
16
18
|
}: DeviceDetailsProps): JSX.Element | null {
|
|
@@ -27,6 +29,7 @@ export function DeviceDetails({
|
|
|
27
29
|
<LockDeviceDetails
|
|
28
30
|
className={className}
|
|
29
31
|
device={device}
|
|
32
|
+
disableLockUnlock={disableLockUnlock}
|
|
30
33
|
onBack={onBack}
|
|
31
34
|
/>
|
|
32
35
|
)
|
|
@@ -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
|
|
@@ -30,6 +30,7 @@ export interface DeviceTableProps {
|
|
|
30
30
|
deviceComparator?: (deviceA: Device, deviceB: Device) => number
|
|
31
31
|
onDeviceClick?: (deviceId: string) => void
|
|
32
32
|
preventDefaultOnDeviceClick?: boolean
|
|
33
|
+
disableLockUnlock?: boolean
|
|
33
34
|
onBack?: () => void
|
|
34
35
|
className?: string
|
|
35
36
|
}
|
|
@@ -48,6 +49,7 @@ export function DeviceTable({
|
|
|
48
49
|
connectedAccountIds,
|
|
49
50
|
onDeviceClick = () => {},
|
|
50
51
|
preventDefaultOnDeviceClick = false,
|
|
52
|
+
disableLockUnlock = false,
|
|
51
53
|
onBack,
|
|
52
54
|
deviceFilter = defaultDeviceFilter,
|
|
53
55
|
deviceComparator = compareByCreatedAtDesc,
|
|
@@ -87,6 +89,7 @@ export function DeviceTable({
|
|
|
87
89
|
onBack={() => {
|
|
88
90
|
setSelectedDeviceId(null)
|
|
89
91
|
}}
|
|
92
|
+
disableLockUnlock={disableLockUnlock}
|
|
90
93
|
/>
|
|
91
94
|
)
|
|
92
95
|
}
|
package/src/lib/version.ts
CHANGED