@seamapi/react 1.48.1 → 1.50.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 +3285 -3280
- package/dist/elements.js.map +1 -1
- package/dist/index.css +336 -2
- 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/ChevronWide.d.ts +2 -0
- package/lib/icons/ChevronWide.js +5 -0
- package/lib/icons/ChevronWide.js.map +1 -0
- package/lib/icons/Copy.js +2 -4
- package/lib/icons/Copy.js.map +1 -1
- 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/AccessCodeDetails/AccessCodeDetails.js +9 -2
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
- 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.d.ts +7 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js +40 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js.map +1 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.d.ts +2 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js +7 -5
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.d.ts +3 -1
- package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js +20 -3
- package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js.map +1 -1
- package/lib/seam/connected-accounts/use-connected-account.d.ts +4 -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/AccordionRow.d.ts +7 -0
- package/lib/ui/layout/AccordionRow.js +8 -0
- package/lib/ui/layout/AccordionRow.js.map +1 -0
- package/lib/ui/layout/DetailRow.d.ts +7 -0
- package/lib/ui/layout/DetailRow.js +5 -0
- package/lib/ui/layout/DetailRow.js.map +1 -0
- package/lib/ui/layout/DetailSection.d.ts +7 -0
- package/lib/ui/layout/DetailSection.js +6 -0
- package/lib/ui/layout/DetailSection.js.map +1 -0
- package/lib/ui/layout/DetailSectionGroup.d.ts +2 -0
- package/lib/ui/layout/DetailSectionGroup.js +5 -0
- package/lib/ui/layout/DetailSectionGroup.js.map +1 -0
- 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/ChevronWide.tsx +21 -0
- package/src/lib/icons/Copy.tsx +6 -21
- package/src/lib/icons/Fan.tsx +36 -0
- package/src/lib/icons/Off.tsx +36 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +23 -2
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +9 -2
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.element.ts +13 -0
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx +121 -0
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx +18 -6
- package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.element.ts +2 -0
- package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.tsx +37 -2
- package/src/lib/seam/connected-accounts/use-connected-account.ts +34 -0
- package/src/lib/seam/index.ts +2 -0
- package/src/lib/ui/layout/AccordionRow.tsx +36 -0
- package/src/lib/ui/layout/DetailRow.tsx +22 -0
- package/src/lib/ui/layout/DetailSection.tsx +25 -0
- package/src/lib/ui/layout/DetailSectionGroup.tsx +7 -0
- 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/_access-code-details.scss +42 -1
- package/src/styles/_climate-setting-schedule-details.scss +44 -0
- package/src/styles/_layout.scss +161 -0
- package/src/styles/_main.scss +2 -0
- package/src/styles/_seam-table.scss +8 -0
- package/src/styles/_thermostat.scss +164 -1
- package/src/styles/_tooltip.scss +1 -0
|
@@ -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
|
+
}
|
|
@@ -8,12 +8,15 @@ import type {
|
|
|
8
8
|
DeviceError,
|
|
9
9
|
} from 'seamapi'
|
|
10
10
|
|
|
11
|
+
import { CopyIcon } from 'lib/icons/Copy.js'
|
|
11
12
|
import { useAccessCode } from 'lib/seam/access-codes/use-access-code.js'
|
|
12
13
|
import { useDeleteAccessCode } from 'lib/seam/access-codes/use-delete-access-code.js'
|
|
13
14
|
import { AccessCodeDevice } from 'lib/seam/components/AccessCodeDetails/AccessCodeDevice.js'
|
|
14
15
|
import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
|
|
15
16
|
import { Alerts } from 'lib/ui/Alert/Alerts.js'
|
|
16
17
|
import { Button } from 'lib/ui/Button.js'
|
|
18
|
+
import { copyToClipboard } from 'lib/ui/clipboard.js'
|
|
19
|
+
import { IconButton } from 'lib/ui/IconButton.js'
|
|
17
20
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
18
21
|
import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
|
|
19
22
|
|
|
@@ -82,7 +85,16 @@ export function AccessCodeDetails({
|
|
|
82
85
|
>
|
|
83
86
|
<span className='seam-label'>{t.accessCode}</span>
|
|
84
87
|
<h5 className='seam-access-code-name'>{name}</h5>
|
|
85
|
-
<div className='seam-code'>
|
|
88
|
+
<div className='seam-code'>
|
|
89
|
+
<span>{accessCode.code}</span>
|
|
90
|
+
<IconButton
|
|
91
|
+
onClick={() => {
|
|
92
|
+
void copyToClipboard(accessCode.code ?? '')
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<CopyIcon />
|
|
96
|
+
</IconButton>
|
|
97
|
+
</div>
|
|
86
98
|
<div className='seam-duration'>
|
|
87
99
|
<Duration accessCode={accessCode} />
|
|
88
100
|
</div>
|
|
@@ -117,7 +129,16 @@ export function AccessCodeDetails({
|
|
|
117
129
|
<div className='seam-details'>
|
|
118
130
|
<div className='seam-row'>
|
|
119
131
|
<div className='seam-heading'>{t.id}:</div>
|
|
120
|
-
<div className='seam-content'>
|
|
132
|
+
<div className='seam-content seam-code-id'>
|
|
133
|
+
<span>{accessCode.access_code_id}</span>
|
|
134
|
+
<IconButton
|
|
135
|
+
onClick={() => {
|
|
136
|
+
void copyToClipboard(accessCode.access_code_id)
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<CopyIcon />
|
|
140
|
+
</IconButton>
|
|
141
|
+
</div>
|
|
121
142
|
</div>
|
|
122
143
|
<div className='seam-row'>
|
|
123
144
|
<div className='seam-heading'>{t.created}:</div>
|
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>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ElementProps } from 'lib/element.js'
|
|
2
|
+
|
|
3
|
+
import type { ClimateSettingScheduleDetailsProps } from './ClimateSettingScheduleDetails.js'
|
|
4
|
+
|
|
5
|
+
export const name = 'seam-climate-setting-schedule-details'
|
|
6
|
+
|
|
7
|
+
export const props: ElementProps<ClimateSettingScheduleDetailsProps> = {
|
|
8
|
+
climateSettingScheduleId: 'string',
|
|
9
|
+
onBack: 'object',
|
|
10
|
+
className: 'string',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { ClimateSettingScheduleDetails as Component } from './ClimateSettingScheduleDetails.js'
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { DeviceDetails } from '../index.js'
|
|
5
|
+
|
|
6
|
+
import { formatDateAndTime } from 'lib/dates.js'
|
|
7
|
+
import { ArrowRightIcon } from 'lib/icons/ArrowRight.js'
|
|
8
|
+
import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
|
|
9
|
+
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
10
|
+
import { DetailRow } from 'lib/ui/layout/DetailRow.js'
|
|
11
|
+
import { DetailSection } from 'lib/ui/layout/DetailSection.js'
|
|
12
|
+
import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
|
|
13
|
+
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
14
|
+
|
|
15
|
+
import { ClimateSettingScheduleCard } from './ClimateSettingScheduleCard.js'
|
|
16
|
+
|
|
17
|
+
export interface ClimateSettingScheduleDetailsProps {
|
|
18
|
+
climateSettingScheduleId: string
|
|
19
|
+
onBack?: () => void
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ClimateSettingScheduleDetails({
|
|
24
|
+
climateSettingScheduleId,
|
|
25
|
+
onBack,
|
|
26
|
+
className,
|
|
27
|
+
}: ClimateSettingScheduleDetailsProps): JSX.Element | null {
|
|
28
|
+
const { climateSettingSchedule } = useClimateSettingSchedule(
|
|
29
|
+
climateSettingScheduleId
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const [selectedDeviceId, selectDevice] = useState<string | null>(null)
|
|
33
|
+
|
|
34
|
+
if (climateSettingSchedule == null) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isManualOverrideAllowed =
|
|
39
|
+
climateSettingSchedule.manual_override_allowed ?? false
|
|
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
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={classNames('seam-climate-setting-schedule-details', className)}
|
|
56
|
+
>
|
|
57
|
+
<ContentHeader title={t.climateSettingSchedule} onBack={onBack} />
|
|
58
|
+
<div className='seam-climate-setting-schedule-details-content'>
|
|
59
|
+
<ClimateSettingScheduleCard
|
|
60
|
+
climateSettingScheduleId={climateSettingScheduleId}
|
|
61
|
+
onSelectDevice={selectDevice}
|
|
62
|
+
/>
|
|
63
|
+
<div className='seam-default-setting-message-container'>
|
|
64
|
+
<span className='seam-default-setting-message'>
|
|
65
|
+
{t.defaultSettingMessagePart1}{' '}
|
|
66
|
+
<span className='seam-default-setting-text'>
|
|
67
|
+
{t.defaultSetting}
|
|
68
|
+
</span>{' '}
|
|
69
|
+
{t.defaultSettingMessagePart2}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
<DetailSectionGroup>
|
|
73
|
+
<DetailSection>
|
|
74
|
+
<DetailRow label={t.startEndTime}>
|
|
75
|
+
<span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
|
|
76
|
+
{`${formatDateAndTime(
|
|
77
|
+
climateSettingSchedule.schedule_starts_at
|
|
78
|
+
)}`}
|
|
79
|
+
<ArrowRightIcon />
|
|
80
|
+
{`${formatDateAndTime(
|
|
81
|
+
climateSettingSchedule.schedule_ends_at
|
|
82
|
+
)}`}
|
|
83
|
+
</span>
|
|
84
|
+
</DetailRow>
|
|
85
|
+
<DetailRow label={t.climateSetting}>
|
|
86
|
+
<ClimateSettingStatus
|
|
87
|
+
climateSetting={climateSettingSchedule}
|
|
88
|
+
iconPlacement='right'
|
|
89
|
+
/>
|
|
90
|
+
</DetailRow>
|
|
91
|
+
<DetailRow label={t.allowManualOverride}>
|
|
92
|
+
<span className='seam-climate-setting-details-value'>
|
|
93
|
+
{isManualOverrideAllowed ? t.on : t.off}
|
|
94
|
+
</span>
|
|
95
|
+
</DetailRow>
|
|
96
|
+
</DetailSection>
|
|
97
|
+
<DetailSection>
|
|
98
|
+
<DetailRow label={t.creationDate}>
|
|
99
|
+
<div className='seam-creation-date'>
|
|
100
|
+
{formatDateAndTime(climateSettingSchedule.created_at)}
|
|
101
|
+
</div>
|
|
102
|
+
</DetailRow>
|
|
103
|
+
</DetailSection>
|
|
104
|
+
</DetailSectionGroup>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const t = {
|
|
111
|
+
climateSettingSchedule: 'Climate setting schedule',
|
|
112
|
+
startEndTime: 'Start/End Time',
|
|
113
|
+
climateSetting: 'Climate setting',
|
|
114
|
+
allowManualOverride: 'Allow manual override',
|
|
115
|
+
creationDate: 'Creation date',
|
|
116
|
+
defaultSettingMessagePart1: 'Thermostat will return to its',
|
|
117
|
+
defaultSetting: 'default setting',
|
|
118
|
+
defaultSettingMessagePart2: 'at end time.',
|
|
119
|
+
on: 'On',
|
|
120
|
+
off: 'Off',
|
|
121
|
+
}
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { isThermostatDevice, type ThermostatDevice } from 'seamapi'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
|
|
4
4
|
import { useDevice } from 'lib/seam/devices/use-device.js'
|
|
5
5
|
import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
|
|
6
6
|
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} />
|
|
@@ -48,7 +58,9 @@ function Content(props: { device: ThermostatDevice }): JSX.Element {
|
|
|
48
58
|
</div>
|
|
49
59
|
</div>
|
|
50
60
|
</div>
|
|
51
|
-
<
|
|
61
|
+
<div className='seam-climate-setting-schedule-device-chevron'>
|
|
62
|
+
<ChevronWideIcon />
|
|
63
|
+
</div>
|
|
52
64
|
</div>
|
|
53
65
|
)
|
|
54
66
|
}
|
package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.element.ts
CHANGED
|
@@ -7,6 +7,8 @@ export const name = 'seam-climate-setting-schedule-table'
|
|
|
7
7
|
export const props: ElementProps<ClimateSettingScheduleTableProps> = {
|
|
8
8
|
deviceId: 'string',
|
|
9
9
|
disableSearch: 'boolean',
|
|
10
|
+
onClimateSettingScheduleClick: 'object',
|
|
11
|
+
preventDefaultOnClimateSettingScheduleClick: 'boolean',
|
|
10
12
|
climateSettingScheduleFilter: 'object',
|
|
11
13
|
climateSettingScheduleComparator: 'object',
|
|
12
14
|
onBack: 'object',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
|
-
import { useMemo, useState } from 'react'
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
3
3
|
import type { ClimateSettingSchedule } from 'seamapi'
|
|
4
4
|
|
|
5
5
|
import { compareByCreatedAtDesc } from 'lib/dates.js'
|
|
6
|
+
import { ClimateSettingScheduleDetails } from 'lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js'
|
|
6
7
|
import {
|
|
7
8
|
useClimateSettingSchedules,
|
|
8
9
|
type UseClimateSettingSchedulesData,
|
|
@@ -20,6 +21,8 @@ import { ClimateSettingScheduleRow } from './ClimateSettingScheduleRow.js'
|
|
|
20
21
|
export interface ClimateSettingScheduleTableProps {
|
|
21
22
|
deviceId: string
|
|
22
23
|
disableSearch?: boolean
|
|
24
|
+
onClimateSettingScheduleClick?: (climateSettingScheduleId: string) => void
|
|
25
|
+
preventDefaultOnClimateSettingScheduleClick?: boolean
|
|
23
26
|
climateSettingScheduleFilter?: (
|
|
24
27
|
climateSettingSchedule: ClimateSettingSchedule,
|
|
25
28
|
searchInputValue: string
|
|
@@ -46,8 +49,11 @@ const defaultClimateSettingScheduleFilter = (
|
|
|
46
49
|
export function ClimateSettingScheduleTable({
|
|
47
50
|
deviceId,
|
|
48
51
|
disableSearch = false,
|
|
52
|
+
onClimateSettingScheduleClick = () => {},
|
|
53
|
+
preventDefaultOnClimateSettingScheduleClick = false,
|
|
49
54
|
climateSettingScheduleFilter = defaultClimateSettingScheduleFilter,
|
|
50
55
|
climateSettingScheduleComparator = compareByCreatedAtDesc,
|
|
56
|
+
|
|
51
57
|
onBack,
|
|
52
58
|
heading = t.climateSettingSchedules,
|
|
53
59
|
className,
|
|
@@ -58,6 +64,10 @@ export function ClimateSettingScheduleTable({
|
|
|
58
64
|
})
|
|
59
65
|
|
|
60
66
|
const [searchInputValue, setSearchInputValue] = useState('')
|
|
67
|
+
const [
|
|
68
|
+
selectedViewClimateSettingScheduleId,
|
|
69
|
+
setSelectedViewClimateSettingScheduleId,
|
|
70
|
+
] = useState<string | null>(null)
|
|
61
71
|
|
|
62
72
|
const filteredClimateSettingSchedules = useMemo(
|
|
63
73
|
() =>
|
|
@@ -74,6 +84,31 @@ export function ClimateSettingScheduleTable({
|
|
|
74
84
|
]
|
|
75
85
|
)
|
|
76
86
|
|
|
87
|
+
const handleClimateSettingScheduleClick = useCallback(
|
|
88
|
+
(climateSettingScheduleId: string): void => {
|
|
89
|
+
onClimateSettingScheduleClick(climateSettingScheduleId)
|
|
90
|
+
if (preventDefaultOnClimateSettingScheduleClick) return
|
|
91
|
+
setSelectedViewClimateSettingScheduleId(climateSettingScheduleId)
|
|
92
|
+
},
|
|
93
|
+
[
|
|
94
|
+
onClimateSettingScheduleClick,
|
|
95
|
+
preventDefaultOnClimateSettingScheduleClick,
|
|
96
|
+
setSelectedViewClimateSettingScheduleId,
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if (selectedViewClimateSettingScheduleId != null) {
|
|
101
|
+
return (
|
|
102
|
+
<ClimateSettingScheduleDetails
|
|
103
|
+
className={className}
|
|
104
|
+
climateSettingScheduleId={selectedViewClimateSettingScheduleId}
|
|
105
|
+
onBack={() => {
|
|
106
|
+
setSelectedViewClimateSettingScheduleId(null)
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
77
112
|
if (isLoading) {
|
|
78
113
|
return <p className={className}>...</p>
|
|
79
114
|
}
|
|
@@ -105,7 +140,7 @@ export function ClimateSettingScheduleTable({
|
|
|
105
140
|
<TableBody>
|
|
106
141
|
<Content
|
|
107
142
|
climateSettingSchedules={filteredClimateSettingSchedules}
|
|
108
|
-
onClimateSettingScheduleClick={
|
|
143
|
+
onClimateSettingScheduleClick={handleClimateSettingScheduleClick}
|
|
109
144
|
/>
|
|
110
145
|
</TableBody>
|
|
111
146
|
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
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 UseConnectedAccountData = ConnectedAccount | null
|
|
13
|
+
|
|
14
|
+
export function useConnectedAccount(
|
|
15
|
+
params: ConnectedAccountsGetRequest
|
|
16
|
+
): UseSeamQueryResult<'connectedAccount', UseConnectedAccountData> {
|
|
17
|
+
const normalizedParams =
|
|
18
|
+
typeof params === 'string' ? { connected_account_id: params } : params
|
|
19
|
+
|
|
20
|
+
const { client } = useSeamClient()
|
|
21
|
+
const { data, ...rest } = useQuery<
|
|
22
|
+
ConnectedAccountsGetResponse['connected_account'] | null,
|
|
23
|
+
SeamError
|
|
24
|
+
>({
|
|
25
|
+
enabled: client != null,
|
|
26
|
+
queryKey: ['connected_accounts', 'get', normalizedParams],
|
|
27
|
+
queryFn: async () => {
|
|
28
|
+
if (client == null) return null
|
|
29
|
+
return await client.connectedAccounts.get(normalizedParams)
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return { ...rest, connectedAccount: data }
|
|
34
|
+
}
|
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'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
|
|
4
|
+
import { useToggle } from 'lib/ui/use-toggle.js'
|
|
5
|
+
|
|
6
|
+
interface AccordionRowProps extends PropsWithChildren {
|
|
7
|
+
label: string
|
|
8
|
+
rightCollapsedContent?: JSX.Element
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function AccordionRow({
|
|
12
|
+
label,
|
|
13
|
+
rightCollapsedContent,
|
|
14
|
+
children,
|
|
15
|
+
}: AccordionRowProps): JSX.Element {
|
|
16
|
+
const [isExpanded, toggle] = useToggle()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className='seam-accordion-row' aria-expanded={isExpanded}>
|
|
20
|
+
<button className='seam-accordion-row-trigger' onClick={toggle}>
|
|
21
|
+
<p className='seam-row-label'>{label}</p>
|
|
22
|
+
<div className='seam-row-inner-wrap'>
|
|
23
|
+
<div className='seam-row-trigger-right-content'>
|
|
24
|
+
{rightCollapsedContent}
|
|
25
|
+
</div>
|
|
26
|
+
<div className='seam-accordion-icon-wrap'>
|
|
27
|
+
<ChevronWideIcon />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</button>
|
|
31
|
+
<div className='seam-accordion-row-content'>
|
|
32
|
+
<div className='seam-accordion-row-inner-content'>{children}</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
interface DetailRowProps {
|
|
4
|
+
label: string
|
|
5
|
+
sublabel?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function DetailRow({
|
|
9
|
+
label,
|
|
10
|
+
sublabel,
|
|
11
|
+
children,
|
|
12
|
+
}: PropsWithChildren<DetailRowProps>): JSX.Element {
|
|
13
|
+
return (
|
|
14
|
+
<div className='seam-detail-row'>
|
|
15
|
+
<div className='seam-detail-row-label-wrap'>
|
|
16
|
+
<p className='seam-row-label'>{label}</p>
|
|
17
|
+
{sublabel != null && <p className='seam-row-sublabel'>{sublabel}</p>}
|
|
18
|
+
</div>
|
|
19
|
+
<div className='seam-detail-row-right-area-content'>{children}</div>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Tooltip } from 'lib/ui/Tooltip/Tooltip.js'
|
|
4
|
+
|
|
5
|
+
interface DetailSectionProps {
|
|
6
|
+
label?: string
|
|
7
|
+
tooltipContent?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function DetailSection({
|
|
11
|
+
label,
|
|
12
|
+
tooltipContent,
|
|
13
|
+
children,
|
|
14
|
+
}: PropsWithChildren<DetailSectionProps>): JSX.Element {
|
|
15
|
+
return (
|
|
16
|
+
<div className='seam-detail-section'>
|
|
17
|
+
<div className='seam-detail-label-wrap'>
|
|
18
|
+
{label != null && <p className='seam-detail-label'>{label}</p>}
|
|
19
|
+
{tooltipContent != null && <Tooltip>{tooltipContent}</Tooltip>}
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div className='seam-detail-group'>{children}</div>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -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
|
}
|