195 lines
8.1 KiB
TypeScript
195 lines
8.1 KiB
TypeScript
import { createRef, useMemo, useState, type Dispatch, type FC, type SetStateAction } from 'react';
|
|
import Parser from '../parser/parser';
|
|
import { Loader } from './Loader';
|
|
|
|
interface ProcedureSelectProps {
|
|
parser: Parser;
|
|
selectedAirport: Airport | undefined;
|
|
selectedRunway: Runway | undefined;
|
|
selectedTerminal: Terminal | undefined;
|
|
setSelectedAirport: Dispatch<SetStateAction<Airport | undefined>>;
|
|
setSelectedRunway: Dispatch<SetStateAction<Runway | undefined>>;
|
|
setSelectedTerminal: Dispatch<SetStateAction<Terminal | undefined>>;
|
|
handleSelection: (transitions: string[], terminal: Terminal) => void;
|
|
}
|
|
|
|
export const ProcedureSelect: FC<ProcedureSelectProps> = ({
|
|
parser,
|
|
selectedAirport,
|
|
selectedRunway,
|
|
selectedTerminal,
|
|
setSelectedAirport,
|
|
setSelectedRunway,
|
|
setSelectedTerminal,
|
|
handleSelection,
|
|
}) => {
|
|
const inputRef = createRef<HTMLInputElement>();
|
|
const [error, setError] = useState<string>();
|
|
const [inFlight, setInFlight] = useState<number>();
|
|
|
|
const runways = useMemo(
|
|
() => parser.runways.filter(({ AirportID }) => AirportID === selectedAirport?.ID),
|
|
[selectedAirport]
|
|
);
|
|
const terminals = useMemo(() => {
|
|
const _terminals = parser.terminals.filter(
|
|
({ AirportID, RwyID }) => AirportID === selectedAirport?.ID && (!RwyID || RwyID === selectedRunway?.ID)
|
|
);
|
|
|
|
return {
|
|
star: _terminals.filter((terminal) => terminal.Proc === 1),
|
|
sid: _terminals.filter((terminal) => terminal.Proc === 2),
|
|
iap: _terminals.filter((terminal) => terminal.Proc === 3),
|
|
};
|
|
}, [selectedAirport, selectedRunway]);
|
|
|
|
return (
|
|
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
|
|
{selectedAirport && (
|
|
<button
|
|
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
|
|
onClick={() => {
|
|
setError(undefined);
|
|
setInFlight(undefined);
|
|
|
|
if (selectedTerminal) {
|
|
setSelectedTerminal(undefined);
|
|
} else if (selectedRunway) {
|
|
setSelectedRunway(undefined);
|
|
} else if (selectedAirport) {
|
|
setSelectedAirport(undefined);
|
|
}
|
|
}}
|
|
>
|
|
Go back
|
|
</button>
|
|
)}
|
|
|
|
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
|
|
{!selectedAirport && (
|
|
<div className="flex w-full flex-col gap-2">
|
|
<h1 className="text-center text-3xl">Enter Airport ICAO</h1>
|
|
<input
|
|
ref={inputRef}
|
|
className="rounded border border-black px-2 py-1 focus:outline-2 focus-visible:outline-2"
|
|
onChange={(e) => {
|
|
if (e.target.value.length <= 4) e.target.value = e.target.value.toUpperCase();
|
|
else e.target.value = e.target.value.slice(0, 4);
|
|
}}
|
|
></input>
|
|
<button
|
|
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
|
|
onClick={() => {
|
|
const airport = parser.airports.find(({ ICAO }) => ICAO === inputRef.current?.value.toUpperCase());
|
|
if (!airport) {
|
|
setError('Airport not found');
|
|
return;
|
|
}
|
|
setSelectedAirport(airport);
|
|
setError(undefined);
|
|
}}
|
|
>
|
|
Select Airport
|
|
</button>
|
|
{error && <span className="text-center text-red-700">{error}</span>}
|
|
</div>
|
|
)}
|
|
|
|
{selectedAirport && !selectedRunway && (
|
|
<div className="flex w-full flex-col gap-2">
|
|
<h1 className="text-center text-3xl">Select Runway</h1>
|
|
{runways.map((runway) => (
|
|
<button
|
|
key={runway.ID}
|
|
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
|
|
onClick={() => setSelectedRunway(runway)}
|
|
>
|
|
Runway {runway.Ident}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{selectedAirport && selectedRunway && !selectedTerminal && (
|
|
<div className="flex w-full flex-col gap-2">
|
|
<h1 className="text-center text-3xl">Select Procedure</h1>
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Arrivals</div>
|
|
{terminals.star.map((terminal) => (
|
|
<button
|
|
key={terminal.ID}
|
|
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2 disabled:bg-gray-100"
|
|
onClick={() => {
|
|
setInFlight(terminal.ID);
|
|
parser.loadTerminal(terminal.ID).then(() => {
|
|
setSelectedTerminal(terminal);
|
|
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
|
|
handleSelection(Array.from(transitions), terminal);
|
|
setInFlight(undefined);
|
|
});
|
|
}}
|
|
disabled={!!inFlight}
|
|
>
|
|
<div className="flex justify-between">
|
|
{terminal.FullName}
|
|
{inFlight === terminal.ID && <Loader size={0.5} />}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Departures</div>
|
|
{terminals.sid.map((terminal) => (
|
|
<button
|
|
key={terminal.ID}
|
|
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2 disabled:bg-gray-100"
|
|
onClick={() => {
|
|
setInFlight(terminal.ID);
|
|
parser.loadTerminal(terminal.ID).then(() => {
|
|
setSelectedTerminal(terminal);
|
|
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
|
|
handleSelection(Array.from(transitions), terminal);
|
|
setInFlight(undefined);
|
|
});
|
|
}}
|
|
disabled={!!inFlight}
|
|
>
|
|
<div className="flex justify-between">
|
|
{terminal.FullName}
|
|
{inFlight === terminal.ID && <Loader size={0.5} />}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Approaches</div>
|
|
{terminals.iap.map((terminal) => (
|
|
<button
|
|
key={terminal.ID}
|
|
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2 disabled:bg-gray-100"
|
|
onClick={() => {
|
|
setInFlight(terminal.ID);
|
|
parser.loadTerminal(terminal.ID).then(() => {
|
|
setSelectedTerminal(terminal);
|
|
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
|
|
handleSelection(Array.from(transitions), terminal);
|
|
setInFlight(undefined);
|
|
});
|
|
}}
|
|
disabled={!!inFlight}
|
|
>
|
|
<div className="flex justify-between">
|
|
{terminal.FullName}
|
|
{inFlight === terminal.ID && <Loader size={0.5} />}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|