@seamapi/react 1.43.0 → 1.45.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 +5353 -5226
- package/dist/elements.js.map +1 -1
- package/dist/index.css +28 -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.d.ts +8 -0
- package/lib/seam/access-codes/use-delete-access-code.js +22 -0
- package/lib/seam/access-codes/use-delete-access-code.js.map +1 -0
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.d.ts +2 -1
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +7 -2
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.d.ts +10 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js +44 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeMenu.js.map +1 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.d.ts +10 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.js +26 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeRow.js.map +1 -0
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.d.ts +2 -1
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js +7 -40
- package/lib/seam/components/AccessCodeTable/AccessCodeTable.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.js +10 -12
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.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-filtered-device-models.d.ts +2 -1
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js +3 -3
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -1
- package/lib/ui/Menu/MenuItem.d.ts +4 -2
- package/lib/ui/Menu/MenuItem.js +7 -4
- package/lib/ui/Menu/MenuItem.js.map +1 -1
- package/lib/ui/thermostat/ClimateSettingStatus.d.ts +9 -0
- package/lib/ui/thermostat/ClimateSettingStatus.js +33 -0
- package/lib/ui/thermostat/ClimateSettingStatus.js.map +1 -0
- 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 +47 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +22 -4
- package/src/lib/seam/components/AccessCodeTable/AccessCodeMenu.tsx +118 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeRow.tsx +73 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.element.ts +1 -0
- package/src/lib/seam/components/AccessCodeTable/AccessCodeTable.tsx +9 -87
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +3 -0
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceFilterArea.tsx +16 -14
- 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-filtered-device-models.ts +5 -3
- package/src/lib/ui/Menu/MenuItem.tsx +17 -6
- package/src/lib/ui/thermostat/ClimateSettingStatus.tsx +88 -0
- package/src/lib/version.ts +1 -1
- package/src/styles/_access-code-details.scss +2 -0
- package/src/styles/_access-code-table.scss +12 -0
- package/src/styles/_main.scss +2 -0
- package/src/styles/_thermostat.scss +21 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { CopyIcon } from 'lib/icons/Copy.js'
|
|
2
|
+
import type { UseAccessCodeData } from 'lib/seam/access-codes/use-access-code.js'
|
|
3
|
+
import { useDeleteAccessCode } from 'lib/seam/access-codes/use-delete-access-code.js'
|
|
4
|
+
import { Button } from 'lib/ui/Button.js'
|
|
5
|
+
import { copyToClipboard } from 'lib/ui/clipboard.js'
|
|
6
|
+
import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
|
|
7
|
+
import { MoreActionsMenu } from 'lib/ui/Menu/MoreActionsMenu.js'
|
|
8
|
+
import { useToggle } from 'lib/ui/use-toggle.js'
|
|
9
|
+
|
|
10
|
+
export interface AccessCodeMenuProps {
|
|
11
|
+
accessCode: NonNullable<UseAccessCodeData>
|
|
12
|
+
onEdit: () => void
|
|
13
|
+
onViewDetails: () => void
|
|
14
|
+
disableEditAccessCode: boolean
|
|
15
|
+
disableDeleteAccessCode: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AccessCodeMenu(props: AccessCodeMenuProps): JSX.Element {
|
|
19
|
+
return (
|
|
20
|
+
<MoreActionsMenu
|
|
21
|
+
menuProps={{
|
|
22
|
+
backgroundProps: {
|
|
23
|
+
className: 'seam-access-code-table-action-menu',
|
|
24
|
+
},
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<Content {...props} />
|
|
28
|
+
</MoreActionsMenu>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Content({
|
|
33
|
+
accessCode,
|
|
34
|
+
onViewDetails,
|
|
35
|
+
disableEditAccessCode,
|
|
36
|
+
disableDeleteAccessCode,
|
|
37
|
+
onEdit,
|
|
38
|
+
}: AccessCodeMenuProps): JSX.Element {
|
|
39
|
+
const [deleteConfirmationVisible, toggleDeleteConfirmation] = useToggle()
|
|
40
|
+
|
|
41
|
+
const deleteAccessCode = useDeleteAccessCode()
|
|
42
|
+
|
|
43
|
+
if (deleteConfirmationVisible) {
|
|
44
|
+
return (
|
|
45
|
+
<div className='seam-delete-confirmation'>
|
|
46
|
+
<span>{t.deleteCodeConfirmation}</span>
|
|
47
|
+
<div className='seam-actions'>
|
|
48
|
+
<Button
|
|
49
|
+
onClick={toggleDeleteConfirmation}
|
|
50
|
+
disabled={deleteAccessCode.isLoading}
|
|
51
|
+
>
|
|
52
|
+
{t.cancelDelete}
|
|
53
|
+
</Button>
|
|
54
|
+
<Button
|
|
55
|
+
variant='solid'
|
|
56
|
+
disabled={deleteAccessCode.isLoading}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
deleteAccessCode.mutate({
|
|
59
|
+
access_code_id: accessCode.access_code_id,
|
|
60
|
+
})
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
{t.confirmDelete}
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
<MenuItem
|
|
73
|
+
onClick={() => {
|
|
74
|
+
void copyToClipboard(accessCode.code ?? '')
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<div className='seam-menu-item-copy'>
|
|
78
|
+
<span>
|
|
79
|
+
{t.copyCode} - {accessCode.code}
|
|
80
|
+
</span>
|
|
81
|
+
<CopyIcon />
|
|
82
|
+
</div>
|
|
83
|
+
</MenuItem>
|
|
84
|
+
<div className='seam-divider' />
|
|
85
|
+
<MenuItem onClick={onViewDetails}>{t.viewCodeDetails}</MenuItem>
|
|
86
|
+
{!disableEditAccessCode && (
|
|
87
|
+
<MenuItem onClick={onEdit}>{t.editCode}</MenuItem>
|
|
88
|
+
)}
|
|
89
|
+
{!disableDeleteAccessCode && (
|
|
90
|
+
<>
|
|
91
|
+
<div className='seam-divider' />
|
|
92
|
+
<MenuItem
|
|
93
|
+
onClick={(event) => {
|
|
94
|
+
event.stopPropagation() // Prevent hiding menu on outside click
|
|
95
|
+
toggleDeleteConfirmation()
|
|
96
|
+
}}
|
|
97
|
+
preventDefaultOnClick
|
|
98
|
+
className='seam-text-danger'
|
|
99
|
+
>
|
|
100
|
+
{t.deleteCode}
|
|
101
|
+
</MenuItem>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const t = {
|
|
109
|
+
copyCode: 'Copy code',
|
|
110
|
+
codeIssue: 'code issue',
|
|
111
|
+
codeIssues: 'code issues',
|
|
112
|
+
editCode: 'Edit code',
|
|
113
|
+
viewCodeDetails: 'View code details',
|
|
114
|
+
deleteCode: 'Delete code',
|
|
115
|
+
deleteCodeConfirmation: 'Delete this code and data?',
|
|
116
|
+
cancelDelete: 'Cancel',
|
|
117
|
+
confirmDelete: 'Delete',
|
|
118
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
|
|
2
|
+
import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
|
|
3
|
+
import { TriangleWarningOutlineIcon } from 'lib/icons/TriangleWarningOutline.js'
|
|
4
|
+
import type { UseAccessCodesData } from 'lib/seam/access-codes/use-access-codes.js'
|
|
5
|
+
import { AccessCodeMenu } from 'lib/seam/components/AccessCodeTable/AccessCodeMenu.js'
|
|
6
|
+
import { CodeDetails } from 'lib/seam/components/AccessCodeTable/CodeDetails.js'
|
|
7
|
+
import { TableCell } from 'lib/ui/Table/TableCell.js'
|
|
8
|
+
import { TableRow } from 'lib/ui/Table/TableRow.js'
|
|
9
|
+
import { Title } from 'lib/ui/typography/Title.js'
|
|
10
|
+
|
|
11
|
+
export interface AccessCodeRowProps {
|
|
12
|
+
accessCode: UseAccessCodesData[number]
|
|
13
|
+
onClick: () => void
|
|
14
|
+
onEdit: () => void
|
|
15
|
+
disableEditAccessCode: boolean
|
|
16
|
+
disableDeleteAccessCode: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function AccessCodeRow({
|
|
20
|
+
onClick,
|
|
21
|
+
accessCode,
|
|
22
|
+
onEdit,
|
|
23
|
+
disableEditAccessCode,
|
|
24
|
+
disableDeleteAccessCode,
|
|
25
|
+
}: AccessCodeRowProps): JSX.Element {
|
|
26
|
+
const errorCount = accessCode.errors.length
|
|
27
|
+
const warningCount = accessCode.warnings.length
|
|
28
|
+
const isPlural = errorCount === 0 || errorCount > 1
|
|
29
|
+
const errorIconTitle = isPlural
|
|
30
|
+
? `${errorCount} ${t.codeIssues}`
|
|
31
|
+
: `${errorCount} ${t.codeIssue}`
|
|
32
|
+
const warningIconTitle = isPlural
|
|
33
|
+
? `${warningCount} ${t.codeIssues}`
|
|
34
|
+
: `${warningCount} ${t.codeIssue}`
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TableRow onClick={onClick}>
|
|
38
|
+
<TableCell className='seam-icon-cell'>
|
|
39
|
+
<div>
|
|
40
|
+
<AccessCodeKeyIcon />
|
|
41
|
+
</div>
|
|
42
|
+
</TableCell>
|
|
43
|
+
<TableCell className='seam-name-cell'>
|
|
44
|
+
<Title className='seam-truncated-text'>{accessCode.name}</Title>
|
|
45
|
+
<CodeDetails accessCode={accessCode} />
|
|
46
|
+
</TableCell>
|
|
47
|
+
<TableCell className='seam-action-cell'>
|
|
48
|
+
{errorCount > 0 && (
|
|
49
|
+
<div className='seam-code-issue-icon-wrap' title={errorIconTitle}>
|
|
50
|
+
<ExclamationCircleOutlineIcon />
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
{errorCount === 0 && warningCount > 0 && (
|
|
54
|
+
<div className='seam-code-issue-icon-wrap' title={warningIconTitle}>
|
|
55
|
+
<TriangleWarningOutlineIcon />
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
<AccessCodeMenu
|
|
59
|
+
accessCode={accessCode}
|
|
60
|
+
onEdit={onEdit}
|
|
61
|
+
onViewDetails={onClick}
|
|
62
|
+
disableDeleteAccessCode={disableDeleteAccessCode}
|
|
63
|
+
disableEditAccessCode={disableEditAccessCode}
|
|
64
|
+
/>
|
|
65
|
+
</TableCell>
|
|
66
|
+
</TableRow>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const t = {
|
|
71
|
+
codeIssue: 'code issue',
|
|
72
|
+
codeIssues: 'code issues',
|
|
73
|
+
}
|
|
@@ -7,6 +7,7 @@ export const name = 'seam-access-code-table'
|
|
|
7
7
|
export const props: ElementProps<Omit<AccessCodeTableProps, 'title'>> = {
|
|
8
8
|
deviceId: 'string',
|
|
9
9
|
disableLockUnlock: 'boolean',
|
|
10
|
+
disableDeleteAccessCode: 'boolean',
|
|
10
11
|
disableSearch: 'boolean',
|
|
11
12
|
accessCodeFilter: 'object',
|
|
12
13
|
accessCodeComparator: 'object',
|
|
@@ -2,11 +2,7 @@ import classNames from 'classnames'
|
|
|
2
2
|
import { useCallback, useMemo, useState } from 'react'
|
|
3
3
|
|
|
4
4
|
import { compareByCreatedAtDesc } from 'lib/dates.js'
|
|
5
|
-
import { AccessCodeKeyIcon } from 'lib/icons/AccessCodeKey.js'
|
|
6
5
|
import { AddIcon } from 'lib/icons/Add.js'
|
|
7
|
-
import { CopyIcon } from 'lib/icons/Copy.js'
|
|
8
|
-
import { ExclamationCircleOutlineIcon } from 'lib/icons/ExclamationCircleOutline.js'
|
|
9
|
-
import { TriangleWarningOutlineIcon } from 'lib/icons/TriangleWarningOutline.js'
|
|
10
6
|
import {
|
|
11
7
|
useAccessCodes,
|
|
12
8
|
type UseAccessCodesData,
|
|
@@ -16,27 +12,21 @@ import {
|
|
|
16
12
|
type AccessCodeFilter,
|
|
17
13
|
AccessCodeHealthBar,
|
|
18
14
|
} from 'lib/seam/components/AccessCodeTable/AccessCodeHealthBar.js'
|
|
19
|
-
import {
|
|
15
|
+
import { AccessCodeRow } from 'lib/seam/components/AccessCodeTable/AccessCodeRow.js'
|
|
20
16
|
import { CreateAccessCodeForm } from 'lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.js'
|
|
21
17
|
import { EditAccessCodeForm } from 'lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js'
|
|
22
|
-
import { copyToClipboard } from 'lib/ui/clipboard.js'
|
|
23
18
|
import { IconButton } from 'lib/ui/IconButton.js'
|
|
24
19
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
25
|
-
import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
|
|
26
|
-
import { MoreActionsMenu } from 'lib/ui/Menu/MoreActionsMenu.js'
|
|
27
20
|
import { EmptyPlaceholder } from 'lib/ui/Table/EmptyPlaceholder.js'
|
|
28
21
|
import { TableBody } from 'lib/ui/Table/TableBody.js'
|
|
29
|
-
import { TableCell } from 'lib/ui/Table/TableCell.js'
|
|
30
22
|
import { TableHeader } from 'lib/ui/Table/TableHeader.js'
|
|
31
|
-
import { TableRow } from 'lib/ui/Table/TableRow.js'
|
|
32
23
|
import { TableTitle } from 'lib/ui/Table/TableTitle.js'
|
|
33
24
|
import { SearchTextField } from 'lib/ui/TextField/SearchTextField.js'
|
|
34
25
|
import { Caption } from 'lib/ui/typography/Caption.js'
|
|
35
|
-
import { Title } from 'lib/ui/typography/Title.js'
|
|
36
26
|
import { useToggle } from 'lib/ui/use-toggle.js'
|
|
37
27
|
|
|
38
28
|
const disableCreateAccessCode = true
|
|
39
|
-
const disableEditAccessCode =
|
|
29
|
+
const disableEditAccessCode = true
|
|
40
30
|
|
|
41
31
|
export interface AccessCodeTableProps {
|
|
42
32
|
deviceId: string
|
|
@@ -59,6 +49,7 @@ export interface AccessCodeTableProps {
|
|
|
59
49
|
*/
|
|
60
50
|
title?: string | null
|
|
61
51
|
className?: string
|
|
52
|
+
disableDeleteAccessCode?: boolean
|
|
62
53
|
}
|
|
63
54
|
|
|
64
55
|
type AccessCode = UseAccessCodesData[number]
|
|
@@ -88,6 +79,7 @@ export function AccessCodeTable({
|
|
|
88
79
|
heading = t.accessCodes,
|
|
89
80
|
title = t.accessCodes,
|
|
90
81
|
className,
|
|
82
|
+
disableDeleteAccessCode = false,
|
|
91
83
|
}: AccessCodeTableProps): JSX.Element {
|
|
92
84
|
const { accessCodes } = useAccessCodes({
|
|
93
85
|
device_id: deviceId,
|
|
@@ -155,6 +147,7 @@ export function AccessCodeTable({
|
|
|
155
147
|
onEdit={() => {
|
|
156
148
|
setSelectedEditAccessCodeId(selectedViewAccessCodeId)
|
|
157
149
|
}}
|
|
150
|
+
disableDeleteAccessCode={disableDeleteAccessCode}
|
|
158
151
|
/>
|
|
159
152
|
)
|
|
160
153
|
}
|
|
@@ -205,6 +198,7 @@ export function AccessCodeTable({
|
|
|
205
198
|
onAccessCodeClick={handleAccessCodeClick}
|
|
206
199
|
onAccessCodeEdit={handleAccessCodeEdit}
|
|
207
200
|
disableEditAccessCode={disableEditAccessCode}
|
|
201
|
+
disableDeleteAccessCode={disableDeleteAccessCode}
|
|
208
202
|
/>
|
|
209
203
|
</TableBody>
|
|
210
204
|
</div>
|
|
@@ -216,12 +210,14 @@ function Content(props: {
|
|
|
216
210
|
onAccessCodeClick: (accessCodeId: string) => void
|
|
217
211
|
onAccessCodeEdit: (accessCodeId: string) => void
|
|
218
212
|
disableEditAccessCode: boolean
|
|
213
|
+
disableDeleteAccessCode: boolean
|
|
219
214
|
}): JSX.Element {
|
|
220
215
|
const {
|
|
221
216
|
accessCodes,
|
|
222
217
|
onAccessCodeClick,
|
|
223
218
|
onAccessCodeEdit,
|
|
224
219
|
disableEditAccessCode,
|
|
220
|
+
disableDeleteAccessCode,
|
|
225
221
|
} = props
|
|
226
222
|
const [filter, setFilter] = useState<AccessCodeFilter | null>(null)
|
|
227
223
|
|
|
@@ -258,6 +254,7 @@ function Content(props: {
|
|
|
258
254
|
onAccessCodeClick(accessCode.access_code_id)
|
|
259
255
|
}}
|
|
260
256
|
disableEditAccessCode={disableEditAccessCode}
|
|
257
|
+
disableDeleteAccessCode={disableDeleteAccessCode}
|
|
261
258
|
onEdit={() => {
|
|
262
259
|
onAccessCodeEdit(accessCode.access_code_id)
|
|
263
260
|
}}
|
|
@@ -267,82 +264,7 @@ function Content(props: {
|
|
|
267
264
|
)
|
|
268
265
|
}
|
|
269
266
|
|
|
270
|
-
function AccessCodeRow(props: {
|
|
271
|
-
accessCode: UseAccessCodesData[number]
|
|
272
|
-
onClick: () => void
|
|
273
|
-
onEdit: () => void
|
|
274
|
-
disableEditAccessCode: boolean
|
|
275
|
-
}): JSX.Element {
|
|
276
|
-
const { onClick, accessCode, onEdit, disableEditAccessCode } = props
|
|
277
|
-
|
|
278
|
-
const errorCount = accessCode.errors.length
|
|
279
|
-
const warningCount = accessCode.warnings.length
|
|
280
|
-
const isPlural = errorCount === 0 || errorCount > 1
|
|
281
|
-
const errorIconTitle = isPlural
|
|
282
|
-
? `${errorCount} ${t.codeIssues}`
|
|
283
|
-
: `${errorCount} ${t.codeIssue}`
|
|
284
|
-
const warningIconTitle = isPlural
|
|
285
|
-
? `${warningCount} ${t.codeIssues}`
|
|
286
|
-
: `${warningCount} ${t.codeIssue}`
|
|
287
|
-
|
|
288
|
-
return (
|
|
289
|
-
<TableRow onClick={onClick}>
|
|
290
|
-
<TableCell className='seam-icon-cell'>
|
|
291
|
-
<div>
|
|
292
|
-
<AccessCodeKeyIcon />
|
|
293
|
-
</div>
|
|
294
|
-
</TableCell>
|
|
295
|
-
<TableCell className='seam-name-cell'>
|
|
296
|
-
<Title className='seam-truncated-text'>{accessCode.name}</Title>
|
|
297
|
-
<CodeDetails accessCode={accessCode} />
|
|
298
|
-
</TableCell>
|
|
299
|
-
<TableCell className='seam-action-cell'>
|
|
300
|
-
{errorCount > 0 && (
|
|
301
|
-
<div className='seam-code-issue-icon-wrap' title={errorIconTitle}>
|
|
302
|
-
<ExclamationCircleOutlineIcon />
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
{errorCount === 0 && warningCount > 0 && (
|
|
306
|
-
<div className='seam-code-issue-icon-wrap' title={warningIconTitle}>
|
|
307
|
-
<TriangleWarningOutlineIcon />
|
|
308
|
-
</div>
|
|
309
|
-
)}
|
|
310
|
-
<MoreActionsMenu
|
|
311
|
-
menuProps={{
|
|
312
|
-
backgroundProps: {
|
|
313
|
-
className: 'seam-access-code-table-action-menu',
|
|
314
|
-
},
|
|
315
|
-
}}
|
|
316
|
-
>
|
|
317
|
-
<MenuItem
|
|
318
|
-
onClick={() => {
|
|
319
|
-
void copyToClipboard(accessCode.code ?? '')
|
|
320
|
-
}}
|
|
321
|
-
>
|
|
322
|
-
<div className='seam-menu-item-copy'>
|
|
323
|
-
<span>
|
|
324
|
-
{t.copyCode} - {accessCode.code}
|
|
325
|
-
</span>
|
|
326
|
-
<CopyIcon />
|
|
327
|
-
</div>
|
|
328
|
-
</MenuItem>
|
|
329
|
-
{!disableEditAccessCode && (
|
|
330
|
-
<>
|
|
331
|
-
<div className='seam-divider' />
|
|
332
|
-
<MenuItem onClick={onEdit}>{t.editCode}</MenuItem>
|
|
333
|
-
</>
|
|
334
|
-
)}
|
|
335
|
-
</MoreActionsMenu>
|
|
336
|
-
</TableCell>
|
|
337
|
-
</TableRow>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
267
|
const t = {
|
|
342
268
|
accessCodes: 'Access Codes',
|
|
343
|
-
copyCode: 'Copy code',
|
|
344
269
|
noAccessCodesMessage: 'Sorry, no access codes were found',
|
|
345
|
-
codeIssue: 'code issue',
|
|
346
|
-
codeIssues: 'code issues',
|
|
347
|
-
editCode: 'Edit code',
|
|
348
270
|
}
|
|
@@ -14,6 +14,7 @@ interface SupportedDeviceContentProps {
|
|
|
14
14
|
resetFilterValue: () => void
|
|
15
15
|
filters: DeviceModelFilters
|
|
16
16
|
brands: string[] | null
|
|
17
|
+
excludedBrands: string[]
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function SupportedDeviceContent({
|
|
@@ -21,12 +22,14 @@ export function SupportedDeviceContent({
|
|
|
21
22
|
filterValue,
|
|
22
23
|
filters,
|
|
23
24
|
brands,
|
|
25
|
+
excludedBrands,
|
|
24
26
|
}: SupportedDeviceContentProps): JSX.Element | null {
|
|
25
27
|
const { deviceModels, isLoading, isError, refetch } = useFilteredDeviceModels(
|
|
26
28
|
{
|
|
27
29
|
filterValue,
|
|
28
30
|
filters,
|
|
29
31
|
brands,
|
|
32
|
+
excludedBrands,
|
|
30
33
|
}
|
|
31
34
|
)
|
|
32
35
|
|
|
@@ -14,6 +14,7 @@ interface SupportedDeviceFilterAreaProps {
|
|
|
14
14
|
filters: DeviceModelFilters
|
|
15
15
|
setFilters: Dispatch<SetStateAction<DeviceModelFilters>>
|
|
16
16
|
brands: string[] | null
|
|
17
|
+
excludedBrands: string[]
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function SupportedDeviceFilterArea({
|
|
@@ -22,10 +23,11 @@ export function SupportedDeviceFilterArea({
|
|
|
22
23
|
filters,
|
|
23
24
|
setFilters,
|
|
24
25
|
brands,
|
|
26
|
+
excludedBrands,
|
|
25
27
|
}: SupportedDeviceFilterAreaProps): JSX.Element {
|
|
26
28
|
const appliedFiltersCount = getAppliedFilterCount(filters)
|
|
27
29
|
|
|
28
|
-
const availableBrands = useAvailableBrands(brands)
|
|
30
|
+
const availableBrands = useAvailableBrands(brands, excludedBrands)
|
|
29
31
|
|
|
30
32
|
const resetFilter = (filterType: keyof DeviceModelFilters): void => {
|
|
31
33
|
setFilters((filters) => ({
|
|
@@ -121,27 +123,27 @@ const getAppliedFilterCount = (filters: DeviceModelFilters): number => {
|
|
|
121
123
|
return count
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
const useAvailableBrands = (
|
|
126
|
+
const useAvailableBrands = (
|
|
127
|
+
brands: string[] | null,
|
|
128
|
+
excludedBrands: string[]
|
|
129
|
+
): string[] => {
|
|
125
130
|
const { deviceModels } = useDeviceModels()
|
|
126
131
|
|
|
127
132
|
if (deviceModels == null) return []
|
|
128
133
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
uniqueBrands.add(deviceModel.brand)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return Array.from(uniqueBrands)
|
|
134
|
+
const availableBrands = deviceModels
|
|
135
|
+
.map(({ brand }) => brand.trim())
|
|
136
|
+
.filter((brand) => {
|
|
137
|
+
// UPSTREAM: API can return an empty value for brand.
|
|
138
|
+
return brand !== ''
|
|
139
|
+
})
|
|
140
140
|
.filter((brand) => {
|
|
141
141
|
if (brands === null) return true
|
|
142
|
-
return brands.includes(brand)
|
|
142
|
+
return brands.includes(brand) && !excludedBrands.includes(brand)
|
|
143
143
|
})
|
|
144
144
|
.map((brand) => capitalize(brand))
|
|
145
|
+
|
|
146
|
+
return Array.from(new Set(availableBrands))
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
const t = {
|
|
@@ -12,6 +12,7 @@ export interface SupportedDeviceTableProps {
|
|
|
12
12
|
*/
|
|
13
13
|
cannotFilter?: boolean
|
|
14
14
|
brands?: string[] | null
|
|
15
|
+
excludedBrands?: string[]
|
|
15
16
|
className?: string
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -19,6 +20,7 @@ export function SupportedDeviceTable({
|
|
|
19
20
|
disableFilter = false,
|
|
20
21
|
cannotFilter,
|
|
21
22
|
brands = null,
|
|
23
|
+
excludedBrands = [],
|
|
22
24
|
className,
|
|
23
25
|
}: SupportedDeviceTableProps = {}): JSX.Element {
|
|
24
26
|
const [filterValue, setFilterValue] = useState('')
|
|
@@ -43,6 +45,7 @@ export function SupportedDeviceTable({
|
|
|
43
45
|
filters={filters}
|
|
44
46
|
setFilters={setFilters}
|
|
45
47
|
brands={brands}
|
|
48
|
+
excludedBrands={excludedBrands}
|
|
46
49
|
/>
|
|
47
50
|
)}
|
|
48
51
|
<SupportedDeviceContent
|
|
@@ -52,6 +55,7 @@ export function SupportedDeviceTable({
|
|
|
52
55
|
filterValue={filterValue}
|
|
53
56
|
filters={filters}
|
|
54
57
|
brands={brands}
|
|
58
|
+
excludedBrands={excludedBrands}
|
|
55
59
|
/>
|
|
56
60
|
</div>
|
|
57
61
|
)
|
|
@@ -12,10 +12,12 @@ export const useFilteredDeviceModels = ({
|
|
|
12
12
|
filterValue,
|
|
13
13
|
filters,
|
|
14
14
|
brands,
|
|
15
|
+
excludedBrands,
|
|
15
16
|
}: {
|
|
16
17
|
filterValue: string
|
|
17
18
|
filters: DeviceModelFilters
|
|
18
19
|
brands: string[] | null
|
|
20
|
+
excludedBrands: string[]
|
|
19
21
|
}): ReturnType<typeof useDeviceModels> => {
|
|
20
22
|
const params: UseDeviceModelsParams = {}
|
|
21
23
|
|
|
@@ -37,12 +39,12 @@ export const useFilteredDeviceModels = ({
|
|
|
37
39
|
return query
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
// UPSTREAM: The API does not have a brands query parameter,
|
|
42
|
+
// UPSTREAM: The API does not have a brands or excludedBrands query parameter,
|
|
41
43
|
// so selected brands are filtered here.
|
|
42
44
|
return {
|
|
43
45
|
...query,
|
|
44
|
-
deviceModels: query.deviceModels?.filter(
|
|
45
|
-
brands.includes(
|
|
46
|
+
deviceModels: query.deviceModels?.filter(
|
|
47
|
+
({ brand }) => brands.includes(brand) && !excludedBrands.includes(brand)
|
|
46
48
|
),
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
1
2
|
import type { PropsWithChildren } from 'react'
|
|
2
3
|
|
|
3
4
|
import { useMenu } from 'lib/ui/Menu/Menu.js'
|
|
4
5
|
|
|
5
6
|
interface MenuItemProps extends PropsWithChildren {
|
|
6
|
-
onClick: () => void
|
|
7
|
+
onClick: (event: React.MouseEvent) => void
|
|
8
|
+
className?: string
|
|
9
|
+
preventDefaultOnClick?: boolean
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
export function MenuItem({
|
|
12
|
+
export function MenuItem({
|
|
13
|
+
onClick,
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
preventDefaultOnClick = false,
|
|
17
|
+
}: MenuItemProps): JSX.Element {
|
|
10
18
|
const { close: closeMenu } = useMenu()
|
|
11
19
|
|
|
12
20
|
return (
|
|
13
21
|
<div
|
|
14
|
-
className='seam-menu-item'
|
|
15
|
-
onClick={() => {
|
|
16
|
-
onClick()
|
|
17
|
-
|
|
22
|
+
className={classNames('seam-menu-item', className)}
|
|
23
|
+
onClick={(event: React.MouseEvent) => {
|
|
24
|
+
onClick(event)
|
|
25
|
+
|
|
26
|
+
if (!preventDefaultOnClick) {
|
|
27
|
+
closeMenu()
|
|
28
|
+
}
|
|
18
29
|
}}
|
|
19
30
|
>
|
|
20
31
|
{children}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ClimateSetting } from 'seamapi'
|
|
2
|
+
|
|
3
|
+
import { ThermostatCoolIcon } from 'lib/icons/ThermostatCool.js'
|
|
4
|
+
import { ThermostatHeatIcon } from 'lib/icons/ThermostatHeat.js'
|
|
5
|
+
import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
|
|
6
|
+
import { ThermostatOffIcon } from 'lib/icons/ThermostatOff.js'
|
|
7
|
+
|
|
8
|
+
interface ClimateSettingStatusProps {
|
|
9
|
+
climateSetting: ClimateSetting
|
|
10
|
+
temperatureUnit?: 'fahrenheit' | 'celsius'
|
|
11
|
+
iconPlacement?: 'left' | 'right'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ClimateSettingStatus({
|
|
15
|
+
climateSetting,
|
|
16
|
+
temperatureUnit = 'fahrenheit',
|
|
17
|
+
iconPlacement = 'left',
|
|
18
|
+
}: ClimateSettingStatusProps): JSX.Element {
|
|
19
|
+
return (
|
|
20
|
+
<div className='seam-climate-setting-status'>
|
|
21
|
+
{iconPlacement === 'left' && (
|
|
22
|
+
<ClimateSettingIcon mode={climateSetting.hvac_mode_setting} />
|
|
23
|
+
)}
|
|
24
|
+
<Content
|
|
25
|
+
mode={climateSetting.hvac_mode_setting}
|
|
26
|
+
coolingSetPoint={climateSetting.cooling_set_point_fahrenheit}
|
|
27
|
+
heatingSetPoint={climateSetting.heating_set_point_fahrenheit}
|
|
28
|
+
temperatureUnit={temperatureUnit}
|
|
29
|
+
/>
|
|
30
|
+
{iconPlacement === 'right' && (
|
|
31
|
+
<ClimateSettingIcon mode={climateSetting.hvac_mode_setting} />
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ClimateSettingIcon(props: {
|
|
38
|
+
mode: ClimateSetting['hvac_mode_setting']
|
|
39
|
+
}): JSX.Element | null {
|
|
40
|
+
const { mode } = props
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className='seam-climate-setting-status-icon'>
|
|
44
|
+
{mode === 'cool' && <ThermostatCoolIcon />}
|
|
45
|
+
{mode === 'heat' && <ThermostatHeatIcon />}
|
|
46
|
+
{mode === 'heatcool' && <ThermostatHeatCoolIcon />}
|
|
47
|
+
{mode === 'off' && <ThermostatOffIcon />}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function Content(props: {
|
|
53
|
+
mode: ClimateSetting['hvac_mode_setting']
|
|
54
|
+
coolingSetPoint:
|
|
55
|
+
| ClimateSetting['cooling_set_point_fahrenheit']
|
|
56
|
+
| ClimateSetting['cooling_set_point_celsius']
|
|
57
|
+
heatingSetPoint:
|
|
58
|
+
| ClimateSetting['heating_set_point_fahrenheit']
|
|
59
|
+
| ClimateSetting['heating_set_point_celsius']
|
|
60
|
+
temperatureUnit: 'fahrenheit' | 'celsius'
|
|
61
|
+
}): JSX.Element | null {
|
|
62
|
+
const { mode, coolingSetPoint, heatingSetPoint, temperatureUnit } = props
|
|
63
|
+
const hasCoolingSetPoint = coolingSetPoint !== undefined
|
|
64
|
+
const hasHeatingSetPoint = heatingSetPoint !== undefined
|
|
65
|
+
const unit =
|
|
66
|
+
temperatureUnit === 'fahrenheit' ? t.degreeFahrenheit : t.degreeCelsius
|
|
67
|
+
|
|
68
|
+
if (mode === 'cool' && hasCoolingSetPoint)
|
|
69
|
+
return <span>{`${coolingSetPoint}${unit}`}</span>
|
|
70
|
+
|
|
71
|
+
if (mode === 'heat' && hasHeatingSetPoint)
|
|
72
|
+
return <span>{`${heatingSetPoint}${unit}`}</span>
|
|
73
|
+
|
|
74
|
+
if (mode === 'heatcool' && hasHeatingSetPoint && hasCoolingSetPoint)
|
|
75
|
+
return (
|
|
76
|
+
<span>{`${heatingSetPoint}${unit} - ${coolingSetPoint}${unit}`}</span>
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (mode === 'off') return <span>{t.off}</span>
|
|
80
|
+
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const t = {
|
|
85
|
+
degreeFahrenheit: '°F',
|
|
86
|
+
degreeCelsius: '°C',
|
|
87
|
+
off: 'Off',
|
|
88
|
+
}
|
package/src/lib/version.ts
CHANGED