fix(viewer): polylines + batched T3 load

- loadSortieData: call applyTrailAndCursor() after sortie load so map
  polylines appear when allPoints already populated from datebar
- loadDiveData: split into Phase1 (track only, batched by 4) + Phase2
  (series + sub, batched by 4) — map draws as soon as tracks load
- loadShipSession split into fetchShipTrack + fetchShipSeries helpers
- T3: filter ship sessions by date (sess.start.slice(0,10) === filterDate)
- Pass date param from loadDate to loadDiveData

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Flag
2026-04-28 14:54:16 +00:00
parent f5debc8afc
commit f5788a01f4

View File

@@ -783,7 +783,7 @@ async function loadDate(date) {
for (const { mission, dives } of missionDives) {
for (const dive of dives) {
allFetches.push(loadDiveData(mission.id, dive.id));
allFetches.push(loadDiveData(mission.id, dive.id, date));
totalShip += dive.ship_session_count || 0;
totalSub += dive.sub_session_count || 0;
}
@@ -848,52 +848,60 @@ async function loadDate(date) {
}
}
async function loadDiveData(missionId, diveId) {
async function loadDiveData(missionId, diveId, filterDate) {
try {
const sResp = await fetch(`${API}/api/dives/${missionId}/${diveId}/sessions`);
const sessions = await sResp.json();
const sessionFetches = [];
// Ship sessions
if (sessions.ship) {
sessions.ship.forEach(sess => {
sessionFetches.push(loadShipSession(missionId, diveId, sess.id));
// T3 FIX: filter ship sessions by date if provided (session.start = "YYYY-MM-DD_HH-MM-SS")
const shipSessions = (sessions.ship || []).filter(sess => {
if (!filterDate) return true;
// sess.start: "2026-04-17_07-43-23" → compare first 10 chars "2026-04-17" to filterDate
return !sess.start || sess.start.slice(0, 10) === filterDate;
});
}
// Sub sessions
if (sessions.sub) {
sessions.sub.forEach(sess => {
sessionFetches.push(loadSubSession(missionId, diveId, sess.id));
});
}
await Promise.all(sessionFetches);
// Phase 1: track-only fetches (needed for map polylines) — batch by 4
const trackFetches = shipSessions.map(sess =>
fetchShipTrack(missionId, diveId, sess.id)
);
await _batchedAll(trackFetches, 4);
// Phase 2: series + sub sessions in background (charts data)
const seriesFetches = [
...shipSessions.map(sess => fetchShipSeries(missionId, diveId, sess.id)),
...(sessions.sub || []).map(sess => loadSubSession(missionId, diveId, sess.id)),
];
await _batchedAll(seriesFetches, 4);
} catch(e) { console.warn('loadDiveData error', diveId, e); }
}
async function loadShipSession(missionId, diveId, sessionId) {
// Parallel fetch: track + series with 20s timeout each — neither blocks the other
const [trackResult, seriesResult] = await Promise.allSettled([
fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/track`,
{ signal: AbortSignal.timeout(20000) }),
fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/series`,
{ signal: AbortSignal.timeout(20000) }),
]);
if (trackResult.status === 'fulfilled' && trackResult.value.ok) {
// Run an array of promise-factories in batches of size n
async function _batchedAll(fns, n) {
for (let i = 0; i < fns.length; i += n) {
await Promise.all(fns.slice(i, i + n));
}
}
async function fetchShipTrack(missionId, diveId, sessionId) {
try {
const d = await trackResult.value.json();
const resp = await fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/track`,
{ signal: AbortSignal.timeout(20000) });
if (!resp.ok) { console.warn('fetchShipTrack fail', sessionId, resp.status); return; }
const d = await resp.json();
const pts = (d.points||[]).map(p => ({
t_ms: isoToMs(p.t), lat: p.lat, lon: p.lon,
heading: p.heading || null, source: sessionId
}));
allPoints.push(...pts);
} catch(e) { console.warn('loadShipSession track json error', sessionId, e); }
} else {
console.warn('loadShipSession track timeout/fail', sessionId,
trackResult.status === 'rejected' ? trackResult.reason.name : trackResult.value?.status);
}
if (seriesResult.status === 'fulfilled' && seriesResult.value.ok) {
} catch(e) { console.warn('fetchShipTrack error', sessionId, e.name); }
}
async function fetchShipSeries(missionId, diveId, sessionId) {
try {
const d = await seriesResult.value.json();
const resp = await fetch(`${API}/api/ship/${missionId}/${diveId}/${sessionId}/series`,
{ signal: AbortSignal.timeout(20000) });
if (!resp.ok) return;
const d = await resp.json();
const motorKeys = Object.keys(d).filter(k => /^M\d+$/.test(k));
motorKeys.forEach((k, i) => {
const pts = d[k];
@@ -901,18 +909,14 @@ async function loadShipSession(missionId, diveId, sessionId) {
pwmUsvTraces.push({
x: pts.map(p => new Date(isoToMs(p.t))),
y: pts.map(p => p.v),
name: k,
type: 'scatter', mode: 'lines',
name: k, type: 'scatter', mode: 'lines',
line: { color: COLORS[i % COLORS.length], width: 1 },
});
});
} catch(e) { console.warn('loadShipSession series json error', sessionId, e); }
} else {
console.warn('loadShipSession series timeout/fail', sessionId,
seriesResult.status === 'rejected' ? seriesResult.reason.name : seriesResult.value?.status);
}
} catch(e) { console.warn('fetchShipSeries error', sessionId, e.name); }
}
async function loadSubSession(missionId, diveId, sessionId) {
// Both usbl_track and series can hang — use parallel with timeout, non-blocking
const [usblResult, seriesResult] = await Promise.allSettled([
@@ -1287,6 +1291,8 @@ async function loadSortieData(sortieId) {
prog.textContent = 'Chargement AUV…';
await loadAuvTabs(sortieId);
prog.textContent = `${sortieId} chargé`;
// POLYLINE FIX: ensure map trail is drawn if allPoints already loaded from datebar
if (allPoints.length > 0) applyTrailAndCursor();
} catch(e) {
prog.textContent = `Erreur: ${e.message}`;
}