# 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.