@seamapi/react 2.8.6 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/elements.js +6853 -6176
- package/dist/elements.js.map +1 -1
- package/dist/index.css +31 -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/debounce.d.ts +6 -0
- package/lib/debounce.js +20 -0
- package/lib/debounce.js.map +1 -0
- package/lib/icons/AccessCodeKey.js +1 -1
- package/lib/icons/AccessCodeKey.js.map +1 -1
- package/lib/icons/ArrowRestart.js +1 -1
- package/lib/icons/ArrowRestart.js.map +1 -1
- package/lib/icons/BatteryLevelWired.js +1 -1
- package/lib/icons/BatteryLevelWired.js.map +1 -1
- package/lib/icons/Bee.js +1 -1
- package/lib/icons/Bee.js.map +1 -1
- package/lib/icons/CheckGreen.js +1 -1
- package/lib/icons/CheckGreen.js.map +1 -1
- package/lib/icons/ClimateSettingSchedule.js +1 -1
- package/lib/icons/ClimateSettingSchedule.js.map +1 -1
- package/lib/icons/Copy.js +1 -1
- package/lib/icons/Copy.js.map +1 -1
- package/lib/icons/DotsEllipsisMore.js +1 -1
- package/lib/icons/DotsEllipsisMore.js.map +1 -1
- package/lib/icons/Edit.js +1 -1
- package/lib/icons/Edit.js.map +1 -1
- package/lib/icons/ExclamationCircle.js +1 -1
- package/lib/icons/ExclamationCircle.js.map +1 -1
- package/lib/icons/ExclamationCircleOutline.js +1 -1
- package/lib/icons/ExclamationCircleOutline.js.map +1 -1
- package/lib/icons/Fan.js +1 -1
- package/lib/icons/Fan.js.map +1 -1
- package/lib/icons/FanOutline.js +1 -1
- package/lib/icons/FanOutline.js.map +1 -1
- package/lib/icons/Info.js +1 -1
- package/lib/icons/Info.js.map +1 -1
- package/lib/icons/InfoBlue.js +1 -1
- package/lib/icons/InfoBlue.js.map +1 -1
- package/lib/icons/InfoDark.js +1 -1
- package/lib/icons/InfoDark.js.map +1 -1
- package/lib/icons/Off.js +1 -1
- package/lib/icons/Off.js.map +1 -1
- package/lib/icons/OnlineStatusAccountOffline.js +1 -1
- package/lib/icons/OnlineStatusAccountOffline.js.map +1 -1
- package/lib/icons/OnlineStatusDeviceOffline.js +1 -1
- package/lib/icons/OnlineStatusDeviceOffline.js.map +1 -1
- package/lib/icons/OnlineStatusOnline.js +1 -1
- package/lib/icons/OnlineStatusOnline.js.map +1 -1
- package/lib/icons/Seam.js +1 -1
- package/lib/icons/Seam.js.map +1 -1
- package/lib/icons/Search.js +1 -1
- package/lib/icons/Search.js.map +1 -1
- package/lib/icons/ThermostatHeat.js +1 -1
- package/lib/icons/ThermostatHeat.js.map +1 -1
- package/lib/icons/ThermostatHeatCool.js +1 -1
- package/lib/icons/ThermostatHeatCool.js.map +1 -1
- package/lib/icons/ThermostatHeatLarge.js +1 -1
- package/lib/icons/ThermostatHeatLarge.js.map +1 -1
- package/lib/icons/ThermostatOff.js +1 -1
- package/lib/icons/ThermostatOff.js.map +1 -1
- package/lib/icons/TriangleWarning.js +1 -1
- package/lib/icons/TriangleWarning.js.map +1 -1
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +111 -17
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.d.ts +2 -2
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.d.ts +2 -4
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js +0 -6
- package/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/use-device-model.d.ts +2 -2
- package/lib/seam/components/SupportedDeviceTable/use-device-models.d.ts +2 -2
- package/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.js.map +1 -1
- package/lib/temperature-bounds.d.ts +2 -1
- package/lib/temperature-bounds.js +16 -0
- package/lib/temperature-bounds.js.map +1 -1
- package/lib/ui/layout/AccordionRow.d.ts +2 -1
- package/lib/ui/layout/AccordionRow.js +2 -2
- package/lib/ui/layout/AccordionRow.js.map +1 -1
- package/lib/ui/thermostat/ClimateModeMenu.d.ts +2 -1
- package/lib/ui/thermostat/ClimateModeMenu.js +2 -3
- package/lib/ui/thermostat/ClimateModeMenu.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -9
- package/src/lib/debounce.ts +33 -0
- package/src/lib/icons/AccessCodeKey.tsx +1 -1
- package/src/lib/icons/ArrowRestart.tsx +1 -1
- package/src/lib/icons/BatteryLevelWired.tsx +1 -1
- package/src/lib/icons/Bee.tsx +1 -1
- package/src/lib/icons/CheckGreen.tsx +1 -1
- package/src/lib/icons/ClimateSettingSchedule.tsx +1 -1
- package/src/lib/icons/Copy.tsx +1 -1
- package/src/lib/icons/DotsEllipsisMore.tsx +1 -1
- package/src/lib/icons/Edit.tsx +1 -1
- package/src/lib/icons/ExclamationCircle.tsx +1 -1
- package/src/lib/icons/ExclamationCircleOutline.tsx +1 -1
- package/src/lib/icons/Fan.tsx +1 -1
- package/src/lib/icons/FanOutline.tsx +1 -1
- package/src/lib/icons/Info.tsx +1 -1
- package/src/lib/icons/InfoBlue.tsx +1 -1
- package/src/lib/icons/InfoDark.tsx +1 -1
- package/src/lib/icons/Off.tsx +1 -1
- package/src/lib/icons/OnlineStatusAccountOffline.tsx +1 -1
- package/src/lib/icons/OnlineStatusDeviceOffline.tsx +1 -1
- package/src/lib/icons/OnlineStatusOnline.tsx +1 -1
- package/src/lib/icons/Seam.tsx +1 -1
- package/src/lib/icons/Search.tsx +1 -1
- package/src/lib/icons/ThermostatHeat.tsx +1 -1
- package/src/lib/icons/ThermostatHeatCool.tsx +1 -1
- package/src/lib/icons/ThermostatHeatLarge.tsx +1 -1
- package/src/lib/icons/ThermostatOff.tsx +1 -1
- package/src/lib/icons/TriangleWarning.tsx +1 -1
- package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +232 -35
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceContent.tsx +3 -3
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.tsx +2 -2
- package/src/lib/seam/components/SupportedDeviceTable/SupportedDeviceRow.tsx +4 -15
- package/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts +2 -2
- package/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts +2 -2
- package/src/lib/seam/components/SupportedDeviceTable/use-filtered-device-models.ts +6 -7
- package/src/lib/temperature-bounds.ts +23 -1
- package/src/lib/ui/layout/AccordionRow.tsx +6 -1
- package/src/lib/ui/thermostat/ClimateModeMenu.tsx +3 -3
- package/src/lib/version.ts +1 -1
- package/src/styles/_layout.scss +13 -2
- package/src/styles/_thermostat.scss +26 -0
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
|
-
import { useState } from 'react'
|
|
3
|
-
import type { ThermostatDevice } from 'seamapi'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import type { HvacModeSetting, ThermostatDevice } from 'seamapi'
|
|
4
4
|
|
|
5
|
+
import { debounce } from 'lib/debounce.js'
|
|
5
6
|
import { BeeIcon } from 'lib/icons/Bee.js'
|
|
7
|
+
import { CheckBlackIcon } from 'lib/icons/CheckBlack.js'
|
|
6
8
|
import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
|
|
7
9
|
import { NestedClimateSettingScheduleTable } from 'lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js'
|
|
8
10
|
import type { CommonProps } from 'lib/seam/components/common-props.js'
|
|
9
11
|
import { useConnectedAccount } from 'lib/seam/connected-accounts/use-connected-account.js'
|
|
10
12
|
import { useClimateSettingSchedules } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedules.js'
|
|
13
|
+
import { useCoolThermostat } from 'lib/seam/thermostats/use-cool-thermostat.js'
|
|
14
|
+
import { useHeatCoolThermostat } from 'lib/seam/thermostats/use-heat-cool-thermostat.js'
|
|
15
|
+
import { useHeatThermostat } from 'lib/seam/thermostats/use-heat-thermostat.js'
|
|
16
|
+
import { useSetThermostatOff } from 'lib/seam/thermostats/use-set-thermostat-off.js'
|
|
11
17
|
import { useUpdateFanMode } from 'lib/seam/thermostats/use-update-fan-mode.js'
|
|
12
18
|
import { useUpdateThermostat } from 'lib/seam/thermostats/use-update-thermostat.js'
|
|
19
|
+
import { getSupportedThermostatModes } from 'lib/temperature-bounds.js'
|
|
20
|
+
import { AccordionRow } from 'lib/ui/layout/AccordionRow.js'
|
|
13
21
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
14
22
|
import { DetailRow } from 'lib/ui/layout/DetailRow.js'
|
|
15
23
|
import { DetailSection } from 'lib/ui/layout/DetailSection.js'
|
|
16
24
|
import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
|
|
17
25
|
import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
|
|
18
26
|
import { Switch } from 'lib/ui/Switch/Switch.js'
|
|
27
|
+
import { ClimateModeMenu } from 'lib/ui/thermostat/ClimateModeMenu.js'
|
|
19
28
|
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
20
29
|
import { FanModeMenu } from 'lib/ui/thermostat/FanModeMenu.js'
|
|
30
|
+
import { TemperatureControlGroup } from 'lib/ui/thermostat/TemperatureControlGroup.js'
|
|
21
31
|
import { ThermostatCard } from 'lib/ui/thermostat/ThermostatCard.js'
|
|
22
32
|
|
|
23
33
|
interface ThermostatDeviceDetailsProps extends CommonProps {
|
|
@@ -105,12 +115,7 @@ export function ThermostatDeviceDetails({
|
|
|
105
115
|
label={t.currentSettings}
|
|
106
116
|
tooltipContent={t.currentSettingsTooltip}
|
|
107
117
|
>
|
|
108
|
-
<
|
|
109
|
-
<ClimateSettingStatus
|
|
110
|
-
climateSetting={device.properties.current_climate_setting}
|
|
111
|
-
temperatureUnit='fahrenheit'
|
|
112
|
-
/>
|
|
113
|
-
</DetailRow>
|
|
118
|
+
<ClimateSettingRow device={device} />
|
|
114
119
|
<FanModeRow device={device} />
|
|
115
120
|
</DetailSection>
|
|
116
121
|
|
|
@@ -166,22 +171,25 @@ function ManualOverrideRow({
|
|
|
166
171
|
|
|
167
172
|
return (
|
|
168
173
|
<>
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
174
|
+
<div className='seam-detail-row-wrap'>
|
|
175
|
+
<DetailRow label={t.allowManualOverride}>
|
|
176
|
+
<Switch
|
|
177
|
+
checked={
|
|
178
|
+
device.properties.default_climate_setting
|
|
179
|
+
?.manual_override_allowed ?? true
|
|
180
|
+
}
|
|
181
|
+
onChange={(checked) => {
|
|
182
|
+
mutate({
|
|
183
|
+
device_id: device.device_id,
|
|
184
|
+
default_climate_setting: {
|
|
185
|
+
manual_override_allowed: checked,
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
</DetailRow>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
185
193
|
<Snackbar
|
|
186
194
|
message={t.manualOverrideSuccess}
|
|
187
195
|
variant='success'
|
|
@@ -204,17 +212,19 @@ function FanModeRow({ device }: { device: ThermostatDevice }): JSX.Element {
|
|
|
204
212
|
|
|
205
213
|
return (
|
|
206
214
|
<>
|
|
207
|
-
<
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
<div className='seam-detail-row-wrap'>
|
|
216
|
+
<DetailRow label={t.fanMode}>
|
|
217
|
+
<FanModeMenu
|
|
218
|
+
mode={device.properties.fan_mode_setting}
|
|
219
|
+
onChange={(fanMode) => {
|
|
220
|
+
mutate({
|
|
221
|
+
device_id: device.device_id,
|
|
222
|
+
fan_mode_setting: fanMode,
|
|
223
|
+
})
|
|
224
|
+
}}
|
|
225
|
+
/>
|
|
226
|
+
</DetailRow>
|
|
227
|
+
</div>
|
|
218
228
|
|
|
219
229
|
<Snackbar
|
|
220
230
|
message={t.fanModeSuccess}
|
|
@@ -234,6 +244,191 @@ function FanModeRow({ device }: { device: ThermostatDevice }): JSX.Element {
|
|
|
234
244
|
)
|
|
235
245
|
}
|
|
236
246
|
|
|
247
|
+
function ClimateSettingRow({
|
|
248
|
+
device,
|
|
249
|
+
}: {
|
|
250
|
+
device: ThermostatDevice
|
|
251
|
+
}): JSX.Element {
|
|
252
|
+
const deviceHeatValue =
|
|
253
|
+
device.properties.current_climate_setting.heating_set_point_fahrenheit
|
|
254
|
+
const deviceCoolValue =
|
|
255
|
+
device.properties.current_climate_setting.cooling_set_point_fahrenheit
|
|
256
|
+
|
|
257
|
+
const supportedModes = getSupportedThermostatModes(device)
|
|
258
|
+
|
|
259
|
+
const [showSuccess, setShowSuccess] = useState(false)
|
|
260
|
+
const [mode, setMode] = useState<HvacModeSetting>(
|
|
261
|
+
(supportedModes.includes('heat_cool') ? 'heat_cool' : supportedModes[0]) ??
|
|
262
|
+
'off'
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const [heatValue, setHeatValue] = useState(
|
|
266
|
+
device.properties.current_climate_setting.heating_set_point_fahrenheit ?? 0
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
const [coolValue, setCoolValue] = useState(
|
|
270
|
+
device.properties.current_climate_setting.cooling_set_point_fahrenheit ?? 0
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const {
|
|
274
|
+
mutate: heatCoolThermostat,
|
|
275
|
+
isSuccess: isHeatCoolSuccess,
|
|
276
|
+
isError: isHeatCoolError,
|
|
277
|
+
} = useHeatCoolThermostat()
|
|
278
|
+
|
|
279
|
+
const {
|
|
280
|
+
mutate: heatThermostat,
|
|
281
|
+
isSuccess: isHeatSuccess,
|
|
282
|
+
isError: isHeatError,
|
|
283
|
+
} = useHeatThermostat()
|
|
284
|
+
|
|
285
|
+
const {
|
|
286
|
+
mutate: coolThermostat,
|
|
287
|
+
isSuccess: isCoolSuccess,
|
|
288
|
+
isError: isCoolError,
|
|
289
|
+
} = useCoolThermostat()
|
|
290
|
+
|
|
291
|
+
const {
|
|
292
|
+
mutate: setThermostatOff,
|
|
293
|
+
isSuccess: isSetOffSuccess,
|
|
294
|
+
isError: isSetOffError,
|
|
295
|
+
} = useSetThermostatOff()
|
|
296
|
+
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
const handler = debounce(() => {
|
|
299
|
+
switch (mode) {
|
|
300
|
+
case 'heat_cool':
|
|
301
|
+
heatCoolThermostat({
|
|
302
|
+
device_id: device.device_id,
|
|
303
|
+
heating_set_point_fahrenheit: heatValue,
|
|
304
|
+
cooling_set_point_fahrenheit: coolValue,
|
|
305
|
+
})
|
|
306
|
+
break
|
|
307
|
+
case 'heat':
|
|
308
|
+
heatThermostat({
|
|
309
|
+
device_id: device.device_id,
|
|
310
|
+
heating_set_point_fahrenheit: heatValue,
|
|
311
|
+
})
|
|
312
|
+
break
|
|
313
|
+
case 'cool':
|
|
314
|
+
coolThermostat({
|
|
315
|
+
device_id: device.device_id,
|
|
316
|
+
cooling_set_point_fahrenheit: coolValue,
|
|
317
|
+
})
|
|
318
|
+
break
|
|
319
|
+
case 'off':
|
|
320
|
+
setThermostatOff({
|
|
321
|
+
device_id: device.device_id,
|
|
322
|
+
})
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
}, 2000)
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
heatValue !== deviceHeatValue ||
|
|
329
|
+
coolValue !== deviceCoolValue ||
|
|
330
|
+
mode === 'off'
|
|
331
|
+
) {
|
|
332
|
+
handler()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return () => {
|
|
336
|
+
handler.cancel()
|
|
337
|
+
}
|
|
338
|
+
}, [
|
|
339
|
+
heatValue,
|
|
340
|
+
coolValue,
|
|
341
|
+
mode,
|
|
342
|
+
deviceHeatValue,
|
|
343
|
+
deviceCoolValue,
|
|
344
|
+
device,
|
|
345
|
+
heatThermostat,
|
|
346
|
+
coolThermostat,
|
|
347
|
+
heatCoolThermostat,
|
|
348
|
+
setThermostatOff,
|
|
349
|
+
])
|
|
350
|
+
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (
|
|
353
|
+
isHeatCoolSuccess ||
|
|
354
|
+
isHeatSuccess ||
|
|
355
|
+
isCoolSuccess ||
|
|
356
|
+
isSetOffSuccess
|
|
357
|
+
) {
|
|
358
|
+
setShowSuccess(true)
|
|
359
|
+
|
|
360
|
+
const timeout = globalThis.setTimeout(() => {
|
|
361
|
+
setShowSuccess(false)
|
|
362
|
+
}, 3000)
|
|
363
|
+
|
|
364
|
+
return () => {
|
|
365
|
+
globalThis.clearTimeout(timeout)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return () => {}
|
|
370
|
+
}, [isHeatCoolSuccess, isHeatSuccess, isCoolSuccess, isSetOffSuccess])
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<>
|
|
374
|
+
<AccordionRow
|
|
375
|
+
label={t.climate}
|
|
376
|
+
leftContent={
|
|
377
|
+
<div
|
|
378
|
+
className={classNames('seam-thermostat-mutation-status', {
|
|
379
|
+
'is-visible': showSuccess,
|
|
380
|
+
})}
|
|
381
|
+
>
|
|
382
|
+
<div className='seam-thermostat-mutation-status-icon'>
|
|
383
|
+
<CheckBlackIcon />
|
|
384
|
+
</div>
|
|
385
|
+
<div className='seam-thermostat-mutation-status-label'>
|
|
386
|
+
{t.saved}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
}
|
|
390
|
+
rightCollapsedContent={
|
|
391
|
+
<ClimateSettingStatus
|
|
392
|
+
climateSetting={device.properties.current_climate_setting}
|
|
393
|
+
temperatureUnit='fahrenheit'
|
|
394
|
+
/>
|
|
395
|
+
}
|
|
396
|
+
>
|
|
397
|
+
<div className='seam-detail-row-end-alignment'>
|
|
398
|
+
{mode !== 'off' && (
|
|
399
|
+
<TemperatureControlGroup
|
|
400
|
+
mode={mode}
|
|
401
|
+
heatValue={heatValue}
|
|
402
|
+
coolValue={coolValue}
|
|
403
|
+
onHeatValueChange={setHeatValue}
|
|
404
|
+
onCoolValueChange={setCoolValue}
|
|
405
|
+
delta={
|
|
406
|
+
Number(
|
|
407
|
+
'min_heating_cooling_delta_fahrenheit' in device.properties &&
|
|
408
|
+
device.properties.min_heating_cooling_delta_fahrenheit
|
|
409
|
+
) ?? 0
|
|
410
|
+
}
|
|
411
|
+
/>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
<ClimateModeMenu
|
|
415
|
+
mode={mode}
|
|
416
|
+
onChange={setMode}
|
|
417
|
+
supportedModes={supportedModes}
|
|
418
|
+
/>
|
|
419
|
+
</div>
|
|
420
|
+
</AccordionRow>
|
|
421
|
+
|
|
422
|
+
<Snackbar
|
|
423
|
+
message={t.climateSettingError}
|
|
424
|
+
variant='error'
|
|
425
|
+
visible={isHeatCoolError || isHeatError || isCoolError || isSetOffError}
|
|
426
|
+
automaticVisibility
|
|
427
|
+
/>
|
|
428
|
+
</>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
237
432
|
const t = {
|
|
238
433
|
thermostat: 'Thermostat',
|
|
239
434
|
climateSchedule: 'scheduled climate',
|
|
@@ -261,4 +456,6 @@ const t = {
|
|
|
261
456
|
fanModeError: 'Error updating fan mode. Please try again.',
|
|
262
457
|
manualOverrideSuccess: 'Successfully updated manual override!',
|
|
263
458
|
manualOverrideError: 'Error updating manual override. Please try again.',
|
|
459
|
+
climateSettingError: 'Error updating climate setting. Please try again.',
|
|
460
|
+
saved: 'Saved',
|
|
264
461
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DeviceModel } from '@seamapi/types/devicedb'
|
|
2
2
|
import { useMemo } from 'react'
|
|
3
3
|
|
|
4
4
|
import { SupportedDeviceManufacturerSection } from 'lib/seam/components/SupportedDeviceTable/SupportedDeviceManufacturerSection.js'
|
|
@@ -141,8 +141,8 @@ function EmptyResult({
|
|
|
141
141
|
|
|
142
142
|
const groupDeviceModelsByManufacturer = (
|
|
143
143
|
deviceModels: UseDeviceModelsData
|
|
144
|
-
): Record<string,
|
|
145
|
-
const result: Record<string,
|
|
144
|
+
): Record<string, DeviceModel[]> => {
|
|
145
|
+
const result: Record<string, DeviceModel[]> = {}
|
|
146
146
|
|
|
147
147
|
for (const model of deviceModels) {
|
|
148
148
|
const { manufacturer } = model
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
DeviceModel,
|
|
3
3
|
ManufacturerAnnotation,
|
|
4
4
|
} from '@seamapi/types/devicedb'
|
|
5
5
|
import classNames from 'classnames'
|
|
@@ -20,7 +20,7 @@ const maxDevicesBeforeCollapsing = 3
|
|
|
20
20
|
|
|
21
21
|
interface SupportedDeviceManufacturerSectionProps {
|
|
22
22
|
manufacturerId: string
|
|
23
|
-
deviceModels:
|
|
23
|
+
deviceModels: DeviceModel[]
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function SupportedDeviceManufacturerSection({
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DeviceModel } from '@seamapi/types/devicedb'
|
|
2
2
|
import classNames from 'classnames'
|
|
3
|
-
import type { DeviceModel } from 'seamapi'
|
|
4
3
|
|
|
5
4
|
import { DotDivider } from 'lib/ui/layout/DotDivider.js'
|
|
6
5
|
|
|
7
6
|
interface SupportedDeviceRowProps {
|
|
8
|
-
deviceModel:
|
|
7
|
+
deviceModel: DeviceModel
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export function SupportedDeviceRow({
|
|
@@ -75,7 +74,7 @@ export function StatusColumn({
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
const supportLevelColors: Record<
|
|
78
|
-
|
|
77
|
+
DeviceModel['manufacturer']['integration'],
|
|
79
78
|
'green' | 'blue' | 'unknown'
|
|
80
79
|
> = {
|
|
81
80
|
stable: 'green',
|
|
@@ -85,20 +84,10 @@ const supportLevelColors: Record<
|
|
|
85
84
|
inquire: 'unknown',
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
const status: Record<
|
|
87
|
+
const status: Record<DeviceModel['manufacturer']['integration'], string> = {
|
|
89
88
|
stable: 'LIVE',
|
|
90
89
|
beta: 'BETA',
|
|
91
90
|
unsupported: 'Inquire',
|
|
92
91
|
planned: 'Inquire',
|
|
93
92
|
inquire: 'Inquire',
|
|
94
93
|
}
|
|
95
|
-
|
|
96
|
-
export const connectionTypeNames: Record<
|
|
97
|
-
DeviceModel['connection_type'],
|
|
98
|
-
string
|
|
99
|
-
> = {
|
|
100
|
-
wifi: 'Wifi',
|
|
101
|
-
zwave: 'Z-Wave',
|
|
102
|
-
zigbee: 'Zigbee',
|
|
103
|
-
unknown: 'Unknown',
|
|
104
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
DeviceModel,
|
|
3
3
|
RouteRequestParams,
|
|
4
4
|
RouteResponse,
|
|
5
5
|
} from '@seamapi/types/devicedb'
|
|
@@ -10,7 +10,7 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js'
|
|
|
10
10
|
import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
|
|
11
11
|
|
|
12
12
|
export type UseDeviceModelParams = DeviceModelsGetParams | string
|
|
13
|
-
export type UseDeviceModelData =
|
|
13
|
+
export type UseDeviceModelData = DeviceModel | null
|
|
14
14
|
|
|
15
15
|
export function useDeviceModel(
|
|
16
16
|
params?: UseDeviceModelParams
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
DeviceModel,
|
|
3
3
|
RouteRequestParams,
|
|
4
4
|
RouteResponse,
|
|
5
5
|
} from '@seamapi/types/devicedb'
|
|
@@ -10,7 +10,7 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js'
|
|
|
10
10
|
import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'
|
|
11
11
|
|
|
12
12
|
export type UseDeviceModelsParams = DeviceModelsListParams
|
|
13
|
-
export type UseDeviceModelsData =
|
|
13
|
+
export type UseDeviceModelsData = DeviceModel[]
|
|
14
14
|
|
|
15
15
|
export function useDeviceModels(
|
|
16
16
|
params?: UseDeviceModelsParams
|
|
@@ -77,13 +77,12 @@ export const useFilteredDeviceModels = ({
|
|
|
77
77
|
// UPSTREAM: API does not parse zero-length arrays correctly.
|
|
78
78
|
includeIf?.length === 0
|
|
79
79
|
? []
|
|
80
|
-
: deviceModels?.filter(
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
80
|
+
: deviceModels?.filter((deviceModel) =>
|
|
81
|
+
manufacturers?.some(
|
|
82
|
+
(manufacturer) =>
|
|
83
|
+
deviceModel.manufacturer.manufacturer_id ===
|
|
84
|
+
manufacturer.manufacturer_id
|
|
85
|
+
)
|
|
87
86
|
),
|
|
88
87
|
}
|
|
89
88
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HvacModeSetting } from 'seamapi'
|
|
1
|
+
import type { HvacModeSetting, ThermostatDevice } from 'seamapi'
|
|
2
2
|
|
|
3
3
|
export interface ControlBounds {
|
|
4
4
|
mode: Exclude<HvacModeSetting, 'off'>
|
|
@@ -51,3 +51,25 @@ export const getTemperatureBounds = (
|
|
|
51
51
|
heat: getHeatBounds(controlBounds),
|
|
52
52
|
cool: getCoolBounds(controlBounds),
|
|
53
53
|
})
|
|
54
|
+
|
|
55
|
+
export const getSupportedThermostatModes = (
|
|
56
|
+
device: ThermostatDevice
|
|
57
|
+
): HvacModeSetting[] => {
|
|
58
|
+
const allModes: HvacModeSetting[] = ['heat', 'cool', 'heat_cool', 'off']
|
|
59
|
+
|
|
60
|
+
return allModes.filter((mode) => {
|
|
61
|
+
switch (mode) {
|
|
62
|
+
case 'cool':
|
|
63
|
+
return device.properties.is_cooling_available
|
|
64
|
+
case 'heat':
|
|
65
|
+
return device.properties.is_heating_available
|
|
66
|
+
case 'heat_cool':
|
|
67
|
+
return (
|
|
68
|
+
device.properties.is_heating_available &&
|
|
69
|
+
device.properties.is_cooling_available
|
|
70
|
+
)
|
|
71
|
+
default:
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -5,11 +5,13 @@ import { useToggle } from 'lib/ui/use-toggle.js'
|
|
|
5
5
|
|
|
6
6
|
interface AccordionRowProps extends PropsWithChildren {
|
|
7
7
|
label: string
|
|
8
|
+
leftContent?: JSX.Element
|
|
8
9
|
rightCollapsedContent?: JSX.Element
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function AccordionRow({
|
|
12
13
|
label,
|
|
14
|
+
leftContent,
|
|
13
15
|
rightCollapsedContent,
|
|
14
16
|
children,
|
|
15
17
|
}: AccordionRowProps): JSX.Element {
|
|
@@ -18,7 +20,10 @@ export function AccordionRow({
|
|
|
18
20
|
return (
|
|
19
21
|
<div className='seam-accordion-row' aria-expanded={isExpanded}>
|
|
20
22
|
<button className='seam-accordion-row-trigger' onClick={toggle}>
|
|
21
|
-
<
|
|
23
|
+
<div className='seam-row-inner-wrap'>
|
|
24
|
+
<p className='seam-row-label'>{label}</p>
|
|
25
|
+
<div className='seam-row-trigger-left-content'>{leftContent}</div>
|
|
26
|
+
</div>
|
|
22
27
|
<div className='seam-row-inner-wrap'>
|
|
23
28
|
<div className='seam-row-trigger-right-content'>
|
|
24
29
|
{rightCollapsedContent}
|
|
@@ -8,16 +8,16 @@ import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
|
|
|
8
8
|
import { Menu } from 'lib/ui/Menu/Menu.js'
|
|
9
9
|
import { ThermoModeMenuOption } from 'lib/ui/thermostat/ThermoModeMenuOption.js'
|
|
10
10
|
|
|
11
|
-
const modes: HvacModeSetting[] = ['heat', 'cool', 'heat_cool', 'off']
|
|
12
|
-
|
|
13
11
|
interface ClimateModeMenuProps {
|
|
14
12
|
mode: HvacModeSetting
|
|
15
13
|
onChange: (mode: HvacModeSetting) => void
|
|
14
|
+
supportedModes?: HvacModeSetting[]
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export function ClimateModeMenu({
|
|
19
18
|
mode,
|
|
20
19
|
onChange,
|
|
20
|
+
supportedModes = ['heat', 'cool', 'heat_cool', 'off'],
|
|
21
21
|
}: ClimateModeMenuProps): JSX.Element {
|
|
22
22
|
return (
|
|
23
23
|
<Menu
|
|
@@ -35,7 +35,7 @@ export function ClimateModeMenu({
|
|
|
35
35
|
className: 'seam-thermo-mode-menu',
|
|
36
36
|
}}
|
|
37
37
|
>
|
|
38
|
-
{
|
|
38
|
+
{supportedModes.map((m) => (
|
|
39
39
|
<ThermoModeMenuOption
|
|
40
40
|
key={m}
|
|
41
41
|
label={t[m]}
|
package/src/lib/version.ts
CHANGED
package/src/styles/_layout.scss
CHANGED
|
@@ -126,6 +126,16 @@
|
|
|
126
126
|
gap: 8px;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
.seam-detail-row-end-alignment {
|
|
130
|
+
width: 100%;
|
|
131
|
+
display: flex;
|
|
132
|
+
justify-content: flex-end;
|
|
133
|
+
align-items: flex-start;
|
|
134
|
+
flex-direction: row;
|
|
135
|
+
gap: 24px;
|
|
136
|
+
padding-top: 12px;
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
.seam-detail-row-rotated-icon {
|
|
130
140
|
transform: rotate(-90deg);
|
|
131
141
|
}
|
|
@@ -168,7 +178,7 @@
|
|
|
168
178
|
|
|
169
179
|
&[aria-expanded='true'] {
|
|
170
180
|
.seam-accordion-row-content {
|
|
171
|
-
max-height:
|
|
181
|
+
max-height: 128px;
|
|
172
182
|
opacity: 1;
|
|
173
183
|
}
|
|
174
184
|
|
|
@@ -183,7 +193,8 @@
|
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
.seam-accordion-row-inner-content {
|
|
186
|
-
|
|
196
|
+
width: 100%;
|
|
197
|
+
height: 128px;
|
|
187
198
|
padding: 0 16px;
|
|
188
199
|
overflow: hidden;
|
|
189
200
|
}
|
|
@@ -703,6 +703,31 @@
|
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
@mixin status {
|
|
707
|
+
.seam-thermostat-mutation-status {
|
|
708
|
+
display: flex;
|
|
709
|
+
justify-content: center;
|
|
710
|
+
align-items: center;
|
|
711
|
+
flex-direction: row;
|
|
712
|
+
gap: 4px;
|
|
713
|
+
opacity: 0;
|
|
714
|
+
transition: 0.2s ease-in-out;
|
|
715
|
+
|
|
716
|
+
&.is-visible {
|
|
717
|
+
opacity: 1;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.seam-thermostat-mutation-status-icon {
|
|
722
|
+
opacity: 0.25;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.seam-thermostat-mutation-status-label {
|
|
726
|
+
font-size: 14px;
|
|
727
|
+
color: colors.$text-gray-2-5;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
706
731
|
@mixin all {
|
|
707
732
|
@include climate-setting-control-group;
|
|
708
733
|
@include temperature-control-group;
|
|
@@ -712,4 +737,5 @@
|
|
|
712
737
|
@include mode-menu-common;
|
|
713
738
|
@include fan-mode-menu;
|
|
714
739
|
@include climate-mode-menu;
|
|
740
|
+
@include status;
|
|
715
741
|
}
|