@seamapi/react 1.49.0 → 1.51.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 +9778 -8689
- package/dist/elements.js.map +1 -1
- package/dist/index.css +170 -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/icons/Bee.d.ts +2 -0
- package/lib/icons/Bee.js +5 -0
- package/lib/icons/Bee.js.map +1 -0
- package/lib/icons/Fan.d.ts +2 -0
- package/lib/icons/Fan.js +7 -0
- package/lib/icons/Fan.js.map +1 -0
- package/lib/icons/Off.d.ts +2 -0
- package/lib/icons/Off.js +7 -0
- package/lib/icons/Off.js.map +1 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.d.ts +2 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js +4 -4
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js +9 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.d.ts +2 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js +6 -4
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js.map +1 -1
- package/lib/seam/components/DeviceDetails/DeviceDetails.js +5 -1
- package/lib/seam/components/DeviceDetails/DeviceDetails.js.map +1 -1
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.d.ts +7 -0
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +66 -0
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -0
- package/lib/seam/connected-accounts/use-connected-account.d.ts +5 -0
- package/lib/seam/connected-accounts/use-connected-account.js +17 -0
- package/lib/seam/connected-accounts/use-connected-account.js.map +1 -0
- package/lib/seam/index.d.ts +2 -0
- package/lib/seam/index.js +2 -0
- package/lib/seam/index.js.map +1 -1
- package/lib/ui/layout/DetailRow.d.ts +3 -1
- package/lib/ui/layout/DetailRow.js +5 -2
- package/lib/ui/layout/DetailRow.js.map +1 -1
- package/lib/ui/thermostat/ClimateSettingStatus.d.ts +1 -1
- package/lib/ui/thermostat/ClimateSettingStatus.js +20 -24
- package/lib/ui/thermostat/ClimateSettingStatus.js.map +1 -1
- package/lib/ui/thermostat/Temperature.d.ts +8 -0
- package/lib/ui/thermostat/Temperature.js +11 -0
- package/lib/ui/thermostat/Temperature.js.map +1 -0
- package/lib/ui/thermostat/ThermostatCard.d.ts +7 -0
- package/lib/ui/thermostat/ThermostatCard.js +46 -0
- package/lib/ui/thermostat/ThermostatCard.js.map +1 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -3
- package/src/lib/icons/Bee.tsx +21 -0
- package/src/lib/icons/Fan.tsx +36 -0
- package/src/lib/icons/Off.tsx +36 -0
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +9 -2
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx +18 -0
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx +14 -4
- package/src/lib/seam/components/DeviceDetails/DeviceDetails.tsx +6 -1
- package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +175 -0
- package/src/lib/seam/connected-accounts/use-connected-account.ts +35 -0
- package/src/lib/seam/index.ts +2 -0
- package/src/lib/ui/layout/DetailRow.tsx +15 -2
- package/src/lib/ui/thermostat/ClimateSettingStatus.tsx +34 -30
- package/src/lib/ui/thermostat/Temperature.tsx +26 -0
- package/src/lib/ui/thermostat/ThermostatCard.tsx +160 -0
- package/src/lib/version.ts +1 -1
- package/src/styles/_device-details.scss +4 -0
- package/src/styles/_layout.scss +36 -0
- package/src/styles/_thermostat.scss +160 -0
- package/src/styles/_tooltip.scss +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
4
4
|
"description": "Seam Components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"@tanstack/react-query": "^4.28.0",
|
|
123
123
|
"classnames": "^2.3.2",
|
|
124
124
|
"luxon": "^3.3.0",
|
|
125
|
-
"seamapi": "^8.9.
|
|
125
|
+
"seamapi": "^8.9.2",
|
|
126
126
|
"uuid": "^9.0.0"
|
|
127
127
|
},
|
|
128
128
|
"devDependencies": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"@mui/icons-material": "^5.11.16",
|
|
134
134
|
"@mui/material": "^5.12.2",
|
|
135
135
|
"@rxfork/r2wc-react-to-web-component": "^2.3.0",
|
|
136
|
-
"@seamapi/fake-seam-connect": "^1.9.
|
|
136
|
+
"@seamapi/fake-seam-connect": "^1.9.1",
|
|
137
137
|
"@storybook/addon-designs": "^7.0.1",
|
|
138
138
|
"@storybook/addon-essentials": "^7.0.2",
|
|
139
139
|
"@storybook/addon-links": "^7.0.2",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Automaically generated by SVGR from assets/icons/*.svg.
|
|
3
|
+
* Do not edit this file or add other components to this directory.
|
|
4
|
+
*/
|
|
5
|
+
import type { SVGProps } from 'react'
|
|
6
|
+
export function BeeIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
width={33}
|
|
11
|
+
height={33}
|
|
12
|
+
fill='none'
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
fill='#5DBE77'
|
|
17
|
+
d='M23.902 25.469c-.18.263-3.2-.2-4.15-5.212-.95-5.01 1.214-5.411 1.214-5.411s2.745-.79 4.086 4.938c1.225 5.21-1.15 5.685-1.15 5.685Zm-8.437-6.917h-.01c-1.468-.98-1.996-1.853-1.996-1.853 1.257-.18 2.724-.958 2.724-.958l.813.568v.01l.834.548s-.18 1.643.106 2.874c0 0-1.014-.168-2.46-1.168m-9.63-5.327s-.422-2.4 4.931-3.18c5.881-.852 6.156 2 6.156 2s.412 2.18-4.646 3.138c-5.068.958-6.62-1.695-6.44-1.958Zm14.149-6.117c.612.421.77 1.242.348 1.853a1.33 1.33 0 0 1-1.858.347 1.322 1.322 0 0 1-.348-1.852 1.33 1.33 0 0 1 1.858-.348Zm4.445 3.022c.612.41.77 1.242.36 1.853-.413.61-1.247.758-1.86.347a1.322 1.322 0 0 1-.348-1.853 1.33 1.33 0 0 1 1.859-.347m2.523 10.58c-.37-3.053-2.006-5.496-2.64-6.19 0 0-.063-.021-.01-.148.053-.126.158-.495.158-.495s0-.042.074-.063c.074-.02.834-.179 1.426-1.042.59-.863.971-2.653-1.046-4.022 0 0-.01 0-.031-.094-.01-.085-.338-1.548-1.225-2.159-.887-.6-2.376-.368-2.46-.347-.085.021-.095 0-.095 0-2.017-1.369-3.548-.358-4.14.505-.59.864-.453 1.632-.453 1.706 0 .074-.032.084-.032.084l-.401.327c-.095.084-.138.042-.138.042-.886-.337-3.769-.969-6.747-.19-2.977.78-4.16 1.874-4.814 2.832-.655.958-1.024 2.874 1.024 4.264 2.048 1.39 4.952 1.263 4.952 1.263.084 0 .095.063.095.063.707 2.506 3.051 3.97 3.21 4.075.158.116 2.386 1.747 4.973 1.484 0 0 .063-.01.095.074 0 0 .95 2.737 2.988 4.127 2.048 1.39 3.696.347 4.35-.61.655-.959 1.246-2.454.887-5.496m-9.914 3.074s-1.964.084-4.213-1.443c-2.25-1.526-2.893-3.38-2.893-3.38-.053-.146-.106-.052-.106-.052-.602.885-.781 1.39-.443 2.8.327 1.359 1.974 2.517 2.09 2.601.116.074 1.795 1.18 3.189.99 1.436-.2 1.848-.558 2.45-1.443 0 0 .063-.084-.085-.073m-4.424 2.39s-.95 0-1.943-.664l-.507.737.507-.747c-.992-.674-1.34-1.558-1.34-1.558-.16-.243-.201 0-.201 0-.55 2.116.39 3.032.475 3.105.095.042 1.298.58 3.072-.705 0 0 .211-.126-.074-.179'
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Automaically generated by SVGR from assets/icons/*.svg.
|
|
3
|
+
* Do not edit this file or add other components to this directory.
|
|
4
|
+
*/
|
|
5
|
+
import type { SVGProps } from 'react'
|
|
6
|
+
export function FanIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
width={18}
|
|
11
|
+
height={18}
|
|
12
|
+
fill='none'
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<mask
|
|
16
|
+
id='fan_svg__a'
|
|
17
|
+
width={18}
|
|
18
|
+
height={18}
|
|
19
|
+
x={0}
|
|
20
|
+
y={0}
|
|
21
|
+
maskUnits='userSpaceOnUse'
|
|
22
|
+
style={{
|
|
23
|
+
maskType: 'alpha',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<path fill='#D9D9D9' d='M0 0h18v18H0z' />
|
|
27
|
+
</mask>
|
|
28
|
+
<g mask='url(#fan_svg__a)'>
|
|
29
|
+
<path
|
|
30
|
+
fill='#9DA1A9'
|
|
31
|
+
d='M9.921 16.456c-.61.164-1.124.106-1.539-.175a1.916 1.916 0 0 1-.81-1.12 2.188 2.188 0 0 1-.037-.963c.059-.33.207-.63.443-.899.218-.238.373-.51.464-.818.091-.306.127-.618.106-.933l-.244-.05a1.154 1.154 0 0 1-.231-.073l-1.495 1.036c-.184.126-.365.24-.544.338-.18.1-.37.177-.574.231-.755.202-1.51.052-2.268-.451-.757-.503-1.306-1.39-1.646-2.66-.164-.61-.106-1.123.174-1.539.28-.415.648-.684 1.104-.806.311-.083.635-.096.971-.038.337.057.64.204.908.44.238.219.51.373.817.465.307.09.619.126.934.106l.05-.245c.017-.081.041-.158.073-.231L5.54 6.577a6.808 6.808 0 0 1-.339-.545 2.446 2.446 0 0 1-.225-.556c-.206-.766-.057-1.528.446-2.286.503-.757 1.39-1.306 2.66-1.646.61-.164 1.123-.105 1.539.175.415.28.683.648.805 1.103.084.311.097.635.04.972a1.818 1.818 0 0 1-.441.907c-.219.238-.374.511-.465.818a2.655 2.655 0 0 0-.106.933l.245.05c.081.017.158.041.231.073l1.49-1.054c.184-.126.363-.235.538-.327.174-.091.363-.165.566-.22.97-.26 1.803-.052 2.497.622s1.173 1.502 1.436 2.485c.164.61.096 1.126-.202 1.546a2.021 2.021 0 0 1-2.075.845 1.865 1.865 0 0 1-.88-.448 2.064 2.064 0 0 0-.818-.464 2.655 2.655 0 0 0-.933-.106l-.05.244a1.154 1.154 0 0 1-.073.231l1.036 1.494c.123.173.232.34.326.5.095.161.167.337.219.529.22.775.082 1.55-.417 2.326-.498.775-1.388 1.335-2.67 1.678Zm-.63-6.378c.3-.08.526-.253.679-.519.153-.266.19-.548.11-.848-.08-.3-.253-.526-.52-.68a1.076 1.076 0 0 0-.847-.109c-.3.08-.526.253-.68.52-.153.265-.19.547-.11.847.081.3.254.526.52.68.266.153.548.19.848.11Z'
|
|
32
|
+
/>
|
|
33
|
+
</g>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Automaically generated by SVGR from assets/icons/*.svg.
|
|
3
|
+
* Do not edit this file or add other components to this directory.
|
|
4
|
+
*/
|
|
5
|
+
import type { SVGProps } from 'react'
|
|
6
|
+
export function OffIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
width={16}
|
|
11
|
+
height={16}
|
|
12
|
+
fill='none'
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<mask
|
|
16
|
+
id='off_svg__a'
|
|
17
|
+
width={16}
|
|
18
|
+
height={16}
|
|
19
|
+
x={0}
|
|
20
|
+
y={0}
|
|
21
|
+
maskUnits='userSpaceOnUse'
|
|
22
|
+
style={{
|
|
23
|
+
maskType: 'alpha',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<path fill='#D9D9D9' d='M.421.5h15v15h-15z' />
|
|
27
|
+
</mask>
|
|
28
|
+
<g mask='url(#off_svg__a)'>
|
|
29
|
+
<path
|
|
30
|
+
fill='#9DA1A9'
|
|
31
|
+
d='M12.78 14.64 11.36 13.22a6.404 6.404 0 0 1-1.633.765 6.127 6.127 0 0 1-1.805.266 6.086 6.086 0 0 1-2.437-.492 6.311 6.311 0 0 1-1.985-1.336 6.312 6.312 0 0 1-1.336-1.985A6.086 6.086 0 0 1 1.671 8c0-.625.089-1.227.266-1.805a6.4 6.4 0 0 1 .766-1.633L1.28 3.141l.89-.891 11.5 11.5-.89.89ZM7.922 13a4.955 4.955 0 0 0 2.532-.688L3.609 5.47A4.956 4.956 0 0 0 2.92 8c0 1.385.487 2.565 1.461 3.54.974.973 2.154 1.46 3.54 1.46Zm5.219-1.563-.906-.906A4.955 4.955 0 0 0 12.922 8c0-1.385-.488-2.565-1.462-3.54C10.486 3.488 9.307 3 7.921 3a4.955 4.955 0 0 0-2.531.688l-.906-.907a6.4 6.4 0 0 1 1.633-.765A6.127 6.127 0 0 1 7.92 1.75c.865 0 1.677.164 2.438.492.76.328 1.422.774 1.984 1.336a6.311 6.311 0 0 1 1.336 1.985c.328.76.492 1.572.492 2.437 0 .625-.088 1.227-.265 1.805a6.399 6.399 0 0 1-.766 1.633Z'
|
|
32
|
+
/>
|
|
33
|
+
</g>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
}
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx
CHANGED
|
@@ -12,22 +12,28 @@ import { ClimateSettingScheduleDeviceBar } from './ClimateSettingScheduleDeviceB
|
|
|
12
12
|
|
|
13
13
|
interface ClimateSettingScheduleCardProps {
|
|
14
14
|
climateSettingScheduleId: string
|
|
15
|
+
onSelectDevice: (deviceId: string) => void
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function ClimateSettingScheduleCard({
|
|
18
19
|
climateSettingScheduleId,
|
|
20
|
+
onSelectDevice,
|
|
19
21
|
}: ClimateSettingScheduleCardProps): JSX.Element {
|
|
20
22
|
return (
|
|
21
23
|
<div className='seam-climate-setting-schedule-card'>
|
|
22
|
-
<Content
|
|
24
|
+
<Content
|
|
25
|
+
climateSettingScheduleId={climateSettingScheduleId}
|
|
26
|
+
onSelectDevice={onSelectDevice}
|
|
27
|
+
/>
|
|
23
28
|
</div>
|
|
24
29
|
)
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
function Content(props: {
|
|
28
33
|
climateSettingScheduleId: string
|
|
34
|
+
onSelectDevice: (deviceId: string) => void
|
|
29
35
|
}): JSX.Element | null {
|
|
30
|
-
const { climateSettingScheduleId } = props
|
|
36
|
+
const { climateSettingScheduleId, onSelectDevice } = props
|
|
31
37
|
|
|
32
38
|
const { climateSettingSchedule } = useClimateSettingSchedule(
|
|
33
39
|
climateSettingScheduleId
|
|
@@ -57,6 +63,7 @@ function Content(props: {
|
|
|
57
63
|
</div>
|
|
58
64
|
</div>
|
|
59
65
|
<ClimateSettingScheduleDeviceBar
|
|
66
|
+
onSelectDevice={onSelectDevice}
|
|
60
67
|
deviceId={climateSettingSchedule.device_id}
|
|
61
68
|
/>
|
|
62
69
|
</div>
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { DeviceDetails } from '../index.js'
|
|
2
5
|
|
|
3
6
|
import { formatDateAndTime } from 'lib/dates.js'
|
|
4
7
|
import { ArrowRightIcon } from 'lib/icons/ArrowRight.js'
|
|
@@ -26,6 +29,8 @@ export function ClimateSettingScheduleDetails({
|
|
|
26
29
|
climateSettingScheduleId
|
|
27
30
|
)
|
|
28
31
|
|
|
32
|
+
const [selectedDeviceId, selectDevice] = useState<string | null>(null)
|
|
33
|
+
|
|
29
34
|
if (climateSettingSchedule == null) {
|
|
30
35
|
return null
|
|
31
36
|
}
|
|
@@ -33,6 +38,18 @@ export function ClimateSettingScheduleDetails({
|
|
|
33
38
|
const isManualOverrideAllowed =
|
|
34
39
|
climateSettingSchedule.manual_override_allowed ?? false
|
|
35
40
|
|
|
41
|
+
if (selectedDeviceId != null) {
|
|
42
|
+
return (
|
|
43
|
+
<DeviceDetails
|
|
44
|
+
className={className}
|
|
45
|
+
deviceId={selectedDeviceId}
|
|
46
|
+
onBack={() => {
|
|
47
|
+
selectDevice(null)
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
return (
|
|
37
54
|
<div
|
|
38
55
|
className={classNames('seam-climate-setting-schedule-details', className)}
|
|
@@ -41,6 +58,7 @@ export function ClimateSettingScheduleDetails({
|
|
|
41
58
|
<div className='seam-climate-setting-schedule-details-content'>
|
|
42
59
|
<ClimateSettingScheduleCard
|
|
43
60
|
climateSettingScheduleId={climateSettingScheduleId}
|
|
61
|
+
onSelectDevice={selectDevice}
|
|
44
62
|
/>
|
|
45
63
|
<div className='seam-default-setting-message-container'>
|
|
46
64
|
<span className='seam-default-setting-message'>
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx
CHANGED
|
@@ -7,8 +7,10 @@ import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
|
7
7
|
|
|
8
8
|
export function ClimateSettingScheduleDeviceBar({
|
|
9
9
|
deviceId,
|
|
10
|
+
onSelectDevice,
|
|
10
11
|
}: {
|
|
11
12
|
deviceId: string
|
|
13
|
+
onSelectDevice: (deviceId: string) => void
|
|
12
14
|
}): JSX.Element | null {
|
|
13
15
|
const { isLoading, device } = useDevice({
|
|
14
16
|
device_id: deviceId,
|
|
@@ -26,14 +28,22 @@ export function ClimateSettingScheduleDeviceBar({
|
|
|
26
28
|
return null
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
return <Content device={device} />
|
|
31
|
+
return <Content device={device} onSelectDevice={onSelectDevice} />
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function Content(props: {
|
|
33
|
-
|
|
34
|
+
function Content(props: {
|
|
35
|
+
device: ThermostatDevice
|
|
36
|
+
onSelectDevice: (deviceId: string) => void
|
|
37
|
+
}): JSX.Element {
|
|
38
|
+
const { device, onSelectDevice } = props
|
|
34
39
|
|
|
35
40
|
return (
|
|
36
|
-
<div
|
|
41
|
+
<div
|
|
42
|
+
className='seam-climate-setting-schedule-device-bar'
|
|
43
|
+
onClick={() => {
|
|
44
|
+
onSelectDevice(device.device_id)
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
37
47
|
<div className='seam-climate-setting-schedule-device-content'>
|
|
38
48
|
<div className='seam-device-image'>
|
|
39
49
|
<DeviceImage device={device} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { isLockDevice } from 'seamapi'
|
|
1
|
+
import { isLockDevice, isThermostatDevice } from 'seamapi'
|
|
2
2
|
|
|
3
3
|
import { LockDeviceDetails } from 'lib/seam/components/DeviceDetails/LockDeviceDetails.js'
|
|
4
|
+
import { ThermostatDeviceDetails } from 'lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js'
|
|
4
5
|
import { useDevice } from 'lib/seam/devices/use-device.js'
|
|
5
6
|
|
|
6
7
|
export interface DeviceDetailsProps {
|
|
@@ -35,5 +36,9 @@ export function DeviceDetails({
|
|
|
35
36
|
)
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
if (isThermostatDevice(device)) {
|
|
40
|
+
return <ThermostatDeviceDetails device={device} />
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
return null
|
|
39
44
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import type { ThermostatDevice } from 'seamapi'
|
|
4
|
+
|
|
5
|
+
import { BeeIcon } from 'lib/icons/Bee.js'
|
|
6
|
+
import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
|
|
7
|
+
import { ClimateSettingScheduleTable } from 'lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js'
|
|
8
|
+
import { useConnectedAccount } from 'lib/seam/connected-accounts/use-connected-account.js'
|
|
9
|
+
import { useClimateSettingSchedules } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedules.js'
|
|
10
|
+
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
11
|
+
import { DetailRow } from 'lib/ui/layout/DetailRow.js'
|
|
12
|
+
import { DetailSection } from 'lib/ui/layout/DetailSection.js'
|
|
13
|
+
import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
|
|
14
|
+
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
15
|
+
import { ThermostatCard } from 'lib/ui/thermostat/ThermostatCard.js'
|
|
16
|
+
|
|
17
|
+
export function ThermostatDeviceDetails(props: {
|
|
18
|
+
device: ThermostatDevice
|
|
19
|
+
onBack?: () => void
|
|
20
|
+
className?: string
|
|
21
|
+
}): JSX.Element | null {
|
|
22
|
+
const { device, onBack, className } = props
|
|
23
|
+
|
|
24
|
+
const [climateSettingsOpen, setClimateSettingsOpen] = useState(false)
|
|
25
|
+
|
|
26
|
+
const { connectedAccount } = useConnectedAccount(device.connected_account_id)
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
climateSettingSchedules,
|
|
30
|
+
isLoading: isLoadingClimateSchedules,
|
|
31
|
+
isError: isErrorClimateSchedules,
|
|
32
|
+
} = useClimateSettingSchedules({
|
|
33
|
+
device_id: device.device_id,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const isClimateSchedulesPlural = climateSettingSchedules?.length !== 1
|
|
37
|
+
|
|
38
|
+
if (climateSettingsOpen) {
|
|
39
|
+
return (
|
|
40
|
+
<ClimateSettingScheduleTable
|
|
41
|
+
deviceId={device.device_id}
|
|
42
|
+
onBack={() => {
|
|
43
|
+
setClimateSettingsOpen(false)
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (device == null) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={classNames('seam-device-details', className)}>
|
|
55
|
+
<ContentHeader title={t.thermostat} onBack={onBack} />
|
|
56
|
+
|
|
57
|
+
<div className='seam-body'>
|
|
58
|
+
<ThermostatCard device={device} />
|
|
59
|
+
|
|
60
|
+
<div className='seam-thermostat-device-details'>
|
|
61
|
+
<DetailSectionGroup>
|
|
62
|
+
<DetailSection
|
|
63
|
+
label={t.scheduledClimates}
|
|
64
|
+
tooltipContent={t.scheduledClimatesTooltip}
|
|
65
|
+
>
|
|
66
|
+
<DetailRow
|
|
67
|
+
label={
|
|
68
|
+
isLoadingClimateSchedules || isErrorClimateSchedules
|
|
69
|
+
? t.viewingClimateSchedules
|
|
70
|
+
: `${climateSettingSchedules?.length} ${
|
|
71
|
+
isClimateSchedulesPlural
|
|
72
|
+
? t.climateSchedules
|
|
73
|
+
: t.climateSchedule
|
|
74
|
+
}`
|
|
75
|
+
}
|
|
76
|
+
onClick={() => {
|
|
77
|
+
setClimateSettingsOpen(true)
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<div className='seam-detail-row-rotated-icon'>
|
|
81
|
+
<ChevronWideIcon />
|
|
82
|
+
</div>
|
|
83
|
+
</DetailRow>
|
|
84
|
+
</DetailSection>
|
|
85
|
+
|
|
86
|
+
<DetailSection
|
|
87
|
+
label={t.currentSettings}
|
|
88
|
+
tooltipContent={t.currentSettingsTooltip}
|
|
89
|
+
>
|
|
90
|
+
<DetailRow label={t.climate}>
|
|
91
|
+
<ClimateSettingStatus
|
|
92
|
+
climateSetting={device.properties.current_climate_setting}
|
|
93
|
+
temperatureUnit='fahrenheit'
|
|
94
|
+
/>
|
|
95
|
+
</DetailRow>
|
|
96
|
+
</DetailSection>
|
|
97
|
+
|
|
98
|
+
<DetailSection
|
|
99
|
+
label={t.defaultSettings}
|
|
100
|
+
tooltipContent={t.defaultSettingsTooltip}
|
|
101
|
+
>
|
|
102
|
+
<DetailRow label={t.defaultClimate}>
|
|
103
|
+
{/* @ts-expect-error not currently included in sdk */}
|
|
104
|
+
{device.properties.default_climate_setting != null ? (
|
|
105
|
+
<ClimateSettingStatus
|
|
106
|
+
climateSetting={device.properties.current_climate_setting}
|
|
107
|
+
temperatureUnit='fahrenheit'
|
|
108
|
+
/>
|
|
109
|
+
) : (
|
|
110
|
+
<p>{t.none}</p>
|
|
111
|
+
)}
|
|
112
|
+
</DetailRow>
|
|
113
|
+
<DetailRow label={t.allowManualOverride}>
|
|
114
|
+
<p>
|
|
115
|
+
{device.properties.current_climate_setting
|
|
116
|
+
.manual_override_allowed
|
|
117
|
+
? t.yes
|
|
118
|
+
: t.no}
|
|
119
|
+
</p>
|
|
120
|
+
</DetailRow>
|
|
121
|
+
</DetailSection>
|
|
122
|
+
|
|
123
|
+
<DetailSection
|
|
124
|
+
label={t.deviceDetails}
|
|
125
|
+
tooltipContent={t.deviceDetailsTooltip}
|
|
126
|
+
>
|
|
127
|
+
<DetailRow label={t.brand}>
|
|
128
|
+
<div className='seam-detail-row-hstack'>
|
|
129
|
+
{device.properties.model.manufacturer_display_name}
|
|
130
|
+
{device.properties.manufacturer === 'ecobee' && <BeeIcon />}
|
|
131
|
+
</div>
|
|
132
|
+
</DetailRow>
|
|
133
|
+
<DetailRow
|
|
134
|
+
label={t.linkedAccount}
|
|
135
|
+
sublabel={
|
|
136
|
+
connectedAccount?.user_identifier?.email ??
|
|
137
|
+
device.connected_account_id
|
|
138
|
+
}
|
|
139
|
+
/>
|
|
140
|
+
<DetailRow label={t.deviceId} sublabel={device.device_id} />
|
|
141
|
+
</DetailSection>
|
|
142
|
+
</DetailSectionGroup>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const t = {
|
|
150
|
+
thermostat: 'Thermostat',
|
|
151
|
+
climateSchedule: 'scheduled climate',
|
|
152
|
+
climateSchedules: 'scheduled climates',
|
|
153
|
+
viewingClimateSchedules: 'View scheduled climates',
|
|
154
|
+
scheduledClimates: 'Scheduled climates',
|
|
155
|
+
scheduledClimatesTooltip:
|
|
156
|
+
"Scheduled climates let you automatically change the thermostat's climate at a set time.",
|
|
157
|
+
currentSettings: 'Current settings',
|
|
158
|
+
currentSettingsTooltip:
|
|
159
|
+
'These are the settings currently on the device. If you change them here, they change on the device.',
|
|
160
|
+
climate: 'Climate',
|
|
161
|
+
defaultSettings: 'Default settings',
|
|
162
|
+
defaultSettingsTooltip:
|
|
163
|
+
'When a scheduled climate reaches its end time, the default settings will kick in.',
|
|
164
|
+
defaultClimate: 'Default climate',
|
|
165
|
+
allowManualOverride: 'Allow manual override',
|
|
166
|
+
deviceDetails: 'Device details',
|
|
167
|
+
deviceDetailsTooltip:
|
|
168
|
+
'When a scheduled climate reaches its end time, the default settings will kick in.',
|
|
169
|
+
brand: 'Brand',
|
|
170
|
+
linkedAccount: 'Linked account',
|
|
171
|
+
deviceId: 'Device ID',
|
|
172
|
+
none: 'None',
|
|
173
|
+
yes: 'Yes',
|
|
174
|
+
no: 'No',
|
|
175
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import type {
|
|
3
|
+
ConnectedAccount,
|
|
4
|
+
ConnectedAccountsGetRequest,
|
|
5
|
+
ConnectedAccountsGetResponse,
|
|
6
|
+
SeamError,
|
|
7
|
+
} from 'seamapi'
|
|
8
|
+
|
|
9
|
+
import { useSeamClient } from 'lib/seam/use-seam-client.js'
|
|
10
|
+
import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
|
|
11
|
+
|
|
12
|
+
export type UseConnectedAccountParams = ConnectedAccountsGetRequest | string
|
|
13
|
+
export type UseConnectedAccountData = ConnectedAccount | null
|
|
14
|
+
|
|
15
|
+
export function useConnectedAccount(
|
|
16
|
+
params: UseConnectedAccountParams
|
|
17
|
+
): UseSeamQueryResult<'connectedAccount', UseConnectedAccountData> {
|
|
18
|
+
const normalizedParams =
|
|
19
|
+
typeof params === 'string' ? { connected_account_id: params } : params
|
|
20
|
+
|
|
21
|
+
const { client } = useSeamClient()
|
|
22
|
+
const { data, ...rest } = useQuery<
|
|
23
|
+
ConnectedAccountsGetResponse['connected_account'] | null,
|
|
24
|
+
SeamError
|
|
25
|
+
>({
|
|
26
|
+
enabled: client != null,
|
|
27
|
+
queryKey: ['connected_accounts', 'get', normalizedParams],
|
|
28
|
+
queryFn: async () => {
|
|
29
|
+
if (client == null) return null
|
|
30
|
+
return await client.connectedAccounts.get(normalizedParams)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return { ...rest, connectedAccount: data }
|
|
35
|
+
}
|
package/src/lib/seam/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './access-codes/use-access-code.js'
|
|
2
2
|
export * from './access-codes/use-access-codes.js'
|
|
3
3
|
export * from './access-codes/use-create-access-code.js'
|
|
4
|
+
export * from './access-codes/use-generate-access-code-code.js'
|
|
4
5
|
export * from './connect-webviews/use-create-connect-webview.js'
|
|
6
|
+
export * from './connected-accounts/use-connected-account.js'
|
|
5
7
|
export * from './devices/use-device.js'
|
|
6
8
|
export * from './devices/use-device-providers.js'
|
|
7
9
|
export * from './devices/use-devices.js'
|
|
@@ -1,16 +1,29 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
1
2
|
import type { PropsWithChildren } from 'react'
|
|
2
3
|
|
|
3
4
|
interface DetailRowProps {
|
|
4
5
|
label: string
|
|
6
|
+
sublabel?: string
|
|
7
|
+
onClick?: () => void
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
export function DetailRow({
|
|
8
11
|
label,
|
|
12
|
+
sublabel,
|
|
13
|
+
onClick,
|
|
9
14
|
children,
|
|
10
15
|
}: PropsWithChildren<DetailRowProps>): JSX.Element {
|
|
11
16
|
return (
|
|
12
|
-
<div
|
|
13
|
-
|
|
17
|
+
<div
|
|
18
|
+
className={classNames('seam-detail-row', {
|
|
19
|
+
'seam-detail-row-clickable': onClick != null,
|
|
20
|
+
})}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
>
|
|
23
|
+
<div className='seam-detail-row-label-wrap'>
|
|
24
|
+
<p className='seam-row-label'>{label}</p>
|
|
25
|
+
{sublabel != null && <p className='seam-row-sublabel'>{sublabel}</p>}
|
|
26
|
+
</div>
|
|
14
27
|
<div className='seam-detail-row-right-area-content'>{children}</div>
|
|
15
28
|
</div>
|
|
16
29
|
)
|
|
@@ -4,9 +4,10 @@ import { ThermostatCoolIcon } from 'lib/icons/ThermostatCool.js'
|
|
|
4
4
|
import { ThermostatHeatIcon } from 'lib/icons/ThermostatHeat.js'
|
|
5
5
|
import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
|
|
6
6
|
import { ThermostatOffIcon } from 'lib/icons/ThermostatOff.js'
|
|
7
|
+
import { Temperature } from 'lib/ui/thermostat/Temperature.js'
|
|
7
8
|
|
|
8
9
|
interface ClimateSettingStatusProps {
|
|
9
|
-
climateSetting:
|
|
10
|
+
climateSetting: ClimateSetting
|
|
10
11
|
temperatureUnit?: 'fahrenheit' | 'celsius'
|
|
11
12
|
iconPlacement?: 'left' | 'right'
|
|
12
13
|
}
|
|
@@ -23,16 +24,14 @@ export function ClimateSettingStatus({
|
|
|
23
24
|
)}
|
|
24
25
|
<Content
|
|
25
26
|
mode={climateSetting.hvac_mode_setting}
|
|
26
|
-
coolingSetPoint={
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
: climateSetting.heating_set_point_celsius
|
|
35
|
-
)}
|
|
27
|
+
coolingSetPoint={{
|
|
28
|
+
fahrenheit: climateSetting.cooling_set_point_fahrenheit,
|
|
29
|
+
celsius: climateSetting.cooling_set_point_celsius,
|
|
30
|
+
}}
|
|
31
|
+
heatingSetPoint={{
|
|
32
|
+
fahrenheit: climateSetting.heating_set_point_fahrenheit,
|
|
33
|
+
celsius: climateSetting.heating_set_point_celsius,
|
|
34
|
+
}}
|
|
36
35
|
temperatureUnit={temperatureUnit}
|
|
37
36
|
/>
|
|
38
37
|
{iconPlacement === 'right' && (
|
|
@@ -51,33 +50,42 @@ function ClimateSettingIcon(props: {
|
|
|
51
50
|
<div className='seam-climate-setting-status-icon'>
|
|
52
51
|
{mode === 'cool' && <ThermostatCoolIcon />}
|
|
53
52
|
{mode === 'heat' && <ThermostatHeatIcon />}
|
|
54
|
-
{mode === '
|
|
53
|
+
{mode === 'heat_cool' && <ThermostatHeatCoolIcon />}
|
|
55
54
|
{mode === 'off' && <ThermostatOffIcon />}
|
|
56
55
|
</div>
|
|
57
56
|
)
|
|
58
57
|
}
|
|
59
58
|
|
|
59
|
+
interface SetPoint {
|
|
60
|
+
fahrenheit: number
|
|
61
|
+
celsius: number
|
|
62
|
+
}
|
|
63
|
+
|
|
60
64
|
function Content(props: {
|
|
61
65
|
mode: ClimateSetting['hvac_mode_setting']
|
|
62
|
-
coolingSetPoint:
|
|
63
|
-
heatingSetPoint:
|
|
66
|
+
coolingSetPoint: Partial<SetPoint>
|
|
67
|
+
heatingSetPoint: Partial<SetPoint>
|
|
64
68
|
temperatureUnit: 'fahrenheit' | 'celsius'
|
|
65
69
|
}): JSX.Element | null {
|
|
66
70
|
const { mode, coolingSetPoint, heatingSetPoint, temperatureUnit } = props
|
|
67
|
-
const hasCoolingSetPoint = coolingSetPoint !== undefined
|
|
68
|
-
const hasHeatingSetPoint = heatingSetPoint !== undefined
|
|
69
|
-
const unit =
|
|
70
|
-
temperatureUnit === 'fahrenheit' ? t.degreeFahrenheit : t.degreeCelsius
|
|
71
71
|
|
|
72
|
-
if (mode === 'cool' &&
|
|
73
|
-
return <
|
|
72
|
+
if (mode === 'cool' && isSetPoint(coolingSetPoint))
|
|
73
|
+
return <Temperature {...coolingSetPoint} unit={temperatureUnit} />
|
|
74
74
|
|
|
75
|
-
if (mode === 'heat' &&
|
|
76
|
-
return <
|
|
75
|
+
if (mode === 'heat' && isSetPoint(heatingSetPoint))
|
|
76
|
+
return <Temperature {...heatingSetPoint} unit={temperatureUnit} />
|
|
77
77
|
|
|
78
|
-
if (
|
|
78
|
+
if (
|
|
79
|
+
mode === 'heat_cool' &&
|
|
80
|
+
isSetPoint(heatingSetPoint) &&
|
|
81
|
+
isSetPoint(coolingSetPoint)
|
|
82
|
+
)
|
|
79
83
|
return (
|
|
80
|
-
<span>
|
|
84
|
+
<span>
|
|
85
|
+
<Temperature {...heatingSetPoint} unit={temperatureUnit} />
|
|
86
|
+
{' - '}
|
|
87
|
+
<Temperature {...coolingSetPoint} unit={temperatureUnit} />
|
|
88
|
+
</span>
|
|
81
89
|
)
|
|
82
90
|
|
|
83
91
|
if (mode === 'off') return <span>{t.off}</span>
|
|
@@ -85,14 +93,10 @@ function Content(props: {
|
|
|
85
93
|
return null
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (Number.isInteger(value)) return value.toFixed()
|
|
91
|
-
return value.toFixed(1)
|
|
96
|
+
function isSetPoint(setPoint: Partial<SetPoint>): setPoint is SetPoint {
|
|
97
|
+
return setPoint.fahrenheit != null && setPoint.celsius != null
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
const t = {
|
|
95
|
-
degreeFahrenheit: '°F',
|
|
96
|
-
degreeCelsius: '°C',
|
|
97
101
|
off: 'Off',
|
|
98
102
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface TemperatureProps {
|
|
2
|
+
fahrenheit: number
|
|
3
|
+
celsius: number
|
|
4
|
+
unit?: 'fahrenheit' | 'celsius'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function Temperature({
|
|
8
|
+
fahrenheit,
|
|
9
|
+
celsius,
|
|
10
|
+
unit = 'fahrenheit',
|
|
11
|
+
}: TemperatureProps): JSX.Element {
|
|
12
|
+
const temperature = unit === 'fahrenheit' ? fahrenheit : celsius
|
|
13
|
+
const degree = unit === 'fahrenheit' ? t.degreeFahrenheit : t.degreeCelsius
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<span>
|
|
17
|
+
{Math.trunc(temperature)}
|
|
18
|
+
{degree}
|
|
19
|
+
</span>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const t = {
|
|
24
|
+
degreeFahrenheit: '°F',
|
|
25
|
+
degreeCelsius: '°C',
|
|
26
|
+
}
|