import dayjs from "dayjs"; import isBetween from "dayjs/plugin/isBetween"; import React, { useCallback, useContext } from "react"; import { BG_COLOR, TEXT_COLOR } from "../../constants"; import DatepickerContext from "../../contexts/DatepickerContext"; import { formatDate, nextMonth, previousMonth, classNames as cn } from "../../helpers"; import { Period } from "../../types"; dayjs.extend(isBetween); interface Props { calendarData: { date: dayjs.Dayjs; days: { previous: number[]; current: number[]; next: number[]; }; }; onClickPreviousDays: (day: number) => void; onClickDay: (day: number) => void; onClickNextDays: (day: number) => void; } const Days: React.FC = ({ calendarData, onClickPreviousDays, onClickDay, onClickNextDays }) => { // Contexts const { primaryColor, period, changePeriod, dayHover, changeDayHover, minDate, maxDate, disabledDates } = useContext(DatepickerContext); // Functions const currentDateClass = useCallback( (item: number) => { const itemDate = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${ item >= 10 ? item : "0" + item }`; if (formatDate(dayjs()) === formatDate(dayjs(itemDate))) return TEXT_COLOR["500"][primaryColor as keyof (typeof TEXT_COLOR)["500"]]; return ""; }, [calendarData.date, primaryColor] ); const activeDateData = useCallback( (day: number) => { const fullDay = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${day}`; let className = ""; if (dayjs(fullDay).isSame(period.start) && dayjs(fullDay).isSame(period.end)) { className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium rounded-full`; } else if (dayjs(fullDay).isSame(period.start)) { className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium ${ dayjs(fullDay).isSame(dayHover) && !period.end ? "c-rounded-full" : "c-rounded-l-full" }`; } else if (dayjs(fullDay).isSame(period.end)) { className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium ${ dayjs(fullDay).isSame(dayHover) && !period.start ? "c-rounded-full" : "c-rounded-r-full" }`; } return { active: dayjs(fullDay).isSame(period.start) || dayjs(fullDay).isSame(period.end), className: className }; }, [calendarData.date, dayHover, period.end, period.start, primaryColor] ); const hoverClassByDay = useCallback( (day: number) => { let className = currentDateClass(day); const fullDay = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${ day >= 10 ? day : "0" + day }`; if (period.start && period.end) { if (dayjs(fullDay).isBetween(period.start, period.end, "day", "[)")) { return ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( day )} dark:bg-white/10`; } } if (!dayHover) { return className; } if (period.start && dayjs(fullDay).isBetween(period.start, dayHover, "day", "[)")) { className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( day )} dark:bg-white/10`; } if (period.end && dayjs(fullDay).isBetween(dayHover, period.end, "day", "[)")) { className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( day )} dark:bg-white/10`; } if (dayHover === fullDay) { const bgColor = BG_COLOR["500"][primaryColor]; className = ` transition-all duration-500 text-white font-medium ${bgColor} ${ period.start ? "rounded-r-full" : "rounded-l-full" }`; } return className; }, [calendarData.date, currentDateClass, dayHover, period.end, period.start, primaryColor] ); const isDateTooEarly = useCallback( (day: number, type: "current" | "previous" | "next") => { if (!minDate) { return false; } const object = { previous: previousMonth(calendarData.date), current: calendarData.date, next: nextMonth(calendarData.date) }; const newDate = object[type as keyof typeof object]; const formattedDate = newDate.set("date", day); return dayjs(formattedDate).isSame(dayjs(minDate), "day") ? false : dayjs(formattedDate).isBefore(dayjs(minDate)); }, [calendarData.date, minDate] ); const isDateTooLate = useCallback( (day: number, type: "current" | "previous" | "next") => { if (!maxDate) { return false; } const object = { previous: previousMonth(calendarData.date), current: calendarData.date, next: nextMonth(calendarData.date) }; const newDate = object[type as keyof typeof object]; const formattedDate = newDate.set("date", day); return dayjs(formattedDate).isSame(dayjs(maxDate), "day") ? false : dayjs(formattedDate).isAfter(dayjs(maxDate)); }, [calendarData.date, maxDate] ); const isDateDisabled = useCallback( (day: number, type: "current" | "previous" | "next") => { if (isDateTooEarly(day, type) || isDateTooLate(day, type)) { return true; } const object = { previous: previousMonth(calendarData.date), current: calendarData.date, next: nextMonth(calendarData.date) }; const newDate = object[type as keyof typeof object]; const formattedDate = `${newDate.year()}-${newDate.month() + 1}-${ day >= 10 ? day : "0" + day }`; if (!disabledDates || (Array.isArray(disabledDates) && !disabledDates.length)) { return false; } let matchingCount = 0; disabledDates?.forEach(dateRange => { if ( dayjs(formattedDate).isAfter(dateRange.startDate) && dayjs(formattedDate).isBefore(dateRange.endDate) ) { matchingCount++; } if ( dayjs(formattedDate).isSame(dateRange.startDate) || dayjs(formattedDate).isSame(dateRange.endDate) ) { matchingCount++; } }); return matchingCount > 0; }, [calendarData.date, isDateTooEarly, isDateTooLate, disabledDates] ); const buttonClass = useCallback( (day: number, type: "current" | "next" | "previous") => { const baseClass = "c-flex c-items-center c-justify-center c-w-12 c-h-12 lg:c-w-10 lg:c-h-10"; if (type === "current") { return cn( baseClass, !activeDateData(day).active ? hoverClassByDay(day) : activeDateData(day).className, isDateDisabled(day, type) && "c-text-gray-400 c-cursor-not-allowed" ); } return cn(baseClass, isDateDisabled(day, type) && "c-cursor-not-allowed", "c-text-gray-400"); }, [activeDateData, hoverClassByDay, isDateDisabled] ); const checkIfHoverPeriodContainsDisabledPeriod = useCallback( (hoverPeriod: Period) => { if (!Array.isArray(disabledDates)) { return false; } for (let i = 0; i < disabledDates.length; i++) { if ( dayjs(hoverPeriod.start).isBefore(disabledDates[i].startDate) && dayjs(hoverPeriod.end).isAfter(disabledDates[i].endDate) ) { return true; } } return false; }, [disabledDates] ); const getMetaData = useCallback(() => { return { previous: previousMonth(calendarData.date), current: calendarData.date, next: nextMonth(calendarData.date) }; }, [calendarData.date]); const hoverDay = useCallback( (day: number, type: string) => { const object = getMetaData(); const newDate = object[type as keyof typeof object]; const newHover = `${newDate.year()}-${newDate.month() + 1}-${ day >= 10 ? day : "0" + day }`; if (period.start && !period.end) { const hoverPeriod = { ...period, end: newHover }; if (dayjs(newHover).isBefore(dayjs(period.start))) { hoverPeriod.start = newHover; hoverPeriod.end = period.start; if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { changePeriod({ start: null, end: period.start }); } } if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { changeDayHover(newHover); } } if (!period.start && period.end) { const hoverPeriod = { ...period, start: newHover }; if (dayjs(newHover).isAfter(dayjs(period.end))) { hoverPeriod.start = period.end; hoverPeriod.end = newHover; if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { changePeriod({ start: period.end, end: null }); } } if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { changeDayHover(newHover); } } }, [ changeDayHover, changePeriod, checkIfHoverPeriodContainsDisabledPeriod, getMetaData, period ] ); const handleClickDay = useCallback( (day: number, type: "previous" | "current" | "next") => { function continueClick() { if (type === "previous") { onClickPreviousDays(day); } if (type === "current") { onClickDay(day); } if (type === "next") { onClickNextDays(day); } } if (disabledDates?.length) { const object = getMetaData(); const newDate = object[type as keyof typeof object]; const clickDay = `${newDate.year()}-${newDate.month() + 1}-${ day >= 10 ? day : "0" + day }`; if (period.start && !period.end) { dayjs(clickDay).isSame(dayHover) && continueClick(); } else if (!period.start && period.end) { dayjs(clickDay).isSame(dayHover) && continueClick(); } else { continueClick(); } } else { continueClick(); } }, [ dayHover, disabledDates?.length, getMetaData, onClickDay, onClickNextDays, onClickPreviousDays, period.end, period.start ] ); return (
{calendarData.days.previous.map((item, index) => ( ))} {calendarData.days.current.map((item, index) => ( ))} {calendarData.days.next.map((item, index) => ( ))}
); }; export default Days;