# Flight Details Accordion (B.1)

## Goal

Add the per-leg details accordion to the React Online Board details page, matching the Angular app's six-panel structure: Registration, Boarding, Deboarding, Aircraft, Meal, On-board Services.

## Scope

This is sub-feature **B.1** of the Flight Details parity work. Other sub-features (mini flights list, day tabs, status header, back button, transfer timeline) are tracked separately.

## Current State

React `OnlineBoardDetailsPage` fetches flight details via `useFlightDetails` + live updates via `useLiveFlightDetails`, and renders per-leg station/time info. It does **not** render any of the expandable detail panels that Angular shows.

## Approach

Extend the React type system to match Angular's nested leg data (`transition.*`, `equipment.aircraft.*`, `equipment.meal[]`, `equipment.aircraft.actual.onBoardServices[]`), then build six focused panel components wrapped by an accordion container. Each panel is conditionally rendered based on data presence and view type.

## Types Extension

Extend `src/features/online-board/types.ts`:

```typescript
export type FlightTransitionStatus =
  | "Finished" | "Expected" | "InProgress" | "Specified" | "Scheduled";

export interface IFlightTransitionItem {
  end: ITimesSet;
  start: ITimesSet;
  status: FlightTransitionStatus;
  isActual: boolean;
}

export interface IFlightTransitions {
  registration?: IFlightTransitionItem;
  boarding?: IFlightTransitionItem;
  deboarding?: IFlightTransitionItem;
}

export type MealType = "Economy" | "Comfort" | "Business" | "Special";

export interface IMealItem {
  type: MealType;
}

export interface IOnBoardService {
  id: number;  // 1-8 maps to specific service icons
  title?: string;
  url?: string;
}

export interface IAircraftInfo {
  title?: string;
  onBoardServices?: IOnBoardService[];
}

export interface IEquipmentFull {
  aircraft?: {
    scheduled?: IAircraftInfo;
    actual?: IAircraftInfo;
    configuration?: string;
  };
  meal?: IMealItem[];
}
```

Extend `IFlightLeg`:

```typescript
export interface IFlightLeg {
  // ... existing fields ...
  equipment: { name?: string; code?: string } & IEquipmentFull;
  transition?: IFlightTransitions;
}
```

Existing `equipment: { name?, code? }` stays for current `FlightCard` usage.

## Component Structure

All new files in `src/features/online-board/components/details-panels/`:

```
details-panels/
├── FlightDetailsAccordion.tsx     # Container with PrimeReact Accordion
├── FlightDetailsAccordion.scss
├── RegistrationPanel.tsx          # Check-in times/status
├── BoardingPanel.tsx              # Boarding times/status
├── DeboardingPanel.tsx            # Deboarding + gate/belt info
├── AircraftPanel.tsx              # Aircraft type + configuration
├── MealPanel.tsx                  # Meal type icons (Econ/Comfort/Business)
├── ServicesPanel.tsx              # On-board services icons
├── shared.ts                      # Shared helpers: formatTime, icon mappings
└── shared.scss                    # Shared panel styling
```

Container contract:

```typescript
export interface FlightDetailsAccordionProps {
  leg: IFlightLeg;
  viewType: "Onlineboard" | "Schedule";
}
```

The container computes visibility for each of the six panels, returns `null` when all are hidden, otherwise renders a `multiple`-mode PrimeReact Accordion with conditional tabs.

Wiring in `OnlineBoardDetailsPage`: for each leg, render `<FlightDetailsAccordion leg={leg} viewType="Onlineboard" />` beneath the existing station/time block.

## Visibility Rules

**Transition panels** (Registration / Boarding / Deboarding):

```typescript
function shouldShowTransition(
  item: IFlightTransitionItem | undefined,
  legStatus: FlightStatus,
  viewType: "Onlineboard" | "Schedule"
): boolean {
  if (viewType === "Schedule") return false;
  if (legStatus === "Cancelled") return false;
  if (!item) return false;
  if (item.status === "Scheduled") return false;
  return true;
}
```

**Aircraft panel**: show when `aircraft.actual?.title || aircraft.scheduled?.title || aircraft.configuration` exists.

**Meal panel**: show when `equipment.meal?.length > 0`.

**Services panel**: show when `equipment.aircraft?.actual?.onBoardServices?.length > 0`.

## Panel Contents

| Panel | Data source | Content |
|-------|-------------|---------|
| Registration | `leg.transition.registration` | Start time (local + UTC), End time, status badge |
| Boarding | `leg.transition.boarding` | Same structure as Registration |
| Deboarding | `leg.transition.deboarding` + `leg.arrival` | Times + status + arrival terminal/gate/bagBelt |
| Aircraft | `leg.equipment.aircraft` | `actual?.title ?? scheduled?.title`, configuration. If both differ, show both with labels. |
| Meal | `leg.equipment.meal[]` | Up to 3 icons (Economy, Comfort, Business) linking to aeroflot.ru info pages |
| Services | `leg.equipment.aircraft.actual.onBoardServices[]` | Service icons mapped by `id` (1-8) |

**Aircraft edge case**: If `actual` exists but has empty title, fall back to `scheduled` (matches Angular's `flight-details-airplane` behavior).

**Service icon mapping** (from Angular `flight-details-services.component.ts`):

```typescript
const SERVICE_ICON_MAP: Record<number, string> = {
  1: "shopping",
  2: "space",
  3: "taxi",
  4: "wifi",
  5: "gsm",
  6: "entertainment",
  7: "entertainment",
  8: "seat_reservation",
};
```

Icons ported from `ClientApp/src/assets/img/service-and-food-icons/` to React's asset directory.

**Meal links** (hardcoded aeroflot.ru URLs from Angular):
- Economy: `https://www.aeroflot.ru/ru-ru/information/onboard/dining?0000#meal-type_0`
- Comfort: `https://www.aeroflot.ru/ru-ru/information/onboard/dining?0000#meal-type_1`
- Business: `https://www.aeroflot.ru/ru-ru/information/onboard/dining?0000#meal-type_2`

**Accordion behavior**: `<Accordion multiple>` — multiple panels can be expanded simultaneously (matches Angular's `<p-accordion multiple>`).

## Testing Plan

**Unit tests** — one per component, co-located (`*.test.tsx`):

- `FlightDetailsAccordion.test.tsx` — Visibility matrix (6 panels × relevant states), returns null when all hidden, multi-select accordion
- `RegistrationPanel.test.tsx` — Renders start/end times + status, handles missing `end` time gracefully
- `BoardingPanel.test.tsx` — Same pattern
- `DeboardingPanel.test.tsx` — Times + status + terminal/gate/bagBelt
- `AircraftPanel.test.tsx` — Actual vs scheduled fallback, configuration display, empty-title edge case
- `MealPanel.test.tsx` — Renders one icon per meal type, skips missing, correct hrefs
- `ServicesPanel.test.tsx` — Icon mapping for all IDs 1-8, link wrapping with `url`, unknown ID fallback

**Integration** — extend `OnlineBoardDetailsPage.test.tsx`:

- With a mock flight including `transition` + `equipment` fields, accordion renders
- Without those fields, accordion is not rendered
- `viewType="Schedule"` hides all three transition panels

**No backend work**: panels consume existing `IFlightLeg` data from `useFlightDetails`. If real API omits `transition` / extended `equipment`, panels gracefully don't render.

## i18n Keys

Reuse Angular keys where possible. Required keys:

- `DETAILS.REGISTRATION`, `DETAILS.BOARDING`, `DETAILS.DEBOARDING`
- `DETAILS.AIRCRAFT`, `DETAILS.MEAL`, `DETAILS.ON_BOARD_SERVICES`
- `DETAILS.SCHEDULED`, `DETAILS.ACTUAL`
- Meal type labels: `DETAILS.MEAL_ECONOMY`, `DETAILS.MEAL_COMFORT`, `DETAILS.MEAL_BUSINESS`

Add to `ru` and `en` locale files at minimum (other languages can fall back to `en`).

## Assets

Copy SVG icons from `ClientApp/src/assets/img/service-and-food-icons/` to `src/assets/service-icons/` (or `public/assets/` depending on the project's existing static asset pattern). 8 service icons + potentially meal-type icons.

## Out of Scope

- Mini flights list sidebar (B.2)
- Day tabs (B.3)
- Status header with action buttons (B.4)
- Back button / flight schedule timeline (B.6)
- Transfer components / multi-leg route timeline (B.5)
- Changes to the REST API or SignalR integration
