willba-component-library 0.2.54 → 0.2.56
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/lib/assets/IconsSvg.d.ts +9 -0
- package/lib/components/FilterBar/FilterBar.stories.d.ts +1 -1
- package/lib/components/FilterCalendar/FilterCalendar.d.ts +1 -1
- package/lib/components/FilterCalendar/FilterCalendar.stories.d.ts +2 -1
- package/lib/components/FilterCalendar/hooks/useFilterCalendar.d.ts +3 -3
- package/lib/core/components/calendar/CalendarTypes.d.ts +8 -6
- package/lib/core/components/calendar/hooks/index.d.ts +3 -0
- package/lib/core/components/calendar/hooks/useCalendarLoadingSpinner.d.ts +7 -0
- package/lib/core/components/calendar/hooks/useCalendarTooltips.d.ts +10 -0
- package/lib/core/components/calendar/hooks/useUpdateDisabledDates.d.ts +13 -0
- package/lib/core/components/calendar/utils/calendarSelectionRules.d.ts +15 -0
- package/lib/core/components/calendar/utils/checkForContinuousSelection.d.ts +10 -0
- package/lib/core/components/calendar/utils/disabledDatesByPage.d.ts +9 -0
- package/lib/core/components/calendar/utils/handleCalendarModifiers.d.ts +46 -0
- package/lib/core/components/calendar/utils/handleRangeContextDisabledDates.d.ts +27 -0
- package/lib/core/components/calendar/utils/index.d.ts +8 -0
- package/lib/core/components/calendar/utils/nightsCount.d.ts +6 -0
- package/lib/core/components/calendar/utils/parseDate.d.ts +7 -0
- package/lib/core/components/calendar/utils/parseDates.d.ts +6 -0
- package/lib/index.d.ts +10 -7
- package/lib/index.esm.js +646 -272
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +646 -272
- package/lib/index.js.map +1 -1
- package/lib/index.umd.js +646 -272
- package/lib/index.umd.js.map +1 -1
- package/lib/themes/useTheme.d.ts +2 -0
- package/package.json +1 -1
- package/src/assets/IconsSvg.tsx +66 -0
- package/src/components/FilterBar/FilterBar.stories.tsx +2 -1
- package/src/components/FilterBar/FilterBar.tsx +1 -1
- package/src/components/FilterCalendar/FilterCalendar.css +8 -9
- package/src/components/FilterCalendar/FilterCalendar.stories.tsx +345 -158
- package/src/components/FilterCalendar/FilterCalendar.tsx +69 -52
- package/src/components/FilterCalendar/FilterCalendarTypes.ts +0 -1
- package/src/components/FilterCalendar/hooks/useFilterCalendar.ts +44 -4
- package/src/core/components/buttons/submit-button/SubmitButton.tsx +1 -4
- package/src/core/components/calendar/Calendar.css +24 -6
- package/src/core/components/calendar/Calendar.tsx +127 -376
- package/src/core/components/calendar/CalendarTypes.ts +9 -4
- package/src/core/components/calendar/hooks/index.ts +3 -0
- package/src/core/components/calendar/hooks/useCalendarLoadingSpinner.tsx +25 -0
- package/src/core/components/calendar/hooks/useCalendarTooltips.tsx +139 -0
- package/src/core/components/calendar/hooks/useUpdateDisabledDates.tsx +94 -0
- package/src/core/components/calendar/utils/calendarSelectionRules.tsx +163 -0
- package/src/core/components/calendar/utils/checkForContinuousSelection.tsx +50 -0
- package/src/core/components/calendar/utils/disabledDatesByPage.tsx +36 -0
- package/src/core/components/calendar/utils/handleCalendarModifiers.tsx +151 -0
- package/src/core/components/calendar/utils/handleRangeContextDisabledDates.tsx +70 -0
- package/src/core/components/calendar/utils/index.ts +8 -0
- package/src/themes/Default.css +6 -0
- package/src/themes/useTheme.tsx +3 -0
- package/src/assets/SpinnerSvg.tsx +0 -40
- package/src/core/utils/handleOverlappingDates.tsx +0 -3
- package/src/core/utils/index.ts +0 -3
- /package/src/core/{utils → components/calendar/utils}/nightsCount.tsx +0 -0
- /package/src/core/{utils → components/calendar/utils}/parseDate.tsx +0 -0
- /package/src/core/{utils → components/calendar/utils}/parseDates.tsx +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { DateRange } from 'react-day-picker'
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
calendarRange?: DateRange
|
|
6
|
+
updateCalendarMonthNavigation?: boolean
|
|
7
|
+
overlappingDate?: DateRange[]
|
|
8
|
+
updateCalendarDefaultMoth?: number
|
|
9
|
+
showFeedback?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useCalendarTooltips = ({
|
|
13
|
+
calendarRange,
|
|
14
|
+
updateCalendarMonthNavigation,
|
|
15
|
+
overlappingDate,
|
|
16
|
+
updateCalendarDefaultMoth,
|
|
17
|
+
showFeedback,
|
|
18
|
+
}: Props) =>
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (typeof document === 'undefined' || !showFeedback) return
|
|
21
|
+
|
|
22
|
+
// Children
|
|
23
|
+
const calendarTooltip = document.querySelector('.will-calendar-tooltip')
|
|
24
|
+
const calendarTooltipCheckOut = document.querySelector(
|
|
25
|
+
'.will-calendar-tooltip-check-out'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const calendarTooltipOverlappingDate = document.querySelector(
|
|
29
|
+
'.will-calendar-tooltip-overlapping-date'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const loadingSpinner = document.querySelector(
|
|
33
|
+
'.will-filter-bar-calendar .will-calendar-spinner'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const calendarTooltipCheckInOnly = document.querySelector(
|
|
37
|
+
'.will-calendar-tooltip-check-in-only'
|
|
38
|
+
)
|
|
39
|
+
const calendarTooltipCheckOutOnly = document.querySelector(
|
|
40
|
+
'.will-calendar-tooltip-check-out-only'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// Parents
|
|
44
|
+
const calendarButtons = document.querySelectorAll(
|
|
45
|
+
'.will-filter-bar-calendar .rdp-cell:has(.booked):not(:has(.disabled-after-check-in))'
|
|
46
|
+
)
|
|
47
|
+
const calendarButtonsCheckOut = document.querySelectorAll(
|
|
48
|
+
'.will-filter-bar-calendar .rdp-cell:has(.booked.disabled-after-check-in)'
|
|
49
|
+
)
|
|
50
|
+
const calendarMonthContainer = document.querySelector(
|
|
51
|
+
'.will-filter-bar-calendar .rdp-months'
|
|
52
|
+
)
|
|
53
|
+
const calendarOverlappingDate = document.querySelectorAll(
|
|
54
|
+
'.will-filter-bar-calendar .rdp-cell:has(.overlapping-date)'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const calendarCheckInOnly = document.querySelectorAll(
|
|
58
|
+
'.will-filter-bar-calendar .rdp-cell:has(.check-in-only)'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const calendarCheckOutOnly = document.querySelectorAll(
|
|
62
|
+
'.will-filter-bar-calendar .rdp-cell:has(.check-out-only)'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Append children to parents
|
|
66
|
+
|
|
67
|
+
const tooltipClonesCheckIn: Element[] = []
|
|
68
|
+
const tooltipClonesCheckOut: Element[] = []
|
|
69
|
+
const tooltipClonesCheckInOnly: Element[] = []
|
|
70
|
+
const tooltipClonesCheckOutOnly: Element[] = []
|
|
71
|
+
const tooltipClonesSpinner: Element[] = []
|
|
72
|
+
const tooltipClonesOverlappingDates: Element[] = []
|
|
73
|
+
|
|
74
|
+
if (calendarTooltip && calendarButtons.length > 0) {
|
|
75
|
+
calendarButtons.forEach((element) => {
|
|
76
|
+
const tooltipClone: Element = calendarTooltip.cloneNode(true) as Element
|
|
77
|
+
element.appendChild(tooltipClone)
|
|
78
|
+
tooltipClonesCheckIn.push(tooltipClone)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (calendarTooltipCheckOut && calendarButtonsCheckOut.length > 0) {
|
|
83
|
+
calendarButtonsCheckOut.forEach((element) => {
|
|
84
|
+
const tooltipClone: Element = calendarTooltipCheckOut.cloneNode(
|
|
85
|
+
true
|
|
86
|
+
) as Element
|
|
87
|
+
element.appendChild(tooltipClone)
|
|
88
|
+
tooltipClonesCheckOut.push(tooltipClone)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (calendarTooltipCheckOutOnly && calendarCheckOutOnly.length > 0) {
|
|
93
|
+
calendarCheckOutOnly.forEach((element) => {
|
|
94
|
+
const tooltipClone: Element = calendarTooltipCheckOutOnly.cloneNode(
|
|
95
|
+
true
|
|
96
|
+
) as Element
|
|
97
|
+
element.appendChild(tooltipClone)
|
|
98
|
+
tooltipClonesCheckOutOnly.push(tooltipClone)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (calendarTooltipCheckInOnly && calendarCheckInOnly.length > 0) {
|
|
103
|
+
calendarCheckInOnly.forEach((element) => {
|
|
104
|
+
const tooltipClone: Element = calendarTooltipCheckInOnly.cloneNode(
|
|
105
|
+
true
|
|
106
|
+
) as Element
|
|
107
|
+
element.appendChild(tooltipClone)
|
|
108
|
+
tooltipClonesCheckInOnly.push(tooltipClone)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (loadingSpinner && calendarMonthContainer) {
|
|
113
|
+
const tooltipClone: Element = loadingSpinner.cloneNode(true) as Element
|
|
114
|
+
calendarMonthContainer.appendChild(tooltipClone)
|
|
115
|
+
tooltipClonesSpinner.push(tooltipClone)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (calendarTooltipOverlappingDate && calendarOverlappingDate.length > 0) {
|
|
119
|
+
calendarOverlappingDate.forEach((element) => {
|
|
120
|
+
const tooltipClone: Element = calendarTooltipOverlappingDate.cloneNode(
|
|
121
|
+
true
|
|
122
|
+
) as Element
|
|
123
|
+
element.appendChild(tooltipClone)
|
|
124
|
+
tooltipClonesOverlappingDates.push(tooltipClone)
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
tooltipClonesCheckIn.forEach((clone) => clone.remove())
|
|
130
|
+
tooltipClonesCheckOut.forEach((clone) => clone.remove())
|
|
131
|
+
tooltipClonesSpinner.forEach((clone) => clone.remove())
|
|
132
|
+
tooltipClonesOverlappingDates.forEach((clone) => clone.remove())
|
|
133
|
+
}
|
|
134
|
+
}, [
|
|
135
|
+
calendarRange,
|
|
136
|
+
updateCalendarMonthNavigation,
|
|
137
|
+
overlappingDate,
|
|
138
|
+
updateCalendarDefaultMoth,
|
|
139
|
+
])
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
2
|
+
import { addDays, format } from 'date-fns'
|
|
3
|
+
import { DateRange } from 'react-day-picker'
|
|
4
|
+
|
|
5
|
+
import { DisableCalendarDates } from '../CalendarTypes'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
disableCalendarDates?: DisableCalendarDates
|
|
9
|
+
calendarRange?: DateRange
|
|
10
|
+
updateCalendarMonthNavigation?: boolean
|
|
11
|
+
updateCalendarDefaultMoth?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useUpdateDisabledDates = ({
|
|
15
|
+
disableCalendarDates,
|
|
16
|
+
calendarRange,
|
|
17
|
+
updateCalendarMonthNavigation,
|
|
18
|
+
updateCalendarDefaultMoth,
|
|
19
|
+
}: Props) => {
|
|
20
|
+
const [overlappingDate, setOverlappingDate] = useState<
|
|
21
|
+
DateRange[] | undefined
|
|
22
|
+
>(undefined)
|
|
23
|
+
|
|
24
|
+
const newDisableCalendarDates = useMemo(() => {
|
|
25
|
+
if (disableCalendarDates?.availableDates) {
|
|
26
|
+
const dateFormat = 'dd-MM-yyyy'
|
|
27
|
+
|
|
28
|
+
const { disabledDates } = disableCalendarDates
|
|
29
|
+
|
|
30
|
+
const { updatedDisabledDates, newOverlappingDates } = (
|
|
31
|
+
disabledDates || []
|
|
32
|
+
).reduce(
|
|
33
|
+
(
|
|
34
|
+
acc: {
|
|
35
|
+
updatedDisabledDates: { to: Date; from: Date }[]
|
|
36
|
+
newOverlappingDates: { to: Date; from: Date }[]
|
|
37
|
+
},
|
|
38
|
+
dateRange
|
|
39
|
+
) => {
|
|
40
|
+
const formattedFromDate = format(dateRange.from, dateFormat)
|
|
41
|
+
const formattedToDate = format(dateRange.to, dateFormat)
|
|
42
|
+
|
|
43
|
+
const hasTwoOverlappingDates =
|
|
44
|
+
disableCalendarDates.availableDates?.some(
|
|
45
|
+
(item) =>
|
|
46
|
+
format(item.lastCheckOut, dateFormat) === formattedFromDate &&
|
|
47
|
+
format(item.lastCheckOut, dateFormat) === formattedToDate
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const hasOneOverlappingDate =
|
|
51
|
+
disableCalendarDates.availableDates?.some(
|
|
52
|
+
(item) =>
|
|
53
|
+
format(item.lastCheckOut, dateFormat) === formattedFromDate &&
|
|
54
|
+
format(item.lastCheckOut, dateFormat) !== formattedToDate
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (hasTwoOverlappingDates) {
|
|
58
|
+
acc.newOverlappingDates.push(dateRange)
|
|
59
|
+
} else if (hasOneOverlappingDate) {
|
|
60
|
+
acc.newOverlappingDates.push(dateRange)
|
|
61
|
+
acc.updatedDisabledDates.push({
|
|
62
|
+
...dateRange,
|
|
63
|
+
from: addDays(dateRange.from, 1),
|
|
64
|
+
})
|
|
65
|
+
} else {
|
|
66
|
+
acc.updatedDisabledDates.push(dateRange)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return acc
|
|
70
|
+
},
|
|
71
|
+
{ updatedDisabledDates: [], newOverlappingDates: [] }
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if (newOverlappingDates.length) {
|
|
75
|
+
setOverlappingDate((prev = []) => [...prev, ...newOverlappingDates])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const newDisableCalendarDates = {
|
|
79
|
+
...disableCalendarDates,
|
|
80
|
+
disabledDates: updatedDisabledDates,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return newDisableCalendarDates
|
|
84
|
+
}
|
|
85
|
+
return disableCalendarDates
|
|
86
|
+
}, [
|
|
87
|
+
disableCalendarDates,
|
|
88
|
+
calendarRange,
|
|
89
|
+
updateCalendarMonthNavigation,
|
|
90
|
+
updateCalendarDefaultMoth,
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
return { newDisableCalendarDates, overlappingDate }
|
|
94
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addDays,
|
|
3
|
+
startOfDay,
|
|
4
|
+
format,
|
|
5
|
+
isAfter,
|
|
6
|
+
isBefore,
|
|
7
|
+
endOfDay,
|
|
8
|
+
} from 'date-fns'
|
|
9
|
+
import { DateRange, Matcher } from 'react-day-picker'
|
|
10
|
+
|
|
11
|
+
import { DisableCalendarDates, RangeContext } from '../CalendarTypes'
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
range: DateRange | undefined
|
|
15
|
+
newDisableCalendarDates?: DisableCalendarDates
|
|
16
|
+
setCalendarRange: (range: DateRange | undefined) => void
|
|
17
|
+
setDisabledDates?: (arg: Matcher[]) => void
|
|
18
|
+
setCalendarHasError?: (arg: boolean) => void
|
|
19
|
+
calendarRange?: DateRange
|
|
20
|
+
overlappingDate?: DateRange[]
|
|
21
|
+
calendarHasError?: boolean
|
|
22
|
+
rangeContext?: RangeContext
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const calendarSelectionRules = ({
|
|
26
|
+
range,
|
|
27
|
+
newDisableCalendarDates,
|
|
28
|
+
setCalendarRange,
|
|
29
|
+
setDisabledDates,
|
|
30
|
+
calendarRange,
|
|
31
|
+
overlappingDate,
|
|
32
|
+
setCalendarHasError,
|
|
33
|
+
rangeContext,
|
|
34
|
+
}: Props) => {
|
|
35
|
+
// Get and parse needed data
|
|
36
|
+
const dateFormat = 'dd-MM-yyyy'
|
|
37
|
+
const rangeFrom = range?.from ? format(range.from, dateFormat) : null
|
|
38
|
+
const rangeTo = range?.to ? format(range.to, dateFormat) : null
|
|
39
|
+
const calendarFrom = calendarRange?.from
|
|
40
|
+
? format(calendarRange?.from, dateFormat)
|
|
41
|
+
: null
|
|
42
|
+
const calendarTo = calendarRange?.to
|
|
43
|
+
? format(calendarRange?.to, dateFormat)
|
|
44
|
+
: null
|
|
45
|
+
const rangeContextFrom = rangeContext?.from
|
|
46
|
+
? format(rangeContext.from, dateFormat)
|
|
47
|
+
: null
|
|
48
|
+
|
|
49
|
+
const overlappingDateFrom = overlappingDate?.length
|
|
50
|
+
? overlappingDate.find((date) =>
|
|
51
|
+
date.from ? format(date.from, dateFormat) === rangeFrom : false
|
|
52
|
+
)
|
|
53
|
+
: null
|
|
54
|
+
|
|
55
|
+
const checkOutRange = newDisableCalendarDates?.availableDates?.length
|
|
56
|
+
? newDisableCalendarDates.availableDates.find(
|
|
57
|
+
(checkInDate) =>
|
|
58
|
+
format(checkInDate.checkIn || 1, dateFormat) ===
|
|
59
|
+
format(range?.from || 1, dateFormat)
|
|
60
|
+
)
|
|
61
|
+
: null
|
|
62
|
+
|
|
63
|
+
// On check-in, disable future dates that are unavailable for checkout
|
|
64
|
+
disableFutureDates({
|
|
65
|
+
rangeFrom,
|
|
66
|
+
checkOutRange,
|
|
67
|
+
setDisabledDates,
|
|
68
|
+
dateFormat,
|
|
69
|
+
newDisableCalendarDates,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Calendar selection rules
|
|
73
|
+
switch (true) {
|
|
74
|
+
case !!overlappingDateFrom:
|
|
75
|
+
// Clear the selection when overlapping dates are detected
|
|
76
|
+
return setCalendarRange(undefined)
|
|
77
|
+
|
|
78
|
+
case !!((!rangeTo && !rangeFrom && calendarFrom) || rangeTo === rangeFrom):
|
|
79
|
+
// Handle double-click on the same date
|
|
80
|
+
return setCalendarRange({ from: calendarRange?.from, to: undefined })
|
|
81
|
+
|
|
82
|
+
case range?.to &&
|
|
83
|
+
calendarRange?.to &&
|
|
84
|
+
isAfter(endOfDay(range.to), endOfDay(calendarRange.to)) &&
|
|
85
|
+
!(
|
|
86
|
+
range?.to &&
|
|
87
|
+
rangeContext?.to &&
|
|
88
|
+
isAfter(startOfDay(range.to), startOfDay(rangeContext.to))
|
|
89
|
+
):
|
|
90
|
+
// Handle checkout selection greater than current checkout
|
|
91
|
+
return setCalendarRange({ from: range?.to, to: undefined })
|
|
92
|
+
|
|
93
|
+
case calendarFrom && rangeFrom && rangeFrom !== calendarFrom:
|
|
94
|
+
// Handle check-in selection prior to current check-in
|
|
95
|
+
return setCalendarRange({ from: range?.from, to: undefined })
|
|
96
|
+
|
|
97
|
+
case checkOutRange && range?.to && checkOutRange.lastCheckOut < range.to:
|
|
98
|
+
return setCalendarRange({ from: range?.to, to: undefined })
|
|
99
|
+
|
|
100
|
+
// Handle checkout selection between the range context and first passible check-in
|
|
101
|
+
case range?.to &&
|
|
102
|
+
rangeContext?.from &&
|
|
103
|
+
isBefore(startOfDay(range.to), startOfDay(rangeContext.from)):
|
|
104
|
+
return setCalendarRange({ from: range?.to, to: undefined })
|
|
105
|
+
|
|
106
|
+
case range?.from &&
|
|
107
|
+
rangeContext?.to &&
|
|
108
|
+
isAfter(startOfDay(range.from), startOfDay(rangeContext.to)):
|
|
109
|
+
return (
|
|
110
|
+
setCalendarRange(undefined),
|
|
111
|
+
setCalendarHasError && setCalendarHasError(true)
|
|
112
|
+
)
|
|
113
|
+
//
|
|
114
|
+
|
|
115
|
+
default:
|
|
116
|
+
// Apply the given range
|
|
117
|
+
return setCalendarRange(range)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/////////
|
|
122
|
+
|
|
123
|
+
const disableFutureDates = ({
|
|
124
|
+
rangeFrom,
|
|
125
|
+
checkOutRange,
|
|
126
|
+
setDisabledDates,
|
|
127
|
+
dateFormat,
|
|
128
|
+
newDisableCalendarDates,
|
|
129
|
+
}: {
|
|
130
|
+
rangeFrom: string | null
|
|
131
|
+
checkOutRange?:
|
|
132
|
+
| NonNullable<DisableCalendarDates['availableDates']>['0']
|
|
133
|
+
| null
|
|
134
|
+
setDisabledDates?: (arg: Matcher[]) => void
|
|
135
|
+
dateFormat: string
|
|
136
|
+
newDisableCalendarDates?: DisableCalendarDates
|
|
137
|
+
}) => {
|
|
138
|
+
if (rangeFrom && checkOutRange && setDisabledDates) {
|
|
139
|
+
// Get and parse data
|
|
140
|
+
const checkIn = addDays(checkOutRange.checkIn, 1)
|
|
141
|
+
const firstCheckOut = addDays(checkOutRange.firstCheckOut, -1)
|
|
142
|
+
const noDatesRange =
|
|
143
|
+
format(checkIn, dateFormat) ===
|
|
144
|
+
format(checkOutRange.firstCheckOut, dateFormat)
|
|
145
|
+
|
|
146
|
+
// -------------------
|
|
147
|
+
|
|
148
|
+
setDisabledDates([
|
|
149
|
+
// Will disable all dates before the check-in date
|
|
150
|
+
// { before: findCheckOutRange?.checkIn },
|
|
151
|
+
|
|
152
|
+
...(newDisableCalendarDates?.disabledDates || []),
|
|
153
|
+
|
|
154
|
+
{
|
|
155
|
+
from: noDatesRange ? undefined : checkIn,
|
|
156
|
+
to: noDatesRange ? undefined : firstCheckOut,
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Will disable all dates after the last possible check-out after a check-in has been selected
|
|
160
|
+
//{ after: checkOutRange?.lastCheckOut },
|
|
161
|
+
])
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DateRange } from 'react-day-picker'
|
|
2
|
+
import { isAfter, isEqual, isBefore, endOfDay } from 'date-fns'
|
|
3
|
+
|
|
4
|
+
import { RangeContext } from '../CalendarTypes'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
setCalendarHasError?: (arg: boolean) => void
|
|
8
|
+
rangeContext?: RangeContext
|
|
9
|
+
calendarRange?: DateRange
|
|
10
|
+
calendarHasError?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Case: If the selected dates do not overlap with the rangeContext during continuous selection,
|
|
14
|
+
// set setCalendarHasError to true. This will display an error message and
|
|
15
|
+
// prevent the submission of the selected dates.
|
|
16
|
+
|
|
17
|
+
export const checkForContinuousSelection = ({
|
|
18
|
+
setCalendarHasError,
|
|
19
|
+
rangeContext,
|
|
20
|
+
calendarRange,
|
|
21
|
+
calendarHasError,
|
|
22
|
+
}: Props) => {
|
|
23
|
+
const calendarRangeFrom = calendarRange?.from || null
|
|
24
|
+
const calendarRangeTo = calendarRange?.to || null
|
|
25
|
+
const rangeContextFrom = rangeContext?.from || null
|
|
26
|
+
const rangeContextTo = rangeContext?.to || null
|
|
27
|
+
|
|
28
|
+
// Checking if rangeFrom is equal to or before rangeContextTo
|
|
29
|
+
const startIsEqualOrBeforeRangeContextEnd =
|
|
30
|
+
calendarRangeFrom && rangeContextTo
|
|
31
|
+
? isBefore(endOfDay(calendarRangeFrom), endOfDay(rangeContextTo)) ||
|
|
32
|
+
isEqual(endOfDay(calendarRangeFrom), endOfDay(rangeContextTo))
|
|
33
|
+
: null
|
|
34
|
+
|
|
35
|
+
// Checking if rangeTo is equal to or after rangeContextFrom
|
|
36
|
+
const endIsEqualOrAfterRangeContextStart =
|
|
37
|
+
calendarRangeTo && rangeContextFrom && rangeContextTo
|
|
38
|
+
? isAfter(endOfDay(calendarRangeTo), endOfDay(rangeContextFrom)) ||
|
|
39
|
+
isEqual(endOfDay(calendarRangeTo), endOfDay(rangeContextFrom))
|
|
40
|
+
: null
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
(rangeContext &&
|
|
44
|
+
calendarRangeFrom &&
|
|
45
|
+
!startIsEqualOrBeforeRangeContextEnd) ||
|
|
46
|
+
(rangeContext && calendarRangeTo && !endIsEqualOrAfterRangeContextStart)
|
|
47
|
+
) {
|
|
48
|
+
setCalendarHasError && !calendarHasError && setCalendarHasError(true)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { addDays } from 'date-fns'
|
|
2
|
+
|
|
3
|
+
import { DisableCalendarDates } from '../CalendarTypes'
|
|
4
|
+
|
|
5
|
+
export const disabledDatesByPage = ({
|
|
6
|
+
newDisableCalendarDates,
|
|
7
|
+
selectedPath,
|
|
8
|
+
today,
|
|
9
|
+
}: {
|
|
10
|
+
newDisableCalendarDates?: DisableCalendarDates
|
|
11
|
+
selectedPath?: string
|
|
12
|
+
today: Date
|
|
13
|
+
}) => {
|
|
14
|
+
const daysToOffsetCalendar =
|
|
15
|
+
newDisableCalendarDates?.disabledDatesByPage && selectedPath
|
|
16
|
+
? [
|
|
17
|
+
newDisableCalendarDates?.disabledDatesByPage?.find(
|
|
18
|
+
(item) => selectedPath === item.page
|
|
19
|
+
),
|
|
20
|
+
]
|
|
21
|
+
: []
|
|
22
|
+
|
|
23
|
+
return daysToOffsetCalendar.length
|
|
24
|
+
? [
|
|
25
|
+
{
|
|
26
|
+
from: addDays(
|
|
27
|
+
today,
|
|
28
|
+
daysToOffsetCalendar.length && daysToOffsetCalendar[0]?.offset
|
|
29
|
+
? daysToOffsetCalendar[0]?.offset
|
|
30
|
+
: -2
|
|
31
|
+
),
|
|
32
|
+
to: addDays(today, -100),
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
: []
|
|
36
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { format, addDays, endOfDay, differenceInDays, isEqual } from 'date-fns'
|
|
2
|
+
import { DateRange, Matcher } from 'react-day-picker'
|
|
3
|
+
|
|
4
|
+
import { DisableCalendarDates, RangeContext } from '../CalendarTypes'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
newDisableCalendarDates?: DisableCalendarDates
|
|
8
|
+
calendarRange?: DateRange
|
|
9
|
+
disabledDatesByPage: {
|
|
10
|
+
from: Date
|
|
11
|
+
to: Date
|
|
12
|
+
}[]
|
|
13
|
+
disabledDates?: Matcher[]
|
|
14
|
+
overlappingDate?: DateRange[]
|
|
15
|
+
rangeContext?: RangeContext
|
|
16
|
+
findFirstPossibleRangeContextCheckIn?: NonNullable<
|
|
17
|
+
DisableCalendarDates['availableDates']
|
|
18
|
+
>['0']
|
|
19
|
+
findLastPossibleRangeContextCheckOut?: NonNullable<
|
|
20
|
+
DisableCalendarDates['availableDates']
|
|
21
|
+
>['0']
|
|
22
|
+
firstPossibleRangeContextCheckIn: Matcher[]
|
|
23
|
+
lastPossibleRangeContextCheckOut: Matcher[]
|
|
24
|
+
currentSelectionLastCheckoutDate?: NonNullable<
|
|
25
|
+
DisableCalendarDates['availableDates']
|
|
26
|
+
>['0']
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const handleCalendarModifiers = ({
|
|
30
|
+
newDisableCalendarDates,
|
|
31
|
+
calendarRange,
|
|
32
|
+
disabledDatesByPage,
|
|
33
|
+
disabledDates,
|
|
34
|
+
overlappingDate,
|
|
35
|
+
rangeContext,
|
|
36
|
+
firstPossibleRangeContextCheckIn,
|
|
37
|
+
lastPossibleRangeContextCheckOut,
|
|
38
|
+
findFirstPossibleRangeContextCheckIn,
|
|
39
|
+
findLastPossibleRangeContextCheckOut,
|
|
40
|
+
currentSelectionLastCheckoutDate,
|
|
41
|
+
}: Props) => {
|
|
42
|
+
// Create range for range context middle selection and current overlapping selection
|
|
43
|
+
const rangeContextMiddleSelection =
|
|
44
|
+
rangeContext?.from && rangeContext?.to
|
|
45
|
+
? Array.from(
|
|
46
|
+
{ length: differenceInDays(rangeContext.to, rangeContext.from) - 1 },
|
|
47
|
+
(_, i) => ({
|
|
48
|
+
from: addDays(rangeContext.from, i + 1),
|
|
49
|
+
to: addDays(rangeContext.from, i + 1),
|
|
50
|
+
})
|
|
51
|
+
).filter(
|
|
52
|
+
(date) =>
|
|
53
|
+
!(
|
|
54
|
+
(calendarRange?.from &&
|
|
55
|
+
isEqual(endOfDay(calendarRange.from), endOfDay(date.from))) ||
|
|
56
|
+
(calendarRange?.to &&
|
|
57
|
+
isEqual(endOfDay(calendarRange.to), endOfDay(date.from)))
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
: []
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
booked: disabledDatesByPage.length
|
|
64
|
+
? disabledDatesByPage
|
|
65
|
+
: disabledDates?.length
|
|
66
|
+
? [
|
|
67
|
+
...disabledDates,
|
|
68
|
+
...firstPossibleRangeContextCheckIn,
|
|
69
|
+
...lastPossibleRangeContextCheckOut,
|
|
70
|
+
]
|
|
71
|
+
: newDisableCalendarDates?.disabledDates?.length
|
|
72
|
+
? [
|
|
73
|
+
...newDisableCalendarDates?.disabledDates,
|
|
74
|
+
...firstPossibleRangeContextCheckIn,
|
|
75
|
+
...lastPossibleRangeContextCheckOut,
|
|
76
|
+
]
|
|
77
|
+
: [],
|
|
78
|
+
|
|
79
|
+
disabledAfterCheckIn: calendarRange?.from
|
|
80
|
+
? [{ after: calendarRange.from }]
|
|
81
|
+
: [],
|
|
82
|
+
|
|
83
|
+
overlappingDate: [
|
|
84
|
+
...(!calendarRange?.from && !!overlappingDate?.length && !rangeContext
|
|
85
|
+
? overlappingDate.map((date) => ({ from: date.from }))
|
|
86
|
+
: []),
|
|
87
|
+
],
|
|
88
|
+
|
|
89
|
+
noActiveSelectionStart:
|
|
90
|
+
rangeContext?.from &&
|
|
91
|
+
!(
|
|
92
|
+
calendarRange?.from &&
|
|
93
|
+
rangeContext?.from &&
|
|
94
|
+
isEqual(endOfDay(rangeContext.from), endOfDay(calendarRange.from))
|
|
95
|
+
)
|
|
96
|
+
? rangeContext.from
|
|
97
|
+
: [],
|
|
98
|
+
|
|
99
|
+
noActiveSelectionMid: [
|
|
100
|
+
...(rangeContextMiddleSelection.length
|
|
101
|
+
? rangeContextMiddleSelection
|
|
102
|
+
: []),
|
|
103
|
+
],
|
|
104
|
+
|
|
105
|
+
noActiveSelectionEnd:
|
|
106
|
+
rangeContext?.to &&
|
|
107
|
+
!(
|
|
108
|
+
calendarRange?.from &&
|
|
109
|
+
rangeContext?.to &&
|
|
110
|
+
isEqual(endOfDay(rangeContext.to), endOfDay(calendarRange.from))
|
|
111
|
+
)
|
|
112
|
+
? rangeContext.to
|
|
113
|
+
: [],
|
|
114
|
+
|
|
115
|
+
checkoutOptionsMid: [
|
|
116
|
+
...(calendarRange?.from &&
|
|
117
|
+
!calendarRange?.to &&
|
|
118
|
+
currentSelectionLastCheckoutDate?.lastCheckOut
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
after: calendarRange.from,
|
|
122
|
+
before: addDays(currentSelectionLastCheckoutDate.lastCheckOut, 1),
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
: []),
|
|
126
|
+
],
|
|
127
|
+
|
|
128
|
+
checkInOnly: [
|
|
129
|
+
...(findFirstPossibleRangeContextCheckIn?.checkIn && rangeContext
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
from: findFirstPossibleRangeContextCheckIn.checkIn,
|
|
133
|
+
to: addDays(rangeContext.from, -1),
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
: []),
|
|
137
|
+
],
|
|
138
|
+
|
|
139
|
+
checkOutOnly: [
|
|
140
|
+
...(findLastPossibleRangeContextCheckOut?.checkIn &&
|
|
141
|
+
findLastPossibleRangeContextCheckOut.lastCheckOut
|
|
142
|
+
? [
|
|
143
|
+
{
|
|
144
|
+
from: addDays(findLastPossibleRangeContextCheckOut.checkIn, 1),
|
|
145
|
+
to: findLastPossibleRangeContextCheckOut.lastCheckOut,
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
: []),
|
|
149
|
+
],
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DateRange, Matcher } from 'react-day-picker'
|
|
2
|
+
import { isEqual, format } from 'date-fns'
|
|
3
|
+
|
|
4
|
+
import { DisableCalendarDates, RangeContext } from '../CalendarTypes'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
rangeContext?: RangeContext
|
|
8
|
+
availableDates?: DisableCalendarDates['availableDates']
|
|
9
|
+
calendarRange?: DateRange
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const handleRangeContextDisabledDates = ({
|
|
13
|
+
rangeContext,
|
|
14
|
+
availableDates,
|
|
15
|
+
calendarRange,
|
|
16
|
+
}: Props) => {
|
|
17
|
+
let findFirstPossibleRangeContextCheckIn:
|
|
18
|
+
| NonNullable<DisableCalendarDates['availableDates']>['0']
|
|
19
|
+
| undefined
|
|
20
|
+
let findLastPossibleRangeContextCheckOut:
|
|
21
|
+
| NonNullable<DisableCalendarDates['availableDates']>['0']
|
|
22
|
+
| undefined
|
|
23
|
+
|
|
24
|
+
let firstPossibleRangeContextCheckIn: Matcher[] = []
|
|
25
|
+
let lastPossibleRangeContextCheckOut: Matcher[] = []
|
|
26
|
+
|
|
27
|
+
if (rangeContext && availableDates?.length) {
|
|
28
|
+
// Find the first possible check-in after the last date gap till the range context
|
|
29
|
+
findFirstPossibleRangeContextCheckIn = availableDates?.find(
|
|
30
|
+
(date) =>
|
|
31
|
+
date.checkIn < rangeContext.from &&
|
|
32
|
+
date.lastCheckOut >= rangeContext.from
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (findFirstPossibleRangeContextCheckIn?.checkIn) {
|
|
36
|
+
firstPossibleRangeContextCheckIn.push({
|
|
37
|
+
before: findFirstPossibleRangeContextCheckIn.checkIn,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find the last possible checkout before the first date gap till the range context
|
|
42
|
+
findLastPossibleRangeContextCheckOut = availableDates?.find((date) =>
|
|
43
|
+
isEqual(rangeContext.to, date.checkIn)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if (findLastPossibleRangeContextCheckOut?.checkIn) {
|
|
47
|
+
lastPossibleRangeContextCheckOut.push({
|
|
48
|
+
after: findLastPossibleRangeContextCheckOut.lastCheckOut,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get last possible check-out dates for current check-in
|
|
54
|
+
const currentSelectionLastCheckoutDate = availableDates?.find((date) => {
|
|
55
|
+
const calendarCheckIn = calendarRange?.from
|
|
56
|
+
? format(calendarRange.from, 'dd.MM.yyyy')
|
|
57
|
+
: null
|
|
58
|
+
const itemCheckIn = format(date.checkIn, 'dd.MM.yyyy')
|
|
59
|
+
|
|
60
|
+
return calendarCheckIn ? itemCheckIn === calendarCheckIn : false
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
findFirstPossibleRangeContextCheckIn,
|
|
65
|
+
findLastPossibleRangeContextCheckOut,
|
|
66
|
+
firstPossibleRangeContextCheckIn,
|
|
67
|
+
lastPossibleRangeContextCheckOut,
|
|
68
|
+
currentSelectionLastCheckoutDate,
|
|
69
|
+
}
|
|
70
|
+
}
|