Add flight details accordion (B.1) design spec
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user