departureselection
Version:
A reusable departure selection component for travel booking applications
513 lines (413 loc) • 14 kB
Markdown
# 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.