Add data model types, datetime utils, and dictionary hook

Port Angular flight types (ISimpleFlight, IFlightLeg, ITimesSet, etc.)
to minimal React-friendly interfaces. Add formatDuration/formatTime/
formatDate/isDayChange as pure functions. Stub useCityName hook as
passthrough pending customer dictionary API endpoint.
This commit is contained in:
2026-04-15 07:55:00 +03:00
parent fc03c08278
commit b3ab73253d
6 changed files with 660 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
import { describe, it, expect } from "vitest";
import type {
FlightStatus,
FlightRequestType,
ITimesSet,
IAirportInfo,
IFlightLeg,
IFlightLegDepartureStation,
IFlightLegArrivalStation,
IParsedFlightId,
IDirectFlight,
IMultiLegFlight,
ISimpleFlight,
IBoardResponse,
IDaysResponse,
} from "./types.js";
/**
* Type-level satisfaction tests. These verify that our interfaces
* accept the shapes the API actually sends. If the types are wrong,
* these assignments fail at compile time.
*/
describe("online-board types", () => {
it("FlightStatus accepts all known API values", () => {
const statuses: FlightStatus[] = [
"Scheduled",
"Sent",
"InFlight",
"Landed",
"Arrived",
"Delayed",
"Cancelled",
"Unknown",
];
expect(statuses).toHaveLength(8);
});
it("FlightRequestType accepts all search modes", () => {
const modes: FlightRequestType[] = [
"flight",
"departure",
"arrival",
"route",
];
expect(modes).toHaveLength(4);
});
it("ITimesSet satisfies the API shape", () => {
const times: ITimesSet = {
dayChange: { value: 0, title: "" },
local: "2025-01-15T10:30:00",
localTime: "10:30",
tzOffset: 3,
utc: "2025-01-15T07:30:00Z",
};
expect(times.localTime).toBe("10:30");
});
it("IAirportInfo satisfies the API shape", () => {
const info: IAirportInfo = {
airport: "Sheremetyevo",
airportCode: "SVO",
city: "Moscow",
cityCode: "MOW",
countryCode: "RU",
};
expect(info.airportCode).toBe("SVO");
});
it("IParsedFlightId holds parsed URL params", () => {
const id: IParsedFlightId = {
carrier: "SU",
flightNumber: "100",
date: "20250115",
};
expect(id.carrier).toBe("SU");
});
it("IDirectFlight satisfies a direct flight shape", () => {
const depStation: IFlightLegDepartureStation = {
scheduled: {
airport: "Sheremetyevo",
airportCode: "SVO",
city: "Moscow",
cityCode: "MOW",
countryCode: "RU",
},
checkingStatus: "Scheduled",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local: "2025-01-15T10:30:00",
localTime: "10:30",
tzOffset: 3,
utc: "2025-01-15T07:30:00Z",
},
},
};
const arrStation: IFlightLegArrivalStation = {
scheduled: {
airport: "Pulkovo",
airportCode: "LED",
city: "St Petersburg",
cityCode: "LED",
countryCode: "RU",
},
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local: "2025-01-15T12:00:00",
localTime: "12:00",
tzOffset: 3,
utc: "2025-01-15T09:00:00Z",
},
},
};
const leg: IFlightLeg = {
arrival: arrStation,
dayChange: 0,
departure: depStation,
equipment: { name: "Airbus A320", code: "320" },
flags: {
checkinAvailable: false,
returnToAirport: false,
routeChanged: false,
},
flyingTime: "01:30",
index: 0,
operatingBy: { carrier: "SU", flightNumber: "100" },
status: "Scheduled",
updated: "2025-01-15T10:00:00Z",
};
const flight: IDirectFlight = {
flightId: {
carrier: "SU",
date: "2025-01-15",
flightNumber: "100",
suffix: "",
},
flyingTime: "01:30",
operatingBy: { carrier: "SU", flightNumber: "100" },
id: "su-100-20250115",
status: "Scheduled",
routeType: "Direct",
leg,
};
expect(flight.routeType).toBe("Direct");
});
it("IMultiLegFlight satisfies a multi-leg shape", () => {
const flight: IMultiLegFlight = {
flightId: {
carrier: "SU",
date: "2025-01-15",
flightNumber: "200",
suffix: "",
},
flyingTime: "05:00",
operatingBy: {},
id: "su-200-20250115",
status: "Scheduled",
routeType: "MultiLeg",
legs: [],
};
expect(flight.routeType).toBe("MultiLeg");
});
it("ISimpleFlight is a union of direct and multi-leg", () => {
const direct: ISimpleFlight = {
flightId: {
carrier: "SU",
date: "2025-01-15",
flightNumber: "100",
suffix: "",
},
flyingTime: "01:30",
operatingBy: {},
id: "su-100",
status: "Scheduled",
routeType: "Direct",
leg: {} as IFlightLeg,
};
expect(direct.status).toBe("Scheduled");
});
it("IBoardResponse matches API response shape", () => {
const resp: IBoardResponse = {
data: {
partners: ["SU"],
routes: [],
daysOfFlight: ["2025-01-15"],
},
};
expect(resp.data.partners).toContain("SU");
});
it("IDaysResponse matches API response shape", () => {
const resp: IDaysResponse = {
days: "1,2,3,4,5",
};
expect(resp.days).toBeTruthy();
});
});
+184
View File
@@ -0,0 +1,184 @@
/**
* Data model types for the Online Board feature.
*
* Ported from Angular typings (ClientApp/src/typings/) — flattened into
* minimal, UI-oriented interfaces with no Angular dependencies.
*/
// ---------------------------------------------------------------------------
// Enums & literals
// ---------------------------------------------------------------------------
/** Flight status values returned by the API */
export type FlightStatus =
| "Scheduled"
| "Sent"
| "InFlight"
| "Landed"
| "Arrived"
| "Delayed"
| "Cancelled"
| "Unknown";
/** Route shape discriminator */
export type RouteType = "Direct" | "MultiLeg" | "Connecting";
/** Search request type — how the user is searching */
export type FlightRequestType = "flight" | "departure" | "arrival" | "route";
// ---------------------------------------------------------------------------
// Time & location primitives
// ---------------------------------------------------------------------------
/** Day-change indicator (e.g. +1, -1 day vs scheduled) */
export interface IDayChange {
value: number;
title: string;
}
/** A single point-in-time with timezone info, as returned by the API */
export interface ITimesSet {
dayChange: IDayChange;
local: string;
localTime: string;
tzOffset: number;
utc: string;
}
/** Airport/city info from the API */
export interface IAirportInfo {
airport: string;
airportCode: string;
city: string;
cityCode: string;
countryCode: string;
country?: string;
}
// ---------------------------------------------------------------------------
// Station (departure / arrival)
// ---------------------------------------------------------------------------
export interface IFlightLegStation {
scheduled: IAirportInfo;
latest?: IAirportInfo;
dispatch?: string;
gate?: string;
terminal?: string;
}
export interface IDepartureStationTimes {
scheduledDeparture: ITimesSet;
estimatedBlockOff?: ITimesSet;
actualBlockOff?: ITimesSet;
}
export interface IArrivalStationTimes {
scheduledArrival: ITimesSet;
estimatedBlockOn?: ITimesSet;
actualBlockOn?: ITimesSet;
}
export interface IFlightLegDepartureStation extends IFlightLegStation {
checkingStatus: string;
parkingStand?: string;
times: IDepartureStationTimes;
}
export interface IFlightLegArrivalStation extends IFlightLegStation {
times: IArrivalStationTimes;
bagBelt?: string;
}
// ---------------------------------------------------------------------------
// Flight leg
// ---------------------------------------------------------------------------
export interface IFlightLegFlags {
checkinAvailable: boolean;
returnToAirport: boolean;
routeChanged: boolean;
}
export interface IFlightLeg {
arrival: IFlightLegArrivalStation;
dayChange: number;
departure: IFlightLegDepartureStation;
equipment: { name?: string; code?: string };
flags: IFlightLegFlags;
flyingTime: string;
index: number;
operatingBy: { carrier?: string; flightNumber?: string };
status: FlightStatus;
updated: string;
}
// ---------------------------------------------------------------------------
// Flight ID
// ---------------------------------------------------------------------------
export interface IFlightId {
carrier: string;
date: string;
flightNumber: string;
suffix: string;
dateLT?: string;
}
export interface IParsedFlightId {
carrier: string;
flightNumber: string;
suffix?: string;
date: string;
}
// ---------------------------------------------------------------------------
// Flight (union of route shapes)
// ---------------------------------------------------------------------------
interface IFlightBase {
flightId: IFlightId;
flyingTime: string;
operatingBy: { carrier?: string; flightNumber?: string };
id: string;
status: FlightStatus;
}
export interface IDirectFlight extends IFlightBase {
routeType: "Direct";
leg: IFlightLeg;
}
export interface IMultiLegFlight extends IFlightBase {
routeType: "MultiLeg";
legs: IFlightLeg[];
}
export interface IConnectingFlight {
flights: ISimpleFlight[];
routeType: "Connecting";
flyingTime: string;
status: FlightStatus;
}
/** A single flight (direct or multi-leg) — the main UI display unit */
export type ISimpleFlight = IDirectFlight | IMultiLegFlight;
/** Any flight shape including connecting */
export type IFlight = ISimpleFlight | IConnectingFlight;
// ---------------------------------------------------------------------------
// API response shapes
// ---------------------------------------------------------------------------
export interface IBoardResponse {
data: {
partners: string[];
routes: ISimpleFlight[];
daysOfFlight: string[];
};
}
export interface IDaysResponse {
days: string;
}