@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
package/src/lib/dates.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DateTime
|
|
1
|
+
import { DateTime } from 'luxon'
|
|
2
2
|
|
|
3
3
|
export const compareByCreatedAtDesc = (
|
|
4
4
|
a: { created_at: string },
|
|
@@ -10,148 +10,32 @@ export const compareByCreatedAtDesc = (
|
|
|
10
10
|
return t1.toMillis() - t2.toMillis()
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
18
|
-
export function getTimezones(): string[] {
|
|
19
|
-
return Intl.supportedValuesOf('timeZone')
|
|
13
|
+
export const getSupportedTimeZones = (): string[] => {
|
|
14
|
+
const timeZones = new Set(Intl.supportedValuesOf('timeZone'))
|
|
15
|
+
timeZones.add('UTC')
|
|
16
|
+
return Array.from(timeZones).sort()
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
* Get the default browser timezone.
|
|
24
|
-
*
|
|
25
|
-
* @returns string
|
|
26
|
-
*/
|
|
27
|
-
export function getBrowserTimezone(): string {
|
|
28
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Takes an IANA timezone, like America/Los_Angeles, into a more readable
|
|
33
|
-
* string: Los Angeles (America).
|
|
34
|
-
* @param timezone
|
|
35
|
-
* @returns string
|
|
36
|
-
*/
|
|
37
|
-
export function getTimezoneLabel(timezone: string): string {
|
|
38
|
-
const [region = '', city = ''] = timezone.replace(/_/g, ' ').split('/')
|
|
39
|
-
return `${city} (${region})`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get a timezones offset from UTC in minutes.
|
|
44
|
-
*
|
|
45
|
-
* @param timezone
|
|
46
|
-
* @returns minutes
|
|
47
|
-
*/
|
|
48
|
-
function getTimezoneOffsetMinutes(timezone: string): number {
|
|
49
|
-
return DateTime.local().setZone(timezone).offset
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Compares 2 timezones (America/Los_angeles) by their offset
|
|
54
|
-
* minutes in ascending order.
|
|
55
|
-
*
|
|
56
|
-
* @param timezoneA
|
|
57
|
-
* @param timezonB
|
|
58
|
-
* @returns number
|
|
59
|
-
*/
|
|
60
|
-
export const compareByTimezoneOffsetAsc = (
|
|
61
|
-
timezoneA: string,
|
|
62
|
-
timezonB: string
|
|
63
|
-
): number =>
|
|
64
|
-
getTimezoneOffsetMinutes(timezoneA) - getTimezoneOffsetMinutes(timezonB)
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get the timezone offset
|
|
68
|
-
* America/Los_angeles -> -07:00
|
|
69
|
-
*
|
|
70
|
-
* eg. America/Los_Angeles -> UTC-07:00
|
|
71
|
-
*
|
|
72
|
-
* @param timezone
|
|
73
|
-
* @returns offset
|
|
74
|
-
*/
|
|
75
|
-
export function getTimezoneOffset(timezone: string): string {
|
|
76
|
-
return IANAZone.create(timezone).formatOffset(Date.now(), 'short')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const formatDateReadable = (
|
|
80
|
-
date: string,
|
|
81
|
-
options: {
|
|
82
|
-
showWeekday?: boolean
|
|
83
|
-
} = {}
|
|
84
|
-
): string => {
|
|
85
|
-
const { showWeekday = true } = options
|
|
19
|
+
export const getSystemTimeZone = (): string => DateTime.now().zoneName ?? 'UTC'
|
|
86
20
|
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
return DateTime.fromFormat(date, 'yyyy-MM-dd').toFormat(format)
|
|
21
|
+
export const formatTimeZone = (timeZone: string): string => {
|
|
22
|
+
const offset = DateTime.now().setZone(timeZone).toFormat("'UTC'Z")
|
|
23
|
+
return `${timeZone.replaceAll('_', ' ')} (${offset})`
|
|
91
24
|
}
|
|
92
25
|
|
|
93
|
-
const
|
|
94
|
-
|
|
26
|
+
export const serializeDateTimePickerValue = (
|
|
27
|
+
dateTime: DateTime,
|
|
28
|
+
timeZone: string
|
|
29
|
+
): string | null => {
|
|
95
30
|
if (!dateTime.isValid) {
|
|
96
31
|
return null
|
|
97
32
|
}
|
|
98
33
|
|
|
99
|
-
return dateTime.toFormat('
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export const formatDateTimeReadable = (dateTime: string): string => {
|
|
103
|
-
const [date = '', time = ''] = dateTime.split('T')
|
|
104
|
-
return `${formatDateReadable(date, { showWeekday: false })} at ${
|
|
105
|
-
formatTimeReadable(time) ?? ''
|
|
106
|
-
}`
|
|
34
|
+
return dateTime.setZone(timeZone).toFormat("yyyy-MM-dd'T'HH:mm:ss")
|
|
107
35
|
}
|
|
108
36
|
|
|
109
|
-
export const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const date = dateTime.toFormat('yyyy-MM-dd')
|
|
115
|
-
const time = dateTime.toFormat('HH:mm:ss')
|
|
116
|
-
return `${date}T${time}`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Takes a date (2023-07-20T00:00:00), and a timezone (America/Los_angeles), and
|
|
121
|
-
* returns an ISO8601 Date (2023-07-20T00:00:00.000-07:00).
|
|
122
|
-
*
|
|
123
|
-
* @param date
|
|
124
|
-
* @param timezone
|
|
125
|
-
* @returns ISOdate
|
|
126
|
-
*/
|
|
127
|
-
export const createIsoDate = (date: string, timezone: string): string => {
|
|
128
|
-
const offset = getTimezoneOffset(timezone)
|
|
129
|
-
return `${date}.000${offset}`
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Takes a ISO datetime string (2023-07-20T00:00:00.000-07:00) and returns
|
|
134
|
-
* the IANA timezone (America/Los_angeles).
|
|
135
|
-
*
|
|
136
|
-
* @param date
|
|
137
|
-
* @returns string
|
|
138
|
-
*/
|
|
139
|
-
export const getTimezoneFromIsoDate = (date: string): string | null =>
|
|
140
|
-
DateTime.fromISO(date).zoneName
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Takes an ISO datetime string (2023-07-20T00:00:00.000-07:00) and returns a string like
|
|
144
|
-
* (Jul 20, 12:00 AM PDT).
|
|
145
|
-
*
|
|
146
|
-
* @param date
|
|
147
|
-
* @returns string
|
|
148
|
-
*
|
|
149
|
-
*/
|
|
150
|
-
export const formatDateAndTime = (date: string): string =>
|
|
151
|
-
DateTime.fromISO(date).toLocaleString({
|
|
152
|
-
month: 'short',
|
|
153
|
-
day: 'numeric',
|
|
154
|
-
hour: 'numeric',
|
|
155
|
-
minute: '2-digit',
|
|
156
|
-
timeZoneName: 'short',
|
|
157
|
-
})
|
|
37
|
+
export const parseDateTimePickerValue = (
|
|
38
|
+
value: string,
|
|
39
|
+
timeZone: string
|
|
40
|
+
): DateTime =>
|
|
41
|
+
DateTime.fromISO(value).setZone(timeZone, { keepLocalTime: true })
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Automatically 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 CheckGreenIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
width={24}
|
|
11
|
+
height={24}
|
|
12
|
+
fill='none'
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<mask
|
|
16
|
+
id='check-green_svg__a'
|
|
17
|
+
width={24}
|
|
18
|
+
height={24}
|
|
19
|
+
x={0}
|
|
20
|
+
y={0}
|
|
21
|
+
maskUnits='userSpaceOnUse'
|
|
22
|
+
style={{
|
|
23
|
+
maskType: 'alpha',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<path fill='#D9D9D9' d='M0 0h24v24H0z' />
|
|
27
|
+
</mask>
|
|
28
|
+
<g mask='url(#check-green_svg__a)'>
|
|
29
|
+
<path
|
|
30
|
+
fill='#27AE60'
|
|
31
|
+
d='m10.6 16.6 7.05-7.05-1.4-1.4-5.65 5.65-2.85-2.85-1.4 1.4 4.25 4.25ZM12 22a9.733 9.733 0 0 1-3.9-.788 10.092 10.092 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.733 9.733 0 0 1 2 12c0-1.383.263-2.683.788-3.9a10.092 10.092 0 0 1 2.137-3.175c.9-.9 1.958-1.613 3.175-2.138A9.743 9.743 0 0 1 12 2c1.383 0 2.683.262 3.9.787a10.105 10.105 0 0 1 3.175 2.138c.9.9 1.612 1.958 2.137 3.175A9.733 9.733 0 0 1 22 12a9.733 9.733 0 0 1-.788 3.9 10.092 10.092 0 0 1-2.137 3.175c-.9.9-1.958 1.612-3.175 2.137A9.733 9.733 0 0 1 12 22Z'
|
|
32
|
+
/>
|
|
33
|
+
</g>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Automatically 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 CloseWhiteIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
width={24}
|
|
11
|
+
height={24}
|
|
12
|
+
fill='none'
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<mask
|
|
16
|
+
id='close-white_svg__a'
|
|
17
|
+
width={24}
|
|
18
|
+
height={24}
|
|
19
|
+
x={0}
|
|
20
|
+
y={0}
|
|
21
|
+
maskUnits='userSpaceOnUse'
|
|
22
|
+
style={{
|
|
23
|
+
maskType: 'alpha',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<path fill='#D9D9D9' d='M0 0h24v24H0z' />
|
|
27
|
+
</mask>
|
|
28
|
+
<g mask='url(#close-white_svg__a)'>
|
|
29
|
+
<path
|
|
30
|
+
fill='#fff'
|
|
31
|
+
d='M6.4 19 5 17.6l5.6-5.6L5 6.4 6.4 5l5.6 5.6L17.6 5 19 6.4 13.4 12l5.6 5.6-1.4 1.4-5.6-5.6L6.4 19Z'
|
|
32
|
+
/>
|
|
33
|
+
</g>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -221,28 +221,25 @@ function Duration(props: { accessCode: AccessCode }): JSX.Element {
|
|
|
221
221
|
)
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const formatDurationDate = (date: string): string =>
|
|
225
|
+
DateTime.fromISO(date).toLocaleString({
|
|
226
226
|
month: 'short',
|
|
227
227
|
day: 'numeric',
|
|
228
228
|
})
|
|
229
|
-
}
|
|
230
229
|
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
const formatTime = (date: string): string =>
|
|
231
|
+
DateTime.fromISO(date).toLocaleString({
|
|
233
232
|
hour: 'numeric',
|
|
234
233
|
minute: '2-digit',
|
|
235
234
|
})
|
|
236
|
-
}
|
|
237
235
|
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
const formatDate = (date: string): string =>
|
|
237
|
+
DateTime.fromISO(date).toLocaleString({
|
|
240
238
|
weekday: 'short',
|
|
241
239
|
month: 'long',
|
|
242
240
|
day: 'numeric',
|
|
243
241
|
year: 'numeric',
|
|
244
242
|
})
|
|
245
|
-
}
|
|
246
243
|
|
|
247
244
|
const errorFilter = (
|
|
248
245
|
error: AccessCodeError | DeviceError | ConnectedAccountError
|
|
@@ -52,12 +52,11 @@ function Duration(props: { accessCode: AccessCode }): JSX.Element {
|
|
|
52
52
|
)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const formatDate = (date: string): string =>
|
|
56
|
+
DateTime.fromISO(date).toLocaleString({
|
|
57
57
|
month: 'long',
|
|
58
58
|
day: 'numeric',
|
|
59
59
|
})
|
|
60
|
-
}
|
|
61
60
|
|
|
62
61
|
const t = {
|
|
63
62
|
code: 'Code',
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { DateTime } from 'luxon'
|
|
2
2
|
import type { ClimateSettingSchedule } from 'seamapi'
|
|
3
3
|
|
|
4
|
-
import { formatDateAndTime } from 'lib/dates.js'
|
|
5
4
|
import { ClimateSettingScheduleIcon } from 'lib/icons/ClimateSettingSchedule.js'
|
|
6
5
|
import { ClimateSettingScheduleDeviceBar } from 'lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js'
|
|
7
6
|
import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
|
|
8
7
|
import { DotDivider } from 'lib/ui/layout/DotDivider.js'
|
|
9
8
|
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
10
|
-
import {
|
|
9
|
+
import { useNow } from 'lib/ui/use-now.js'
|
|
10
|
+
|
|
11
|
+
import { formatDateTime } from './dates.js'
|
|
11
12
|
|
|
12
13
|
interface ClimateSettingScheduleCardProps {
|
|
13
14
|
climateSettingScheduleId: string
|
|
@@ -74,25 +75,24 @@ function ClimateSettingScheduleTiming(props: {
|
|
|
74
75
|
}): JSX.Element | null {
|
|
75
76
|
const { climateSettingSchedule } = props
|
|
76
77
|
|
|
77
|
-
const
|
|
78
|
+
const now = useNow()
|
|
78
79
|
|
|
79
|
-
if (
|
|
80
|
+
if (now === null) return null
|
|
80
81
|
|
|
81
82
|
const startTime = DateTime.fromISO(climateSettingSchedule.schedule_starts_at)
|
|
82
83
|
const endTime = DateTime.fromISO(climateSettingSchedule.schedule_ends_at)
|
|
83
84
|
|
|
84
|
-
if (
|
|
85
|
+
if (now < startTime)
|
|
85
86
|
return (
|
|
86
87
|
<span>
|
|
87
|
-
{t.starts}{
|
|
88
|
-
{formatDateAndTime(climateSettingSchedule.schedule_starts_at)}
|
|
88
|
+
{t.starts} {formatDateTime(climateSettingSchedule.schedule_starts_at)}
|
|
89
89
|
</span>
|
|
90
90
|
)
|
|
91
91
|
|
|
92
|
-
if (startTime <=
|
|
92
|
+
if (startTime <= now && now <= endTime)
|
|
93
93
|
return (
|
|
94
94
|
<span>
|
|
95
|
-
{t.ends} {
|
|
95
|
+
{t.ends} {formatDateTime(climateSettingSchedule.schedule_starts_at)}
|
|
96
96
|
</span>
|
|
97
97
|
)
|
|
98
98
|
|
package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import { useState } from 'react'
|
|
|
3
3
|
|
|
4
4
|
import { useComponentTelemetry } from 'lib/telemetry/index.js'
|
|
5
5
|
|
|
6
|
-
import { formatDateAndTime } from 'lib/dates.js'
|
|
7
6
|
import { ArrowRightIcon } from 'lib/icons/ArrowRight.js'
|
|
8
7
|
import { ClimateSettingScheduleCard } from 'lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js'
|
|
9
8
|
import {
|
|
@@ -18,6 +17,8 @@ import { DetailSection } from 'lib/ui/layout/DetailSection.js'
|
|
|
18
17
|
import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
|
|
19
18
|
import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
|
|
20
19
|
|
|
20
|
+
import { formatDateTime } from './dates.js'
|
|
21
|
+
|
|
21
22
|
export interface ClimateSettingScheduleDetailsProps extends CommonProps {
|
|
22
23
|
climateSettingScheduleId: string
|
|
23
24
|
}
|
|
@@ -89,13 +90,9 @@ export function ClimateSettingScheduleDetails({
|
|
|
89
90
|
<DetailSection>
|
|
90
91
|
<DetailRow label={t.startEndTime}>
|
|
91
92
|
<span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
|
|
92
|
-
{
|
|
93
|
-
climateSettingSchedule.schedule_starts_at
|
|
94
|
-
)}`}
|
|
93
|
+
{formatDateTime(climateSettingSchedule.schedule_starts_at)}
|
|
95
94
|
<ArrowRightIcon />
|
|
96
|
-
{
|
|
97
|
-
climateSettingSchedule.schedule_ends_at
|
|
98
|
-
)}`}
|
|
95
|
+
{formatDateTime(climateSettingSchedule.schedule_ends_at)}
|
|
99
96
|
</span>
|
|
100
97
|
</DetailRow>
|
|
101
98
|
<DetailRow label={t.climateSetting}>
|
|
@@ -113,7 +110,7 @@ export function ClimateSettingScheduleDetails({
|
|
|
113
110
|
<DetailSection>
|
|
114
111
|
<DetailRow label={t.creationDate}>
|
|
115
112
|
<div className='seam-creation-date'>
|
|
116
|
-
{
|
|
113
|
+
{formatDateTime(climateSettingSchedule.created_at)}
|
|
117
114
|
</div>
|
|
118
115
|
</DetailRow>
|
|
119
116
|
</DetailSection>
|
package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.tsx
CHANGED
|
@@ -48,12 +48,11 @@ function Duration(props: {
|
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const formatDate = (date: string): string =>
|
|
52
|
+
DateTime.fromISO(date).toLocaleString({
|
|
53
53
|
month: 'long',
|
|
54
54
|
day: 'numeric',
|
|
55
55
|
})
|
|
56
|
-
}
|
|
57
56
|
|
|
58
57
|
const t = {
|
|
59
58
|
starts: 'Starts',
|
|
@@ -3,7 +3,6 @@ import type { SeamError } from 'seamapi'
|
|
|
3
3
|
|
|
4
4
|
import { useComponentTelemetry } from 'lib/telemetry/index.js'
|
|
5
5
|
|
|
6
|
-
import { createIsoDate } from 'lib/dates.js'
|
|
7
6
|
import { useCreateAccessCode } from 'lib/seam/access-codes/use-create-access-code.js'
|
|
8
7
|
import {
|
|
9
8
|
type CommonProps,
|
|
@@ -82,7 +81,7 @@ function useSubmitCreateAccessCode(params: { onSuccess: () => void }): {
|
|
|
82
81
|
const submit = (data: AccessCodeFormSubmitData): void => {
|
|
83
82
|
resetResponseErrors()
|
|
84
83
|
|
|
85
|
-
const { name, code, type, device, startDate, endDate
|
|
84
|
+
const { name, code, type, device, startDate, endDate } = data
|
|
86
85
|
if (name === '') {
|
|
87
86
|
return
|
|
88
87
|
}
|
|
@@ -97,8 +96,8 @@ function useSubmitCreateAccessCode(params: { onSuccess: () => void }): {
|
|
|
97
96
|
name,
|
|
98
97
|
code,
|
|
99
98
|
device_id: device.device_id,
|
|
100
|
-
starts_at:
|
|
101
|
-
ends_at:
|
|
99
|
+
starts_at: startDate,
|
|
100
|
+
ends_at: endDate,
|
|
102
101
|
},
|
|
103
102
|
{
|
|
104
103
|
onSuccess,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useComponentTelemetry } from 'lib/telemetry/index.js'
|
|
2
2
|
|
|
3
|
-
import { createIsoDate } from 'lib/dates.js'
|
|
4
3
|
import type { CommonProps } from 'lib/seam/components/common-props.js'
|
|
5
4
|
import { useCreateClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-create-climate-setting-schedule.js'
|
|
6
5
|
import {
|
|
@@ -34,8 +33,7 @@ function useSubmitCreateClimateSettingSchedule(onSuccess?: () => void): {
|
|
|
34
33
|
} {
|
|
35
34
|
const { mutate, isLoading: isSubmitting } = useCreateClimateSettingSchedule()
|
|
36
35
|
const submit = (data: ClimateSettingScheduleFormSubmitData): void => {
|
|
37
|
-
const { name, deviceId, startDate, endDate,
|
|
38
|
-
data
|
|
36
|
+
const { name, deviceId, startDate, endDate, climateSetting } = data
|
|
39
37
|
|
|
40
38
|
if (isSubmitting) {
|
|
41
39
|
return
|
|
@@ -45,8 +43,8 @@ function useSubmitCreateClimateSettingSchedule(onSuccess?: () => void): {
|
|
|
45
43
|
{
|
|
46
44
|
name,
|
|
47
45
|
device_id: deviceId,
|
|
48
|
-
schedule_starts_at:
|
|
49
|
-
schedule_ends_at:
|
|
46
|
+
schedule_starts_at: startDate,
|
|
47
|
+
schedule_ends_at: endDate,
|
|
50
48
|
...climateSetting,
|
|
51
49
|
},
|
|
52
50
|
{
|
|
@@ -128,10 +128,7 @@ export function ThermostatDeviceDetails(
|
|
|
128
128
|
</DetailRow>
|
|
129
129
|
</DetailSection>
|
|
130
130
|
|
|
131
|
-
<DetailSection
|
|
132
|
-
label={t.deviceDetails}
|
|
133
|
-
tooltipContent={t.deviceDetailsTooltip}
|
|
134
|
-
>
|
|
131
|
+
<DetailSection label={t.deviceDetails}>
|
|
135
132
|
<DetailRow label={t.brand}>
|
|
136
133
|
<div className='seam-detail-row-hstack'>
|
|
137
134
|
{device.properties.model.manufacturer_display_name}
|
|
@@ -172,8 +169,6 @@ const t = {
|
|
|
172
169
|
defaultClimate: 'Default climate',
|
|
173
170
|
allowManualOverride: 'Allow manual override',
|
|
174
171
|
deviceDetails: 'Device details',
|
|
175
|
-
deviceDetailsTooltip:
|
|
176
|
-
'When a scheduled climate reaches its end time, the default settings will kick in.',
|
|
177
172
|
brand: 'Brand',
|
|
178
173
|
linkedAccount: 'Linked account',
|
|
179
174
|
deviceId: 'Device ID',
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useComponentTelemetry } from 'lib/telemetry/index.js'
|
|
2
2
|
|
|
3
|
-
import { createIsoDate } from 'lib/dates.js'
|
|
4
3
|
import {
|
|
5
4
|
useAccessCode,
|
|
6
5
|
type UseAccessCodeData,
|
|
@@ -93,7 +92,7 @@ function useSubmitEditAccessCode(
|
|
|
93
92
|
const submit = (data: AccessCodeFormSubmitData): void => {
|
|
94
93
|
resetResponseErrors()
|
|
95
94
|
|
|
96
|
-
const { name, code, type, device, startDate, endDate
|
|
95
|
+
const { name, code, type, device, startDate, endDate } = data
|
|
97
96
|
if (name === '') {
|
|
98
97
|
return
|
|
99
98
|
}
|
|
@@ -110,8 +109,8 @@ function useSubmitEditAccessCode(
|
|
|
110
109
|
code,
|
|
111
110
|
device_id: device.device_id,
|
|
112
111
|
type: 'time_bound',
|
|
113
|
-
starts_at:
|
|
114
|
-
ends_at:
|
|
112
|
+
starts_at: startDate,
|
|
113
|
+
ends_at: endDate,
|
|
115
114
|
},
|
|
116
115
|
{
|
|
117
116
|
onSuccess,
|
|
@@ -36,7 +36,10 @@ export function FilterCategoryMenu({
|
|
|
36
36
|
onAllOptionSelect,
|
|
37
37
|
buttonLabel,
|
|
38
38
|
}: FilterCategoryMenuProps): JSX.Element {
|
|
39
|
-
const
|
|
39
|
+
const sortedOptions = [...options].sort((a, b) => a.localeCompare(b))
|
|
40
|
+
const usableOptions = hideAllOption
|
|
41
|
+
? sortedOptions
|
|
42
|
+
: [allLabel, ...sortedOptions]
|
|
40
43
|
|
|
41
44
|
return (
|
|
42
45
|
<div className='seam-supported-device-table-filter-menu-wrap'>
|
|
@@ -49,20 +52,22 @@ export function FilterCategoryMenu({
|
|
|
49
52
|
</button>
|
|
50
53
|
)}
|
|
51
54
|
>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
<div className='seam-supported-device-table-filter-menu-content'>
|
|
56
|
+
{usableOptions.map((option, index) => (
|
|
57
|
+
<MenuItem
|
|
58
|
+
key={`${index}:${option}`}
|
|
59
|
+
onClick={() => {
|
|
60
|
+
if (option === allLabel) {
|
|
61
|
+
onAllOptionSelect?.()
|
|
62
|
+
} else {
|
|
63
|
+
onSelect(option)
|
|
64
|
+
}
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<span>{option}</span>
|
|
68
|
+
</MenuItem>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
66
71
|
</Menu>
|
|
67
72
|
</div>
|
|
68
73
|
)
|