Revert "Convert schedule WeekTabs to day-of-week strip"
This reverts commit c097ab21fe.
This commit is contained in:
@@ -297,16 +297,6 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
||||
<WeekTabs
|
||||
selectedMonday={selectedMonday}
|
||||
onNavigate={handleWeekChange}
|
||||
onSelectDay={(dayYmd) => {
|
||||
// Day group sections are keyed by `data-day=${ymd}`
|
||||
// (see DayGroupedFlightList). Scroll the matching one
|
||||
// into view; this stays a no-op if it's not rendered.
|
||||
if (typeof document === "undefined") return;
|
||||
const el = document.querySelector(
|
||||
`[data-day="${dayYmd}"]`,
|
||||
);
|
||||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}}
|
||||
/>
|
||||
{inbound && (
|
||||
<div
|
||||
|
||||
@@ -36,19 +36,15 @@
|
||||
&__tab {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 6px 4px;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
color: #1f6fb8;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
transition: background 120ms;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
line-height: 1.1;
|
||||
|
||||
&:hover { background: #e8f0fa; }
|
||||
|
||||
@@ -57,18 +53,7 @@
|
||||
color: #022040;
|
||||
font-weight: 600;
|
||||
box-shadow: inset 0 -2px 0 #2563eb;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
&__day-num {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__day-name {
|
||||
font-size: 11px;
|
||||
text-transform: lowercase;
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,46 @@
|
||||
/**
|
||||
* Day-of-week tabs for the schedule pages.
|
||||
* Weekly date-range tabs for the schedule pages.
|
||||
*
|
||||
* Mirrors Angular's schedule day strip (`20 пн 21 вт 22 ср …`) — seven
|
||||
* tabs spanning Mon–Sun of the active week. Prev/next navigates by
|
||||
* whole weeks; clicking a day highlights it and (optionally) scrolls
|
||||
* the corresponding day group into view via `onSelectDay`.
|
||||
* Mirrors Angular's `week-tabs` (Monday-anchored 7-day windows shown
|
||||
* as `13 апр - 19 апр`). Distinct from the onlineboard's `DayTabs`
|
||||
* which is daily.
|
||||
*
|
||||
* Public API: still keyed off `selectedMonday` (yyyy-MM-dd Monday) and
|
||||
* `onNavigate(mondayYmd)` so the parent's URL plumbing keeps working.
|
||||
* Emits the Monday yyyy-MM-dd of the chosen week on click.
|
||||
*/
|
||||
|
||||
import { type FC, useMemo } from "react";
|
||||
import { type FC, useMemo, useState } from "react";
|
||||
import { useLocale } from "@/i18n/useLocale.js";
|
||||
import "./WeekTabs.scss";
|
||||
|
||||
const PAGE_SIZE = 7;
|
||||
const WEEKS_BEFORE = 1;
|
||||
const WEEKS_AFTER = 30;
|
||||
|
||||
export interface WeekTabsProps {
|
||||
/** Monday yyyy-MM-dd of the currently active week. */
|
||||
selectedMonday: string;
|
||||
/** Fired with the Monday yyyy-MM-dd of the chosen week (prev/next). */
|
||||
/** Fired with the Monday yyyy-MM-dd of the chosen week. */
|
||||
onNavigate: (mondayYmd: string) => void;
|
||||
/** Optional yyyy-MM-dd of the highlighted day within the week. */
|
||||
selectedDayYmd?: string;
|
||||
/** Optional callback when a specific day tab is clicked. */
|
||||
onSelectDay?: (dayYmd: string) => void;
|
||||
}
|
||||
|
||||
interface DayEntry {
|
||||
date: Date;
|
||||
interface WeekEntry {
|
||||
monday: Date;
|
||||
sunday: Date;
|
||||
ymd: string;
|
||||
dayNum: string;
|
||||
weekday: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function startOfWeekMonday(d: Date): Date {
|
||||
const out = new Date(d);
|
||||
const day = out.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
||||
const diff = (day + 6) % 7; // distance back to Monday
|
||||
out.setDate(out.getDate() - diff);
|
||||
out.setHours(0, 0, 0, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
function fmt(date: Date, fmt: Intl.DateTimeFormat): string {
|
||||
return fmt.format(date).replace(/\.$/, "");
|
||||
}
|
||||
|
||||
function ymd(d: Date): string {
|
||||
@@ -39,67 +50,44 @@ function ymd(d: Date): string {
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function parseYmd(s: string): Date | null {
|
||||
if (!s || s.length !== 10) return null;
|
||||
const y = parseInt(s.slice(0, 4), 10);
|
||||
const m = parseInt(s.slice(5, 7), 10) - 1;
|
||||
const d = parseInt(s.slice(8, 10), 10);
|
||||
if (Number.isNaN(y) || Number.isNaN(m) || Number.isNaN(d)) return null;
|
||||
return new Date(y, m, d);
|
||||
}
|
||||
|
||||
function shiftWeek(monday: Date, deltaWeeks: number): string {
|
||||
const next = new Date(monday);
|
||||
next.setDate(monday.getDate() + deltaWeeks * 7);
|
||||
return ymd(next);
|
||||
}
|
||||
|
||||
function todayYmd(): string {
|
||||
return ymd(new Date());
|
||||
}
|
||||
|
||||
export const WeekTabs: FC<WeekTabsProps> = ({
|
||||
selectedMonday,
|
||||
onNavigate,
|
||||
selectedDayYmd,
|
||||
onSelectDay,
|
||||
}) => {
|
||||
export const WeekTabs: FC<WeekTabsProps> = ({ selectedMonday, onNavigate }) => {
|
||||
const { language } = useLocale();
|
||||
// 'пн', 'вт', … — short weekday in active locale. Built once per
|
||||
// locale; the `.replace(/\.$/, "")` strips Russian's trailing dot.
|
||||
const weekdayFmt = useMemo(
|
||||
() => new Intl.DateTimeFormat(language, { weekday: "short" }),
|
||||
// Angular shows month abbreviated ("13 апр - 19 апр"). Build once
|
||||
// per locale; the month part comes through in the locale's natural
|
||||
// short form.
|
||||
const dayMonthFmt = useMemo(
|
||||
() => new Intl.DateTimeFormat(language, { day: "numeric", month: "short" }),
|
||||
[language],
|
||||
);
|
||||
|
||||
const monday = useMemo(() => parseYmd(selectedMonday), [selectedMonday]);
|
||||
|
||||
const days: DayEntry[] = useMemo(() => {
|
||||
if (!monday) return [];
|
||||
const out: DayEntry[] = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(monday);
|
||||
d.setDate(monday.getDate() + i);
|
||||
const weeks: WeekEntry[] = useMemo(() => {
|
||||
const out: WeekEntry[] = [];
|
||||
const today = new Date();
|
||||
const start = startOfWeekMonday(today);
|
||||
start.setDate(start.getDate() - WEEKS_BEFORE * 7);
|
||||
for (let i = 0; i < WEEKS_BEFORE + WEEKS_AFTER + 1; i++) {
|
||||
const monday = new Date(start);
|
||||
monday.setDate(start.getDate() + i * 7);
|
||||
const sunday = new Date(monday);
|
||||
sunday.setDate(monday.getDate() + 6);
|
||||
out.push({
|
||||
date: d,
|
||||
ymd: ymd(d),
|
||||
dayNum: String(d.getDate()),
|
||||
weekday: weekdayFmt.format(d).replace(/\.$/, "").toLowerCase(),
|
||||
monday,
|
||||
sunday,
|
||||
ymd: ymd(monday),
|
||||
label: `${fmt(monday, dayMonthFmt)} - ${fmt(sunday, dayMonthFmt)}`,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}, [monday, weekdayFmt]);
|
||||
}, [dayMonthFmt]);
|
||||
|
||||
if (!monday || days.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const initialPage = Math.max(
|
||||
0,
|
||||
Math.floor(weeks.findIndex((w) => w.ymd === selectedMonday) / PAGE_SIZE),
|
||||
);
|
||||
const [page, setPage] = useState(Number.isFinite(initialPage) ? initialPage : 0);
|
||||
const totalPages = Math.max(1, Math.ceil(weeks.length / PAGE_SIZE));
|
||||
|
||||
const today = todayYmd();
|
||||
const highlight = selectedDayYmd && days.some((d) => d.ymd === selectedDayYmd)
|
||||
? selectedDayYmd
|
||||
: days.some((d) => d.ymd === today)
|
||||
? today
|
||||
: days[0]!.ymd;
|
||||
const visible = weeks.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
|
||||
|
||||
return (
|
||||
<nav
|
||||
@@ -110,27 +98,25 @@ export const WeekTabs: FC<WeekTabsProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
className="week-tabs__nav week-tabs__nav--prev"
|
||||
onClick={() => onNavigate(shiftWeek(monday, -1))}
|
||||
aria-label="Previous week"
|
||||
disabled={page === 0}
|
||||
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||
aria-label="Previous week range"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<div className="week-tabs__list">
|
||||
{days.map((d) => {
|
||||
const isActive = d.ymd === highlight;
|
||||
{visible.map((w) => {
|
||||
const isActive = w.ymd === selectedMonday;
|
||||
return (
|
||||
<button
|
||||
key={d.ymd}
|
||||
key={w.ymd}
|
||||
type="button"
|
||||
className={`week-tabs__tab${isActive ? " week-tabs__tab--active" : ""}`}
|
||||
data-active={isActive ? "true" : "false"}
|
||||
data-testid={`week-tab-${d.ymd}`}
|
||||
onClick={() => {
|
||||
if (onSelectDay) onSelectDay(d.ymd);
|
||||
}}
|
||||
data-testid={`week-tab-${w.ymd}`}
|
||||
onClick={() => onNavigate(w.ymd)}
|
||||
>
|
||||
<span className="week-tabs__day-num">{d.dayNum}</span>
|
||||
<span className="week-tabs__day-name">{d.weekday}</span>
|
||||
{w.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -138,8 +124,9 @@ export const WeekTabs: FC<WeekTabsProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
className="week-tabs__nav week-tabs__nav--next"
|
||||
onClick={() => onNavigate(shiftWeek(monday, 1))}
|
||||
aria-label="Next week"
|
||||
disabled={page >= totalPages - 1}
|
||||
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
aria-label="Next week range"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user