From df79213186e090f0725c1e705adcfe725dd84adc Mon Sep 17 00:00:00 2001 From: gnezim Date: Fri, 17 Apr 2026 01:55:48 +0300 Subject: [PATCH] Add back button + flight schedule timeline (B.6) design spec --- ...17-back-button-schedule-timeline-design.md | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-17-back-button-schedule-timeline-design.md diff --git a/docs/superpowers/specs/2026-04-17-back-button-schedule-timeline-design.md b/docs/superpowers/specs/2026-04-17-back-button-schedule-timeline-design.md new file mode 100644 index 00000000..8e5df49c --- /dev/null +++ b/docs/superpowers/specs/2026-04-17-back-button-schedule-timeline-design.md @@ -0,0 +1,312 @@ +# Back Button + Flight Schedule Timeline (B.6) + +## Goal + +Add two Angular-parity elements to the React Online Board details page: a back-navigation button in the header (replacing `PageTabs`), and a flight-schedule timeline showing scheduled times, flight duration, and a days-of-operation indicator with the week's valid-through date range. + +## Scope + +Sub-feature **B.6** of the Flight Details parity work. Online Board details page only. + +## Current State + +- React details page uses `` in `PageLayout.headerLeft`. +- No flight-schedule timeline component exists. +- `IFlightLeg` type has no `daysOfWeek` field. + +## Approach + +Two small independent component families: + +1. **`DetailsBackButton`** — stateless ``-based button styled as the Angular back button. Replaces `PageTabs` in the details page's `headerLeft` slot. +2. **`FlightSchedule`** — single-tab PrimeReact `Accordion` with the Schedule section (times + duration) inside, plus a persistent Days-of-week strip + date-range note below. + +## Types Extension + +`src/features/online-board/types.ts` gets: + +```typescript +export interface IDaysOfWeek { + current: string; // 7-char bit string for today (e.g., "1000010") + flight: string; // 7-char bit string for scheduled days (e.g., "1111111") +} +``` + +And `IFlightLeg` gains: + +```typescript +export interface IFlightLeg { + // ... existing fields ... + daysOfWeek?: IDaysOfWeek; +} +``` + +Position 0 = Monday, position 6 = Sunday. Character `"1"` means active; any other character means inactive. + +## Architecture + +### `DetailsBackButton` + +File: `src/features/online-board/components/DetailsBackButton/DetailsBackButton.tsx`. + +```typescript +export interface DetailsBackButtonProps { + locale: string; +} +``` + +Renders: + +```tsx + + + {t("SHARED.BACK-BOARD")} + +``` + +Always visible. Uses React Router `` (client-side nav). No conditional rendering. + +### `FlightSchedule` + +File: `src/features/online-board/components/FlightSchedule/FlightSchedule.tsx`. + +```typescript +export interface FlightScheduleProps { + flight: ISimpleFlight; +} +``` + +Renders `null` when `firstLeg.daysOfWeek?.flight` is undefined or empty string. + +Otherwise renders: + +1. **PrimeReact ``** with a single `` containing: + - Row: label `SHARED.DEPARTURE-SCHEDULED` + value `firstLeg.departure.times.scheduledDeparture.local` + - Row: label `SHARED.ARRIVAL-SCHEDULED` + value `lastLeg.arrival.times.scheduledArrival.local` + - Row: label `SHARED.PATH-TIME` + value `flight.flyingTime` +2. **Below the accordion** (always visible): + - Section title: `SHARED.DAYS-EXECUTE-FLIGHT` + - `` + - Note: `t("SHARED.NOTE-TIME-SCHEDULE").replace("{START_DATE}", start).replace("{END_DATE}", end)` + +Where `start` / `end` come from `getWeekDateRange(firstLeg.departure.times.scheduledDeparture.local)`. + +Default `activeIndex={0}` opens the tab on mount (matches Angular's `selected = true`). + +### `DaysOfWeekStrip` + +File: `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.tsx`. + +```typescript +export interface DaysOfWeekStripProps { + flightBitString: string; +} +``` + +Renders 7 spans in a flex row. For index `i = 0..6`: + +- Active if `flightBitString[i] === "1"` +- Label: `t("DAYS." + (i + 1))` (keys `DAYS.1` through `DAYS.7`) +- Class: `day` (active) or `day day--inactive` (inactive) + +### `weekDateRange` helper + +File: `src/features/online-board/components/FlightSchedule/weekDateRange.ts`. + +```typescript +import { parseISO, startOfISOWeek, endOfISOWeek, format, isValid } from "date-fns"; + +export function getWeekDateRange(scheduledLocal: string | undefined): { + start: string; + end: string; +} { + if (!scheduledLocal) return { start: "", end: "" }; + const d = parseISO(scheduledLocal); + if (!isValid(d)) return { start: "", end: "" }; + return { + start: format(startOfISOWeek(d), "dd.MM.yyyy"), + end: format(endOfISOWeek(d), "dd.MM.yyyy"), + }; +} +``` + +Pure function — no React, no side effects. ISO week starts on Monday, ends on Sunday. + +### Integration + +In `src/features/online-board/components/OnlineBoardDetailsPage.tsx`: + +1. Replace `headerLeft={}` with `headerLeft={}` in the main happy-path return. + - Also replace in the loading/error/not-found branches' `commonLayoutProps` for consistency. +2. After `` (or after the flying-time div), add ``. + +## Styling + +### `DetailsBackButton.scss` + +```scss +.details-back-button { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: #e3f0ff; + color: #1a3a5c; + border-radius: 6px; + text-decoration: none; + font-size: 14px; + font-family: inherit; + cursor: pointer; + + &:hover { background: #c7dff5; } + + &__arrow { + display: inline-block; + font-size: 16px; + } +} +``` + +### `FlightSchedule.scss` + +```scss +.flight-schedule { + background: #fff; + border-radius: 8px; + padding: 16px 24px; + margin-top: 16px; + + .p-accordion-content { + padding: 12px 0; + } + + &__row { + display: flex; + justify-content: space-between; + padding: 4px 0; + } + + &__label { color: #666; } + &__value { font-weight: 500; } + + &__days-section { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid #eee; + } + + &__section-title { + font-size: 12px; + color: #666; + text-transform: uppercase; + margin-bottom: 8px; + } + + &__note { + margin-top: 12px; + font-size: 12px; + color: #666; + font-style: italic; + } +} + +.days-of-week-strip { + display: flex; + gap: 8px; + flex-wrap: wrap; + + .day { + padding: 8px 12px; + border-radius: 6px; + background: #e6f1fb; + font-size: 14px; + font-weight: 500; + color: #1a3a5c; + + &--inactive { + background: #f6f6f6; + color: rgba(102, 102, 102, 0.5); + } + } +} +``` + +## Testing + +### `DetailsBackButton.test.tsx` +- Renders link with `href="/ru/onlineboard"` for `locale="ru"` +- Renders link label from `SHARED.BACK-BOARD` key +- Has `data-testid="details-back-button"` +- Renders arrow icon + +### `DaysOfWeekStrip.test.tsx` +- Renders 7 day boxes +- First 3 active for `"1110000"`, last 4 have `--inactive` modifier +- All active for `"1111111"` +- All inactive for `"0000000"` +- Renders DAYS.1 through DAYS.7 labels in order + +### `weekDateRange.test.ts` +- Returns Monday/Sunday formatted `dd.MM.yyyy` for a mid-week date +- Handles a Sunday input (returns that week's Mon-Sun) +- Handles a Monday input (returns same day as start) +- Returns `{ start: "", end: "" }` for invalid input +- Returns `{ start: "", end: "" }` for undefined input + +### `FlightSchedule.test.tsx` +- Returns null when firstLeg has no `daysOfWeek` +- Returns null when `daysOfWeek.flight` is empty string +- Renders scheduled times + duration when `daysOfWeek` is present +- Renders DaysOfWeekStrip with `daysOfWeek.flight` +- Substitutes `{START_DATE}` and `{END_DATE}` into the note + +### `OnlineBoardDetailsPage.test.tsx` (update existing) +- `headerLeft` renders `DetailsBackButton` (add `data-testid` assertion) +- `FlightSchedule` rendered below legs when `daysOfWeek` is present +- `FlightSchedule` not rendered when `daysOfWeek` is absent + +## i18n + +All required keys already present in React locale files. Confirmed via grep: + +- `DAYS.1` through `DAYS.7` +- `SHARED.SCHEDULE-FLIGHT`, `SHARED.DAYS-EXECUTE-FLIGHT`, `SHARED.NOTE-TIME-SCHEDULE` +- `SHARED.PATH-TIME`, `SHARED.DEPARTURE-SCHEDULED`, `SHARED.ARRIVAL-SCHEDULED` +- `SHARED.BACK-BOARD` + +No new keys needed. + +## Out of Scope + +- Schedule feature's back button (would navigate to `/schedule` — different viewType) +- Schedule feature's flight-schedule (only needed on Online Board details per Angular's `!flight.isMultiLeg` gate in Schedule variant) +- Custom accordion primitive extraction (we use PrimeReact directly) +- Changes to existing breadcrumbs (back button is additional navigation, not replacing breadcrumbs) + +## Files Touched + +### New + +- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.tsx` +- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.scss` +- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.test.tsx` +- `src/features/online-board/components/DetailsBackButton/index.ts` +- `src/features/online-board/components/FlightSchedule/FlightSchedule.tsx` +- `src/features/online-board/components/FlightSchedule/FlightSchedule.scss` +- `src/features/online-board/components/FlightSchedule/FlightSchedule.test.tsx` +- `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.tsx` +- `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx` +- `src/features/online-board/components/FlightSchedule/weekDateRange.ts` +- `src/features/online-board/components/FlightSchedule/weekDateRange.test.ts` +- `src/features/online-board/components/FlightSchedule/index.ts` + +### Modified + +- `src/features/online-board/types.ts` — add `IDaysOfWeek`, extend `IFlightLeg` with optional `daysOfWeek` +- `src/features/online-board/types.test.ts` — test new types +- `src/features/online-board/components/OnlineBoardDetailsPage.tsx` — swap PageTabs for DetailsBackButton in 4 PageLayout call sites; add `` below legs +- `src/features/online-board/components/OnlineBoardDetailsPage.test.tsx` — update mocks and add integration tests