Add flight details accordion (B.1) design spec

This commit is contained in:
2026-04-16 21:47:18 +03:00
parent 9a278c3170
commit 45a6cee9d8
@@ -0,0 +1,213 @@
# 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