import { useImperativeHandle, useLayoutEffect, forwardRef, useEffect, useState, type Ref, useRef } from "react"; import { TouchableOpacity, type ViewStyle, View } from "react-native"; import type { IMonthSelectorRef, CalendarMonth } from "./type"; import type IMonthSelectorProps from "./type"; import stylesheet, { useStyles } from "./stylesheet"; import { dateSelect } from "./util"; import { NCoreUIKitLocalize, NCoreUIKitTheme, NCoreUIKitToast } from "../../core/hooks"; import { type Mutable, webStyle } from "../../types"; import { ChevronRight as ChevronRightIcon, ChevronLeft as ChevronLeftIcon } from "lucide-react-native"; import moment from "moment"; import Text from "../text"; const MONTH_LINES = [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9, 10, 11 ] ]; const MonthSelector = ({ multipleSelectMinimumRequiredDayCount, monthOfYearLengthType = "long", isShowTodayIndicator = true, viewDate: viewDateProp, multipleSelectDayLimit, setIsSheetContentReady, selectMultipleObject, selectObject, dateRange, maxDate, minDate, variant, date }: IMonthSelectorProps, ref: Ref) => { const { radiuses, borders, spaces, colors } = NCoreUIKitTheme.useContext(); const { localize } = NCoreUIKitLocalize.useContext(); const selectDate = dateSelect({ dateRange, variant, date }); const [ viewDate, setViewDate ] = useState(viewDateProp ? viewDateProp : selectDate); const [ yearMonths, setYearMonths ] = useState>([]); const allSelectedMonths = useRef>([]); const { nextPrevToolChevronButton: nextPrevToolChevronButtonDynamicStyle, nextPrevToolContainer: nextPrevToolContainerDynamicStyle, todayIndicator: todayIndicatorDynamicStyle, day: dayDynamicStyle } = useStyles({ radiuses, borders, colors, spaces }); useEffect(() => { const allMonths = getMonthsOfViewYear({ tDate: viewDate }); setYearMonths(allMonths); }, [ dateRange, viewDate, date ]); useLayoutEffect(() => { if(yearMonths && yearMonths.length) setIsSheetContentReady(true); }, [ yearMonths ]); useEffect(() => { if(viewDateProp) { setViewDate(viewDateProp); } }, [viewDateProp]); useImperativeHandle( ref, () => ({ changeCurrentYear: (date: Date) => { setViewDate(date); } }), [] ); const getMonthsOfViewYear = ({ tDate }: { tDate: Date }): Array => { allSelectedMonths.current = []; const targetDate = moment(new Date(tDate)); const startOfCalendar = targetDate.clone().startOf("year"); const months = monthOfYearLengthType === "short" ? moment.monthsShort() : moment.months(); const totalMonths = months.length; const allMonths = Array.from({ length: totalMonths }).map((_, index) => { const currentMonth = startOfCalendar.clone().add(index, "months"); let isSelected = false; if(variant === "single" && date) { allSelectedMonths.current = [date]; if(moment(currentMonth).isSame(date, "month")) isSelected = true; } else if(variant === "range" && dateRange && dateRange.start) { if(dateRange.end) { allSelectedMonths.current = Array.from({ length: moment(dateRange.end).diff(dateRange.start, "months") + 1 }, (_, i) => moment(dateRange.start).clone().add(i, "months").toDate() ); if(moment(currentMonth).isBetween(dateRange.start, dateRange.end, "month", "[]")) isSelected = true; } else if(moment(currentMonth).isSame(dateRange.start)) { allSelectedMonths.current = [dateRange.start]; isSelected = true; } } let isDisabled = false; if(minDate && moment(currentMonth).isBefore(moment(minDate).startOf("month"))) { isDisabled = true; } if(maxDate && moment(currentMonth).isAfter(moment(maxDate).endOf("month"))) { isDisabled = true; } const monthNumber = currentMonth.month(); return { originalIndex: allSelectedMonths.current.findIndex((month) => moment(currentMonth).isSame(month, "month")), isCurrentYear: currentMonth.isSame(targetDate, "year"), isToday: currentMonth.isSame(moment(), "month"), title: months[monthNumber] as string, monthNumber: monthNumber + 1, monthOfYear: monthNumber, date: currentMonth, isDisabled, isSelected }; }); return allMonths; }; const renderDay = ({ monthIndex, monthItem }: { monthItem: CalendarMonth; monthIndex: number; }) => { const isLastItemSelected = monthItem.isSelected && monthItem.originalIndex === allSelectedMonths.current.length - 1; const isFirstItemSelected = monthItem.isSelected && monthItem.originalIndex === 0; const selectionStyle: Array | null> = [ isFirstItemSelected && allSelectedMonths.current.length > 1 ? { borderBottomRightRadius: 0, borderTopRightRadius: 0 } : null, isLastItemSelected && allSelectedMonths.current.length > 1 ? { borderBottomLeftRadius: 0, borderTopLeftRadius: 0 } : null ]; let dayTitleColor: keyof NCoreUIKit.TextContentColors = "mid"; if(monthItem.isSelected && !isLastItemSelected && !isFirstItemSelected) { dayTitleColor = "emphasized"; selectionStyle.push({ borderBottomRightRadius: 0, borderBottomLeftRadius: 0, borderTopRightRadius: 0, borderTopLeftRadius: 0 }); } else if(monthItem.isSelected) { dayTitleColor = "onPrimary"; } if(!monthItem.isCurrentYear || monthItem.isDisabled) { dayTitleColor = "disabled"; } return { if(!monthItem.isCurrentYear) { return; } if(variant === "single") { const newDate = date as Date; newDate.setMonth(monthItem.date.toDate().getMonth()); selectObject(newDate); } else if(variant === "range") { if(!dateRange || !dateRange.start) { selectMultipleObject({ start: new Date(monthItem.date.toDate().setHours(0, 0, 0)), end: undefined }); return; } if(dateRange && moment(dateRange.start).isSame(monthItem.date, "month")) { selectMultipleObject({ start: undefined, end: undefined }); return; } if(dateRange && !dateRange.end) { if(multipleSelectDayLimit && moment(monthItem.date).diff(dateRange.start, "month") >= multipleSelectDayLimit) { NCoreUIKitToast.open({ title: localize("maximum-selection-number-of-days-limit-exceeds") }); return; } if(multipleSelectMinimumRequiredDayCount && moment(monthItem.date).diff(dateRange.start, "month") + 1 < multipleSelectMinimumRequiredDayCount) { NCoreUIKitToast.open({ title: localize("minimum-selection-number-of-days-required-not-provided") }); return; } if(moment(monthItem.date).isBefore(dateRange.start)) { selectMultipleObject({ start: new Date(monthItem.date.toDate().setHours(0, 0, 0)), end: undefined }); } else { selectMultipleObject({ end: new Date(monthItem.date.toDate().setHours(23, 59, 59)), start: dateRange?.start }); } return; } selectMultipleObject({ start: monthItem.date.toDate(), end: undefined }); } }} style={[ stylesheet.day, dayDynamicStyle, monthItem.isSelected ? { backgroundColor: colors.content.container.primary, borderColor: colors.content.container.primary } : null, monthItem.isSelected && !isLastItemSelected && !isFirstItemSelected ? { backgroundColor: colors.content.container.emphasized, borderColor: colors.content.container.emphasized } : null, monthItem.isSelected && (monthItem.isDisabled || !monthItem.isCurrentYear) ? { opacity: 0.33 } : null, ...selectionStyle ]} > {monthItem.title} {isShowTodayIndicator && monthItem.isToday ? : null} ; }; const renderNextPrevTool = () => { return { const prevViewDate = moment(viewDate).clone().subtract(1, "years"); setViewDate(prevViewDate.toDate()); }} style={[ nextPrevToolChevronButtonDynamicStyle ]} > {moment(viewDate).format("YYYY")} { const nextViewDate = moment(viewDate).clone().add(1, "years"); setViewDate(nextViewDate.toDate()); }} style={[ nextPrevToolChevronButtonDynamicStyle ]} > ; }; return {renderNextPrevTool()} {yearMonths && yearMonths.length ? MONTH_LINES.map((mItem, mIndex) => { const currentMonths = mItem.map(mI => yearMonths[mI]); return {currentMonths.map((monthItem, monthIndex) => { return renderDay({ monthItem: monthItem as CalendarMonth, monthIndex }); })} ; }) : null} ; }; export default forwardRef(MonthSelector);