From 4c487ab1b220859c05c56aa66161a0133e630a38 Mon Sep 17 00:00:00 2001 From: gnezim Date: Sun, 19 Apr 2026 23:24:06 +0300 Subject: [PATCH] Render Connecting flights + Angular grid for schedule rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Connecting (multi-leg via transit) flights are now folded into a synthetic MultiLeg shape with combined flight numbers (SU 6188, SU 6233) and per-leg airline logos, matching Angular's schedule-list-flight-header. - Schedule grid now uses Angular's 8-column layout (80/120/100/240/100/100/240/16). The middle status icon is replaced by a duration column with the blue clock icon and '3ч. 48мин.' / '4h 19m' formatting. - Multi-leg airline logos use the round badge variant (separate round.png assets) so two carriers fit side-by-side without overlap. - Action buttons removed from collapsed rows — Angular only shows flight-actions in the expanded body. Added chevron column for every schedule card and made schedule cards expandable by default. - Removed 'Туда: MOW → KUF' subhead from outbound section, matching Angular's bare flight list under the column header. --- .../components/ScheduleSearchPage.tsx | 51 +++++++++-- src/ui/flights/FlightCard.scss | 31 +++++++ src/ui/flights/FlightCard.tsx | 86 +++++++++++++------ src/ui/flights/FlightList.tsx | 5 +- src/ui/flights/OperatorLogo.scss | 10 ++- src/ui/flights/OperatorLogo.tsx | 19 ++-- 6 files changed, 156 insertions(+), 46 deletions(-) diff --git a/src/features/schedule/components/ScheduleSearchPage.tsx b/src/features/schedule/components/ScheduleSearchPage.tsx index 97a637c9..bae46aeb 100644 --- a/src/features/schedule/components/ScheduleSearchPage.tsx +++ b/src/features/schedule/components/ScheduleSearchPage.tsx @@ -65,12 +65,50 @@ function formatApiDate(yyyymmdd: string): string { } /** - * Extract simple flights from the mixed IFlight[] response for rendering. + * Convert the mixed IFlight[] response into a flat ISimpleFlight[] for + * rendering. Connecting flights are folded into a synthetic MultiLeg + * shape so the existing FlightCard can render them with combined leg + * numbers, both airline logos, and the total flying time — matching + * Angular's `schedule-list-flight-header` for connecting flights. */ -function extractSimpleFlights(flights: Array<{ routeType: string }>): ISimpleFlight[] { - return flights.filter( - (f): f is ISimpleFlight => f.routeType === "Direct" || f.routeType === "MultiLeg", - ); +function extractSimpleFlights( + flights: Array<{ routeType: string }>, +): ISimpleFlight[] { + const out: ISimpleFlight[] = []; + for (const f of flights) { + if (f.routeType === "Direct" || f.routeType === "MultiLeg") { + out.push(f as unknown as ISimpleFlight); + continue; + } + if (f.routeType === "Connecting") { + const conn = f as unknown as { + flights: ISimpleFlight[]; + flyingTime: string; + status: import("@/features/online-board/types.js").FlightStatus; + }; + const first = conn.flights[0]; + if (!first) continue; + const allLegs: import("@/features/online-board/types.js").IFlightLeg[] = []; + for (const child of conn.flights) { + if (child.routeType === "Direct") allLegs.push(child.leg); + else allLegs.push(...child.legs); + } + const synthetic = { + routeType: "MultiLeg", + flightId: first.flightId, + flyingTime: conn.flyingTime, + operatingBy: first.operatingBy, + id: conn.flights.map((c) => c.id).join("+"), + status: conn.status, + legs: allLegs, + // Carry through the original child flight numbers so the header + // can display 'SU 6188, SU 6233'. + _childFlightIds: conn.flights.map((c) => c.flightId), + } as unknown as ISimpleFlight; + out.push(synthetic); + } + } + return out; } export const ScheduleSearchPage: FC = ({ params }) => { @@ -214,9 +252,6 @@ export const ScheduleSearchPage: FC = ({ params }) => {
-

- {t("SCHEDULE.OUTBOUND")}: {outbound.departure} → {outbound.arrival} -

= ({ const depTimes = depStation.times; const arrTimes = arrStation.times; - const flightNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`; + // Connecting flights get folded into a synthetic MultiLeg shape with + // an extra `_childFlightIds` array so we can render `SU 6188, SU 6233` + // — Angular's `schedule-list-flight-header` does the same via + // `flightNumber` pipe over `getFlights()`. + const childFlightIds = (flight as ISimpleFlight & { + _childFlightIds?: { carrier: string; flightNumber: string; suffix?: string }[]; + })._childFlightIds; + const flightNumber = childFlightIds && childFlightIds.length > 1 + ? childFlightIds + .map((id) => `${id.carrier} ${id.flightNumber}${id.suffix ?? ""}`) + .join(", ") + : `${flight.flightId.carrier} ${flight.flightId.flightNumber}`; const carrier = operatingCarrier(flight.operatingBy) ?? flight.flightId.carrier; const isMultiLeg = flight.routeType === "MultiLeg"; const aircraftName = @@ -104,6 +141,10 @@ export const FlightCard: FC = ({ departureLeg.equipment?.aircraft?.scheduled?.title ?? null; + // Total duration shown in the middle column on schedule rows. Prefer + // the flight-level flyingTime; fall back to the primary leg. + const flightDuration = flight.flyingTime || departureLeg.flyingTime || ""; + const [expanded, setExpanded] = useState(Boolean(initialExpanded)); const rowClickable = expandable || Boolean(onClick); const toggleExpanded = (): void => { @@ -180,6 +221,11 @@ export const FlightCard: FC = ({ key={`${operatingCarrier(leg.operatingBy) ?? carrier}-${i}`} carrier={operatingCarrier(leg.operatingBy) ?? carrier} locale={language} + // Collapsed multi-leg schedule rows show two small + // round airline badges side-by-side — matches + // Angular's `operator-logo-and-model` with + // `round="!expanded || !direct"`. + round={direction === "schedule" && !expanded} /> )) : } @@ -206,9 +252,18 @@ export const FlightCard: FC = ({ />
-
- -
+ {direction === "schedule" ? ( +
+
+ ) : ( +
+ +
+ )}
= ({ />
- {direction === "schedule" ? ( -
- e.stopPropagation()} - > - {t("SHARED.BUY-TICKET") || "Купить"} - - e.stopPropagation()} - > - {t("SHARED.DETAILS")} - -
- ) : expandable && ( + {(expandable || direction === "schedule") && (