Queue UI fixes: disable would-409 controls, hover trace from landing airport, grounded queues draw dotted lines (ISS-077, v0.41.0)
CI / build + unit + e2e (pull_request) Successful in 9m11s
CI / build + unit + e2e (pull_request) Successful in 9m11s
This commit is contained in:
+15
-3
@@ -207,6 +207,11 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
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-077 (v0.41.0): while flying, the Relocate click would POST /queue — block it when the selected
|
||||
// destination is already active or queued (the server rejects that with 409). Grounded planes are unaffected.
|
||||
const relocateBlocked =
|
||||
relocatePlane?.status === 'flying' && relocateDest !== null &&
|
||||
(relocatePlane.destIcao === relocateDest.icao || relocatePlane.queue.includes(relocateDest.icao));
|
||||
|
||||
// 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
|
||||
@@ -408,6 +413,10 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
)}
|
||||
{cargo.map((pkg) => {
|
||||
const penalty = abandonPenalty(pkg, clockMs);
|
||||
// ISS-077 (v0.41.0): while flying the click would POST /queue — disable it when the
|
||||
// destination is already active or queued (the server rejects that with 409). Grounded
|
||||
// clicks go to /fly, which has no such conflict.
|
||||
const alreadyQueued = p.status === 'flying' && (p.destIcao === pkg.destIcao || p.queue.includes(pkg.destIcao));
|
||||
return (
|
||||
// ISS-025 (v0.22.0): hovering a loaded package draws its full origin→dest trace on the map —
|
||||
// same as job-list rows — and keeps this plane's halo lit via planeId. Leaving the row restores
|
||||
@@ -424,7 +433,8 @@ 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}
|
||||
disabled={actionsBusy || alreadyQueued}
|
||||
title={alreadyQueued ? 'Already in flight queue' : undefined}
|
||||
// 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/${p.id}/${p.status === 'flying' ? 'queue' : 'fly'}`, { destIcao: pkg.destIcao })}
|
||||
@@ -551,7 +561,9 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
onChange={(e) => setRelocateDestIcao(e.target.value || null)}
|
||||
>
|
||||
{airportOptions?.map((a) => (
|
||||
<option key={a.icao} value={a.icao}>
|
||||
// ISS-077 (v0.41.0): a flying plane's active/queued airports can't be re-selected — the
|
||||
// server would reject them with 409; native disabled options, no text change.
|
||||
<option key={a.icao} value={a.icao} disabled={relocatePlane?.status === 'flying' && (relocatePlane.destIcao === a.icao || relocatePlane.queue.includes(a.icao))}>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
@@ -560,7 +572,7 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
type="button"
|
||||
data-testid="relocate-go"
|
||||
className="pane-btn"
|
||||
disabled={actionsBusy || relocatePlane === null || relocateDest === null}
|
||||
disabled={actionsBusy || relocatePlane === null || relocateDest === null || relocateBlocked}
|
||||
// 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={() => {
|
||||
|
||||
+11
-1
@@ -153,10 +153,20 @@ export default function FlightMap({ state, hovered, applySnapshot }: MapProps) {
|
||||
// ISS-040 (v0.23.2): an on-board package's trace starts from the carrier plane's LIVE position,
|
||||
// not its origin airport — identical to before while grounded at the pickup, different once it
|
||||
// has departed with cargo still loaded. No carrier in state yet → fall back to the ICAO trace.
|
||||
// ISS-077 (v0.41.0): a FLYING carrier's trace starts at its LANDING airport (destIcao), not the
|
||||
// live mid-flight position — the hover previews what happens AFTER this leg lands, and the solid
|
||||
// active-leg + dotted queue layers already show where it is now. Grounded / no active leg keeps
|
||||
// today's behavior exactly (live position = the plane's airport). destIcao missing from the
|
||||
// index → fall back to the live position rather than guessing.
|
||||
const idx = airportsIndexRef.current ?? new Map();
|
||||
const carrier = h?.kind === 'package' && h.planeId ? stateRef.current?.planes.find((p) => p.id === h.planeId) : undefined;
|
||||
const landing = carrier?.status === 'flying' && carrier.destIcao ? idx.get(carrier.destIcao) : undefined;
|
||||
void hoverSource.setData(
|
||||
carrier ? hoverTraceGeoJson({ lat: carrier.lat, lon: carrier.lon }, toIcao, idx) : hoverRouteGeoJson(fromIcao, toIcao, idx),
|
||||
landing
|
||||
? hoverTraceGeoJson(landing, toIcao, idx)
|
||||
: carrier
|
||||
? hoverTraceGeoJson({ lat: carrier.lat, lon: carrier.lon }, toIcao, idx)
|
||||
: hoverRouteGeoJson(fromIcao, toIcao, idx),
|
||||
);
|
||||
}
|
||||
// Package hovers ring BOTH endpoints (v0.15.0 behavior); 'route' hovers ring the TARGET only —
|
||||
|
||||
Reference in New Issue
Block a user