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.
Files changed (58) hide show
  1. package/lib/assets/IconsSvg.d.ts +9 -0
  2. package/lib/components/FilterBar/FilterBar.stories.d.ts +1 -1
  3. package/lib/components/FilterCalendar/FilterCalendar.d.ts +1 -1
  4. package/lib/components/FilterCalendar/FilterCalendar.stories.d.ts +2 -1
  5. package/lib/components/FilterCalendar/hooks/useFilterCalendar.d.ts +3 -3
  6. package/lib/core/components/calendar/CalendarTypes.d.ts +8 -6
  7. package/lib/core/components/calendar/hooks/index.d.ts +3 -0
  8. package/lib/core/components/calendar/hooks/useCalendarLoadingSpinner.d.ts +7 -0
  9. package/lib/core/components/calendar/hooks/useCalendarTooltips.d.ts +10 -0
  10. package/lib/core/components/calendar/hooks/useUpdateDisabledDates.d.ts +13 -0
  11. package/lib/core/components/calendar/utils/calendarSelectionRules.d.ts +15 -0
  12. package/lib/core/components/calendar/utils/checkForContinuousSelection.d.ts +10 -0
  13. package/lib/core/components/calendar/utils/disabledDatesByPage.d.ts +9 -0
  14. package/lib/core/components/calendar/utils/handleCalendarModifiers.d.ts +46 -0
  15. package/lib/core/components/calendar/utils/handleRangeContextDisabledDates.d.ts +27 -0
  16. package/lib/core/components/calendar/utils/index.d.ts +8 -0
  17. package/lib/core/components/calendar/utils/nightsCount.d.ts +6 -0
  18. package/lib/core/components/calendar/utils/parseDate.d.ts +7 -0
  19. package/lib/core/components/calendar/utils/parseDates.d.ts +6 -0
  20. package/lib/index.d.ts +10 -7
  21. package/lib/index.esm.js +646 -272
  22. package/lib/index.esm.js.map +1 -1
  23. package/lib/index.js +646 -272
  24. package/lib/index.js.map +1 -1
  25. package/lib/index.umd.js +646 -272
  26. package/lib/index.umd.js.map +1 -1
  27. package/lib/themes/useTheme.d.ts +2 -0
  28. package/package.json +1 -1
  29. package/src/assets/IconsSvg.tsx +66 -0
  30. package/src/components/FilterBar/FilterBar.stories.tsx +2 -1
  31. package/src/components/FilterBar/FilterBar.tsx +1 -1
  32. package/src/components/FilterCalendar/FilterCalendar.css +8 -9
  33. package/src/components/FilterCalendar/FilterCalendar.stories.tsx +345 -158
  34. package/src/components/FilterCalendar/FilterCalendar.tsx +69 -52
  35. package/src/components/FilterCalendar/FilterCalendarTypes.ts +0 -1
  36. package/src/components/FilterCalendar/hooks/useFilterCalendar.ts +44 -4
  37. package/src/core/components/buttons/submit-button/SubmitButton.tsx +1 -4
  38. package/src/core/components/calendar/Calendar.css +24 -6
  39. package/src/core/components/calendar/Calendar.tsx +127 -376
  40. package/src/core/components/calendar/CalendarTypes.ts +9 -4
  41. package/src/core/components/calendar/hooks/index.ts +3 -0
  42. package/src/core/components/calendar/hooks/useCalendarLoadingSpinner.tsx +25 -0
  43. package/src/core/components/calendar/hooks/useCalendarTooltips.tsx +139 -0
  44. package/src/core/components/calendar/hooks/useUpdateDisabledDates.tsx +94 -0
  45. package/src/core/components/calendar/utils/calendarSelectionRules.tsx +163 -0
  46. package/src/core/components/calendar/utils/checkForContinuousSelection.tsx +50 -0
  47. package/src/core/components/calendar/utils/disabledDatesByPage.tsx +36 -0
  48. package/src/core/components/calendar/utils/handleCalendarModifiers.tsx +151 -0
  49. package/src/core/components/calendar/utils/handleRangeContextDisabledDates.tsx +70 -0
  50. package/src/core/components/calendar/utils/index.ts +8 -0
  51. package/src/themes/Default.css +6 -0
  52. package/src/themes/useTheme.tsx +3 -0
  53. package/src/assets/SpinnerSvg.tsx +0 -40
  54. package/src/core/utils/handleOverlappingDates.tsx +0 -3
  55. package/src/core/utils/index.ts +0 -3
  56. /package/src/core/{utils → components/calendar/utils}/nightsCount.tsx +0 -0
  57. /package/src/core/{utils → components/calendar/utils}/parseDate.tsx +0 -0
  58. /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
+ }