@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.
Files changed (38) hide show
  1. package/dist/elements.js +3607 -3516
  2. package/dist/elements.js.map +1 -1
  3. package/lib/icons/TriangleWarningOutline.d.ts +2 -0
  4. package/lib/icons/TriangleWarningOutline.js +3 -0
  5. package/lib/icons/TriangleWarningOutline.js.map +1 -0
  6. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +2 -1
  7. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +3 -3
  8. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  9. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.d.ts +2 -1
  10. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js +8 -6
  11. package/lib/seam/components/AccessCodeDetails/AccessCodeDevice.js.map +1 -1
  12. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.d.ts +10 -0
  13. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js +29 -0
  14. package/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js.map +1 -0
  15. package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
  16. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +30 -10
  17. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
  18. package/lib/seam/components/DeviceDetails/DeviceDetails.d.ts +2 -1
  19. package/lib/seam/components/DeviceDetails/DeviceDetails.js +2 -2
  20. package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
  21. package/lib/seam/components/DeviceDetails/LockDeviceDetails.d.ts +1 -0
  22. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js +4 -4
  23. package/lib/seam/components/DeviceDetails/LockDeviceDetails.js.map +1 -1
  24. package/lib/seam/components/DeviceTable/DeviceTable.d.ts +2 -1
  25. package/lib/seam/components/DeviceTable/DeviceTable.js +2 -2
  26. package/lib/seam/components/DeviceTable/DeviceTable.js.map +1 -1
  27. package/lib/version.d.ts +1 -1
  28. package/lib/version.js +1 -1
  29. package/package.json +1 -1
  30. package/src/lib/icons/TriangleWarningOutline.tsx +18 -0
  31. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +4 -0
  32. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDevice.tsx +23 -10
  33. package/src/lib/seam/components/AccessCodeTable/AccessCodeHealthBar.tsx +68 -0
  34. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +47 -6
  35. package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +3 -0
  36. package/src/lib/seam/components/DeviceDetails/LockDeviceDetails.tsx +14 -9
  37. package/src/lib/seam/components/DeviceTable/DeviceTable.tsx +3 -0
  38. 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
- return new RegExp(value, 'i').test(accessCode.name ?? '')
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
- {accessCodes.map((accessCode) => (
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?.length ?? 0
193
+ const errorCount = accessCode.errors.length
194
+ const warningCount = accessCode.warnings.length
162
195
  const isPlural = errorCount === 0 || errorCount > 1
163
- const issueIconTitle = isPlural
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 key={accessCode.access_code_id} onClick={onClick}>
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={issueIconTitle}>
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
- <Button
102
- size='small'
103
- onClick={() => {
104
- toggleLock.mutate()
105
- }}
106
- >
107
- {toggleLockLabel}
108
- </Button>
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
  }
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '1.22.0'
1
+ const seamapiReactVersion = '1.23.0'
2
2
 
3
3
  export default seamapiReactVersion