import { addDays, eachDayOfInterval, format, isValid, parseISO, } from "date-fns"; import React, { useCallback, useMemo, useState } from "react"; import { Modal, StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle, } from "react-native"; import { Calendar, DateData } from "react-native-calendars"; import { MarkedDates } from "react-native-calendars/src/types"; import { ThemeType, useTheme } from "../../theme"; // --- Component Props --- interface DateRangePickerProps { departureDate?: string; // Raw date value e.g., "2025-12-31" returnDate?: string; // Raw date value e.g., "2025-12-31" displayDepartureValue?: string; // Pre-formatted string for display displayReturnValue?: string; // Pre-formatted string for display isOneWay?: boolean; onDatesChange: (dates: { departure: string | undefined; return: string | undefined; }) => void; departureLabel?: string; returnLabel?: string; placeholder?: string; calendarThemeOverrides?: object; // Style Props outerContainerStyle?: StyleProp; inputContainerStyle?: StyleProp; inputGroupStyle?: StyleProp; labelStyle?: StyleProp; labelContainerStyle?: StyleProp; inputTextStyle?: StyleProp; separatorStyle?: StyleProp; calendarContainerStyle?: StyleProp; } // --- Component --- const DateRangePicker: React.FC = ({ departureDate, returnDate, displayDepartureValue, displayReturnValue, isOneWay = false, onDatesChange, departureLabel = "DEPARTURE", returnLabel = "RETURN", placeholder = "Select Date", calendarThemeOverrides = {}, outerContainerStyle, inputContainerStyle, inputGroupStyle, labelStyle, inputTextStyle, separatorStyle, calendarContainerStyle, labelContainerStyle, }) => { const { theme, isRTL } = useTheme(); const styles = useMemo(() => themedStyles(theme, isRTL), [theme, isRTL]); const [isCalendarVisible, setIsCalendarVisible] = useState(false); const handleDayPress = useCallback( (day: DateData) => { let newDeparture = departureDate; let newReturn = returnDate; let shouldCloseCalendar = false; if (isOneWay) { newDeparture = day.dateString; newReturn = undefined; shouldCloseCalendar = true; } else { if (!newDeparture || (newDeparture && newReturn)) { newDeparture = day.dateString; newReturn = undefined; } else if (newDeparture && !newReturn) { try { const depDate = parseISO(newDeparture); const selectedDate = parseISO(day.dateString); if (isValid(depDate) && isValid(selectedDate)) { if (selectedDate >= depDate) { newReturn = day.dateString; shouldCloseCalendar = true; } else { newDeparture = day.dateString; newReturn = undefined; } } else { newDeparture = day.dateString; newReturn = undefined; } } catch (e) { newDeparture = day.dateString; newReturn = undefined; } } } onDatesChange({ departure: newDeparture, return: newReturn }); if (shouldCloseCalendar) { setIsCalendarVisible(false); } }, [departureDate, returnDate, isOneWay, onDatesChange], ); const markedDates = useMemo((): MarkedDates => { const marked: MarkedDates = {}; const primaryColor = theme.primary; const onPrimaryColor = theme.onPrimary; const rangeColor = theme.secondary; const onRangeColor = theme.onSecondary; if (departureDate) { marked[departureDate] = { startingDay: !returnDate || departureDate === returnDate, color: primaryColor, textColor: onPrimaryColor, selected: true, }; } if (returnDate) { marked[returnDate] = { ...marked[returnDate], endingDay: true, color: primaryColor, textColor: onPrimaryColor, selected: true, }; } if (departureDate && returnDate && departureDate !== returnDate) { try { const start = addDays(parseISO(departureDate), 1); const end = parseISO(returnDate); if (isValid(start) && isValid(end) && end > start) { const interval = eachDayOfInterval({ start, end }); interval.forEach((date) => { const dateString = format(date, "yyyy-MM-dd"); if (dateString !== returnDate) { marked[dateString] = { color: rangeColor, textColor: onRangeColor, selected: true, }; } }); } } catch (e) { console.error("Error creating date interval:", e); } } return marked; }, [departureDate, returnDate, theme]); const calendarTheme = useMemo(() => { return { backgroundColor: theme.surface, calendarBackground: theme.surface, textSectionTitleColor: theme.onSurface, selectedDayBackgroundColor: theme.primary, selectedDayTextColor: theme.onPrimary, todayTextColor: theme.primary, dayTextColor: theme.onSurface, textDisabledColor: theme.disabled, dotColor: theme.primary, selectedDotColor: theme.onPrimary, arrowColor: theme.primary, disabledArrowColor: theme.disabled, monthTextColor: theme.onSurface, indicatorColor: theme.primary, // textDayFontWeight: "500", // textMonthFontWeight: "bold", // textDayHeaderFontWeight: "bold", textDayFontSize: 14, textMonthFontSize: 16, textDayHeaderFontSize: 12, ...calendarThemeOverrides, }; }, [theme, calendarThemeOverrides]); const displayDeparture = displayDepartureValue || placeholder; const displayReturn = displayReturnValue || placeholder; return ( <> {departureLabel} {!isOneWay && ( {returnLabel} )} setIsCalendarVisible(true)} activeOpacity={0.7} > {displayDeparture} {!isOneWay && ( <> {displayReturn} )} setIsCalendarVisible(false)} animationType="fade" > setIsCalendarVisible(false)} > true}> ); }; const themedStyles = (theme: ThemeType, isRTL: boolean) => StyleSheet.create({ outerContainer: {}, labelsContainer: { flexDirection: "row", alignItems: "center" }, labelGroup: { flex: 1, justifyContent: "center", marginBottom: 4 }, label: { color: theme.labelText, fontSize: 12, fontWeight: "600", textTransform: "uppercase", textAlign: "left", }, inputContainer: { flexDirection: "row", borderColor: theme.border, borderWidth: 1, borderRadius: 8, minHeight: 50, backgroundColor: theme.surface, alignItems: "center", paddingHorizontal: 12, overflow: "hidden", }, dateInputGroup: { flex: 1, paddingVertical: 8, justifyContent: "center" }, inputText: { color: theme.onSurface, fontSize: 15, fontWeight: "500", textAlign: "left", }, placeholderText: { color: theme.helper, fontWeight: "400" }, dateSeparator: { width: 1, height: "60%", backgroundColor: theme.border, marginHorizontal: 8, }, modalOverlay: { flex: 1, backgroundColor: "rgba(0, 0, 0, 0.6)", justifyContent: "center", padding: 20, }, calendarContainer: { backgroundColor: theme.surface, borderRadius: 12, paddingVertical: 5, paddingHorizontal: 5, width: "100%", shadowColor: "#000", shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.15, shadowRadius: 10, elevation: 8, }, }); export default DateRangePicker;