@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.
- package/README.md +2 -2
- package/dist/elements.js +4358 -4252
- 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/seam/access-codes/use-delete-access-code.js +9 -0
- package/lib/seam/access-codes/use-delete-access-code.js.map +1 -1
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +5 -2
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +65 -10
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.d.ts +1 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js +13 -5
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.d.ts +2 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.js +10 -5
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +40 -24
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
- package/lib/ui/Menu/Menu.d.ts +2 -1
- package/lib/ui/Menu/Menu.js +2 -1
- package/lib/ui/Menu/Menu.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/seam/access-codes/use-delete-access-code.ts +15 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +3 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +169 -71
- package/src/lib/seam/components/AccessCodeTable/AccessCodeMenu.tsx +34 -13
- package/src/lib/seam/components/AccessCodeTable/AccessCodeRow.tsx +24 -9
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +62 -53
- package/src/lib/ui/Menu/Menu.tsx +3 -0
- package/src/lib/version.ts +1 -1
- 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
|
|
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
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
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-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const warningIconTitle =
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
|
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
|
|
134
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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={
|
|
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
|
-
|
|
364
|
-
|
|
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
|
}
|