Files
pfadi-bussle/components/calendar.tsx
2022-03-30 23:56:08 +02:00

155 lines
4.5 KiB
TypeScript

import React from 'react'
import classnames from 'classnames'
import {
isPast,
isSameDay,
isAfter,
isWithinInterval,
endOfDay,
} from 'date-fns'
import Calendar from 'react-calendar'
import useBookedDays from '../helpers/useDaysBooked'
import { dateFormatBackend } from '../helpers/date'
import { getNextBigger, getNextSmaller } from '../helpers/array'
import Loading from './loading'
export default function MyCalendar({
start,
end,
onChange = null,
className,
}: {
start?: string
end?: string
onChange?: (arg0: any) => void
className?: string
}) {
const { daysBooked, daysBookedError, daysBookedLoading } = useBookedDays()
const startDate = (start && new Date(start)) || null
const endDate = (end && new Date(end)) || null
const inSelection = !!start && !end
const prevBooked = inSelection && getNextSmaller(daysBooked, start)
const nextBooked = inSelection && getNextBigger(daysBooked, start)
function isDateSelected(date: Date) {
if (!startDate) {
return false
}
if (isSameDay(date, startDate)) {
return true
}
// if end is before start, it is not within
if (endDate && !isAfter(endDate, startDate)) {
return false
}
return isWithinInterval(date, {
start: startDate,
end: endDate || startDate,
})
}
function tileClassName({ date, view }) {
const isMonthView = view === 'month'
const isDaysBookedLoaded = !!daysBooked
const isInPast = isPast(endOfDay(date))
const isBooked = daysBooked?.includes(dateFormatBackend(date))
const isSelected = isDateSelected(date)
return classnames({
'react-calendar__tile--past': isMonthView && isInPast,
'react-calendar__tile--booked':
isMonthView && isDaysBookedLoaded && isBooked && !isInPast,
'react-calendar__tile--free':
isMonthView &&
isDaysBookedLoaded &&
!isBooked &&
!isInPast &&
!isSelected,
'react-calendar__tile--selected-start':
isMonthView && isSameDay(date, startDate),
'react-calendar__tile--selected-end':
isMonthView && isSameDay(date, endDate),
'react-calendar__tile--selected': isSelected,
'react-calendar__tile--unselectable':
inSelection &&
((prevBooked && date < new Date(prevBooked)) ||
(nextBooked && date > new Date(nextBooked))),
'react-calendar__tile--not-interactive': !onChange,
})
}
if (daysBookedError) {
return (
<div>
Entschuldigen Sie, aber die Buchungszeiten konnten nicht geladen werden.
Versuchen Sie es später nochmals.
</div>
)
}
return (
<div className={className}>
<h2 className="text-xl">Belegungsplan</h2>
<Loading loading={daysBookedLoading}>
<Calendar
defaultActiveStartDate={startDate}
minDate={new Date()}
// @ts-ignore
onClickDay={(
date: Date,
event: React.MouseEvent<HTMLInputElement>
) => {
if (!onChange) {
return
}
const dateAsBackendFormat = dateFormatBackend(date);
event.preventDefault()
event.stopPropagation()
const targetClassList = event.currentTarget.classList
if (
targetClassList.contains('react-calendar__tile--past') ||
targetClassList.contains('react-calendar__tile--booked') ||
targetClassList.contains('react-calendar__tile--unselectable')
) {
return
}
// when startDate missing or both are already set
if (!startDate || (!!startDate && !!endDate)) {
onChange({ startDate: dateAsBackendFormat, endDate: null })
return
}
// when startDate set, but end missing
if (isAfter(date, startDate)) {
onChange({ endDate: dateAsBackendFormat })
} else {
onChange({ startDate: dateAsBackendFormat, endDate: dateAsBackendFormat })
}
}}
tileClassName={tileClassName}
value={null}
/>
</Loading>
<div className="mt-3 flex justify-center">
<div>
<div className="inline-block w-5 h-5 bg-green-200 rounded align-text-bottom"></div>
<span className="ml-2">Frei</span>
</div>
<div>
<div className="ml-6 inline-block w-5 h-5 bg-red-200 rounded align-text-bottom"></div>
<span className="ml-2">Belegt</span>
</div>
</div>
</div>
)
}