import { ThemeType, useTheme } from "../../theme"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { StyleProp, StyleSheet, Text, TouchableOpacity, View, ViewStyle, TextStyle, ScrollView, I18nManager, } from "react-native"; import { NumericStepper, NumericStepperProps } from "../NumericStepper"; import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; import RNPickerSelect from "react-native-picker-select"; import { Ionicons } from "@expo/vector-icons"; export interface IndividualGuest { id: string; age: number; } export interface RoomState { id: string; [key: string]: any; } export interface GuestConfigType { key: string; name: string; description: string; defaultValue: number; minValue: number; maxValue?: number; ageRange?: { min: number; max: number }; defaultAge?: number; } const DEFAULT_GUEST_CONFIG: GuestConfigType[] = [ { key: "adults", name: "Adults", description: "17+ years old", defaultValue: 2, minValue: 1, maxValue: 5, }, { key: "children", name: "Children", description: "0 - 17 years old", defaultValue: 0, minValue: 0, maxValue: 4, ageRange: { min: 0, max: 17 }, defaultAge: 8, }, ]; const createNewRoom = (guestConfig: GuestConfigType[]): RoomState => { const newRoom: RoomState = { id: Date.now().toString() + Math.random().toString(36).substring(2, 9), }; guestConfig.forEach((guest) => { if (guest.ageRange) { newRoom[guest.key] = Array.from( { length: guest.defaultValue }, (_, i) => ({ id: `${guest.key}_${i}_${Date.now()}`, age: guest.defaultAge ?? guest.ageRange?.min ?? 0, }) ); } else { newRoom[guest.key] = guest.defaultValue; } }); return newRoom; }; export interface HotelSummaryProps { guestConfigData?: GuestConfigType[]; selection?: RoomState[]; onSelectionChange?: (rooms: RoomState[]) => void; maxRooms?: number; minRooms?: number; roomTitlePrefix?: string; removeRoomText?: string; addRoomText?: string; formatChildAgeLabel?: (guestType: string, index: number) => string; containerStyle?: StyleProp; roomContainerStyle?: StyleProp; roomListContainerStyle?: StyleProp; roomHeaderStyle?: StyleProp; roomTitleStyle?: StyleProp; removeRoomButtonStyle?: StyleProp; removeRoomTextStyle?: StyleProp; guestRowStyle?: StyleProp; guestLabelContainerStyle?: StyleProp; guestNameStyle?: StyleProp; guestDescriptionStyle?: StyleProp; agePickerRowStyle?: StyleProp; agePickerLabelStyle?: StyleProp; addRoomButtonStyle?: StyleProp; addRoomButtonTextStyle?: StyleProp; stepperContainerStyle?: NumericStepperProps["style"]; stepperButtonStyle?: NumericStepperProps["buttonStyle"]; stepperButtonTextStyle?: NumericStepperProps["buttonTextStyle"]; stepperValueTextStyle?: NumericStepperProps["valueTextStyle"]; overrideTheme?: ThemeType } const HotelSummary: React.FC = ({ guestConfigData: guestConfigProp = DEFAULT_GUEST_CONFIG, selection, onSelectionChange, maxRooms = 5, minRooms = 1, roomTitlePrefix = "Room", removeRoomText = "Remove", addRoomText = "+ Add Room", formatChildAgeLabel = (guestType, index) => `${guestType} ${index} Age`, containerStyle, roomContainerStyle, roomHeaderStyle, roomTitleStyle, removeRoomButtonStyle, removeRoomTextStyle, guestRowStyle, guestLabelContainerStyle, guestNameStyle, guestDescriptionStyle, addRoomButtonStyle, addRoomButtonTextStyle, stepperContainerStyle, stepperButtonStyle, stepperButtonTextStyle, stepperValueTextStyle, roomListContainerStyle, agePickerRowStyle, agePickerLabelStyle, overrideTheme }) => { const { theme: defaultTheme } = useTheme(); let theme = overrideTheme || defaultTheme; const styles = useMemo(() => themedStyles(theme, I18nManager.isRTL), [theme]); const guestConfig = useMemo(() => guestConfigProp, [guestConfigProp]); const [rooms, setRooms] = useState( selection && selection.length >= minRooms ? selection : Array.from({ length: minRooms }, () => createNewRoom(guestConfig)) ); const triggerSelectionChange = useCallback( (updatedRooms: RoomState[]) => { onSelectionChange?.(updatedRooms); }, [onSelectionChange] ); const addRoom = useCallback(() => { setRooms((prevRooms) => { if (prevRooms.length >= maxRooms) { return prevRooms; } const updatedRooms = [...prevRooms, createNewRoom(guestConfig)]; triggerSelectionChange(updatedRooms); return updatedRooms; }); }, [maxRooms, triggerSelectionChange, guestConfig]); const removeRoom = useCallback( (roomIdToRemove: string) => { setRooms((prevRooms) => { if (prevRooms.length <= minRooms) { return prevRooms; } const updatedRooms = prevRooms.filter( (room) => room.id !== roomIdToRemove ); triggerSelectionChange(updatedRooms); return updatedRooms; }); }, [triggerSelectionChange, minRooms] ); const handleGuestCountChange = useCallback( ( roomId: string, guestKey: string, action: "increment" | "decrement" ) => { setRooms((prevRooms) => { const guestInfo = guestConfig.find((g) => g.key === guestKey); if (!guestInfo) return prevRooms; let valueChanged = false; const updatedRooms = prevRooms.map((room) => { if (room.id !== roomId) return room; const currentVal = room[guestKey]; let newRoomState = { ...room }; if (guestInfo.ageRange) { // Handle array of guests const currentArray = (currentVal || []) as IndividualGuest[]; if (action === "increment") { if (currentArray.length < (guestInfo.maxValue ?? 99)) { const newGuest: IndividualGuest = { id: `${guestKey}_${Date.now()}`, age: guestInfo.defaultAge ?? guestInfo.ageRange.min, }; newRoomState[guestKey] = [...currentArray, newGuest]; valueChanged = true; } } else if (action === "decrement") { if (currentArray.length > guestInfo.minValue) { newRoomState[guestKey] = currentArray.slice(0, -1); valueChanged = true; } } } else { // Handle numeric count const min = guestInfo.minValue; const max = guestInfo.maxValue ?? 99; let newValue = currentVal; if (action === "increment") newValue = Math.min(max, currentVal + 1); else newValue = Math.max(min, currentVal - 1); if (newValue !== currentVal) { newRoomState[guestKey] = newValue; valueChanged = true; } } return newRoomState; }); if (valueChanged) { triggerSelectionChange(updatedRooms); } return updatedRooms; }); }, [guestConfig, triggerSelectionChange] ); const handleIndividualAgeChange = useCallback( (roomId: string, guestKey: string, guestId: string, newAge: number) => { if (newAge === null || newAge === undefined) return; setRooms((prevRooms) => { const updatedRooms = prevRooms.map((room) => { if (room.id === roomId) { const guestArray = (room[guestKey] as IndividualGuest[]).map( (guest) => (guest.id === guestId ? { ...guest, age: newAge } : guest) ); return { ...room, [guestKey]: guestArray }; } return room; }); triggerSelectionChange(updatedRooms); return updatedRooms; }); }, [triggerSelectionChange] ); const getAgePickerItems = (config: GuestConfigType | undefined) => { if (!config?.ageRange) return []; const { min, max } = config.ageRange; return Array.from({ length: max - min + 1 }, (_, i) => ({ label: `${min + i}`, value: min + i, })); }; useEffect(() => { onSelectionChange?.(rooms); }, []); return ( {rooms.map((room, index) => ( {`${roomTitlePrefix} ${index + 1}`} {rooms.length > minRooms && ( removeRoom(room.id)} > {removeRoomText} )} {guestConfig.map((guest) => ( {guest.name} {guest.description} handleGuestCountChange(room.id, guest.key, "increment") } onDecrement={() => handleGuestCountChange(room.id, guest.key, "decrement") } style={stepperContainerStyle} buttonStyle={stepperButtonStyle} buttonTextStyle={stepperButtonTextStyle} valueTextStyle={stepperValueTextStyle} /> {guest.ageRange && room[guest.key]?.map( (individualGuest: IndividualGuest, guestIndex: number) => ( {formatChildAgeLabel(guest.name, guestIndex + 1)} handleIndividualAgeChange( room.id, guest.key, individualGuest.id, value ) } items={getAgePickerItems(guest)} placeholder={{}} style={{ inputIOS: styles.pickerInput, inputAndroid: styles.pickerInput, iconContainer: styles.pickerIconContainer, }} textInputProps={{ pointerEvents: "none" }} useNativeAndroidPickerStyle={false} Icon={() => ( )} /> ) )} ))} ))} {rooms.length < maxRooms && ( {addRoomText} )} ); }; const themedStyles = (theme: ThemeType, isRTL: boolean) => StyleSheet.create({ container: { backgroundColor: theme.surface, padding: 16, // borderRadius: 8, width: "100%", }, roomContainer: { marginBottom: 20, paddingBottom: 10, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: theme.border, }, roomHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 10, }, roomTitle: { fontSize: 18, fontWeight: "bold", color: theme.onSurface, }, removeRoomText: { fontSize: 14, color: theme.error, fontWeight: "500", }, guestRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingVertical: 12, }, guestLabelContainer: { flexDirection: "column", alignItems: "flex-start", flexShrink: 1, // marginRight: isRTL ? 0 : 8, // marginLeft: isRTL ? 8 : 0, }, guestName: { fontSize: 16, fontWeight: "bold", color: theme.onSurface, }, guestDescription: { fontSize: 12, color: theme.onSurface, opacity: 0.8, }, addRoomButton: { marginTop: 10, alignSelf: "flex-start", }, addRoomButtonText: { fontSize: 16, fontWeight: "bold", color: theme.primary, }, agePickerRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingVertical: 8, paddingHorizontal: 16, backgroundColor: theme.background, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: theme.divider, }, agePickerLabel: { fontSize: 14, color: theme.onBackground, }, pickerContainer: { width: 120, height: 40, borderWidth: 1, borderColor: theme.border, borderRadius: 8, justifyContent: "center", }, pickerInput: { fontSize: 14, paddingHorizontal: 10, paddingVertical: 8, color: theme.onSurface, }, pickerIconContainer: { top: 10, right: 10, }, }); export default HotelSummary;