# Flights Mini-List Sidebar (B.2)

## Goal

Add a left sidebar on the Online Board flight details page showing all flights with the same flight number on different dates, matching Angular's mini-list behavior. Clicking an item navigates to that flight's details; the current flight is highlighted and auto-scrolled into view.

## Scope

Sub-feature **B.2** of the Flight Details parity work. Online Board only — the Schedule details page mini-list (accordion grouped by date) is a separate concern and out of scope here.

## Current State

- React `OnlineBoardDetailsPage` uses a custom standalone layout (no sidebar).
- `useFlightDetails` fetches `/onlineboard/details` and returns `response.data.routes[0]`, discarding the rest of the array.
- The Angular equivalent (`online-board-flights-mini-list`) renders a left-column sidebar (285px desktop, hidden below 1050px) with the full `routes` array.

## Approach

Three changes, three focused commits:

1. **Extend `useFlightDetails`** to return both `flight` (for current display) and `allFlights` (for the list). The API already returns the array; we're just exposing it.
2. **Add `FlightsMiniList` + `FlightsMiniListItem`** components with auto-scroll-into-view and Link-based navigation.
3. **Refactor `OnlineBoardDetailsPage`** to use `PageLayout`, putting the mini-list in `contentLeft`.

## Architecture

### Hook: `useFlightDetails` (extended)

Before:
```typescript
interface UseFlightDetailsResult {
  flight: ISimpleFlight | null;
  loading: boolean;
  error: ApiError | null;
}
```

After:
```typescript
interface UseFlightDetailsResult {
  flight: ISimpleFlight | null;
  allFlights: ISimpleFlight[];
  loading: boolean;
  error: ApiError | null;
}
```

Implementation: store `response.data.routes` as `allFlights`, set `flight = allFlights[0] ?? null`.

### Component: `FlightsMiniList`

```typescript
interface FlightsMiniListProps {
  flights: ISimpleFlight[];
  currentFlight: ISimpleFlight;
}
```

- Returns `null` when `flights.length <= 1` (nothing to navigate between).
- Maps to `FlightsMiniListItem` per flight.
- Uses a `Map<string, HTMLAnchorElement>` ref to track item nodes by `flight.id`.
- `useEffect([currentFlight.id])` calls `scrollIntoView({ block: "center", behavior: "smooth" })` on the selected item.

### Component: `FlightsMiniListItem`

```typescript
interface FlightsMiniListItemProps {
  flight: ISimpleFlight;
  isSelected: boolean;
}
```

- Renders a React Router `<Link>` to the flight's details URL.
- URL built via `buildFlightDetailsUrl(flight, lang)` — add helper to `src/features/online-board/url.ts` if absent.
- DOM structure:
  - `.mini-list__flight-number` (e.g., "SU 0022")
  - `.mini-list__content` 2x2 CSS Grid:
    - Row 1: `.mini-list__dep-time` | `.mini-list__status-icon` | `.mini-list__arr-time`
    - Row 2: `.mini-list__dep-station` | — | `.mini-list__arr-station`
- Times prefer latest (`actualBlockOff?.local ?? scheduledDeparture.local`).
- Multi-leg: use `legs[0].departure` and `legs[legs.length - 1].arrival`.
- Direct: use `leg.departure` and `leg.arrival`.
- Applies `--selected` modifier class when `isSelected`.
- Status icon: existing `FlightStatus` component.

### URL Helper

If `src/features/online-board/url.ts` doesn't already export `buildFlightDetailsUrl`, add:

```typescript
export function buildFlightDetailsUrl(flight: ISimpleFlight, lang: string): string {
  const { carrier, flightNumber, suffix, date } = flight.flightId;
  const suffixStr = suffix ?? "";
  // date is already YYYYMMDD in IFlightId
  return `/${lang}/onlineboard/${carrier}${flightNumber}${suffixStr}-${date}`;
}
```

### Integration: `OnlineBoardDetailsPage`

Wrap the current content in `PageLayout`:

```tsx
return (
  <>
    <SeoHead {...seoProps} />
    <JsonLdRenderer data={jsonLdData} />
    <PageLayout
      headerLeft={<PageTabs viewType="onlineboard" />}
      title={<h1 className="flight-details__title">{flightNumberFormatted}</h1>}
      breadcrumbs={[
        { label: t("BREADCRUMBS.ONLINEBOARD"), href: `/${locale}/onlineboard` },
        { label: flightNumberFormatted },
      ]}
      contentLeft={
        <FlightsMiniList
          flights={allFlights}
          currentFlight={displayFlight}
        />
      }
    >
      <div className="flight-details">
        {/* existing content: connection status, summary card, legs, accordion, flying time */}
      </div>
    </PageLayout>
  </>
);
```

Loading/error/not-found states render inside the `PageLayout` children so the sidebar still shows.

## Styling

`FlightsMiniList.scss`:

```scss
.mini-list {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 170px);
  overflow-y: auto;
  background: #fff;
  border-radius: 8px;

  &__item {
    padding: 12px;
    border-bottom: 1px solid #e0e0e0;
    text-decoration: none;
    color: inherit;
    display: block;

    &:last-child { border-bottom: none; }
    &:hover { background: #f8f9fa; }
    &--selected {
      border: 2px solid #2060c0;
      border-radius: 4px;
    }
  }

  &__flight-number {
    font-size: 12px;
    color: #666;
    margin-bottom: 4px;
  }

  &__content {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-rows: auto auto;
    gap: 8px 12px;
  }

  &__dep-time, &__arr-time {
    font-size: 16px;
    font-weight: 500;
    color: #1a3a5c;
  }
  &__arr-time { text-align: right; }

  &__status-icon { grid-column: 2; grid-row: 1; }
  &__dep-station { grid-column: 1; grid-row: 2; }
  &__arr-station { grid-column: 3; grid-row: 2; text-align: right; }
}
```

Responsive hiding (tablet/mobile) is handled by `PageLayout` — no extra CSS needed at the mini-list level.

## Testing

### Hook test (extend `useFlightDetails.test.ts`)

- Returns `allFlights` = `response.data.routes` array.
- Returns `flight === allFlights[0]`.
- Returns `allFlights: []` when response is empty or errors.

### `FlightsMiniList.test.tsx`

- Returns null when `flights.length <= 1`.
- Renders one item per flight.
- Highlights the item matching `currentFlight.id`.
- Calls `scrollIntoView` on the selected item's element (mocked).
- Works with both Direct and MultiLeg flights.

### `FlightsMiniListItem.test.tsx`

- Renders flight number, dep/arr times, station codes.
- Has `data-testid="mini-list-item-{id}"`.
- `<Link>` `href` points to the correct details URL.
- Applies `--selected` modifier when `isSelected=true`.
- Multi-leg flight: shows first leg's departure and last leg's arrival stations.

### `OnlineBoardDetailsPage.test.tsx` (update existing)

- Still passes the accordion integration tests from B.1.
- Renders `PageLayout` wrapper (check for page-layout class or testid).
- Mini-list visible when mock returns multiple flights.
- Mini-list not visible when mock returns single flight.

## i18n Keys

Reuse existing. Only one new key likely needed: `BREADCRUMBS.ONLINEBOARD` (check existing — the start page likely uses it already). No new keys specific to the mini-list contents (flight number, times, station codes are data, not labels).

## Out of Scope

- Schedule details page mini-list (separate accordion-grouped component).
- Day tabs (B.3).
- Status header action buttons (B.4).
- Transfer / multi-leg timeline (B.5).
- Back button / flight schedule timeline (B.6).
- Changes to SignalR integration.
