cronofy-elements 1.44.0 → 1.46.1
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/build/CronofyElements.v1.46.1.js +2 -0
- package/build/{CronofyElements.v1.44.0.js.LICENSE.txt → CronofyElements.v1.46.1.js.LICENSE.txt} +0 -0
- package/build/npm/CronofyElements.js +2 -2
- package/demo/date-time-picker.ejs +2 -7
- package/demo/rules.ejs +7 -1
- package/package.json +56 -56
- package/src/js/components/AvailabilityRules/AvailabilityRules.js +15 -1
- package/src/js/components/AvailabilityRules/CalendarSelector.js +1 -1
- package/src/js/components/AvailabilityRules/Calendars.js +1 -1
- package/src/js/components/AvailabilityRules/TimeZoneDisplay.js +13 -2
- package/src/js/components/AvailabilityRules/Wrapper.js +45 -34
- package/src/js/components/AvailabilityRules/scss/_base.buttons.scss +58 -0
- package/src/js/components/AvailabilityRules/scss/_base.theme.scss +4 -0
- package/src/js/components/AvailabilityRules/scss/_components.timezoneselector.scss +74 -0
- package/src/js/components/AvailabilityRules/scss/_generic.reset.scss +13 -0
- package/src/js/components/AvailabilityRules/scss/_settings.colours.scss +12 -0
- package/src/js/components/AvailabilityRules/scss/availabilityrules.scss +5 -0
- package/src/js/components/AvailabilityRules/utils/tz-utils.js +44 -0
- package/src/js/components/DateTimePicker/Calendar.js +1 -1
- package/src/js/components/DateTimePicker/CalendarHeader.js +1 -1
- package/src/js/components/DateTimePicker/Confirm.js +1 -1
- package/src/js/components/DateTimePicker/DateTimePicker.js +1 -4
- package/src/js/components/DateTimePicker/DayButton.js +1 -1
- package/src/js/components/DateTimePicker/Details.js +5 -2
- package/src/js/components/DateTimePicker/SequencedSlotButton.js +1 -1
- package/src/js/components/DateTimePicker/SlotButton.js +1 -1
- package/src/js/components/DateTimePicker/Wrapper.js +115 -38
- package/src/js/components/DateTimePicker/contexts/status-reducer.js +28 -7
- package/src/js/components/DateTimePicker/utils/slots.js +8 -76
- package/src/js/components/{DateTimePicker → generic}/TimeZoneSelector.js +10 -16
- package/src/js/{components/DateTimePicker/contexts → contexts}/tz-context.js +0 -0
- package/src/js/{components/DateTimePicker/utils → helpers}/tz-list.js +0 -0
- package/tests/AvailabilityRules/__snapshots__/AvailabilityRules.test.js.snap +37 -6
- package/tests/DateTimePicker/SequencedSlotButton.test.js +1 -1
- package/tests/DateTimePicker/SlotButton.test.js +1 -1
- package/tests/DateTimePicker/contexts/status-reducer.test.js +175 -21
- package/tests/DateTimePicker/utils.test.js +8 -0
- package/tests/components/TimezoneSelector.test.js +124 -0
- package/build/CronofyElements.v1.44.0.js +0 -2
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
getMonthObjectsFromQuery,
|
|
6
6
|
parseTzList,
|
|
7
7
|
getInitialSelectedTzid,
|
|
8
|
-
getMonthsLoadingFromQuery,
|
|
9
8
|
getDurationFromQuery,
|
|
10
9
|
} from "./utils/slots";
|
|
11
10
|
import { getMonthsInDisplay, parseTimeSlots } from "./utils/calendar";
|
|
@@ -18,7 +17,7 @@ import { I18nProvider } from "../../contexts/i18n-context";
|
|
|
18
17
|
import { LogProvider } from "../../contexts/log-context";
|
|
19
18
|
import { ThemeProvider } from "./contexts/theme-context";
|
|
20
19
|
import { StatusProvider } from "./contexts/status-context";
|
|
21
|
-
import { TzProvider } from "
|
|
20
|
+
import { TzProvider } from "../../contexts/tz-context";
|
|
22
21
|
|
|
23
22
|
const DateTimePicker = ({ options }) => {
|
|
24
23
|
const statusOptions = useMemo(() => {
|
|
@@ -37,7 +36,6 @@ const DateTimePicker = ({ options }) => {
|
|
|
37
36
|
const startDateObject = moment(options.config.startDate, "YYYY-MM-DD");
|
|
38
37
|
const currentMonthObject = selectedDateObject ?? startDateObject;
|
|
39
38
|
const currentMonth = currentMonthObject.format("YYYY-MM");
|
|
40
|
-
const monthsLoading = getMonthsLoadingFromQuery(options.query, options.tzid);
|
|
41
39
|
const monthsInView = getMonthsInDisplay(currentMonth, options.config.startDay);
|
|
42
40
|
|
|
43
41
|
const monthlyView = {
|
|
@@ -70,7 +68,6 @@ const DateTimePicker = ({ options }) => {
|
|
|
70
68
|
query: options.query,
|
|
71
69
|
months,
|
|
72
70
|
monthlyView,
|
|
73
|
-
monthsLoading,
|
|
74
71
|
selected: false,
|
|
75
72
|
startDay: options.config.startDay,
|
|
76
73
|
focusedDay: options.config.selectedDay,
|
|
@@ -4,7 +4,7 @@ import moment from "moment-timezone";
|
|
|
4
4
|
import { useI18n } from "../../contexts/i18n-context";
|
|
5
5
|
import { useStatus } from "./contexts/status-context";
|
|
6
6
|
import { useTheme } from "./contexts/theme-context";
|
|
7
|
-
import { useTz } from "
|
|
7
|
+
import { useTz } from "../../contexts/tz-context";
|
|
8
8
|
|
|
9
9
|
const DayButton = ({ day, selected = false, focused = false }) => {
|
|
10
10
|
const i18n = useI18n();
|
|
@@ -2,11 +2,14 @@ import React, { memo } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { useI18n } from "../../contexts/i18n-context";
|
|
4
4
|
import { useTheme } from "./contexts/theme-context";
|
|
5
|
-
import TimeZoneSelector from "
|
|
5
|
+
import TimeZoneSelector from "../generic/TimeZoneSelector";
|
|
6
|
+
|
|
7
|
+
import { useTz } from "../../contexts/tz-context";
|
|
6
8
|
|
|
7
9
|
const Details = ({ duration, locale }) => {
|
|
8
10
|
const i18n = useI18n();
|
|
9
11
|
const theme = useTheme();
|
|
12
|
+
const [tz, setTz] = useTz();
|
|
10
13
|
|
|
11
14
|
return (
|
|
12
15
|
<div className={theme.classBuilder("details")}>
|
|
@@ -14,7 +17,7 @@ const Details = ({ duration, locale }) => {
|
|
|
14
17
|
<p className={theme.classBuilder("details--tz-label")}>
|
|
15
18
|
<strong>{i18n.t("time_zone")}:</strong>
|
|
16
19
|
</p>
|
|
17
|
-
<TimeZoneSelector locale={locale} />
|
|
20
|
+
<TimeZoneSelector locale={locale} theme={theme} tz={tz} setTz={setTz} />
|
|
18
21
|
</div>
|
|
19
22
|
<div className={theme.classBuilder("details--duration")}>
|
|
20
23
|
{duration && (
|
|
@@ -4,7 +4,7 @@ import moment from "moment-timezone";
|
|
|
4
4
|
import { useI18n } from "../../contexts/i18n-context";
|
|
5
5
|
import { useStatus } from "./contexts/status-context";
|
|
6
6
|
import { useTheme } from "./contexts/theme-context";
|
|
7
|
-
import { useTz } from "
|
|
7
|
+
import { useTz } from "../../contexts/tz-context";
|
|
8
8
|
|
|
9
9
|
const SequencedSlotButton = ({ slot }) => {
|
|
10
10
|
const i18n = useI18n();
|
|
@@ -4,7 +4,7 @@ import moment from "moment-timezone";
|
|
|
4
4
|
import { useI18n } from "../../contexts/i18n-context";
|
|
5
5
|
import { useStatus } from "./contexts/status-context";
|
|
6
6
|
import { useTheme } from "./contexts/theme-context";
|
|
7
|
-
import { useTz } from "
|
|
7
|
+
import { useTz } from "../../contexts/tz-context";
|
|
8
8
|
|
|
9
9
|
const SlotButton = ({ slot }) => {
|
|
10
10
|
const i18n = useI18n();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
import moment from "moment";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { cropPeriodsArbitrarily, getSlots } from "./utils/slots";
|
|
5
5
|
|
|
6
6
|
import Calendar from "./Calendar";
|
|
7
7
|
import Confirm from "./Confirm";
|
|
@@ -14,7 +14,7 @@ import SlotsList from "./SlotsList";
|
|
|
14
14
|
|
|
15
15
|
import { useStatus } from "./contexts/status-context";
|
|
16
16
|
import { useTheme } from "./contexts/theme-context";
|
|
17
|
-
import { useTz } from "
|
|
17
|
+
import { useTz } from "../../contexts/tz-context";
|
|
18
18
|
|
|
19
19
|
const Wrapper = () => {
|
|
20
20
|
const [status, dispatchStatus] = useStatus();
|
|
@@ -24,51 +24,109 @@ const Wrapper = () => {
|
|
|
24
24
|
const [loadingCalendar, setLoadingCalendar] = useState(true);
|
|
25
25
|
|
|
26
26
|
const confirmButtonRef = useRef();
|
|
27
|
+
const firstRender = useRef(true);
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const fetchMonthSlots = (query, month) => {
|
|
30
|
+
return getSlots({
|
|
31
|
+
query,
|
|
32
|
+
auth: status.auth,
|
|
33
|
+
tzid: status.tzid,
|
|
34
|
+
sequence: status.sequenced_availability,
|
|
35
|
+
})
|
|
36
|
+
.then(res => {
|
|
37
|
+
if (res.length < 512) {
|
|
38
|
+
dispatchStatus({
|
|
39
|
+
type: "SET_SLOTS",
|
|
40
|
+
slots: res,
|
|
41
|
+
tzid: tz.selectedTzid.tzid,
|
|
42
|
+
month,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
35
46
|
|
|
36
|
-
return fetch({
|
|
37
|
-
query,
|
|
38
|
-
auth: status.auth,
|
|
39
|
-
tzid: status.tzid,
|
|
40
|
-
}).then(res => {
|
|
41
47
|
dispatchStatus({
|
|
42
48
|
type: "SET_SLOTS",
|
|
43
49
|
slots: res,
|
|
44
50
|
tzid: tz.selectedTzid.tzid,
|
|
45
|
-
month: month,
|
|
46
51
|
});
|
|
52
|
+
|
|
53
|
+
// If we get here, the API has returned the maximum number
|
|
54
|
+
// of slots allowed, so we need to crop the query and try
|
|
55
|
+
// again to ensure we haven't missed any slots.
|
|
56
|
+
const startOfLastSlot = res[res.length - 1].start;
|
|
57
|
+
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
|
|
58
|
+
|
|
59
|
+
const boundsForCropping = {
|
|
60
|
+
start: startOfLastSlot,
|
|
61
|
+
end: endOfLastPeriod,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const croppedPeriods = cropPeriodsArbitrarily(
|
|
65
|
+
query.query_periods,
|
|
66
|
+
boundsForCropping
|
|
67
|
+
);
|
|
68
|
+
const croppedQuery = { ...query, query_periods: croppedPeriods };
|
|
69
|
+
|
|
70
|
+
// Rerun the query
|
|
71
|
+
return fetchMonthSlots(croppedQuery, month);
|
|
72
|
+
})
|
|
73
|
+
.catch(error => {
|
|
74
|
+
if (error.type === 422) {
|
|
75
|
+
dispatchStatus({
|
|
76
|
+
type: "ERROR_LOADING_SLOTS",
|
|
77
|
+
error,
|
|
78
|
+
month,
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
|
|
47
83
|
});
|
|
48
|
-
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!status.query.query_periods) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
49
90
|
|
|
50
91
|
const currentMonth = status.months.find(m => m.current);
|
|
51
92
|
|
|
93
|
+
const croppedMonths = status.months.filter(el => {
|
|
94
|
+
return status.monthlyView.monthsInView.some(f => {
|
|
95
|
+
return f === el.month && el.loading && !el.current;
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
52
99
|
// Get slots for current month
|
|
53
100
|
fetchMonthSlots(currentMonth.query, currentMonth.month)
|
|
54
101
|
.then(() => {
|
|
55
|
-
// Get slots for
|
|
56
|
-
|
|
57
|
-
remainingMonths.forEach(month =>
|
|
58
|
-
fetchMonthSlots(month.query, month.month).catch(res => {
|
|
59
|
-
dispatchStatus({
|
|
60
|
-
type: "ERROR_LOADING_SLOTS",
|
|
61
|
-
error: res,
|
|
62
|
-
month: month.month,
|
|
63
|
-
});
|
|
64
|
-
})
|
|
65
|
-
);
|
|
102
|
+
// Get slots for remaining months
|
|
103
|
+
croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month));
|
|
66
104
|
})
|
|
67
105
|
.catch(error => {
|
|
68
106
|
dispatchStatus({ type: "ERROR_GETTING_SLOTS", error });
|
|
69
107
|
});
|
|
70
108
|
}, []);
|
|
71
109
|
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
//stops this from firing on first render which would double up the initial API calls
|
|
112
|
+
if (firstRender.current) {
|
|
113
|
+
firstRender.current = false;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (status.monthlyView.monthsInView) {
|
|
117
|
+
//check if any of the months in view haven't been fetched yet
|
|
118
|
+
const croppedMonths = status.months.filter(el => {
|
|
119
|
+
return status.monthlyView.monthsInView.some(f => {
|
|
120
|
+
return f === el.month && el.loading === true;
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
//if they haven't, fetch them.
|
|
124
|
+
if (croppedMonths.length > 0) {
|
|
125
|
+
croppedMonths.forEach(month => fetchMonthSlots(month.query, month.month));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, [status.monthlyView.monthsInView]);
|
|
129
|
+
|
|
72
130
|
useEffect(() => {
|
|
73
131
|
if (!status.slotInjectionPoint) {
|
|
74
132
|
return;
|
|
@@ -97,6 +155,15 @@ const Wrapper = () => {
|
|
|
97
155
|
const finishedCallingAllSlots = fetchCount === monthsCount;
|
|
98
156
|
const hasAvailableDays = status.availableDays.length;
|
|
99
157
|
|
|
158
|
+
// No slots found but not all months called so try the next month
|
|
159
|
+
if (!finishedCallingAllSlots && !hasAvailableDays && fetchCount >= 2) {
|
|
160
|
+
const croppedMonths = status.months.filter(el => {
|
|
161
|
+
return el.loading === true;
|
|
162
|
+
});
|
|
163
|
+
fetchMonthSlots(croppedMonths[0].query, croppedMonths[0].month);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
100
167
|
// No slots available after all queries have been made
|
|
101
168
|
if (finishedCallingAllSlots && !hasAvailableDays) {
|
|
102
169
|
dispatchStatus({ type: "NO_SLOTS_FOUND" });
|
|
@@ -117,15 +184,25 @@ const Wrapper = () => {
|
|
|
117
184
|
return;
|
|
118
185
|
}
|
|
119
186
|
|
|
120
|
-
// For allowing only this to be called once
|
|
187
|
+
// For allowing only this to be called once slots have been fetched
|
|
121
188
|
// Since we know the first month that is being fetched will be for the initial selected day
|
|
122
|
-
if (status.selectedDay && fetchCount
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
189
|
+
if (status.selectedDay && fetchCount >= 1) {
|
|
190
|
+
const currentMonth = moment(status.selectedDay, "YYYY-MM-DD").format("YYYY-MM");
|
|
191
|
+
const month = status.months.filter(el => {
|
|
192
|
+
return el.month === currentMonth;
|
|
193
|
+
})[0];
|
|
194
|
+
const monthStillLoading = month ? month.loading : false;
|
|
195
|
+
const dayIsAvailable = status.availableDays.includes(status.selectedDay);
|
|
196
|
+
|
|
197
|
+
//Only set the selectedDay if the month is finished loading or if the day has slots available
|
|
198
|
+
if (!monthStillLoading || dayIsAvailable) {
|
|
199
|
+
dispatchStatus({
|
|
200
|
+
type: "SELECT_DAY",
|
|
201
|
+
day: status.selectedDay,
|
|
202
|
+
tzid: tz.selectedTzid.tzid,
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
129
206
|
}
|
|
130
207
|
}, [status.slotFetchCount, status.availableDays]);
|
|
131
208
|
|
|
@@ -159,9 +236,9 @@ const Wrapper = () => {
|
|
|
159
236
|
}, [tz.selectedTzid.tzid]);
|
|
160
237
|
|
|
161
238
|
useEffect(() => {
|
|
162
|
-
if (status.
|
|
239
|
+
if (status.months) {
|
|
163
240
|
for (let month of status.monthlyView.monthsInView) {
|
|
164
|
-
const monthLoading = status.
|
|
241
|
+
const monthLoading = status.months.find(m => m.month === month);
|
|
165
242
|
if (monthLoading && monthLoading.loading) {
|
|
166
243
|
setLoadingCalendar(true);
|
|
167
244
|
return;
|
|
@@ -170,7 +247,7 @@ const Wrapper = () => {
|
|
|
170
247
|
}
|
|
171
248
|
}
|
|
172
249
|
}
|
|
173
|
-
}, [status.monthlyView, status.
|
|
250
|
+
}, [status.monthlyView, status.months]);
|
|
174
251
|
|
|
175
252
|
return (
|
|
176
253
|
<section className={theme.classBuilder()} style={theme.customProperties}>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
addSequencedSlotsToObject,
|
|
10
10
|
} from "../utils/slots";
|
|
11
11
|
import { getMonthsInDisplay, parseTimeSlots } from "../utils/calendar";
|
|
12
|
+
import { uniqueItems } from "../../../helpers/utils";
|
|
12
13
|
|
|
13
14
|
export const statusReducer = (state, action) => {
|
|
14
15
|
const { type, ...actionBody } = action;
|
|
@@ -62,9 +63,9 @@ export const statusReducer = (state, action) => {
|
|
|
62
63
|
body: action.error.body,
|
|
63
64
|
},
|
|
64
65
|
};
|
|
65
|
-
const
|
|
66
|
+
const months = removeMonthFromLoading(state.months, action.month);
|
|
66
67
|
state.callback(notification);
|
|
67
|
-
return { ...state,
|
|
68
|
+
return { ...state, months };
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
case "NO_SLOTS_FOUND": {
|
|
@@ -99,19 +100,39 @@ export const statusReducer = (state, action) => {
|
|
|
99
100
|
|
|
100
101
|
case "SET_SLOTS": {
|
|
101
102
|
if (!action.slots.length > 0) {
|
|
102
|
-
const
|
|
103
|
+
const months = removeMonthFromLoading(state.months, action.month);
|
|
103
104
|
return {
|
|
104
105
|
...state,
|
|
105
|
-
|
|
106
|
+
months,
|
|
106
107
|
slotFetchCount: state.slotFetchCount + 1,
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// Add action slots to state slots
|
|
110
112
|
const slotsObject = state.sequenced_availability
|
|
111
113
|
? addSequencedSlotsToObject(state.slots, action.slots)
|
|
112
114
|
: addSlotsToObject(state.slots, action.slots);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
|
|
116
|
+
// since we already know the available days for state slots
|
|
117
|
+
// find available days for action.slots and add them to state availableDays
|
|
118
|
+
// this way we don't have to loop through the entirety of the state slotsObject
|
|
119
|
+
// every time new slots are rendered
|
|
120
|
+
const actionSlotsObject = state.sequenced_availability
|
|
121
|
+
? addSequencedSlotsToObject({}, action.slots)
|
|
122
|
+
: addSlotsToObject({}, action.slots);
|
|
123
|
+
|
|
124
|
+
const availableDays = uniqueItems([
|
|
125
|
+
...state.availableDays,
|
|
126
|
+
...getAvailableDays(actionSlotsObject, action.tzid),
|
|
127
|
+
]).sort();
|
|
128
|
+
|
|
129
|
+
// if month is passed in, we render that month as done
|
|
130
|
+
// this change is for continiously loading slots as opposed to waiting for entire month
|
|
131
|
+
// to be loaded
|
|
132
|
+
let months = state.months;
|
|
133
|
+
if (action.month) {
|
|
134
|
+
months = removeMonthFromLoading(state.months, action.month);
|
|
135
|
+
}
|
|
115
136
|
|
|
116
137
|
const injectionPoint = state.sequenced_availability
|
|
117
138
|
? getLocalDayFromUtc(action.slots[0].sequence[0].start, action.tzid)
|
|
@@ -120,7 +141,7 @@ export const statusReducer = (state, action) => {
|
|
|
120
141
|
return {
|
|
121
142
|
...state,
|
|
122
143
|
availableDays,
|
|
123
|
-
|
|
144
|
+
months,
|
|
124
145
|
slots: slotsObject,
|
|
125
146
|
slotFetchCount: state.slotFetchCount + 1,
|
|
126
147
|
slotInjectionPoint: injectionPoint,
|
|
@@ -4,7 +4,7 @@ import { getAvailability, getSequencedAvailability } from "../../../helpers/conn
|
|
|
4
4
|
import { uniqueItems, humanizeTzName } from "../../../helpers/utils";
|
|
5
5
|
import { errorMessages } from "../../../helpers/logging";
|
|
6
6
|
|
|
7
|
-
import { defaultTimeZones } from "
|
|
7
|
+
import { defaultTimeZones } from "../../../helpers/tz-list";
|
|
8
8
|
|
|
9
9
|
export const getMonthsCoveredByPeriod = (period, tzid) => {
|
|
10
10
|
const start = moment
|
|
@@ -92,6 +92,7 @@ export const getMonthObjectsFromQuery = (query, tzid, current = false) => {
|
|
|
92
92
|
return {
|
|
93
93
|
month,
|
|
94
94
|
current: currentMonth === month,
|
|
95
|
+
loading: true,
|
|
95
96
|
query: {
|
|
96
97
|
...query,
|
|
97
98
|
query_periods: croppedQueryPeriods,
|
|
@@ -101,55 +102,10 @@ export const getMonthObjectsFromQuery = (query, tzid, current = false) => {
|
|
|
101
102
|
});
|
|
102
103
|
};
|
|
103
104
|
|
|
104
|
-
export const getSlots = ({ query, auth, tzid,
|
|
105
|
-
getAvailability
|
|
106
|
-
auth.token,
|
|
107
|
-
auth.domains.apiDomain,
|
|
108
|
-
query,
|
|
109
|
-
"DateTimePicker",
|
|
110
|
-
tzid,
|
|
111
|
-
auth.demo
|
|
112
|
-
).then(res => {
|
|
113
|
-
if (res.status === 422) {
|
|
114
|
-
throw {
|
|
115
|
-
type: 422,
|
|
116
|
-
message: errorMessages[422].message,
|
|
117
|
-
body: res.errors,
|
|
118
|
-
docsSlug: errorMessages[422].docsSlug,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// This will intentionally throw an error if the
|
|
122
|
-
// result is not in the correct format:
|
|
123
|
-
const returnedSlots = parseSlotsResult(res);
|
|
124
|
-
|
|
125
|
-
const allSlots = [...slots, ...returnedSlots];
|
|
126
|
-
|
|
127
|
-
if (returnedSlots.length < 512) return [...slots, ...returnedSlots];
|
|
128
|
-
|
|
129
|
-
// If we get here, the API has returned the maximum number
|
|
130
|
-
// of slots allowed, so we need to crop the query and try
|
|
131
|
-
// again to ensure we haven't missed any slots.
|
|
132
|
-
|
|
133
|
-
const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start;
|
|
134
|
-
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
|
|
135
|
-
const boundsForCropping = {
|
|
136
|
-
start: startOfLastSlot,
|
|
137
|
-
end: endOfLastPeriod,
|
|
138
|
-
};
|
|
139
|
-
const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping);
|
|
140
|
-
const croppedQuery = { ...query, query_periods: croppedPeriods };
|
|
141
|
-
|
|
142
|
-
// Rerun the query
|
|
143
|
-
return getSlots({
|
|
144
|
-
query: croppedQuery,
|
|
145
|
-
auth,
|
|
146
|
-
tzid,
|
|
147
|
-
slots: allSlots,
|
|
148
|
-
});
|
|
149
|
-
});
|
|
105
|
+
export const getSlots = ({ query, auth, tzid, sequence = false }) => {
|
|
106
|
+
const availabilityFetch = sequence ? getSequencedAvailability : getAvailability;
|
|
150
107
|
|
|
151
|
-
|
|
152
|
-
getSequencedAvailability(
|
|
108
|
+
return availabilityFetch(
|
|
153
109
|
auth.token,
|
|
154
110
|
auth.domains.apiDomain,
|
|
155
111
|
query,
|
|
@@ -157,7 +113,7 @@ export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) =>
|
|
|
157
113
|
tzid,
|
|
158
114
|
auth.demo
|
|
159
115
|
).then(res => {
|
|
160
|
-
if (res.
|
|
116
|
+
if (res.status === 422) {
|
|
161
117
|
throw {
|
|
162
118
|
type: 422,
|
|
163
119
|
message: errorMessages[422].message,
|
|
@@ -167,33 +123,9 @@ export const getSequencedSlots = ({ query, auth, tzid, slots = [] }) =>
|
|
|
167
123
|
}
|
|
168
124
|
// This will intentionally throw an error if the
|
|
169
125
|
// result is not in the correct format:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const allSlots = [...slots, ...returnedSlots];
|
|
173
|
-
|
|
174
|
-
if (returnedSlots.length < 512) return [...slots, ...returnedSlots];
|
|
175
|
-
|
|
176
|
-
// If we get here, the API has returned the maximum number
|
|
177
|
-
// of slots allowed, so we need to crop the query and try
|
|
178
|
-
// again to ensure we haven't missed any slots.
|
|
179
|
-
|
|
180
|
-
const startOfLastSlot = returnedSlots[returnedSlots.length - 1].start;
|
|
181
|
-
const endOfLastPeriod = query.query_periods[query.query_periods.length - 1].end;
|
|
182
|
-
const boundsForCropping = {
|
|
183
|
-
start: startOfLastSlot,
|
|
184
|
-
end: endOfLastPeriod,
|
|
185
|
-
};
|
|
186
|
-
const croppedPeriods = cropPeriodsArbitrarily(query.query_periods, boundsForCropping);
|
|
187
|
-
const croppedQuery = { ...query, query_periods: croppedPeriods };
|
|
188
|
-
|
|
189
|
-
// Rerun the query
|
|
190
|
-
return getSequencedSlots({
|
|
191
|
-
query: croppedQuery,
|
|
192
|
-
auth,
|
|
193
|
-
tzid,
|
|
194
|
-
slots: allSlots,
|
|
195
|
-
});
|
|
126
|
+
return parseSlotsResult(res);
|
|
196
127
|
});
|
|
128
|
+
};
|
|
197
129
|
|
|
198
130
|
export const parseQuery = query => {
|
|
199
131
|
if (!query.bookable_events) {
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
|
-
import { useTheme } from "./contexts/theme-context";
|
|
4
|
-
|
|
5
3
|
import { tzi18n } from "../../helpers/i18n";
|
|
6
|
-
import { useTz } from "./contexts/tz-context";
|
|
7
|
-
|
|
8
|
-
const TimeZoneSelector = ({ locale }) => {
|
|
9
|
-
const theme = useTheme();
|
|
10
|
-
const [tz, setTz] = useTz();
|
|
11
4
|
|
|
5
|
+
const TimeZoneSelector = ({ locale, theme, tz, setTz }) => {
|
|
12
6
|
const [showList, setShowList] = useState(() => false);
|
|
13
7
|
const [focused, setFocused] = useState(() => false);
|
|
14
8
|
|
|
@@ -31,27 +25,26 @@ const TimeZoneSelector = ({ locale }) => {
|
|
|
31
25
|
};
|
|
32
26
|
|
|
33
27
|
const handleKeyDown = e => {
|
|
28
|
+
const list = tz.list;
|
|
34
29
|
switch (e.key) {
|
|
35
30
|
case "ArrowDown":
|
|
36
31
|
e.preventDefault();
|
|
37
|
-
const nextFocusedItemIndex =
|
|
38
|
-
const nextItem =
|
|
39
|
-
nextFocusedItemIndex > tz.list.length - 1 ? 0 : nextFocusedItemIndex;
|
|
32
|
+
const nextFocusedItemIndex = list.findIndex(item => focused === item.tzid) + 1;
|
|
33
|
+
const nextItem = nextFocusedItemIndex > list.length - 1 ? 0 : nextFocusedItemIndex;
|
|
40
34
|
|
|
41
|
-
setFocused(
|
|
35
|
+
setFocused(list[nextItem].tzid);
|
|
42
36
|
|
|
43
37
|
break;
|
|
44
38
|
case "ArrowUp":
|
|
45
39
|
e.preventDefault();
|
|
46
|
-
const prevFocusedItemIndex =
|
|
47
|
-
const prevItem =
|
|
48
|
-
prevFocusedItemIndex < 0 ? tz.list.length - 1 : prevFocusedItemIndex;
|
|
40
|
+
const prevFocusedItemIndex = list.findIndex(item => focused === item.tzid) - 1;
|
|
41
|
+
const prevItem = prevFocusedItemIndex < 0 ? list.length - 1 : prevFocusedItemIndex;
|
|
49
42
|
|
|
50
|
-
setFocused(
|
|
43
|
+
setFocused(list[prevItem].tzid);
|
|
51
44
|
|
|
52
45
|
break;
|
|
53
46
|
case "Enter":
|
|
54
|
-
const tz =
|
|
47
|
+
const tz = list.find(item => focused === item.tzid);
|
|
55
48
|
handleOptionSelect(tz);
|
|
56
49
|
break;
|
|
57
50
|
case "Escape":
|
|
@@ -124,6 +117,7 @@ const TimeZoneSelector = ({ locale }) => {
|
|
|
124
117
|
<button
|
|
125
118
|
className={theme.classBuilder("timezone-selector--button")}
|
|
126
119
|
id="timezone-button"
|
|
120
|
+
type="button"
|
|
127
121
|
aria-haspopup="listbox"
|
|
128
122
|
aria-label="Choose a timezone"
|
|
129
123
|
aria-expanded={showList}
|
|
File without changes
|
|
File without changes
|
|
@@ -125,7 +125,7 @@ exports[`Snapshot test of Availability Rules Elements renders AvailabilityRules
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
.emotion-1 {
|
|
128
|
-
z-index:
|
|
128
|
+
z-index: 5;
|
|
129
129
|
position: relative;
|
|
130
130
|
width: 100%;
|
|
131
131
|
display: -webkit-box;
|
|
@@ -562,6 +562,7 @@ exports[`Snapshot test of Availability Rules Elements renders AvailabilityRules
|
|
|
562
562
|
display: -ms-flexbox;
|
|
563
563
|
display: flex;
|
|
564
564
|
width: 100%;
|
|
565
|
+
padding-right: 1em;
|
|
565
566
|
}
|
|
566
567
|
|
|
567
568
|
@media (max-width:650px) {
|
|
@@ -571,6 +572,11 @@ exports[`Snapshot test of Availability Rules Elements renders AvailabilityRules
|
|
|
571
572
|
}
|
|
572
573
|
|
|
573
574
|
.emotion-58 {
|
|
575
|
+
padding: 0.3em 0.6em 0 0;
|
|
576
|
+
margin: 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.emotion-59 {
|
|
574
580
|
-webkit-appearance: none;
|
|
575
581
|
-moz-appearance: none;
|
|
576
582
|
appearance: none;
|
|
@@ -598,7 +604,7 @@ exports[`Snapshot test of Availability Rules Elements renders AvailabilityRules
|
|
|
598
604
|
}
|
|
599
605
|
|
|
600
606
|
<div
|
|
601
|
-
class="PREFIX emotion-0"
|
|
607
|
+
class="PREFIX AR emotion-0"
|
|
602
608
|
>
|
|
603
609
|
<div
|
|
604
610
|
class="PREFIX__calendars emotion-1"
|
|
@@ -999,12 +1005,37 @@ exports[`Snapshot test of Availability Rules Elements renders AvailabilityRules
|
|
|
999
1005
|
<div
|
|
1000
1006
|
class="PREFIX__timezone emotion-57"
|
|
1001
1007
|
>
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1008
|
+
<p
|
|
1009
|
+
class="emotion-58"
|
|
1010
|
+
>
|
|
1011
|
+
Time Zone
|
|
1012
|
+
</p>
|
|
1013
|
+
<div
|
|
1014
|
+
class="PREFIX__timezone-selector AR__timezone-selector"
|
|
1015
|
+
>
|
|
1016
|
+
<button
|
|
1017
|
+
aria-expanded="false"
|
|
1018
|
+
aria-haspopup="listbox"
|
|
1019
|
+
aria-label="Choose a timezone"
|
|
1020
|
+
class="PREFIX__timezone-selector--button AR__timezone-selector--button"
|
|
1021
|
+
id="timezone-button"
|
|
1022
|
+
type="button"
|
|
1023
|
+
>
|
|
1024
|
+
London
|
|
1025
|
+
<svg
|
|
1026
|
+
aria-hidden="true"
|
|
1027
|
+
class="PREFIX__timezone-selector--icon AR__timezone-selector--icon"
|
|
1028
|
+
viewBox="0 0 13 7"
|
|
1029
|
+
>
|
|
1030
|
+
<path
|
|
1031
|
+
d="M.809 1.315L2.124 0l4.383 4.342L10.876.013l1.315 1.302L6.507 7l-.939-.938z"
|
|
1032
|
+
/>
|
|
1033
|
+
</svg>
|
|
1034
|
+
</button>
|
|
1035
|
+
</div>
|
|
1005
1036
|
</div>
|
|
1006
1037
|
<button
|
|
1007
|
-
class="PREFIX__submit emotion-
|
|
1038
|
+
class="PREFIX__submit emotion-59"
|
|
1008
1039
|
type="button"
|
|
1009
1040
|
>
|
|
1010
1041
|
Save new rules
|
|
@@ -7,7 +7,7 @@ import SequencedSlotButton from "../../src/js/components/DateTimePicker/Sequence
|
|
|
7
7
|
import { I18nProvider } from "../../src/js/contexts/i18n-context";
|
|
8
8
|
import { ThemeProvider } from "../../src/js/components/DateTimePicker/contexts/theme-context";
|
|
9
9
|
import { StatusProvider } from "../../src/js/components/DateTimePicker/contexts/status-context";
|
|
10
|
-
import { TzProvider } from "../../src/js/
|
|
10
|
+
import { TzProvider } from "../../src/js/contexts/tz-context";
|
|
11
11
|
|
|
12
12
|
const wrapper = ({ children, status }) => (
|
|
13
13
|
<ThemeProvider options={{ name: "DTP" }}>
|
|
@@ -7,7 +7,7 @@ import SlotButton from "../../src/js/components/DateTimePicker/SlotButton";
|
|
|
7
7
|
import { I18nProvider } from "../../src/js/contexts/i18n-context";
|
|
8
8
|
import { ThemeProvider } from "../../src/js/components/DateTimePicker/contexts/theme-context";
|
|
9
9
|
import { StatusProvider } from "../../src/js/components/DateTimePicker/contexts/status-context";
|
|
10
|
-
import { TzProvider } from "../../src/js/
|
|
10
|
+
import { TzProvider } from "../../src/js/contexts/tz-context";
|
|
11
11
|
|
|
12
12
|
const wrapper = ({ children, status }) => (
|
|
13
13
|
<ThemeProvider options={{ name: "DTP" }}>
|