@seamapi/react 1.61.0 → 1.61.2
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 +3679 -3727
- package/dist/elements.js.map +1 -1
- package/dist/index.css +134 -25
- package/dist/index.css.map +1 -1
- package/dist/index.min.css +1 -1
- package/dist/index.min.css.map +1 -1
- package/lib/dates.d.ts +6 -67
- package/lib/dates.js +13 -111
- package/lib/dates.js.map +1 -1
- package/lib/icons/CheckGreen.d.ts +2 -0
- package/lib/icons/CheckGreen.js +7 -0
- package/lib/icons/CheckGreen.js.map +1 -0
- package/lib/icons/CloseWhite.d.ts +2 -0
- package/lib/icons/CloseWhite.js +7 -0
- package/lib/icons/CloseWhite.js.map +1 -0
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +14 -20
- package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
- package/lib/seam/components/AccessCodeTable/CodeDetails.js +4 -6
- package/lib/seam/components/AccessCodeTable/CodeDetails.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js +8 -8
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js +2 -2
- package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js.map +1 -1
- package/lib/seam/components/ClimateSettingScheduleDetails/dates.d.ts +1 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/dates.js +9 -0
- package/lib/seam/components/ClimateSettingScheduleDetails/dates.js.map +1 -0
- package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js +4 -6
- package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js.map +1 -1
- package/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.js +3 -4
- package/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.js.map +1 -1
- package/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.js +3 -4
- package/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.js.map +1 -1
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js +1 -2
- package/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.js.map +1 -1
- package/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js +3 -4
- package/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js.map +1 -1
- package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js +12 -9
- package/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.js.map +1 -1
- package/lib/ui/AccessCodeForm/AccessCodeForm.d.ts +1 -1
- package/lib/ui/AccessCodeForm/AccessCodeForm.js +45 -46
- package/lib/ui/AccessCodeForm/AccessCodeForm.js.map +1 -1
- package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.d.ts +8 -7
- package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js +8 -4
- package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js.map +1 -1
- package/lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.d.ts +8 -0
- package/lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.js +17 -0
- package/lib/ui/AccessCodeForm/{AccessCodeFormTimezonePicker.js.map → AccessCodeFormTimeZonePicker.js.map} +1 -1
- package/lib/ui/AccessCodeForm/AccessCodeFormTimes.d.ts +3 -2
- package/lib/ui/AccessCodeForm/AccessCodeFormTimes.js +3 -2
- package/lib/ui/AccessCodeForm/AccessCodeFormTimes.js.map +1 -1
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.d.ts +2 -2
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js +9 -9
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js.map +1 -1
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.d.ts +3 -3
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js +4 -4
- package/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js.map +1 -1
- package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.d.ts → ClimateSettingScheduleFormTimeZonePicker.d.ts} +2 -2
- package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.js → ClimateSettingScheduleFormTimeZonePicker.js} +5 -5
- package/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.js.map → ClimateSettingScheduleFormTimeZonePicker.js.map} +1 -1
- package/lib/ui/LoadingToast/LoadingToast.js +12 -14
- package/lib/ui/LoadingToast/LoadingToast.js.map +1 -1
- package/lib/ui/Menu/Menu.js +32 -25
- package/lib/ui/Menu/Menu.js.map +1 -1
- package/lib/ui/Snackbar/Snackbar.d.ts +16 -0
- package/lib/ui/Snackbar/Snackbar.js +38 -0
- package/lib/ui/Snackbar/Snackbar.js.map +1 -0
- package/lib/ui/TimeZonePicker/TimeZonePicker.d.ts +8 -0
- package/lib/ui/TimeZonePicker/TimeZonePicker.js +28 -0
- package/lib/ui/TimeZonePicker/TimeZonePicker.js.map +1 -0
- package/lib/ui/device/BatteryStatus.js +10 -6
- package/lib/ui/device/BatteryStatus.js.map +1 -1
- package/lib/ui/thermostat/ClimateModeMenu.js +4 -4
- package/lib/ui/thermostat/ClimateModeMenu.js.map +1 -1
- package/lib/ui/use-now.d.ts +2 -0
- package/lib/ui/{use-current-time.js → use-now.js} +2 -2
- package/lib/ui/use-now.js.map +1 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
- package/src/lib/dates.ts +19 -135
- package/src/lib/icons/CheckGreen.tsx +36 -0
- package/src/lib/icons/CloseWhite.tsx +36 -0
- package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +6 -9
- package/src/lib/seam/components/AccessCodeTable/CodeDetails.tsx +2 -3
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +9 -9
- package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx +5 -8
- package/src/lib/seam/components/ClimateSettingScheduleDetails/dates.ts +10 -0
- package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.tsx +2 -3
- package/src/lib/seam/components/CreateAccessCodeForm/CreateAccessCodeForm.tsx +3 -4
- package/src/lib/seam/components/CreateClimateSettingScheduleForm/CreateClimateSettingScheduleForm.tsx +3 -5
- package/src/lib/seam/components/DeviceDetails/ThermostatDeviceDetails.tsx +1 -6
- package/src/lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.tsx +3 -4
- package/src/lib/seam/components/SupportedDeviceTable/FilterCategoryMenu.tsx +20 -15
- package/src/lib/telemetry/client.ts +1 -0
- package/src/lib/ui/AccessCodeForm/AccessCodeForm.tsx +61 -67
- package/src/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.tsx +28 -18
- package/src/lib/ui/AccessCodeForm/{AccessCodeFormTimezonePicker.tsx → AccessCodeFormTimeZonePicker.tsx} +10 -10
- package/src/lib/ui/AccessCodeForm/AccessCodeFormTimes.tsx +9 -5
- package/src/lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.tsx +12 -12
- package/src/lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.tsx +10 -10
- package/src/lib/ui/ClimateSettingForm/{ClimateSettingScheduleFormTimezonePicker.tsx → ClimateSettingScheduleFormTimeZonePicker.tsx} +8 -8
- package/src/lib/ui/LoadingToast/LoadingToast.tsx +13 -16
- package/src/lib/ui/Menu/Menu.tsx +50 -40
- package/src/lib/ui/Snackbar/Snackbar.tsx +97 -0
- package/src/lib/ui/TimeZonePicker/TimeZonePicker.tsx +69 -0
- package/src/lib/ui/device/BatteryStatus.tsx +23 -14
- package/src/lib/ui/thermostat/ClimateModeMenu.tsx +4 -4
- package/src/lib/ui/{use-current-time.ts → use-now.ts} +1 -1
- package/src/lib/version.ts +1 -1
- package/src/styles/_access-code-form.scss +4 -4
- package/src/styles/_climate-setting-schedule-form.scss +1 -1
- package/src/styles/_colors.scss +2 -0
- package/src/styles/_loading_toast.scss +5 -16
- package/src/styles/_main.scss +4 -2
- package/src/styles/_motion.scss +34 -0
- package/src/styles/_snackbar.scss +107 -0
- package/src/styles/_supported-device-table.scss +9 -0
- package/src/styles/{_timezone-picker.scss → _time-zone-picker.scss} +3 -3
- package/lib/ui/AccessCodeForm/AccessCodeFormTimezonePicker.d.ts +0 -8
- package/lib/ui/AccessCodeForm/AccessCodeFormTimezonePicker.js +0 -17
- package/lib/ui/TimezonePicker/TimezonePicker.d.ts +0 -8
- package/lib/ui/TimezonePicker/TimezonePicker.js +0 -28
- package/lib/ui/TimezonePicker/TimezonePicker.js.map +0 -1
- package/lib/ui/use-current-time.d.ts +0 -2
- package/lib/ui/use-current-time.js.map +0 -1
- package/src/lib/ui/TimezonePicker/TimezonePicker.tsx +0 -70
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
|
+
import { DateTime } from 'luxon'
|
|
2
3
|
import { useState } from 'react'
|
|
3
4
|
import { useForm } from 'react-hook-form'
|
|
4
5
|
import { type AccessCode, type CommonDevice, isLockDevice } from 'seamapi'
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
get24HoursLater,
|
|
8
|
-
getBrowserTimezone,
|
|
9
|
-
getNow,
|
|
10
|
-
getTimezoneFromIsoDate,
|
|
11
|
-
} from 'lib/dates.js'
|
|
7
|
+
import { getSystemTimeZone } from 'lib/dates.js'
|
|
12
8
|
import type { UseAccessCodeData } from 'lib/seam/access-codes/use-access-code.js'
|
|
13
9
|
import { useGenerateAccessCodeCode } from 'lib/seam/access-codes/use-generate-access-code-code.js'
|
|
14
10
|
import type { UseDeviceData } from 'lib/seam/devices/use-device.js'
|
|
15
11
|
import { AccessCodeFormDatePicker } from 'lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js'
|
|
16
12
|
import { AccessCodeFormTimes } from 'lib/ui/AccessCodeForm/AccessCodeFormTimes.js'
|
|
17
|
-
import {
|
|
13
|
+
import { AccessCodeFormTimeZonePicker } from 'lib/ui/AccessCodeForm/AccessCodeFormTimeZonePicker.js'
|
|
18
14
|
import { Button } from 'lib/ui/Button.js'
|
|
19
15
|
import { FormField } from 'lib/ui/FormField.js'
|
|
20
16
|
import { InputLabel } from 'lib/ui/InputLabel.js'
|
|
@@ -30,7 +26,7 @@ export interface AccessCodeFormSubmitData {
|
|
|
30
26
|
device: NonNullable<UseDeviceData>
|
|
31
27
|
startDate: string
|
|
32
28
|
endDate: string
|
|
33
|
-
|
|
29
|
+
timeZone: string
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
export interface ResponseErrors {
|
|
@@ -71,14 +67,13 @@ function Content({
|
|
|
71
67
|
accessCode?.type ?? 'ongoing'
|
|
72
68
|
)
|
|
73
69
|
const [datePickerVisible, setDatePickerVisible] = useState(false)
|
|
74
|
-
const [
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
getAccessCodeDate('starts_at', accessCode) ?? getNow()
|
|
70
|
+
const [timeZone, setTimeZone] = useState<string>(getSystemTimeZone())
|
|
71
|
+
|
|
72
|
+
const [startDate, setStartDate] = useState<DateTime>(
|
|
73
|
+
getAccessCodeDate('starts_at', accessCode) ?? getNow(timeZone)
|
|
79
74
|
)
|
|
80
|
-
const [endDate, setEndDate] = useState<
|
|
81
|
-
getAccessCodeDate('ends_at', accessCode) ??
|
|
75
|
+
const [endDate, setEndDate] = useState<DateTime>(
|
|
76
|
+
getAccessCodeDate('ends_at', accessCode) ?? getOneDayFromNow(timeZone)
|
|
82
77
|
)
|
|
83
78
|
|
|
84
79
|
const save = (data: { name: string; code: string }): void => {
|
|
@@ -86,14 +81,24 @@ function Content({
|
|
|
86
81
|
return
|
|
87
82
|
}
|
|
88
83
|
|
|
84
|
+
const start = startDate.toISO()
|
|
85
|
+
if (start == null) {
|
|
86
|
+
throw new Error(`Invalid start date: ${startDate.invalidReason}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const end = endDate.toISO()
|
|
90
|
+
if (end == null) {
|
|
91
|
+
throw new Error(`Invalid end date: ${endDate.invalidReason}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
onSubmit({
|
|
90
95
|
name: data.name,
|
|
91
96
|
code: data.code,
|
|
92
97
|
type,
|
|
93
98
|
device,
|
|
94
|
-
startDate,
|
|
95
|
-
endDate,
|
|
96
|
-
|
|
99
|
+
startDate: start,
|
|
100
|
+
endDate: end,
|
|
101
|
+
timeZone,
|
|
97
102
|
})
|
|
98
103
|
}
|
|
99
104
|
|
|
@@ -108,19 +113,23 @@ function Content({
|
|
|
108
113
|
code: accessCode?.code ?? '',
|
|
109
114
|
},
|
|
110
115
|
})
|
|
111
|
-
const [
|
|
116
|
+
const [timeZonePickerVisible, toggleTimeZonePicker] = useToggle()
|
|
112
117
|
|
|
113
118
|
const { isLoading: isGeneratingCode, mutate: generateCode } =
|
|
114
119
|
useGenerateAccessCodeCode()
|
|
115
120
|
|
|
116
121
|
const submit = (): void => {}
|
|
117
122
|
|
|
118
|
-
if (
|
|
123
|
+
if (timeZonePickerVisible) {
|
|
119
124
|
return (
|
|
120
|
-
<
|
|
121
|
-
value={
|
|
122
|
-
onChange={
|
|
123
|
-
|
|
125
|
+
<AccessCodeFormTimeZonePicker
|
|
126
|
+
value={timeZone}
|
|
127
|
+
onChange={(timeZone: string) => {
|
|
128
|
+
setTimeZone(timeZone)
|
|
129
|
+
setStartDate(startDate.setZone(timeZone))
|
|
130
|
+
setEndDate(endDate.setZone(timeZone))
|
|
131
|
+
}}
|
|
132
|
+
onClose={toggleTimeZonePicker}
|
|
124
133
|
/>
|
|
125
134
|
)
|
|
126
135
|
}
|
|
@@ -132,8 +141,8 @@ function Content({
|
|
|
132
141
|
setStartDate={setStartDate}
|
|
133
142
|
endDate={endDate}
|
|
134
143
|
setEndDate={setEndDate}
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
timeZone={timeZone}
|
|
145
|
+
onChangeTimeZone={toggleTimeZonePicker}
|
|
137
146
|
onBack={() => {
|
|
138
147
|
setDatePickerVisible(false)
|
|
139
148
|
}}
|
|
@@ -158,24 +167,6 @@ function Content({
|
|
|
158
167
|
)
|
|
159
168
|
}
|
|
160
169
|
|
|
161
|
-
const validateCodeLength = (value: string): boolean | string => {
|
|
162
|
-
if (!isLockDevice(device)) {
|
|
163
|
-
return true
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (device.properties.supported_code_lengths == null) {
|
|
167
|
-
return true
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (device.properties.supported_code_lengths.includes(value.length)) {
|
|
171
|
-
return true
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return t.codeLengthError(
|
|
175
|
-
device.properties.supported_code_lengths.join(', ')
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
170
|
const hasCodeError = errors.code != null || responseErrors?.code != null
|
|
180
171
|
|
|
181
172
|
const codeLengthRequirement = getCodeLengthRequirement(device)
|
|
@@ -226,7 +217,8 @@ function Content({
|
|
|
226
217
|
inputProps={{
|
|
227
218
|
...register('code', {
|
|
228
219
|
required: t.codeRequiredError,
|
|
229
|
-
validate:
|
|
220
|
+
validate: (value: string) =>
|
|
221
|
+
validateCodeLength(device, value),
|
|
230
222
|
}),
|
|
231
223
|
}}
|
|
232
224
|
/>
|
|
@@ -306,28 +298,26 @@ function Content({
|
|
|
306
298
|
)
|
|
307
299
|
}
|
|
308
300
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
301
|
+
const validateCodeLength = (
|
|
302
|
+
device: CommonDevice,
|
|
303
|
+
value: string
|
|
304
|
+
): boolean | string => {
|
|
305
|
+
if (!isLockDevice(device)) {
|
|
306
|
+
return true
|
|
314
307
|
}
|
|
315
308
|
|
|
316
|
-
if (
|
|
317
|
-
return
|
|
309
|
+
if (device.properties.supported_code_lengths == null) {
|
|
310
|
+
return true
|
|
318
311
|
}
|
|
319
312
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const timezone = getTimezoneFromIsoDate(date)
|
|
323
|
-
if (timezone == null) {
|
|
324
|
-
return undefined
|
|
313
|
+
if (device.properties.supported_code_lengths.includes(value.length)) {
|
|
314
|
+
return true
|
|
325
315
|
}
|
|
326
316
|
|
|
327
|
-
return
|
|
317
|
+
return t.codeLengthError(device.properties.supported_code_lengths.join(', '))
|
|
328
318
|
}
|
|
329
319
|
|
|
330
|
-
|
|
320
|
+
const getCodeLengthRequirement = (device: CommonDevice): string | null => {
|
|
331
321
|
if (!isLockDevice(device)) {
|
|
332
322
|
return null
|
|
333
323
|
}
|
|
@@ -354,25 +344,29 @@ const sequentialNumbers = Array.from({ length: 100 }, (_, index) => index).join(
|
|
|
354
344
|
''
|
|
355
345
|
)
|
|
356
346
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
347
|
+
const isSequential = (numbers: string): boolean =>
|
|
348
|
+
sequentialNumbers.includes(numbers)
|
|
360
349
|
|
|
361
|
-
|
|
350
|
+
const getAccessCodeDate = (
|
|
362
351
|
date: 'starts_at' | 'ends_at',
|
|
363
352
|
accessCode?: NonNullable<UseAccessCodeData>
|
|
364
|
-
):
|
|
353
|
+
): DateTime | null => {
|
|
365
354
|
if (accessCode == null) {
|
|
366
|
-
return
|
|
355
|
+
return null
|
|
367
356
|
}
|
|
368
357
|
|
|
369
358
|
if (accessCode.type !== 'time_bound') {
|
|
370
|
-
return
|
|
359
|
+
return null
|
|
371
360
|
}
|
|
372
361
|
|
|
373
|
-
return accessCode[date]
|
|
362
|
+
return DateTime.fromISO(accessCode[date])
|
|
374
363
|
}
|
|
375
364
|
|
|
365
|
+
const getNow = (timeZone: string): DateTime => DateTime.now().setZone(timeZone)
|
|
366
|
+
|
|
367
|
+
const getOneDayFromNow = (timeZone: string): DateTime =>
|
|
368
|
+
DateTime.now().setZone(timeZone).plus({ days: 1 })
|
|
369
|
+
|
|
376
370
|
const t = {
|
|
377
371
|
addNewAccessCode: 'Add new access code',
|
|
378
372
|
editAccessCode: 'Edit access code',
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DateTime } from 'luxon'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatTimeZone,
|
|
5
|
+
parseDateTimePickerValue,
|
|
6
|
+
serializeDateTimePickerValue,
|
|
7
|
+
} from 'lib/dates.js'
|
|
2
8
|
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
|
|
3
9
|
import { DateTimePicker } from 'lib/ui/DateTimePicker/DateTimePicker.js'
|
|
4
10
|
import { FormField } from 'lib/ui/FormField.js'
|
|
@@ -6,48 +12,52 @@ import { InputLabel } from 'lib/ui/InputLabel.js'
|
|
|
6
12
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
7
13
|
|
|
8
14
|
interface AccessCodeFormDatePickerProps {
|
|
9
|
-
startDate:
|
|
10
|
-
setStartDate: (
|
|
11
|
-
endDate:
|
|
12
|
-
setEndDate: (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
startDate: DateTime
|
|
16
|
+
setStartDate: (dateTime: DateTime) => void
|
|
17
|
+
endDate: DateTime
|
|
18
|
+
setEndDate: (dateTime: DateTime) => void
|
|
19
|
+
timeZone: string
|
|
20
|
+
onChangeTimeZone: () => void
|
|
15
21
|
onBack: (() => void) | undefined
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export function AccessCodeFormDatePicker({
|
|
19
|
-
|
|
25
|
+
timeZone,
|
|
20
26
|
onBack,
|
|
21
27
|
startDate,
|
|
22
28
|
setStartDate,
|
|
23
29
|
endDate,
|
|
24
30
|
setEndDate,
|
|
25
|
-
|
|
31
|
+
onChangeTimeZone,
|
|
26
32
|
}: AccessCodeFormDatePickerProps): JSX.Element {
|
|
27
33
|
return (
|
|
28
34
|
<div className='seam-schedule-picker'>
|
|
29
35
|
<ContentHeader title={t.timingTitle} onBack={onBack} />
|
|
30
36
|
<div className='seam-content'>
|
|
31
|
-
<div className='seam-
|
|
32
|
-
<span className='seam-label'>{t.
|
|
33
|
-
<span className='seam-selected' onClick={
|
|
34
|
-
{
|
|
37
|
+
<div className='seam-time-zone'>
|
|
38
|
+
<span className='seam-label'>{t.selectedTimeZoneLabel}</span>
|
|
39
|
+
<span className='seam-selected' onClick={onChangeTimeZone}>
|
|
40
|
+
{formatTimeZone(timeZone)}
|
|
35
41
|
<ChevronRightIcon />
|
|
36
42
|
</span>
|
|
37
43
|
</div>
|
|
38
44
|
<FormField>
|
|
39
45
|
<InputLabel>{t.startTimeLabel}</InputLabel>
|
|
40
46
|
<DateTimePicker
|
|
41
|
-
value={startDate ?? ''}
|
|
42
|
-
onChange={
|
|
47
|
+
value={serializeDateTimePickerValue(startDate, timeZone) ?? ''}
|
|
48
|
+
onChange={(value: string) => {
|
|
49
|
+
setStartDate(parseDateTimePickerValue(value, timeZone))
|
|
50
|
+
}}
|
|
43
51
|
size='large'
|
|
44
52
|
/>
|
|
45
53
|
</FormField>
|
|
46
54
|
<FormField>
|
|
47
55
|
<InputLabel>{t.endTimeLabel}</InputLabel>
|
|
48
56
|
<DateTimePicker
|
|
49
|
-
value={endDate ?? ''}
|
|
50
|
-
onChange={
|
|
57
|
+
value={serializeDateTimePickerValue(endDate, timeZone) ?? ''}
|
|
58
|
+
onChange={(value: string) => {
|
|
59
|
+
setEndDate(parseDateTimePickerValue(value, timeZone))
|
|
60
|
+
}}
|
|
51
61
|
size='large'
|
|
52
62
|
/>
|
|
53
63
|
</FormField>
|
|
@@ -58,7 +68,7 @@ export function AccessCodeFormDatePicker({
|
|
|
58
68
|
|
|
59
69
|
const t = {
|
|
60
70
|
timingTitle: 'Timing',
|
|
61
|
-
|
|
71
|
+
selectedTimeZoneLabel: 'All times in',
|
|
62
72
|
startTimeLabel: 'Start',
|
|
63
73
|
endTimeLabel: 'End',
|
|
64
74
|
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
4
|
-
import {
|
|
4
|
+
import { TimeZonePicker } from 'lib/ui/TimeZonePicker/TimeZonePicker.js'
|
|
5
5
|
|
|
6
|
-
interface
|
|
6
|
+
interface AccessCodeFormTimeZonePickerProps {
|
|
7
7
|
value: string
|
|
8
|
-
onChange: (
|
|
8
|
+
onChange: (timeZone: string) => void
|
|
9
9
|
onClose: () => void
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function
|
|
12
|
+
export function AccessCodeFormTimeZonePicker({
|
|
13
13
|
onChange,
|
|
14
14
|
value,
|
|
15
15
|
onClose,
|
|
16
|
-
}:
|
|
16
|
+
}: AccessCodeFormTimeZonePickerProps): JSX.Element {
|
|
17
17
|
const [title, setTitle] = useState(t.titleAuto)
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<div className='seam-access-code-
|
|
20
|
+
<div className='seam-access-code-time-zone-picker'>
|
|
21
21
|
<ContentHeader title={title} onBack={onClose} />
|
|
22
22
|
<div className='seam-content'>
|
|
23
|
-
<
|
|
23
|
+
<TimeZonePicker
|
|
24
24
|
value={value}
|
|
25
25
|
onChange={onChange}
|
|
26
|
-
|
|
27
|
-
setTitle(
|
|
26
|
+
onManualTimeZoneSelected={(manualTimeZoneSelected) => {
|
|
27
|
+
setTitle(manualTimeZoneSelected ? t.titleManual : t.titleAuto)
|
|
28
28
|
}}
|
|
29
29
|
/>
|
|
30
30
|
</div>
|
|
@@ -36,5 +36,5 @@ const t = {
|
|
|
36
36
|
titleAuto: 'Time Zone (automatic)',
|
|
37
37
|
titleManual: 'Time Zone (manual)',
|
|
38
38
|
utc: 'UTC',
|
|
39
|
-
|
|
39
|
+
setTimeZoneManuallyLabel: 'Use local time zone',
|
|
40
40
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DateTime } from 'luxon'
|
|
2
|
+
|
|
2
3
|
import { EditIcon } from 'lib/icons/Edit.js'
|
|
3
4
|
import { IconButton } from 'lib/ui/IconButton.js'
|
|
4
5
|
|
|
5
6
|
interface AccessCodeFormTimesProps {
|
|
6
|
-
startDate:
|
|
7
|
-
endDate:
|
|
7
|
+
startDate: DateTime
|
|
8
|
+
endDate: DateTime
|
|
8
9
|
onEdit: () => void
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -17,9 +18,9 @@ export function AccessCodeFormTimes({
|
|
|
17
18
|
<div className='seam-selected-times'>
|
|
18
19
|
<div>
|
|
19
20
|
<span className='seam-label'>{t.startTimeLabel}</span>
|
|
20
|
-
<span className='seam-time'>{
|
|
21
|
+
<span className='seam-time'>{formatDateTime(startDate)}</span>
|
|
21
22
|
<span className='seam-label'>{t.endTimeLabel}</span>
|
|
22
|
-
<span className='seam-time'>{
|
|
23
|
+
<span className='seam-time'>{formatDateTime(endDate)}</span>
|
|
23
24
|
</div>
|
|
24
25
|
<IconButton onClick={onEdit} className='seam-show-date-picker-btn'>
|
|
25
26
|
<EditIcon />
|
|
@@ -28,6 +29,9 @@ export function AccessCodeFormTimes({
|
|
|
28
29
|
)
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
const formatDateTime = (dateTime: DateTime): string =>
|
|
33
|
+
dateTime.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)
|
|
34
|
+
|
|
31
35
|
const t = {
|
|
32
36
|
startTimeLabel: 'Start',
|
|
33
37
|
endTimeLabel: 'End',
|
|
@@ -3,17 +3,17 @@ import { useState } from 'react'
|
|
|
3
3
|
import { useForm } from 'react-hook-form'
|
|
4
4
|
import type { ClimateSetting } from 'seamapi'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getSystemTimeZone } from 'lib/dates.js'
|
|
7
7
|
import { ClimateSettingScheduleFormDeviceSelect } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormDeviceSelect.js'
|
|
8
8
|
import { ClimateSettingScheduleFormNameAndSchedule } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormNameAndSchedule.js'
|
|
9
|
-
import {
|
|
9
|
+
import { ClimateSettingScheduleFormTimeZonePicker } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleFormTimeZonePicker.js'
|
|
10
10
|
|
|
11
11
|
export interface ClimateSettingScheduleFormSubmitData {
|
|
12
12
|
name: string
|
|
13
13
|
deviceId: string
|
|
14
14
|
startDate: string
|
|
15
15
|
endDate: string
|
|
16
|
-
|
|
16
|
+
timeZone: string
|
|
17
17
|
climateSetting: ClimateSetting
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@ export interface ClimateSettingScheduleFormFields {
|
|
|
29
29
|
deviceId: string
|
|
30
30
|
startDate: string
|
|
31
31
|
endDate: string
|
|
32
|
-
|
|
32
|
+
timeZone: string
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function ClimateSettingScheduleForm({
|
|
@@ -54,18 +54,18 @@ function Content({
|
|
|
54
54
|
name: '',
|
|
55
55
|
startDate: '',
|
|
56
56
|
endDate: '',
|
|
57
|
-
|
|
57
|
+
timeZone: getSystemTimeZone(),
|
|
58
58
|
},
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
const deviceId = watch('deviceId')
|
|
62
|
-
const
|
|
62
|
+
const timeZone = watch('timeZone')
|
|
63
63
|
|
|
64
64
|
const [page, setPage] = useState<
|
|
65
65
|
| 'device_select'
|
|
66
66
|
| 'default_setting'
|
|
67
67
|
| 'name_and_schedule'
|
|
68
|
-
| '
|
|
68
|
+
| 'time_zone_select'
|
|
69
69
|
| 'climate_setting'
|
|
70
70
|
>('device_select')
|
|
71
71
|
|
|
@@ -95,17 +95,17 @@ function Content({
|
|
|
95
95
|
onNext={() => {
|
|
96
96
|
setPage('climate_setting')
|
|
97
97
|
}}
|
|
98
|
-
|
|
99
|
-
setPage('
|
|
98
|
+
onChangeTimeZone={() => {
|
|
99
|
+
setPage('time_zone_select')
|
|
100
100
|
}}
|
|
101
|
-
|
|
101
|
+
timeZone={timeZone}
|
|
102
102
|
/>
|
|
103
103
|
)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if (page === '
|
|
106
|
+
if (page === 'time_zone_select') {
|
|
107
107
|
return (
|
|
108
|
-
<
|
|
108
|
+
<ClimateSettingScheduleFormTimeZonePicker
|
|
109
109
|
control={control}
|
|
110
110
|
onClose={() => {
|
|
111
111
|
setPage('name_and_schedule')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Control, Controller } from 'react-hook-form'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { formatTimeZone } from 'lib/dates.js'
|
|
4
4
|
import { ChevronRightIcon } from 'lib/icons/ChevronRight.js'
|
|
5
5
|
import { useDevice } from 'lib/seam/devices/use-device.js'
|
|
6
6
|
import { Button } from 'lib/ui/Button.js'
|
|
@@ -15,22 +15,22 @@ interface ClimateSettingScheduleFormNameAndScheduleProps {
|
|
|
15
15
|
title: string
|
|
16
16
|
control: Control<ClimateSettingScheduleFormFields>
|
|
17
17
|
deviceId: string
|
|
18
|
-
|
|
18
|
+
timeZone: string
|
|
19
19
|
onBack: () => void
|
|
20
20
|
onCancel: (() => void) | undefined
|
|
21
21
|
onNext: () => void
|
|
22
|
-
|
|
22
|
+
onChangeTimeZone: () => void
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function ClimateSettingScheduleFormNameAndSchedule({
|
|
26
26
|
title,
|
|
27
27
|
control,
|
|
28
28
|
deviceId,
|
|
29
|
-
|
|
29
|
+
timeZone,
|
|
30
30
|
onBack,
|
|
31
31
|
onCancel,
|
|
32
32
|
onNext,
|
|
33
|
-
|
|
33
|
+
onChangeTimeZone,
|
|
34
34
|
}: ClimateSettingScheduleFormNameAndScheduleProps): JSX.Element {
|
|
35
35
|
const { device } = useDevice({
|
|
36
36
|
device_id: deviceId,
|
|
@@ -68,10 +68,10 @@ export function ClimateSettingScheduleFormNameAndSchedule({
|
|
|
68
68
|
|
|
69
69
|
<FormField>
|
|
70
70
|
<InputLabel>{t.startTimeLabel}</InputLabel>
|
|
71
|
-
<div className='seam-
|
|
72
|
-
<span className='seam-label'>{t.
|
|
73
|
-
<span className='seam-selected' onClick={
|
|
74
|
-
{
|
|
71
|
+
<div className='seam-time-zone'>
|
|
72
|
+
<span className='seam-label'>{t.selectedTimeZoneLabel}</span>
|
|
73
|
+
<span className='seam-selected' onClick={onChangeTimeZone}>
|
|
74
|
+
{formatTimeZone(timeZone)}
|
|
75
75
|
<ChevronRightIcon />
|
|
76
76
|
</span>
|
|
77
77
|
</div>
|
|
@@ -116,5 +116,5 @@ const t = {
|
|
|
116
116
|
cancel: 'Cancel',
|
|
117
117
|
save: 'Save',
|
|
118
118
|
next: 'Next',
|
|
119
|
-
|
|
119
|
+
selectedTimeZoneLabel: 'All times in',
|
|
120
120
|
}
|
|
@@ -3,17 +3,17 @@ import { type Control, Controller } from 'react-hook-form'
|
|
|
3
3
|
|
|
4
4
|
import type { ClimateSettingScheduleFormFields } from 'lib/ui/ClimateSettingForm/ClimateSettingScheduleForm.js'
|
|
5
5
|
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
|
|
6
|
-
import {
|
|
6
|
+
import { TimeZonePicker } from 'lib/ui/TimeZonePicker/TimeZonePicker.js'
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface ClimateSettingScheduleFormTimeZonePickerProps {
|
|
9
9
|
control: Control<ClimateSettingScheduleFormFields>
|
|
10
10
|
onClose: () => void
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function ClimateSettingScheduleFormTimeZonePicker({
|
|
14
14
|
control,
|
|
15
15
|
onClose,
|
|
16
|
-
}:
|
|
16
|
+
}: ClimateSettingScheduleFormTimeZonePickerProps): JSX.Element {
|
|
17
17
|
const [title, setTitle] = useState(t.titleAuto)
|
|
18
18
|
|
|
19
19
|
return (
|
|
@@ -21,13 +21,13 @@ export function ClimateSettingScheduleFormTimezonePicker({
|
|
|
21
21
|
<ContentHeader title={title} onBack={onClose} />
|
|
22
22
|
<div className='seam-main'>
|
|
23
23
|
<Controller
|
|
24
|
-
name='
|
|
24
|
+
name='timeZone'
|
|
25
25
|
control={control}
|
|
26
26
|
render={({ field }) => (
|
|
27
|
-
<
|
|
27
|
+
<TimeZonePicker
|
|
28
28
|
{...field}
|
|
29
|
-
|
|
30
|
-
setTitle(
|
|
29
|
+
onManualTimeZoneSelected={(manualTimeZoneSelected) => {
|
|
30
|
+
setTitle(manualTimeZoneSelected ? t.titleManual : t.titleAuto)
|
|
31
31
|
}}
|
|
32
32
|
/>
|
|
33
33
|
)}
|
|
@@ -21,26 +21,23 @@ export function LoadingToast({
|
|
|
21
21
|
const [showToast, setShowToast] = useState(isLoading)
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const removeTimeout = globalThis.setTimeout(() => {
|
|
30
|
-
setShowToast(false)
|
|
31
|
-
}, 1500)
|
|
32
|
-
|
|
33
|
-
return () => {
|
|
34
|
-
globalThis.clearTimeout(hideTimeout)
|
|
35
|
-
globalThis.clearTimeout(removeTimeout)
|
|
36
|
-
}
|
|
24
|
+
if (isLoading) {
|
|
25
|
+
setHidden(false)
|
|
26
|
+
setShowToast(true)
|
|
27
|
+
return () => {}
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const hideTimeout = globalThis.setTimeout(() => {
|
|
31
|
+
setHidden(true)
|
|
32
|
+
}, 1000)
|
|
33
|
+
|
|
34
|
+
const removeTimeout = globalThis.setTimeout(() => {
|
|
35
|
+
setShowToast(false)
|
|
36
|
+
}, 1500)
|
|
41
37
|
|
|
42
38
|
return () => {
|
|
43
|
-
|
|
39
|
+
globalThis.clearTimeout(hideTimeout)
|
|
40
|
+
globalThis.clearTimeout(removeTimeout)
|
|
44
41
|
}
|
|
45
42
|
}, [isLoading])
|
|
46
43
|
|