mirror of
https://github.com/tomru/pfadi-bussle.git
synced 2026-03-03 14:37:13 +01:00
159 lines
4.6 KiB
TypeScript
159 lines
4.6 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 className="booking_calendar" loading={daysBookedLoading}>
|
|
<Calendar
|
|
defaultActiveStartDate={startDate}
|
|
locale="de"
|
|
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>
|
|
)
|
|
}
|