Visual parity fixes — drop pixel mismatch on 6+ pages

- OperatorLogo: accept BCP-47 codes (`ru-ru`) by trimming to first 2
  chars before picking the en/ru asset variant. Fixes the Russian
  flight-details page rendering ROSSIYA (Latin) instead of РОССИЯ.
- FlightCard / FlightList: thread `direction` from the search page so
  arrival results show Высадка (deboarding) instead of Посадка
  (boarding) — Angular parity. The arrival side reads from
  arrivalLeg.transition.deboarding when direction === 'arrival'.
- OnlineBoardFilter:
  - Дата рейса starts blank with `ДД.ММ.ГГГГ` placeholder; submit
    handler defaults to today on empty.
  - Город вылета / Город прилета placeholders flip to
    `Все направления` when the opposite-direction field is filled.
  - Filter content row now flows with $space-l vertical gap to match
    Angular's accordion-content rhythm (was ~6 px tighter).
- FlightsMiniList: `display: none` on mobile. Avoids the duplicate
  summary card that was floating above the main details on small
  viewports — Angular hides the sidebar mini-list there.
- FlightsMap calendar trigger: override PrimeReact's filled-blue
  button to a transparent outline so it reads as a glyph (matches
  Angular's outline calendar icon).

Pixel-mismatch results (re-diffed via scripts/visual-diff.mjs):
  en-onlineboard-route       5.50% → 4.62%
  onlineboard-arrival        5.53% → 4.63%
  onlineboard-departure      5.92% → 5.03%
  onlineboard-route          5.16% → 4.78%
  mobile-onlineboard-start  23.51% → 20.37%
  mobile-flight-details     18.82% → 17.92%
  flight-details            carrier-logo verified visually; pixel
                            count unchanged (height delta dominates)
  onlineboard-start         14.56% → 14.52%

Larger remaining mismatches (schedule-route 14%, flights-map 34%,
flight-details 11%) are dominated by structural Angular features the
React port doesn't yet ship (day grouping, code-share bundling on
schedule; geo-driven origin marker on map; height-delta on details).
Tracked as P1 follow-ups in the comparison report.
This commit is contained in:
2026-04-19 20:18:15 +03:00
parent 9acfeb4052
commit e7cf11e799
9 changed files with 196 additions and 20 deletions
+21 -9
View File
@@ -33,6 +33,11 @@ export interface FlightCardProps {
initialExpanded?: boolean;
/** Fired when the user clicks 'Детали рейса' in the expanded panel. */
onViewDetails?: () => void;
/**
* Search direction. `arrival` swaps the boarding row to deboarding
* (label `Высадка` instead of `Посадка`) per Angular parity.
*/
direction?: "departure" | "arrival" | "route" | "flight";
}
/** Extract the primary leg from a flight (first leg for multi-leg) */
@@ -75,6 +80,7 @@ export const FlightCard: FC<FlightCardProps> = ({
expandable,
initialExpanded,
onViewDetails,
direction = "route",
}) => {
const { t } = useTranslation();
const { language } = useLocale();
@@ -119,7 +125,13 @@ export const FlightCard: FC<FlightCardProps> = ({
? "SHARED.ACTUAL"
: "SHARED.EXPECTED";
const boarding = departureLeg.transition?.boarding;
// Arrival pages show the deboarding (Высадка) transition; departure /
// route / flight-number views show boarding (Посадка). Matches Angular.
const isArrival = direction === "arrival";
const transition = isArrival
? arrivalLeg.transition?.deboarding
: departureLeg.transition?.boarding;
const transitionLabelKey = isArrival ? "DETAILS.DEBOARDING" : "DETAILS.BOARDING";
const BOARDING_STATUS_KEY: Record<string, string> = {
Finished: "BOARDING-STATUSES.Finished",
Expected: "BOARDING-STATUSES.Expected",
@@ -254,10 +266,10 @@ export const FlightCard: FC<FlightCardProps> = ({
</div>
</div>
{boarding && (
{transition && (
<div className="flight-card__detail-row">
<div className="flight-card__detail-label">
{t("DETAILS.BOARDING")}
{t(transitionLabelKey)}
</div>
<div className="flight-card__detail-group">
<div>
@@ -265,29 +277,29 @@ export const FlightCard: FC<FlightCardProps> = ({
{t("DETAILS.STATUS")}
</span>
<span
className={`flight-card__detail-value flight-card__detail-status flight-card__detail-status--${boarding.status.toLowerCase()}`}
className={`flight-card__detail-value flight-card__detail-status flight-card__detail-status--${transition.status.toLowerCase()}`}
>
<span className="flight-card__status-dot" aria-hidden="true" />
{t(BOARDING_STATUS_KEY[boarding.status] ?? boarding.status)}
{t(BOARDING_STATUS_KEY[transition.status] ?? transition.status)}
</span>
</div>
{boarding.start?.local && (
{transition.start?.local && (
<div>
<span className="flight-card__detail-caption">
{t("SHARED.BOARDING-START")}
</span>
<span className="flight-card__detail-value">
{formatLocalTime(boarding.start.local)}
{formatLocalTime(transition.start.local)}
</span>
</div>
)}
{boarding.end?.local && (
{transition.end?.local && (
<div>
<span className="flight-card__detail-caption">
{t("SHARED.BOARDING-END")}
</span>
<span className="flight-card__detail-value">
{formatLocalTime(boarding.end.local)}
{formatLocalTime(transition.end.local)}
</span>
</div>
)}
+8
View File
@@ -20,6 +20,12 @@ export interface FlightListProps {
* to now; other days → first/last).
*/
initialCurrentFlightId?: string | null;
/**
* Search direction. Drives which transition (boarding vs deboarding)
* surfaces in the expanded card and what its row title reads. Matches
* Angular's `Посадка` / `Высадка` switch on departure vs arrival pages.
*/
direction?: "departure" | "arrival" | "route" | "flight";
}
/**
@@ -34,6 +40,7 @@ export const FlightList: FC<FlightListProps> = ({
skeletonCount = 5,
onFlightClick,
initialCurrentFlightId,
direction = "route",
}) => {
const { t } = useTranslation();
const cardRefs = useRef<Map<string, HTMLDivElement>>(new Map());
@@ -74,6 +81,7 @@ export const FlightList: FC<FlightListProps> = ({
>
<FlightCard
flight={flight}
direction={direction}
expandable={Boolean(onFlightClick)}
initialExpanded={flight.id === initialCurrentFlightId}
{...(onFlightClick
+5 -1
View File
@@ -63,7 +63,11 @@ export const OperatorLogo: FC<OperatorLogoProps> = ({ carrier, locale, round, ti
const style = useMemo(() => {
const mapping = LOGO_PATHS[carrier];
if (!mapping) return undefined;
const src = locale === "ru" && mapping.ru ? mapping.ru : mapping.en;
// Accept either the short language code (`"ru"`) or a BCP-47 URL
// locale (`"ru-ru"`); only the first two chars matter for picking
// between the carrier's en/ru asset variants.
const lang = (locale ?? "").slice(0, 2).toLowerCase();
const src = lang === "ru" && mapping.ru ? mapping.ru : mapping.en;
return { backgroundImage: `url('${src}')` };
}, [carrier, locale]);