Skip to content

Commit 065ad1e

Browse files
authored
feat(Thermostats): add manual override toggle for ClimateScheduleDetails (#609)
1 parent 9595fa6 commit 065ad1e

File tree

2 files changed

+173
-49
lines changed

2 files changed

+173
-49
lines changed

src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
} from 'lib/seam/components/common-props.js'
1010
import { NestedDeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
1111
import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
12+
import { useUpdateClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-update-climate-setting-schedule.js'
1213
import { useComponentTelemetry } from 'lib/telemetry/index.js'
1314
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
1415
import { DetailRow } from 'lib/ui/layout/DetailRow.js'
1516
import { DetailSection } from 'lib/ui/layout/DetailSection.js'
1617
import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
18+
import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
19+
import { Switch } from 'lib/ui/Switch/Switch.js'
1720
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
1821

1922
import { formatDateTime } from './dates.js'
@@ -45,6 +48,8 @@ export function ClimateSettingScheduleDetails({
4548
climateSettingScheduleId
4649
)
4750

51+
const { mutate, isSuccess, isError } = useUpdateClimateSettingSchedule()
52+
4853
const [selectedDeviceId, selectDevice] = useState<string | null>(null)
4954

5055
if (climateSettingSchedule == null) {
@@ -75,55 +80,82 @@ export function ClimateSettingScheduleDetails({
7580
}
7681

7782
return (
78-
<div
79-
className={classNames('seam-climate-setting-schedule-details', className)}
80-
>
81-
<ContentHeader title={t.climateSettingSchedule} onBack={onBack} />
82-
<div className='seam-climate-setting-schedule-details-content'>
83-
<ClimateSettingScheduleCard
84-
climateSettingScheduleId={climateSettingScheduleId}
85-
onSelectDevice={selectDevice}
86-
/>
87-
<div className='seam-default-setting-message-container'>
88-
<span className='seam-default-setting-message'>
89-
{t.defaultSettingMessagePart1}{' '}
90-
<span className='seam-default-setting-text'>
91-
{t.defaultSetting}
92-
</span>{' '}
93-
{t.defaultSettingMessagePart2}
94-
</span>
83+
<>
84+
<div
85+
className={classNames(
86+
'seam-climate-setting-schedule-details',
87+
className
88+
)}
89+
>
90+
<ContentHeader title={t.climateSettingSchedule} onBack={onBack} />
91+
<div className='seam-climate-setting-schedule-details-content'>
92+
<ClimateSettingScheduleCard
93+
climateSettingScheduleId={climateSettingScheduleId}
94+
onSelectDevice={selectDevice}
95+
/>
96+
<div className='seam-default-setting-message-container'>
97+
<span className='seam-default-setting-message'>
98+
{t.defaultSettingMessagePart1}{' '}
99+
<span className='seam-default-setting-text'>
100+
{t.defaultSetting}
101+
</span>{' '}
102+
{t.defaultSettingMessagePart2}
103+
</span>
104+
</div>
105+
<DetailSectionGroup>
106+
<DetailSection>
107+
<DetailRow label={t.startEndTime}>
108+
<span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
109+
{formatDateTime(climateSettingSchedule.schedule_starts_at)}
110+
<ArrowRightIcon />
111+
{formatDateTime(climateSettingSchedule.schedule_ends_at)}
112+
</span>
113+
</DetailRow>
114+
<DetailRow label={t.climateSetting}>
115+
<ClimateSettingStatus
116+
climateSetting={climateSettingSchedule}
117+
iconPlacement='right'
118+
/>
119+
</DetailRow>
120+
<DetailRow label={t.allowManualOverride}>
121+
<span className='seam-climate-setting-details-value'>
122+
<Switch
123+
checked={isManualOverrideAllowed}
124+
onChange={(checked) => {
125+
mutate({
126+
climate_setting_schedule_id:
127+
climateSettingSchedule.climate_setting_schedule_id,
128+
manual_override_allowed: checked,
129+
})
130+
}}
131+
/>
132+
</span>
133+
</DetailRow>
134+
</DetailSection>
135+
<DetailSection>
136+
<DetailRow label={t.creationDate}>
137+
<div className='seam-creation-date'>
138+
{formatDateTime(climateSettingSchedule.created_at)}
139+
</div>
140+
</DetailRow>
141+
</DetailSection>
142+
</DetailSectionGroup>
95143
</div>
96-
<DetailSectionGroup>
97-
<DetailSection>
98-
<DetailRow label={t.startEndTime}>
99-
<span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
100-
{formatDateTime(climateSettingSchedule.schedule_starts_at)}
101-
<ArrowRightIcon />
102-
{formatDateTime(climateSettingSchedule.schedule_ends_at)}
103-
</span>
104-
</DetailRow>
105-
<DetailRow label={t.climateSetting}>
106-
<ClimateSettingStatus
107-
climateSetting={climateSettingSchedule}
108-
iconPlacement='right'
109-
/>
110-
</DetailRow>
111-
<DetailRow label={t.allowManualOverride}>
112-
<span className='seam-climate-setting-details-value'>
113-
{isManualOverrideAllowed ? t.on : t.off}
114-
</span>
115-
</DetailRow>
116-
</DetailSection>
117-
<DetailSection>
118-
<DetailRow label={t.creationDate}>
119-
<div className='seam-creation-date'>
120-
{formatDateTime(climateSettingSchedule.created_at)}
121-
</div>
122-
</DetailRow>
123-
</DetailSection>
124-
</DetailSectionGroup>
125144
</div>
126-
</div>
145+
<Snackbar
146+
message={t.manualOverrideSuccess}
147+
variant='success'
148+
visible={isSuccess}
149+
automaticVisibility
150+
/>
151+
152+
<Snackbar
153+
message={t.manualOverrideError}
154+
variant='error'
155+
visible={isError}
156+
automaticVisibility
157+
/>
158+
</>
127159
)
128160
}
129161

@@ -136,6 +168,6 @@ const t = {
136168
defaultSettingMessagePart1: 'Thermostat will return to its',
137169
defaultSetting: 'default setting',
138170
defaultSettingMessagePart2: 'at end time.',
139-
on: 'On',
140-
off: 'Off',
171+
manualOverrideSuccess: 'Successfully updated manual override!',
172+
manualOverrideError: 'Error updating manual override. Please try again.',
141173
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {
2+
useMutation,
3+
type UseMutationResult,
4+
useQueryClient,
5+
} from '@tanstack/react-query'
6+
import type {
7+
ClimateSettingSchedule,
8+
ClimateSettingSchedulesListResponse,
9+
ClimateSettingScheduleUpdateRequest,
10+
ClimateSettingScheduleUpdateResponse,
11+
SeamError,
12+
} from 'seamapi'
13+
14+
import { NullSeamClientError, useSeamClient } from 'lib/index.js'
15+
16+
type UseUpdateClimateSettingScheduleData =
17+
ClimateSettingScheduleUpdateResponse['climate_setting_schedule']
18+
type UseUpdateClimateSettingScheduleMutationParams =
19+
ClimateSettingScheduleUpdateRequest
20+
21+
export function useUpdateClimateSettingSchedule(): UseMutationResult<
22+
UseUpdateClimateSettingScheduleData,
23+
SeamError,
24+
UseUpdateClimateSettingScheduleMutationParams
25+
> {
26+
const { client } = useSeamClient()
27+
const queryClient = useQueryClient()
28+
29+
return useMutation<
30+
UseUpdateClimateSettingScheduleData,
31+
SeamError,
32+
UseUpdateClimateSettingScheduleMutationParams
33+
>({
34+
mutationFn: async (
35+
mutationPararms: UseUpdateClimateSettingScheduleMutationParams
36+
) => {
37+
if (client === null) throw new NullSeamClientError()
38+
return await client.thermostats.climateSettingSchedules.update(
39+
mutationPararms
40+
)
41+
},
42+
onSuccess: (updated) => {
43+
queryClient.setQueryData<ClimateSettingSchedule>(
44+
[
45+
'thermostats',
46+
'climate_setting_schedules',
47+
'get',
48+
{
49+
climate_setting_schedule_id: updated.climate_setting_schedule_id,
50+
},
51+
],
52+
(climateSettingSchedule) => {
53+
if (climateSettingSchedule == null) {
54+
return
55+
}
56+
57+
return {
58+
...climateSettingSchedule,
59+
...updated,
60+
}
61+
}
62+
)
63+
64+
queryClient.setQueryData<
65+
ClimateSettingSchedulesListResponse['climate_setting_schedules']
66+
>(
67+
[
68+
'thermostats',
69+
'climate_setting_schedules',
70+
'list',
71+
{ device_id: updated.device_id },
72+
],
73+
(climateSettingSchedules): ClimateSettingSchedule[] => {
74+
if (climateSettingSchedules == null) {
75+
return []
76+
}
77+
78+
return climateSettingSchedules.map((climateSettingSchedule) => {
79+
if (climateSettingSchedule.device_id === updated.device_id) {
80+
return {
81+
...climateSettingSchedule,
82+
...updated,
83+
}
84+
}
85+
86+
return climateSettingSchedule
87+
})
88+
}
89+
)
90+
},
91+
})
92+
}

0 commit comments

Comments
 (0)