UNPKG

departureselection

Version:

A reusable departure selection component for travel booking applications

513 lines (413 loc) 14 kB
# jetsetgo_departure-selection A highly customizable React component for handling travel departure and return journey selections. ## Installation ```bash npm install jetsetgo_departure-selection ``` ## Quick Start ```tsx import { DepartureSelection } from 'jetsetgo_departure-selection'; function App() { return ( <DepartureSelection initialData={{ departure: outboundServices, return: returnServices }} uiConfig={{ continueToReturnText: "Continue to Return Journey", finalContinueText: "Complete Booking", backToOutboundText: "Back to Outbound", backText: "Back", outboundTitle: "Select Outbound Journey", returnTitle: "Select Return Journey", pageTitle: "Choose your journey", pageSubtitle: "Select your preferred travel times", outboundButtonMode: "continue" // 'continue' | 'complete' }} onComplete={handleComplete} onDateSelect={handleDateSelect} onServiceSelect={handleServiceSelect} onContextChange={handleContextChange} /> ); } ``` ## Event Communication The component provides a comprehensive event system to keep the parent application informed about user interactions. ### Date Selection Events Triggered when the user selects a new date: ```typescript interface DateSelectEvent { date: string; // The newly selected date (YYYY-MM-DD) previousDate: string; // The previously selected date context: 'outbound' | 'return'; // Current selection context } // Example usage const handleDateSelect = (event: DateSelectEvent) => { console.log('Date changed:', event.date); console.log('Previous date:', event.previousDate); console.log('Current context:', event.context); }; ``` ### Service Selection Events Emitted when a user selects a service: ```typescript interface ServiceSelectEvent { serviceId: number; // ID of the selected service context: 'outbound' | 'return'; // Whether this is outbound or return service: Service; // Full service object } // Example usage const handleServiceSelect = (event: ServiceSelectEvent) => { console.log('Selected service:', event.serviceId); console.log('Selection context:', event.context); console.log('Service details:', event.service); }; ``` ### Context Change Events Triggered when switching between outbound and return selection: ```typescript interface ContextChangeEvent { previousContext: 'outbound' | 'return'; nextContext: 'outbound' | 'return'; selectedOutboundId: number | null; selectedReturnId: number | null; } // Example usage const handleContextChange = (event: ContextChangeEvent) => { console.log('Switching from', event.previousContext, 'to', event.nextContext); console.log('Current selections:', { outbound: event.selectedOutboundId, return: event.selectedReturnId }); }; ``` ### Completion Event Triggered when the user completes their selection: ```typescript interface CompletionData { selectedDepartureId: number | null; selectedReturnId: number | null; departureService: Service | null; returnService: Service | null; date: string; totalCost: number; } // Example usage const handleComplete = (data: CompletionData) => { console.log('Selection completed:', data); }; ``` ## Features ### Dynamic Loading States The component supports loading states for data updates, commonly used when fetching new data after date changes: ```tsx function BookingApp() { const [isLoading, setIsLoading] = useState(false); const [loadingContext, setLoadingContext] = useState<'outbound' | 'return' | 'both'>('both'); const [services, setServices] = useState({ departure: initialDepartureServices, return: initialReturnServices }); const handleDateSelect = async (event: DateSelectEvent) => { // 1. Start loading state for the relevant section setIsLoading(true); setLoadingContext(event.context); try { // 2. Fetch new data for the selected date const newServices = await fetchServicesForDate(event.date); // 3. Update the services data setServices(newServices); } catch (error) { console.error('Failed to fetch services:', error); } finally { // 4. Stop loading state setIsLoading(false); } }; return ( <DepartureSelection initialData={services} isLoading={isLoading} loadingContext={loadingContext} onDateSelect={handleDateSelect} // ... other props /> ); } ``` When `isLoading` is true: - A loading overlay appears over the specified section(s) - The UI remains interactive but selections are disabled - Smooth transitions handle the loading state changes - The date selector remains accessible ### Data Persistence The component includes built-in state persistence: ```typescript // Export current selections to JSON const handleExport = () => { const data = exportSelections(); // Save data to file/storage }; // Import saved selections const handleImport = (savedData: string) => { importSelections(savedData); }; ``` ## Props ### Required Props | Prop | Type | Description | |------|------|-------------| | `initialData` | `{ departure: Service[]; return: Service[]; }` | The outbound and return services to display | | `onComplete` | `(data: CompletionData) => void` | Callback when selection is complete | | `uiConfig` | `UIConfig` | Configuration for UI text and labels | ### Optional Props | Prop | Type | Description | |------|------|-------------| | `selectedOutboundId` | `number \| null` | Pre-selected outbound service ID | | `selectedReturnId` | `number \| null` | Pre-selected return service ID | | `currentContext` | `'outbound' \| 'return'` | Initial selection context | | `onDateSelect` | `(event: DateSelectEvent) => void` | Date selection callback | | `onServiceSelect` | `(event: ServiceSelectEvent) => void` | Service selection callback | | `onContextChange` | `(event: ContextChangeEvent) => void` | Context change callback | | `onBack` | `() => void` | Callback for back button click | | `isLoading` | `boolean` | Whether the component is loading new data | | `loadingContext` | `'outbound' \| 'return' \| 'both'` | Which section is loading | ## UI Configuration The `uiConfig` prop allows customization of all text elements: ```typescript interface UIConfig { continueToReturnText: string; // Text for continue to return button finalContinueText: string; // Text for final continue button backToOutboundText: string; // Text for back to outbound button backText: string; // Text for back button outboundTitle: string; // Title for outbound selection returnTitle: string; // Title for return selection pageTitle: string; // Main page title pageSubtitle: string; // Page subtitle outboundButtonMode: 'continue' | 'complete'; // Controls which button appears in outbound context } ``` ### Outbound Button Mode The `outboundButtonMode` configuration controls which button appears after selecting an outbound service. Available options: | Option | Description | |--------|-------------| | `'continue'` | (Default) Shows the "Continue to Return Journey" button (`continueToReturnText`). Use this for return journeys where users need to select both outbound and return services. | | `'complete'` | Shows the "Complete Booking" button (`finalContinueText`). Use this for one-way journeys where only an outbound service selection is needed. | Example usage: ```tsx // For a one-way journey where return selection is not needed <DepartureSelection uiConfig={{ ...otherConfig, outboundButtonMode: "complete", // Shows "Complete Booking" button finalContinueText: "Complete Booking" }} // ... other props /> // For a return journey where both selections are required <DepartureSelection uiConfig={{ ...otherConfig, outboundButtonMode: "continue", // Shows "Continue to Return Journey" button continueToReturnText: "Continue to Return Journey" }} // ... other props /> ``` ## Service Data Structure Services should follow this structure: ```typescript interface Service { service_id: number; can_accept: string; resource_name: string; route_name: string; departing_from: string; travelling_to: string; departure_time: string; arrival_time: string; departure_date: string; total_cost: number; pats: Pat[]; flags: Flag[]; } ``` ## Styling The component uses Tailwind CSS classes by default. You can override styles by: 1. Using the provided class names 2. Using CSS modules (coming soon) 3. Using styled-components (coming soon) ## Browser Support - Chrome (latest) - Firefox (latest) - Safari (latest) - Edge (latest) ## Utility Functions The component exports several utility functions for working with dates and services: ### Date Utilities ```typescript import { getDefaultDate, generateDateRange, shiftDates } from 'jetsetgo_departure-selection'; // Get the default date from services const defaultDate = getDefaultDate(services); // Generate a range of dates (default: 1 day before and after) const dateRange = generateDateRange('2024-01-15', 2); // Shift dates forward or backward const newDates = shiftDates(currentDates, 'forward'); ``` ### Service Utilities ```typescript import { sortServices, filterServices } from 'jetsetgo_departure-selection'; // Sort services by departure time or price const sortedServices = sortServices(services, 'departure'); // Filter services based on availability const availableServices = filterServices(services, false); ``` ## Type Exports The package exports TypeScript types for all major interfaces: ```typescript import type { Service, Pat, Flag, DepartureSelectionProps, UIConfig, DateSelectEvent, ServiceSelectEvent, ContextChangeEvent, DepartureSelections } from 'jetsetgo_departure-selection'; // Example usage const config: UIConfig = { continueToReturnText: "Next", finalContinueText: "Complete", backToOutboundText: "Back", backText: "Previous", outboundTitle: "Select Departure", returnTitle: "Select Return", pageTitle: "Travel Selection", pageSubtitle: "Choose your journey", outboundButtonMode: "continue" // 'continue' | 'complete' }; ``` State Communication Overview The component communicates state changes through three main callback props: onContextChange: Tracks the current view context and selections onServiceSelect: Reports individual service selections onDateSelect: Reports date selection changes Tracking Context State The onContextChange callback is triggered in these scenarios: interface ContextChangeEvent { previousContext: 'outbound' | 'return'; nextContext: 'outbound' | 'return'; selectedOutboundId: number | null; selectedReturnId: number | null; } Triggered when: Component mounts (initial load) User switches between outbound/return views Service selections change Example usage: function ParentApp() { const handleContextChange = (event: ContextChangeEvent) => { console.log('Current view:', event.nextContext); console.log('Selected outbound:', event.selectedOutboundId); console.log('Selected return:', event.selectedReturnId); }; return ( <DepartureSelection onContextChange={handleContextChange} // ... other props /> ); } Tracking Service Selections The onServiceSelect callback provides detailed service information: interface ServiceSelectEvent { serviceId: number; context: 'outbound' | 'return'; service: Service; // Full service object } Triggered when: User selects an outbound service User selects a return service Example: function ParentApp() { const handleServiceSelect = (event: ServiceSelectEvent) => { console.log('Service selected in context:', event.context); console.log('Selected service:', event.service); }; return ( <DepartureSelection onServiceSelect={handleServiceSelect} // ... other props /> ); } Tracking Date Changes The onDateSelect callback reports date changes: interface DateSelectEvent { date: string; previousDate: string; context: 'outbound' | 'return'; } Triggered when: User selects a new date User navigates dates using arrows Example: function ParentApp() { const handleDateSelect = (event: DateSelectEvent) => { console.log('New date:', event.date); console.log('Previous date:', event.previousDate); console.log('Current context:', event.context); }; return ( <DepartureSelection onDateSelect={handleDateSelect} // ... other props /> ); } Complete Parent App Example Here's a complete example showing how to track all state changes: function ParentApp() { const [currentContext, setCurrentContext] = useState<'outbound' | 'return'>('outbound'); const [selections, setSelections] = useState({ outboundId: null as number | null, returnId: null as number | null, currentDate: null as string | null }); const handleContextChange = (event: ContextChangeEvent) => { setCurrentContext(event.nextContext); setSelections(prev => ({ ...prev, outboundId: event.selectedOutboundId, returnId: event.selectedReturnId })); }; const handleServiceSelect = (event: ServiceSelectEvent) => { setSelections(prev => ({ ...prev, [event.context === 'outbound' ? 'outboundId' : 'returnId']: event.serviceId })); }; const handleDateSelect = (event: DateSelectEvent) => { setSelections(prev => ({ ...prev, currentDate: event.date })); }; return ( <DepartureSelection onContextChange={handleContextChange} onServiceSelect={handleServiceSelect} onDateSelect={handleDateSelect} currentContext={currentContext} selectedOutboundId={selections.outboundId} selectedReturnId={selections.returnId} // ... other required props /> ); } This setup provides complete visibility into the component's internal state and user interactions.