@seamapi/react 3.0.2 → 3.1.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 (35) hide show
  1. package/README.md +2 -2
  2. package/dist/elements.js +4358 -4252
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +3 -0
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.min.css +1 -1
  7. package/dist/index.min.css.map +1 -1
  8. package/lib/seam/access-codes/use-delete-access-code.js +9 -0
  9. package/lib/seam/access-codes/use-delete-access-code.js.map +1 -1
  10. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +5 -2
  11. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +65 -10
  12. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  13. package/lib/seam/components/AccessCodeTable/AccessCodeMenu.d.ts +1 -0
  14. package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js +13 -5
  15. package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js.map +1 -1
  16. package/lib/seam/components/AccessCodeTable/AccessCodeRow.d.ts +2 -1
  17. package/lib/seam/components/AccessCodeTable/AccessCodeRow.js +10 -5
  18. package/lib/seam/components/AccessCodeTable/AccessCodeRow.js.map +1 -1
  19. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +40 -24
  20. package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
  21. package/lib/ui/Menu/Menu.d.ts +2 -1
  22. package/lib/ui/Menu/Menu.js +2 -1
  23. package/lib/ui/Menu/Menu.js.map +1 -1
  24. package/lib/version.d.ts +1 -1
  25. package/lib/version.js +1 -1
  26. package/package.json +1 -1
  27. package/src/lib/seam/access-codes/use-delete-access-code.ts +15 -0
  28. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +3 -0
  29. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +169 -71
  30. package/src/lib/seam/components/AccessCodeTable/AccessCodeMenu.tsx +34 -13
  31. package/src/lib/seam/components/AccessCodeTable/AccessCodeRow.tsx +24 -9
  32. package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +62 -53
  33. package/src/lib/ui/Menu/Menu.tsx +3 -0
  34. package/src/lib/version.ts +1 -1
  35. package/src/styles/_seam-table.scss +4 -0
@@ -1,7 +1,7 @@
1
1
  import type { AccessCode } from '@seamapi/types/connect'
2
2
  import classNames from 'classnames'
3
3
  import { DateTime } from 'luxon'
4
- import { useState } from 'react'
4
+ import { useCallback, useEffect, useState } from 'react'
5
5
 
6
6
  import { CopyIcon } from 'lib/icons/Copy.js'
7
7
  import { useAccessCode } from 'lib/seam/access-codes/use-access-code.js'
@@ -12,6 +12,7 @@ import {
12
12
  withRequiredCommonProps,
13
13
  } from 'lib/seam/components/common-props.js'
14
14
  import { NestedDeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
15
+ import { NestedEditAccessCodeForm } from 'lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js'
15
16
  import {
16
17
  accessCodeErrorFilter,
17
18
  accessCodeWarningFilter,
@@ -22,11 +23,15 @@ import { Button } from 'lib/ui/Button.js'
22
23
  import { copyToClipboard } from 'lib/ui/clipboard.js'
23
24
  import { IconButton } from 'lib/ui/IconButton.js'
24
25
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
26
+ import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
25
27
  import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
26
28
 
27
29
  export interface AccessCodeDetailsProps extends CommonProps {
28
30
  accessCodeId: string
29
- onEdit: () => void
31
+ onEdit?: () => void
32
+ preventDefaultOnEdit?: boolean
33
+ onDelete?: () => void
34
+ preventDefaultOnDelete?: boolean
30
35
  }
31
36
 
32
37
  export const NestedAccessCodeDetails =
@@ -35,6 +40,9 @@ export const NestedAccessCodeDetails =
35
40
  export function AccessCodeDetails({
36
41
  accessCodeId,
37
42
  onEdit,
43
+ preventDefaultOnEdit = false,
44
+ onDelete,
45
+ preventDefaultOnDelete = false,
38
46
  errorFilter = () => true,
39
47
  warningFilter = () => true,
40
48
  disableCreateAccessCode = false,
@@ -52,12 +60,72 @@ export function AccessCodeDetails({
52
60
  const { accessCode } = useAccessCode({ access_code_id: accessCodeId })
53
61
  const [selectedDeviceId, selectDevice] = useState<string | null>(null)
54
62
  const { mutate: deleteCode, isPending: isDeleting } = useDeleteAccessCode()
63
+ const [editFormOpen, setEditFormOpen] = useState<boolean>(false)
64
+
65
+ const [accessCodeResult, setAccessCodeResult] = useState<
66
+ 'updated' | 'deleted' | null
67
+ >(null)
68
+ const [snackbarMessage, setSnackbarMessage] = useState<string>('')
69
+
70
+ // Circumvent Snackbar bug that causes it to switch to default message
71
+ // while the dismiss animation is playing
72
+ useEffect(() => {
73
+ if (accessCodeResult !== null) {
74
+ setSnackbarMessage(accessCodeResultToMessage(accessCodeResult))
75
+ }
76
+ }, [accessCodeResult])
77
+
78
+ const handleEdit = useCallback((): void => {
79
+ onEdit?.()
80
+ if (preventDefaultOnEdit) return
81
+ setEditFormOpen(true)
82
+ }, [onEdit, preventDefaultOnEdit, setEditFormOpen])
83
+
84
+ const handleDelete = useCallback((): void => {
85
+ onDelete?.()
86
+ if (preventDefaultOnDelete) return
87
+ if (accessCode == null) return
88
+ deleteCode(
89
+ { access_code_id: accessCode.access_code_id },
90
+ {
91
+ onSuccess: () => {
92
+ setAccessCodeResult('deleted')
93
+ },
94
+ }
95
+ )
96
+ }, [accessCode, deleteCode, onDelete, preventDefaultOnDelete])
55
97
 
56
98
  if (accessCode == null) {
57
99
  return null
58
100
  }
59
101
 
60
102
  const name = accessCode.name ?? t.fallbackName
103
+ const isAccessCodeBeingRemoved = accessCode.status === 'removing'
104
+
105
+ if (editFormOpen) {
106
+ return (
107
+ <NestedEditAccessCodeForm
108
+ accessCodeId={accessCode.access_code_id}
109
+ errorFilter={errorFilter}
110
+ warningFilter={warningFilter}
111
+ disableLockUnlock={disableLockUnlock}
112
+ disableCreateAccessCode={disableCreateAccessCode}
113
+ disableEditAccessCode={disableEditAccessCode}
114
+ disableDeleteAccessCode={disableDeleteAccessCode}
115
+ disableResourceIds={disableResourceIds}
116
+ disableConnectedAccountInformation={disableConnectedAccountInformation}
117
+ disableClimateSettingSchedules={disableClimateSettingSchedules}
118
+ onBack={() => {
119
+ setEditFormOpen(false)
120
+ }}
121
+ onSuccess={() => {
122
+ setAccessCodeResult('updated')
123
+ setEditFormOpen(false)
124
+ }}
125
+ className={className}
126
+ />
127
+ )
128
+ }
61
129
 
62
130
  if (selectedDeviceId != null) {
63
131
  return (
@@ -96,92 +164,114 @@ export function AccessCodeDetails({
96
164
  variant: 'warning' as const,
97
165
  message: warning.message,
98
166
  })),
167
+
168
+ ...(isAccessCodeBeingRemoved
169
+ ? [
170
+ {
171
+ variant: 'warning' as const,
172
+ message: t.warningRemoving,
173
+ },
174
+ ]
175
+ : []),
99
176
  ]
100
177
 
101
178
  return (
102
- <div className={classNames('seam-access-code-details', className)}>
103
- <ContentHeader title='Access code' onBack={onBack} />
104
- <div className='seam-summary'>
105
- <div
106
- className={classNames(
107
- 'seam-top',
108
- alerts.length > 0 && 'seam-top-has-alerts'
109
- )}
110
- >
111
- <span className='seam-label'>{t.accessCode}</span>
112
- <h5 className='seam-access-code-name'>{name}</h5>
113
- <div className='seam-code'>
114
- <span>{accessCode.code}</span>
115
- <IconButton
116
- onClick={() => {
117
- void copyToClipboard(accessCode.code ?? '')
118
- }}
119
- >
120
- <CopyIcon />
121
- </IconButton>
122
- </div>
123
- <div className='seam-duration'>
124
- <Duration accessCode={accessCode} />
125
- </div>
126
- </div>
127
- <Alerts alerts={alerts} className='seam-alerts-padded' />
128
- <AccessCodeDevice
129
- deviceId={accessCode.device_id}
130
- disableLockUnlock={disableLockUnlock}
131
- onSelectDevice={selectDevice}
132
- />
133
- </div>
134
- {(!disableEditAccessCode || !disableDeleteAccessCode) && (
135
- <div className='seam-actions'>
136
- {!disableEditAccessCode && (
137
- <Button size='small' onClick={onEdit} disabled={isDeleting}>
138
- {t.editCode}
139
- </Button>
140
- )}
141
- {!disableDeleteAccessCode && (
142
- <Button
143
- size='small'
144
- onClick={() => {
145
- deleteCode({ access_code_id: accessCode.access_code_id })
146
- }}
147
- disabled={isDeleting}
148
- >
149
- {t.deleteCode}
150
- </Button>
151
- )}
152
- </div>
153
- )}
154
- <div className='seam-details'>
155
- {!disableResourceIds && (
156
- <div className='seam-row'>
157
- <div className='seam-heading'>{t.id}:</div>
158
- <div className='seam-content seam-code-id'>
159
- <span>{accessCode.access_code_id}</span>
179
+ <>
180
+ <Snackbar
181
+ variant='success'
182
+ message={snackbarMessage}
183
+ visible={accessCodeResult != null}
184
+ autoDismiss
185
+ onClose={() => {
186
+ setAccessCodeResult(null)
187
+ }}
188
+ />
189
+ <div className={classNames('seam-access-code-details', className)}>
190
+ <ContentHeader title='Access code' onBack={onBack} />
191
+ <div className='seam-summary'>
192
+ <div
193
+ className={classNames(
194
+ 'seam-top',
195
+ alerts.length > 0 && 'seam-top-has-alerts'
196
+ )}
197
+ >
198
+ <span className='seam-label'>{t.accessCode}</span>
199
+ <h5 className='seam-access-code-name'>{name}</h5>
200
+ <div className='seam-code'>
201
+ <span>{accessCode.code}</span>
160
202
  <IconButton
161
203
  onClick={() => {
162
- void copyToClipboard(accessCode.access_code_id)
204
+ void copyToClipboard(accessCode.code ?? '')
163
205
  }}
164
206
  >
165
207
  <CopyIcon />
166
208
  </IconButton>
167
209
  </div>
210
+ <div className='seam-duration'>
211
+ <Duration accessCode={accessCode} />
212
+ </div>
213
+ </div>
214
+ <Alerts alerts={alerts} className='seam-alerts-padded' />
215
+ <AccessCodeDevice
216
+ deviceId={accessCode.device_id}
217
+ disableLockUnlock={disableLockUnlock}
218
+ onSelectDevice={selectDevice}
219
+ />
220
+ </div>
221
+ {(!disableEditAccessCode || !disableDeleteAccessCode) && (
222
+ <div className='seam-actions'>
223
+ {!disableEditAccessCode && (
224
+ <Button
225
+ size='small'
226
+ onClick={handleEdit}
227
+ disabled={isAccessCodeBeingRemoved || isDeleting}
228
+ >
229
+ {t.editCode}
230
+ </Button>
231
+ )}
232
+ {!disableDeleteAccessCode && (
233
+ <Button
234
+ size='small'
235
+ onClick={handleDelete}
236
+ disabled={isAccessCodeBeingRemoved || isDeleting}
237
+ >
238
+ {t.deleteCode}
239
+ </Button>
240
+ )}
168
241
  </div>
169
242
  )}
170
- <div className='seam-row'>
171
- <div className='seam-heading'>{t.created}:</div>
172
- <div className='seam-content'>
173
- {formatDate(accessCode.created_at)}
243
+ <div className='seam-details'>
244
+ {!disableResourceIds && (
245
+ <div className='seam-row'>
246
+ <div className='seam-heading'>{t.id}:</div>
247
+ <div className='seam-content seam-code-id'>
248
+ <span>{accessCode.access_code_id}</span>
249
+ <IconButton
250
+ onClick={() => {
251
+ void copyToClipboard(accessCode.access_code_id)
252
+ }}
253
+ >
254
+ <CopyIcon />
255
+ </IconButton>
256
+ </div>
257
+ </div>
258
+ )}
259
+ <div className='seam-row'>
260
+ <div className='seam-heading'>{t.created}:</div>
261
+ <div className='seam-content'>
262
+ {formatDate(accessCode.created_at)}
263
+ </div>
174
264
  </div>
175
- </div>
176
265
 
177
- <div className='seam-row seam-schedule'>
178
- <div className='seam-heading'>{t.timing}:</div>
179
- <div className='seam-content'>
180
- <ScheduleInfo accessCode={accessCode} />
266
+ <div className='seam-row seam-schedule'>
267
+ <div className='seam-heading'>{t.timing}:</div>
268
+ <div className='seam-content'>
269
+ <ScheduleInfo accessCode={accessCode} />
270
+ </div>
181
271
  </div>
182
272
  </div>
183
273
  </div>
184
- </div>
274
+ </>
185
275
  )
186
276
  }
187
277
 
@@ -266,6 +356,11 @@ const formatDate = (date: string): string =>
266
356
  year: 'numeric',
267
357
  })
268
358
 
359
+ const accessCodeResultToMessage = (result: 'updated' | 'deleted'): string => {
360
+ if (result === 'deleted') return t.accessCodeDeleted
361
+ return t.accessCodeUpdated
362
+ }
363
+
269
364
  const t = {
270
365
  accessCode: 'Access code',
271
366
  fallbackName: 'Code',
@@ -282,4 +377,7 @@ const t = {
282
377
  at: 'at',
283
378
  editCode: 'Edit code',
284
379
  deleteCode: 'Delete code',
380
+ warningRemoving: 'This access code is currently being removed.',
381
+ accessCodeUpdated: 'Access code updated',
382
+ accessCodeDeleted: 'Access code is being removed',
285
383
  }
@@ -11,54 +11,75 @@ import { useToggle } from 'lib/ui/use-toggle.js'
11
11
  export interface AccessCodeMenuProps {
12
12
  accessCode: AccessCode
13
13
  onEdit: () => void
14
+ onDeleteSuccess: () => void
14
15
  onViewDetails: () => void
15
16
  disableEditAccessCode: boolean
16
17
  disableDeleteAccessCode: boolean
17
18
  }
18
19
 
19
20
  export function AccessCodeMenu(props: AccessCodeMenuProps): JSX.Element {
21
+ const [deleteConfirmationVisible, toggleDeleteConfirmation] = useToggle()
22
+
20
23
  return (
21
24
  <MoreActionsMenu
22
25
  menuProps={{
23
26
  backgroundProps: {
24
27
  className: 'seam-table-action-menu',
25
28
  },
29
+ onClose: () => {
30
+ if (deleteConfirmationVisible) {
31
+ toggleDeleteConfirmation()
32
+ }
33
+ },
26
34
  }}
27
35
  >
28
- <Content {...props} />
36
+ <Content
37
+ {...props}
38
+ deleteConfirmationVisible={deleteConfirmationVisible}
39
+ toggleDeleteConfirmation={toggleDeleteConfirmation}
40
+ />
29
41
  </MoreActionsMenu>
30
42
  )
31
43
  }
32
44
 
45
+ interface ContentProps extends AccessCodeMenuProps {
46
+ deleteConfirmationVisible: boolean
47
+ toggleDeleteConfirmation: () => void
48
+ }
49
+
33
50
  function Content({
34
51
  accessCode,
35
52
  onViewDetails,
36
53
  disableEditAccessCode,
37
54
  disableDeleteAccessCode,
38
55
  onEdit,
39
- }: AccessCodeMenuProps): JSX.Element {
40
- const [deleteConfirmationVisible, toggleDeleteConfirmation] = useToggle()
41
-
56
+ onDeleteSuccess,
57
+ deleteConfirmationVisible,
58
+ toggleDeleteConfirmation,
59
+ }: ContentProps): JSX.Element {
42
60
  const deleteAccessCode = useDeleteAccessCode()
61
+ const isAccessCodeBeingRemoved = accessCode.status === 'removing'
43
62
 
44
63
  if (deleteConfirmationVisible) {
45
64
  return (
46
65
  <div className='seam-delete-confirmation'>
47
66
  <span>{t.deleteCodeConfirmation}</span>
48
67
  <div className='seam-actions'>
49
- <Button
50
- onClick={toggleDeleteConfirmation}
51
- disabled={deleteAccessCode.isPending}
52
- >
68
+ <Button disabled={deleteAccessCode.isPending}>
53
69
  {t.cancelDelete}
54
70
  </Button>
55
71
  <Button
56
72
  variant='solid'
57
73
  disabled={deleteAccessCode.isPending}
58
74
  onClick={() => {
59
- deleteAccessCode.mutate({
60
- access_code_id: accessCode.access_code_id,
61
- })
75
+ deleteAccessCode.mutate(
76
+ {
77
+ access_code_id: accessCode.access_code_id,
78
+ },
79
+ {
80
+ onSuccess: onDeleteSuccess,
81
+ }
82
+ )
62
83
  }}
63
84
  >
64
85
  {t.confirmDelete}
@@ -84,10 +105,10 @@ function Content({
84
105
  </MenuItem>
85
106
  <div className='seam-divider' />
86
107
  <MenuItem onClick={onViewDetails}>{t.viewCodeDetails}</MenuItem>
87
- {!disableEditAccessCode && (
108
+ {!disableEditAccessCode && !isAccessCodeBeingRemoved && (
88
109
  <MenuItem onClick={onEdit}>{t.editCode}</MenuItem>
89
110
  )}
90
- {!disableDeleteAccessCode && (
111
+ {!disableDeleteAccessCode && !isAccessCodeBeingRemoved && (
91
112
  <>
92
113
  <div className='seam-divider' />
93
114
  <MenuItem
@@ -1,4 +1,5 @@
1
1
  import type { AccessCode } from '@seamapi/types/connect'
2
+ import classNames from 'classnames'
2
3
 
3
4
  import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
4
5
  import { TriangleWarningOutlineIcon } from 'lib/icons/TriangleWarningOutline.js'
@@ -13,6 +14,7 @@ export interface AccessCodeRowProps {
13
14
  accessCode: AccessCode
14
15
  onClick: () => void
15
16
  onEdit: () => void
17
+ onDeleteSuccess: () => void
16
18
  disableEditAccessCode: boolean
17
19
  disableDeleteAccessCode: boolean
18
20
  }
@@ -21,25 +23,37 @@ export function AccessCodeRow({
21
23
  onClick,
22
24
  accessCode,
23
25
  onEdit,
26
+ onDeleteSuccess,
24
27
  disableEditAccessCode,
25
28
  disableDeleteAccessCode,
26
29
  }: AccessCodeRowProps): JSX.Element {
30
+ const isAccessCodeBeingRemoved = accessCode.status === 'removing'
31
+
27
32
  const errorCount = accessCode.errors.length
28
33
  const warningCount = accessCode.warnings.length
29
- const isPlural = errorCount === 0 || errorCount > 1
30
- const errorIconTitle = isPlural
31
- ? `${errorCount} ${t.codeIssues}`
32
- : `${errorCount} ${t.codeIssue}`
33
- const warningIconTitle = isPlural
34
- ? `${warningCount} ${t.codeIssues}`
35
- : `${warningCount} ${t.codeIssue}`
34
+ const errorIconTitle =
35
+ errorCount === 0 || errorCount > 1
36
+ ? `${errorCount} ${t.codeIssues}`
37
+ : `${errorCount} ${t.codeIssue}`
38
+ const warningIconTitle =
39
+ warningCount === 0 || warningCount > 1
40
+ ? `${warningCount} ${t.codeIssues}`
41
+ : `${warningCount} ${t.codeIssue}`
36
42
 
37
43
  return (
38
44
  <TableRow onClick={onClick}>
39
- <TableCell className='seam-icon-cell'>
45
+ <TableCell
46
+ className={classNames('seam-icon-cell', {
47
+ 'seam-grayed-out': isAccessCodeBeingRemoved,
48
+ })}
49
+ >
40
50
  <AccessCodeMainIcon accessCode={accessCode} />
41
51
  </TableCell>
42
- <TableCell className='seam-name-cell'>
52
+ <TableCell
53
+ className={classNames('seam-name-cell', {
54
+ 'seam-grayed-out': isAccessCodeBeingRemoved,
55
+ })}
56
+ >
43
57
  <Title className='seam-truncated-text'>{accessCode.name}</Title>
44
58
  <CodeDetails accessCode={accessCode} />
45
59
  </TableCell>
@@ -63,6 +77,7 @@ export function AccessCodeRow({
63
77
  <AccessCodeMenu
64
78
  accessCode={accessCode}
65
79
  onEdit={onEdit}
80
+ onDeleteSuccess={onDeleteSuccess}
66
81
  onViewDetails={onClick}
67
82
  disableDeleteAccessCode={disableDeleteAccessCode}
68
83
  disableEditAccessCode={disableEditAccessCode}
@@ -1,6 +1,6 @@
1
1
  import type { AccessCode } from '@seamapi/types/connect'
2
2
  import classNames from 'classnames'
3
- import { useCallback, useMemo, useState } from 'react'
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
4
 
5
5
  import { compareByCreatedAtDesc } from 'lib/dates.js'
6
6
  import { AddIcon } from 'lib/icons/Add.js'
@@ -48,20 +48,6 @@ export interface AccessCodeTableProps extends CommonProps {
48
48
  heading?: string | null
49
49
  }
50
50
 
51
- const defaultAccessCodeFilter = (
52
- accessCode: AccessCode,
53
- searchInputValue: string
54
- ): boolean => {
55
- const value = searchInputValue.trim().toLowerCase()
56
- if (value === '') return true
57
- const name = accessCode.name ?? ''
58
- const code = accessCode.code ?? ''
59
- return (
60
- name.trim().toLowerCase().includes(value) ||
61
- code.trim().toLowerCase().includes(value)
62
- )
63
- }
64
-
65
51
  export function AccessCodeTable({
66
52
  deviceId,
67
53
  disableSearch = false,
@@ -127,11 +113,21 @@ export function AccessCodeTable({
127
113
  )
128
114
 
129
115
  const [accessCodeResult, setAccessCodeResult] = useState<
130
- 'created' | 'updated' | null
116
+ 'created' | 'updated' | 'deleted' | null
131
117
  >(null)
118
+ const [snackbarMessage, setSnackbarMessage] = useState<string>('')
132
119
 
133
- const accessCodeResultMessage =
134
- accessCodeResult === 'created' ? t.accesCodeCreated : t.accesCodeUpdated
120
+ const handleAccessCodeDeleteSuccess = useCallback((): void => {
121
+ setAccessCodeResult('deleted')
122
+ }, [setAccessCodeResult])
123
+
124
+ // Circumvent Snackbar bug that causes it to switch to default message
125
+ // while the dismiss animation is playing
126
+ useEffect(() => {
127
+ if (accessCodeResult !== null) {
128
+ setSnackbarMessage(accessCodeResultToMessage(accessCodeResult))
129
+ }
130
+ }, [accessCodeResult])
135
131
 
136
132
  if (selectedEditAccessCodeId != null) {
137
133
  return (
@@ -159,38 +155,22 @@ export function AccessCodeTable({
159
155
 
160
156
  if (selectedViewAccessCodeId != null) {
161
157
  return (
162
- <>
163
- <Snackbar
164
- variant='success'
165
- message={accessCodeResultMessage}
166
- visible={accessCodeResult != null}
167
- autoDismiss
168
- onClose={() => {
169
- setAccessCodeResult(null)
170
- }}
171
- />
172
- <NestedAccessCodeDetails
173
- accessCodeId={selectedViewAccessCodeId}
174
- onEdit={() => {
175
- setSelectedEditAccessCodeId(selectedViewAccessCodeId)
176
- }}
177
- errorFilter={errorFilter}
178
- warningFilter={warningFilter}
179
- disableLockUnlock={disableLockUnlock}
180
- disableCreateAccessCode={disableCreateAccessCode}
181
- disableEditAccessCode={disableEditAccessCode}
182
- disableDeleteAccessCode={disableDeleteAccessCode}
183
- disableResourceIds={disableResourceIds}
184
- disableConnectedAccountInformation={
185
- disableConnectedAccountInformation
186
- }
187
- disableClimateSettingSchedules={disableClimateSettingSchedules}
188
- onBack={() => {
189
- setSelectedViewAccessCodeId(null)
190
- }}
191
- className={className}
192
- />
193
- </>
158
+ <NestedAccessCodeDetails
159
+ accessCodeId={selectedViewAccessCodeId}
160
+ errorFilter={errorFilter}
161
+ warningFilter={warningFilter}
162
+ disableLockUnlock={disableLockUnlock}
163
+ disableCreateAccessCode={disableCreateAccessCode}
164
+ disableEditAccessCode={disableEditAccessCode}
165
+ disableDeleteAccessCode={disableDeleteAccessCode}
166
+ disableResourceIds={disableResourceIds}
167
+ disableConnectedAccountInformation={disableConnectedAccountInformation}
168
+ disableClimateSettingSchedules={disableClimateSettingSchedules}
169
+ onBack={() => {
170
+ setSelectedViewAccessCodeId(null)
171
+ }}
172
+ className={className}
173
+ />
194
174
  )
195
175
  }
196
176
 
@@ -220,7 +200,7 @@ export function AccessCodeTable({
220
200
  <>
221
201
  <Snackbar
222
202
  variant='success'
223
- message={accessCodeResultMessage}
203
+ message={snackbarMessage}
224
204
  visible={accessCodeResult != null}
225
205
  autoDismiss
226
206
  onClose={() => {
@@ -267,6 +247,7 @@ export function AccessCodeTable({
267
247
  accessCodes={filteredAccessCodes}
268
248
  onAccessCodeClick={handleAccessCodeClick}
269
249
  onAccessCodeEdit={handleAccessCodeEdit}
250
+ onAccessCodeDeleteSuccess={handleAccessCodeDeleteSuccess}
270
251
  errorFilter={errorFilter}
271
252
  warningFilter={warningFilter}
272
253
  disableEditAccessCode={disableEditAccessCode}
@@ -295,6 +276,7 @@ function Content(props: {
295
276
  accessCodes: AccessCode[]
296
277
  onAccessCodeClick: (accessCodeId: string) => void
297
278
  onAccessCodeEdit: (accessCodeId: string) => void
279
+ onAccessCodeDeleteSuccess: (accessCodeId: string) => void
298
280
  errorFilter: (error: AccessCode['errors'][number]) => boolean
299
281
  warningFilter: (warning: AccessCode['warnings'][number]) => boolean
300
282
  disableEditAccessCode: boolean
@@ -304,6 +286,7 @@ function Content(props: {
304
286
  accessCodes,
305
287
  onAccessCodeClick,
306
288
  onAccessCodeEdit,
289
+ onAccessCodeDeleteSuccess,
307
290
  errorFilter,
308
291
  warningFilter,
309
292
  disableEditAccessCode,
@@ -350,18 +333,44 @@ function Content(props: {
350
333
  onEdit={() => {
351
334
  onAccessCodeEdit(accessCode.access_code_id)
352
335
  }}
336
+ onDeleteSuccess={() => {
337
+ onAccessCodeDeleteSuccess(accessCode.access_code_id)
338
+ }}
353
339
  />
354
340
  ))}
355
341
  </>
356
342
  )
357
343
  }
358
344
 
345
+ const defaultAccessCodeFilter = (
346
+ accessCode: AccessCode,
347
+ searchInputValue: string
348
+ ): boolean => {
349
+ const value = searchInputValue.trim().toLowerCase()
350
+ if (value === '') return true
351
+ const name = accessCode.name ?? ''
352
+ const code = accessCode.code ?? ''
353
+ return (
354
+ name.trim().toLowerCase().includes(value) ||
355
+ code.trim().toLowerCase().includes(value)
356
+ )
357
+ }
358
+
359
+ const accessCodeResultToMessage = (
360
+ result: 'created' | 'updated' | 'deleted'
361
+ ): string => {
362
+ if (result === 'created') return t.accessCodeCreated
363
+ if (result === 'deleted') return t.accessCodeDeleted
364
+ return t.accessCodeUpdated
365
+ }
366
+
359
367
  const t = {
360
368
  accessCodes: 'Access Codes',
361
369
  noAccessCodesMessage: 'Sorry, no access codes were found',
362
370
  loading: 'Loading access codes',
363
- accesCodeUpdated: 'Access code updated',
364
- accesCodeCreated: 'Access code created',
371
+ accessCodeUpdated: 'Access code updated',
372
+ accessCodeCreated: 'Access code created',
373
+ accessCodeDeleted: 'Access code is being removed',
365
374
  tryAgain: 'Try again',
366
375
  fallbackErrorMessage: 'Access codes could not be loaded',
367
376
  }