/** * Flights Map API functions. * * Pure functions -- each takes an `ApiClient` as a parameter (dependency * injection). No React hooks, no context, no side effects. * * @module */ import type { ApiClient } from "@/shared/api/client.js"; import type { FlightsMapSearchParams, FlightsMapCalendarParams, IDestinationsResponse, IFlightsMapDaysResponse, } from "./types.js"; // --------------------------------------------------------------------------- // API functions // --------------------------------------------------------------------------- /** * Search flight destinations/routes on the map. * Maps to: `GET destinations/1?departure=...&arrival=...&dateFrom=...&dateTo=...&connections=0` */ export async function searchDestinations( client: ApiClient, params: FlightsMapSearchParams, ): Promise { const query: Record = { departure: params.departure, dateFrom: params.dateFrom, dateTo: params.dateTo, connections: String(params.connections ?? 0), }; if (params.arrival) { query["arrival"] = params.arrival; } return client.get(`flights/1/${client.locale}/destinations`, query); } /** * Get available calendar days for a flights-map route. * Maps to: `GET days/{date}/200/{routeSegment}/flights-map/v1` * * The `days` response field is a binary string ("0110...") where each * character represents one day starting from `date`. We parse it into * an array of available date strings. */ export async function getFlightsMapCalendar( client: ApiClient, params: FlightsMapCalendarParams, ): Promise { const routeSegment = buildRouteSegment(params); if (!routeSegment) return []; const path = `flights/v1/${client.locale}/days/${params.date}/200/${routeSegment}/flights-map/`; const response = await client.get(path); return parseBinaryDays(response.days, params.date); } // --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- function buildRouteSegment(params: FlightsMapCalendarParams): string | null { if (params.departure && params.arrival) { return params.connections ? `connections/${params.departure}-${params.arrival}-1` : `route/${params.departure}-${params.arrival}`; } if (params.departure) { return `departure/${params.departure}`; } return null; } /** * Parse a binary days string ("01101...") into an array of ISO date strings * representing the available (=1) days, starting from `startDate`. */ function parseBinaryDays(days: string, startDate: string): string[] { if (!days) return []; const result: string[] = []; const start = parseYyyymmdd(startDate); if (!start) return []; for (let i = 0; i < days.length; i++) { if (days[i] === "1") { const d = new Date(start); d.setDate(d.getDate() + i); result.push(formatYyyymmdd(d)); } } return result; } function parseYyyymmdd(s: string): Date | null { if (s.length !== 8) return null; const year = Number(s.slice(0, 4)); const month = Number(s.slice(4, 6)) - 1; const day = Number(s.slice(6, 8)); const d = new Date(year, month, day); return Number.isNaN(d.getTime()) ? null : d; } function formatYyyymmdd(d: Date): string { const y = d.getFullYear().toString(); const m = (d.getMonth() + 1).toString().padStart(2, "0"); const day = d.getDate().toString().padStart(2, "0"); return `${y}${m}${day}`; }