Add back button + flight schedule timeline (B.6) design spec

This commit is contained in:
2026-04-17 01:55:48 +03:00
parent 1f53dd1135
commit df79213186
@@ -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 `<PageTabs viewType="onlineboard" />` in `PageLayout.headerLeft`.
- No flight-schedule timeline component exists.
- `IFlightLeg` type has no `daysOfWeek` field.
## Approach
Two small independent component families:
1. **`DetailsBackButton`** — stateless `<Link>`-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
<Link
to={`/${locale}/onlineboard`}
className="details-back-button"
data-testid="details-back-button"
>
<span className="details-back-button__arrow" aria-hidden="true"></span>
<span>{t("SHARED.BACK-BOARD")}</span>
</Link>
```
Always visible. Uses React Router `<Link>` (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 `<Accordion multiple={false} activeIndex={0}>`** with a single `<AccordionTab header={t("SHARED.SCHEDULE-FLIGHT")}>` 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`
- `<DaysOfWeekStrip flightBitString={firstLeg.daysOfWeek.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={<PageTabs viewType="onlineboard" />}` with `headerLeft={<DetailsBackButton locale={locale} />}` in the main happy-path return.
- Also replace in the loading/error/not-found branches' `commonLayoutProps` for consistency.
2. After `<FlightLegs legs={legs} />` (or after the flying-time div), add `<FlightSchedule flight={displayFlight} />`.
## 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 `<FlightSchedule>` below legs
- `src/features/online-board/components/OnlineBoardDetailsPage.test.tsx` — update mocks and add integration tests