Close the remaining high-impact parity gaps
Batch of fixes identified by the comparison audit:
Schedule search page (ScheduleSearchPage):
- Resolve IATA codes to city/airport names, so the H1 reads
'Маршрут: Шереметьево - Санкт-Петербург' instead of 'SVO - LED'.
- Breadcrumb trail now includes the human-friendly route as its
last entry.
Details page (OnlineBoardDetailsPage):
- Hide the 'Перелет N' leg header for single-leg flights (Angular
parity — that label is only meaningful for multi-leg routes).
- Translate the leg status through FLIGHT-STATUSES.* instead of
emitting the raw enum ('Cancelled' → 'Отменен', etc.).
- Humanize leg and total flying time through formatDuration so the
page reads '1ч 25м' rather than '01:25:00'.
Details meal panel (MealPanel):
- Use the same FOOD.* translation keys as Angular, so labels become
'Эконом класс / Комфорт класс / Бизнес класс / Специальное
питание'.
- Add the Special-meal icon + link (was stubbed out previously).
Accessibility:
- Route the English aria-labels through new SHARED.A11Y-* keys in
DayTabs pagination, FlightListSkeleton, ScrollUpButton and
PrintButton.
Breadcrumbs:
- Render the 'Главная' crumb as a link even when it's the only /
last item (it was dropping to plain text on start pages). Angular
always links it to aeroflot.ru.
Tests updated to assert the new translated labels and duration
formatting; 1258 tests passing.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { FC } from "react";
|
||||
import { useTranslation } from "@/i18n/provider.js";
|
||||
import "./FlightListSkeleton.scss";
|
||||
|
||||
export interface FlightListSkeletonProps {
|
||||
@@ -13,8 +14,9 @@ export interface FlightListSkeletonProps {
|
||||
export const FlightListSkeleton: FC<FlightListSkeletonProps> = ({
|
||||
count = 5,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flight-list-skeleton" aria-busy="true" aria-label="Loading flights">
|
||||
<div className="flight-list-skeleton" aria-busy="true" aria-label={t("SHARED.A11Y-LOADING-FLIGHTS")}>
|
||||
{Array.from({ length: count }, (_, i) => (
|
||||
<div key={i} className="flight-list-skeleton__row">
|
||||
<div className="flight-list-skeleton__cell flight-list-skeleton__cell--number" />
|
||||
|
||||
@@ -31,21 +31,28 @@ export const Breadcrumbs: FC<BreadcrumbsProps> = ({ items = [] }) => {
|
||||
return (
|
||||
<nav className="breadcrumbs" aria-label="breadcrumb" data-testid="breadcrumbs">
|
||||
<ol className="breadcrumbs__list">
|
||||
{allItems.map((item, index) => (
|
||||
<li
|
||||
key={`${item.label}-${index}`}
|
||||
className={`breadcrumbs__item${index === allItems.length - 1 ? " breadcrumbs__item--active" : ""}`}
|
||||
>
|
||||
{item.url && index < allItems.length - 1 ? (
|
||||
<a href={item.url} className="breadcrumbs__link">{item.label}</a>
|
||||
) : (
|
||||
<span className="breadcrumbs__text">{item.label}</span>
|
||||
)}
|
||||
{index < allItems.length - 1 && (
|
||||
<span className="breadcrumbs__separator">></span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
{allItems.map((item, index) => {
|
||||
const isLast = index === allItems.length - 1;
|
||||
// The Home crumb (index 0) is always a link, even on start pages
|
||||
// where it happens to be the only/last item — matches Angular,
|
||||
// which always renders 'Главная' with the aeroflot.ru href.
|
||||
const showAsLink = Boolean(item.url) && (index === 0 || !isLast);
|
||||
return (
|
||||
<li
|
||||
key={`${item.label}-${index}`}
|
||||
className={`breadcrumbs__item${isLast ? " breadcrumbs__item--active" : ""}`}
|
||||
>
|
||||
{showAsLink ? (
|
||||
<a href={item.url} className="breadcrumbs__link">{item.label}</a>
|
||||
) : (
|
||||
<span className="breadcrumbs__text">{item.label}</span>
|
||||
)}
|
||||
{!isLast && (
|
||||
<span className="breadcrumbs__separator">></span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
*/
|
||||
|
||||
import { type FC, useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "@/i18n/provider.js";
|
||||
import "./ScrollUpButton.scss";
|
||||
|
||||
const SCROLL_THRESHOLD = 300;
|
||||
|
||||
export const ScrollUpButton: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,7 +36,7 @@ export const ScrollUpButton: FC = () => {
|
||||
type="button"
|
||||
className="scroll-up-button"
|
||||
onClick={scrollToTop}
|
||||
aria-label="Scroll to top"
|
||||
aria-label={t("SHARED.A11Y-SCROLL-TO-TOP")}
|
||||
data-testid="scroll-up-button"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
|
||||
Reference in New Issue
Block a user