7d8cb63600
StationDisplay, TimeGroup, FlightStatus, DurationDisplay compose into FlightCard; FlightList renders a list of cards with skeleton loading. All components are props-driven with no data fetching.
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
import type { FC } from "react";
|
|
import type { ISimpleFlight, IFlightLeg } from "@/features/online-board/types.js";
|
|
import { StationDisplay } from "./StationDisplay.js";
|
|
import { TimeGroup } from "./TimeGroup.js";
|
|
import { FlightStatus } from "./FlightStatus.js";
|
|
import { DurationDisplay } from "./DurationDisplay.js";
|
|
|
|
export interface FlightCardProps {
|
|
flight: ISimpleFlight;
|
|
}
|
|
|
|
/** Extract the primary leg from a flight (first leg for multi-leg) */
|
|
function getPrimaryLeg(flight: ISimpleFlight): IFlightLeg {
|
|
if (flight.routeType === "Direct") return flight.leg;
|
|
const first = flight.legs[0];
|
|
if (!first) throw new Error("Multi-leg flight has no legs");
|
|
return first;
|
|
}
|
|
|
|
/** Extract the final leg (last leg for multi-leg, same as primary for direct) */
|
|
function getFinalLeg(flight: ISimpleFlight): IFlightLeg {
|
|
if (flight.routeType === "Direct") return flight.leg;
|
|
const last = flight.legs[flight.legs.length - 1];
|
|
if (!last) throw new Error("Multi-leg flight has no legs");
|
|
return last;
|
|
}
|
|
|
|
/** Parse flyingTime "HH:mm" string to total minutes */
|
|
function flyingTimeToMinutes(flyingTime: string): number {
|
|
const parts = flyingTime.split(":");
|
|
if (parts.length !== 2) return 0;
|
|
const hours = parseInt(parts[0] ?? "0", 10);
|
|
const minutes = parseInt(parts[1] ?? "0", 10);
|
|
return hours * 60 + minutes;
|
|
}
|
|
|
|
/**
|
|
* A single flight row in search results.
|
|
*
|
|
* Composes StationDisplay + TimeGroup + FlightStatus + DurationDisplay.
|
|
*/
|
|
export const FlightCard: FC<FlightCardProps> = ({ flight }) => {
|
|
const departureLeg = getPrimaryLeg(flight);
|
|
const arrivalLeg = getFinalLeg(flight);
|
|
|
|
const depStation = departureLeg.departure;
|
|
const arrStation = arrivalLeg.arrival;
|
|
const depTimes = depStation.times;
|
|
const arrTimes = arrStation.times;
|
|
|
|
const flightNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
|
|
|
|
return (
|
|
<div className="flight-card" data-flight-id={flight.id}>
|
|
<div className="flight-card__number">{flightNumber}</div>
|
|
|
|
<div className="flight-card__route">
|
|
<div className="flight-card__departure">
|
|
<StationDisplay
|
|
airportCode={depStation.scheduled.airportCode}
|
|
airportName={depStation.scheduled.airport}
|
|
cityName={depStation.scheduled.city}
|
|
/>
|
|
<TimeGroup
|
|
scheduled={depTimes.scheduledDeparture.local}
|
|
actual={depTimes.actualBlockOff?.local}
|
|
dayChange={depTimes.actualBlockOff?.dayChange.value}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flight-card__duration">
|
|
<DurationDisplay minutes={flyingTimeToMinutes(flight.flyingTime)} />
|
|
</div>
|
|
|
|
<div className="flight-card__arrival">
|
|
<StationDisplay
|
|
airportCode={arrStation.scheduled.airportCode}
|
|
airportName={arrStation.scheduled.airport}
|
|
cityName={arrStation.scheduled.city}
|
|
/>
|
|
<TimeGroup
|
|
scheduled={arrTimes.scheduledArrival.local}
|
|
actual={arrTimes.actualBlockOn?.local}
|
|
dayChange={arrTimes.actualBlockOn?.dayChange.value}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flight-card__status">
|
|
<FlightStatus status={flight.status} />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|