Queue via fly-to + Relocate; remove dedicated queue-add input (ISS-053, v0.32.0)
CI / build + unit + e2e (pull_request) Successful in 10m18s
CI / build + unit + e2e (pull_request) Successful in 10m18s
- LeftPane fly-to button enabled for any plane state; posts to /queue when the plane is flying, else /fly - Relocate lists ALL planes (relocatable); relocate-go posts to /queue for a flying selected plane, else /fly - Delete queue-add select + Add-to-queue button and shared queueAddDestIcao state; leg rows/cancel kept - e2e/queue.spec.ts test 1 adds its queued leg via the Relocate section (starter is flying) - e2e/two-planes.spec.ts per-plane-queues test adds the starter's second leg via API POST /queue
This commit is contained in:
+24
-45
@@ -106,10 +106,6 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
// a sensible default (first grounded plane with fuel below max).
|
||||
const [refuelPlaneId, setRefuelPlaneId] = useState<string | null>(null);
|
||||
|
||||
// ISS-043 (v0.26.0): flight-queue "add" destination — one shared pick across every on-board
|
||||
// block's queue-add select (the e2e fleet is a single plane; per-block state would be dead weight).
|
||||
const [queueAddDestIcao, setQueueAddDestIcao] = useState<string | null>(null);
|
||||
|
||||
// ISS-010 (v0.18.0): one-time /api/airports fetch per page life — destination options for the
|
||||
// Relocate section, sorted by name so e2e selection is stable. Same promise-ref pattern as Map.tsx;
|
||||
// on failure airportOptions stays null and the Relocate button simply remains disabled. Moved here
|
||||
@@ -209,17 +205,20 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
? state.planes.filter((p) => p.status === 'flying' || cargoWeightFor(state, p.id) > 0 || (p.queue?.length ?? 0) > 0)
|
||||
: [];
|
||||
|
||||
// ISS-023 (v0.21.0): Relocate — any GROUNDED plane can be repositioned to any airport via the
|
||||
// existing fly endpoint (pure repositioning, no package needed since v0.12.0). Moved here from
|
||||
// the right-pane market row; with state null both fall back to null and the section isn't rendered.
|
||||
const grounded = state ? state.planes.filter((p) => p.status === 'available') : [];
|
||||
const relocatePlane = grounded.find((p) => p.id === relocatePlaneId) ?? grounded[0] ?? null;
|
||||
// ISS-023 (v0.21.0): Relocate — any plane can be repositioned to any airport via the existing fly
|
||||
// endpoint (pure repositioning, no package needed since v0.12.0). Moved here from the right-pane
|
||||
// market row; with state null both fall back to null and the section isn't rendered.
|
||||
// ISS-053 (v0.32.0): widened from grounded-only to ALL planes — a flying plane's relocation is
|
||||
// queued instead of started immediately (see relocate-go below). `grounded` stays for Refuel.
|
||||
const relocatable = state ? state.planes : [];
|
||||
const relocatePlane = relocatable.find((p) => p.id === relocatePlaneId) ?? relocatable[0] ?? null;
|
||||
const relocateDest =
|
||||
airportOptions === null ? null : airportOptions.find((a) => a.icao === relocateDestIcao) ?? airportOptions[0] ?? null;
|
||||
|
||||
// ISS-035 (v0.23.0): Refuel-to-full — any GROUNDED plane with fuel below max can be topped up via the
|
||||
// existing refuel endpoint (no body → server defaults to mode 'full'). Moved here from the right-pane
|
||||
// market row; reuses `grounded` above, and with state null falls back to null.
|
||||
// market row; grounded-only, and with state null falls back to null.
|
||||
const grounded = relocatable.filter((p) => p.status === 'available');
|
||||
const refuelable = grounded.filter((p) => p.fuel < p.maxFuel);
|
||||
const refuelPlane =
|
||||
(state?.planes.find((p) => p.id === refuelPlaneId && p.status === 'available' && p.fuel < p.maxFuel)) ??
|
||||
@@ -422,8 +421,10 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
type="button"
|
||||
data-testid={`fly-to-${pkg.id}`}
|
||||
className="pane-btn onboard-fly-btn"
|
||||
disabled={actionsBusy || plane.status !== 'available'}
|
||||
onClick={() => void runAction(`/api/planes/${plane.id}/fly`, { destIcao: pkg.destIcao })}
|
||||
disabled={actionsBusy}
|
||||
// ISS-053 (v0.32.0): a flying plane can't start another leg — the click queues it instead,
|
||||
// so "Fly to …" works from any state and the queue needs no dedicated input.
|
||||
onClick={() => void runAction(`/api/planes/${plane.id}/${plane.status === 'flying' ? 'queue' : 'fly'}`, { destIcao: pkg.destIcao })}
|
||||
>
|
||||
<span className="onboard-fly-label" title={`Fly to ${pkg.destName}`}>
|
||||
Fly to {pkg.destName}
|
||||
@@ -462,8 +463,9 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* ISS-043 (v0.26.0): flight queue — the not-yet-started legs with cancel, plus an
|
||||
add control reusing this pane's already-fetched airport options (no second fetch). */}
|
||||
{/* ISS-043 (v0.26.0): flight queue — the not-yet-started legs with cancel.
|
||||
ISS-053 (v0.32.0): the dedicated add input is gone — fly-to and Relocate post to
|
||||
/queue while a plane is flying, so no separate entry point is needed. */}
|
||||
<div className="queue-subsection">
|
||||
<span className="queue-heading">Queue</span>
|
||||
{/* ISS-055 (v0.31.0): testids scoped by plane id — with two on-board blocks the
|
||||
@@ -484,32 +486,6 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="market-row">
|
||||
<select
|
||||
data-testid={`queue-add-dest-${plane.id}`}
|
||||
aria-label="Airport to add to the flight queue"
|
||||
value={queueAddDestIcao ?? ''}
|
||||
onChange={(e) => setQueueAddDestIcao(e.target.value || null)}
|
||||
>
|
||||
<option value="">Select an airport…</option>
|
||||
{airportOptions?.map((a) => (
|
||||
<option key={a.icao} value={a.icao}>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
data-testid={`queue-add-${plane.id}`}
|
||||
className="pane-btn"
|
||||
disabled={actionsBusy || queueAddDestIcao === null}
|
||||
onClick={() => {
|
||||
if (queueAddDestIcao) void runAction(`/api/planes/${plane.id}/queue`, { destIcao: queueAddDestIcao });
|
||||
}}
|
||||
>
|
||||
Add to queue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -521,7 +497,7 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
everything else about our planes, between On board and Time. */}
|
||||
{/* ISS-027 (v0.22.0): hovering the section previews the planned relocation on the map —
|
||||
dashed trace from the selected plane's airport to the destination + a ring on the target.
|
||||
With no grounded plane or no loaded airport options there is nothing to preview → null. */}
|
||||
With no planes or no loaded airport options there is nothing to preview → null. */}
|
||||
<section
|
||||
className="pane-section"
|
||||
data-testid="relocate-section"
|
||||
@@ -535,8 +511,9 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
onMouseLeave={() => onHover(null)}
|
||||
>
|
||||
<h3 className="pane-heading">Relocate</h3>
|
||||
{/* ISS-010 (v0.18.0): relocate a grounded plane — the fly endpoint already allows pure
|
||||
repositioning, this row just exposes it for planes with nothing on board. */}
|
||||
{/* ISS-010 (v0.18.0): relocate a plane — the fly endpoint already allows pure
|
||||
repositioning, this row just exposes it for planes with nothing on board.
|
||||
ISS-053 (v0.32.0): lists ALL planes; a flying one's relocation is queued below. */}
|
||||
<div className="market-row market-row-relocate">
|
||||
<select
|
||||
data-testid="relocate-plane"
|
||||
@@ -544,7 +521,7 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
value={relocatePlane?.id ?? ''}
|
||||
onChange={(e) => setRelocatePlaneId(e.target.value || null)}
|
||||
>
|
||||
{grounded.map((p) => (
|
||||
{relocatable.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{PLANE_TYPES[p.model]?.name ?? p.model} — {p.locationIcao}
|
||||
</option>
|
||||
@@ -567,9 +544,11 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
data-testid="relocate-go"
|
||||
className="pane-btn"
|
||||
disabled={actionsBusy || relocatePlane === null || relocateDest === null}
|
||||
// ISS-053 (v0.32.0): a flying plane can't start another leg — queue it instead, so the
|
||||
// relocation takes off when the current one lands.
|
||||
onClick={() => {
|
||||
if (relocatePlane && relocateDest) {
|
||||
void runAction(`/api/planes/${relocatePlane.id}/fly`, { destIcao: relocateDest.icao });
|
||||
void runAction(`/api/planes/${relocatePlane.id}/${relocatePlane.status === 'flying' ? 'queue' : 'fly'}`, { destIcao: relocateDest.icao });
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user