diff --git a/src/features/online-board/api.ts b/src/features/online-board/api.ts index 4a1f7910..ada773f9 100644 --- a/src/features/online-board/api.ts +++ b/src/features/online-board/api.ts @@ -99,8 +99,10 @@ export async function getFlightDetails( * Get available calendar days for a given search context. * Maps to: `GET /v1/days/{date}/31/{searchType}/{searchParams}/board/` * - * The API returns `{ days: "2025-01-01,2025-01-02,..." }` — a single - * comma-separated string. This function splits it into `string[]`. + * The API returns `{ days: "1110101..." }` — a 31-char bitmask where each + * character represents a day starting from (baseDate - 1). '1' = flights + * available, '0' = no flights. This function converts enabled positions + * into yyyyMMdd date strings. */ export async function getCalendarDays( client: ApiClient, @@ -110,7 +112,7 @@ export async function getCalendarDays( const path = `flights/v1/${client.locale}/days/${params.date}/31/${searchSegment}/board/`; const response = await client.get(path); - return parseCalendarDays(response.days); + return parseCalendarDays(response.days, params.date); } // --------------------------------------------------------------------------- @@ -133,7 +135,60 @@ function buildCalendarSearchSegment(params: CalendarParams): string { } } -function parseCalendarDays(days: string): string[] { +/** + * Parse a calendar days bitmask into an array of yyyyMMdd date strings. + * + * The API returns a 31-char string of '1' and '0'. Each position maps to + * a day starting from (baseDate - 1 day). Positions with '1' are enabled. + * + * Matches Angular's search-page-base.component.ts logic: + * date.setDate(date.getDate() - 1); + * for (var i = 0; i < res.days.length; i++) { ... date.setDate(date.getDate() + 1); } + */ +function parseCalendarDays(days: string, baseDate: string): string[] { if (!days) return []; + + // If it looks like a bitmask (only 0s and 1s), convert to dates + if (/^[01]+$/.test(days)) { + return bitmaskToDates(days, baseDate); + } + + // Fallback: comma-separated date list (legacy format) return days.split(",").map((d) => d.trim()).filter(Boolean); } + +/** + * Convert a bitmask string to yyyyMMdd date strings. + * Base date is the search date; iteration starts from (baseDate - 1 day). + */ +function bitmaskToDates(bitmask: string, baseDate: string): string[] { + // Parse baseDate — could be "yyyy-MM-ddT00:00:00" or "yyyyMMdd" + let year: number, month: number, day: number; + if (baseDate.includes("-")) { + const parts = baseDate.split("T")[0]!.split("-"); + year = parseInt(parts[0]!, 10); + month = parseInt(parts[1]!, 10) - 1; + day = parseInt(parts[2]!, 10); + } else { + year = parseInt(baseDate.slice(0, 4), 10); + month = parseInt(baseDate.slice(4, 6), 10) - 1; + day = parseInt(baseDate.slice(6, 8), 10); + } + + // Start from baseDate - 1 day (matching Angular) + const cursor = new Date(year, month, day); + cursor.setDate(cursor.getDate() - 1); + + const result: string[] = []; + for (let i = 0; i < bitmask.length; i++) { + if (bitmask[i] === "1") { + const y = cursor.getFullYear().toString(); + const m = (cursor.getMonth() + 1).toString().padStart(2, "0"); + const d = cursor.getDate().toString().padStart(2, "0"); + result.push(`${y}${m}${d}`); + } + cursor.setDate(cursor.getDate() + 1); + } + + return result; +} diff --git a/src/features/online-board/components/OnlineBoardFilter.tsx b/src/features/online-board/components/OnlineBoardFilter.tsx index d4897791..da085628 100644 --- a/src/features/online-board/components/OnlineBoardFilter.tsx +++ b/src/features/online-board/components/OnlineBoardFilter.tsx @@ -45,23 +45,49 @@ function validateFlightNumber(value: string): string | null { return null; } -export const OnlineBoardFilter: FC = () => { +export interface OnlineBoardFilterProps { + /** Pre-populate filter from URL params on search results pages */ + initialDeparture?: string; + initialArrival?: string; + initialDate?: string; + initialTab?: AccordionTab; + initialFlightNumber?: string; +} + +function yyyymmddToDate(yyyymmdd: string): Date { + const y = parseInt(yyyymmdd.slice(0, 4), 10); + const m = parseInt(yyyymmdd.slice(4, 6), 10) - 1; + const d = parseInt(yyyymmdd.slice(6, 8), 10); + return new Date(y, m, d); +} + +export const OnlineBoardFilter: FC = ({ + initialDeparture, + initialArrival, + initialDate, + initialTab, + initialFlightNumber, +}) => { const { t } = useTranslation(); const navigate = useNavigate(); const routeParams = useParams<{ lang: string }>(); const lang = routeParams.lang ?? "ru"; - const [activeTab, setActiveTab] = useState("route"); + const [activeTab, setActiveTab] = useState(initialTab ?? "route"); // Flight number fields - const [flightNumber, setFlightNumber] = useState(""); - const [flightDate, setFlightDate] = useState(new Date()); + const [flightNumber, setFlightNumber] = useState(initialFlightNumber ?? ""); + const [flightDate, setFlightDate] = useState( + initialTab === "flight" && initialDate ? yyyymmddToDate(initialDate) : new Date(), + ); const [flightNumberError, setFlightNumberError] = useState(null); // Route fields - const [routeDeparture, setRouteDeparture] = useState(""); - const [routeArrival, setRouteArrival] = useState(""); - const [routeDate, setRouteDate] = useState(new Date()); + const [routeDeparture, setRouteDeparture] = useState(initialDeparture ?? ""); + const [routeArrival, setRouteArrival] = useState(initialArrival ?? ""); + const [routeDate, setRouteDate] = useState( + initialDate ? yyyymmddToDate(initialDate) : new Date(), + ); const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]); // City autocomplete search diff --git a/src/features/online-board/components/OnlineBoardSearchPage.tsx b/src/features/online-board/components/OnlineBoardSearchPage.tsx index f628f722..bc13733b 100644 --- a/src/features/online-board/components/OnlineBoardSearchPage.tsx +++ b/src/features/online-board/components/OnlineBoardSearchPage.tsx @@ -37,6 +37,16 @@ export interface OnlineBoardSearchPageProps { params: OnlineBoardParams & { type: "flight" | "departure" | "arrival" | "route" }; } +/** + * Format a yyyyMMdd date string for display in the calendar strip. + * Shows the day number (e.g. "15", "16"). + */ +function formatDayLabel(yyyymmdd: string): string { + if (yyyymmdd.length !== 8) return yyyymmdd; + const day = parseInt(yyyymmdd.slice(6, 8), 10); + return String(day); +} + /** * Convert yyyyMMdd URL date to API format (yyyy-MM-ddT00:00:00). */ @@ -229,7 +239,34 @@ export const OnlineBoardSearchPage: FC = ({ ]} contentLeft={ <> - + } @@ -243,7 +280,7 @@ export const OnlineBoardSearchPage: FC = ({ className={`calendar-day${day === params.date ? " calendar-day--active" : ""}`} onClick={() => handleDateChange(day)} > - {day} + {formatDayLabel(day)} ))}