feat(viewer): calendar date-picker + mission name from backend 8766
- date-picker bar with datalist highlighting available dates - fetch /api/data-dates from 192.168.0.83:8766 on load - mission name + session count shown on date change - Aujourd hui button sets today - slider 24h intact
This commit is contained in:
@@ -9,6 +9,51 @@
|
|||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; display: flex; flex-direction: column; height: 100vh; }
|
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; display: flex; flex-direction: column; height: 100vh; }
|
||||||
#map { flex: 1; }
|
#map { flex: 1; }
|
||||||
|
|
||||||
|
/* ── top bar: date picker ── */
|
||||||
|
#datebar {
|
||||||
|
background: #0d1b2a;
|
||||||
|
padding: 6px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid #0f3460;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
#datebar label { font-size: 11px; color: #a0c4ff; white-space: nowrap; }
|
||||||
|
#date-picker {
|
||||||
|
background: #16213e;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: 1px solid #0f3460;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#date-picker::-webkit-calendar-picker-indicator { filter: invert(1); }
|
||||||
|
#btn-today {
|
||||||
|
background: #0f3460;
|
||||||
|
color: #a0c4ff;
|
||||||
|
border: 1px solid #1a5276;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#btn-today:hover { background: #1a5276; }
|
||||||
|
#mission-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #e94560;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
#dates-hint {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #556;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── bottom bar: slider ── */
|
||||||
#controls {
|
#controls {
|
||||||
background: #16213e;
|
background: #16213e;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@@ -23,7 +68,19 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<!-- ── Date picker bar ── -->
|
||||||
|
<div id="datebar">
|
||||||
|
<label>Date mission:</label>
|
||||||
|
<input type="date" id="date-picker">
|
||||||
|
<button id="btn-today">Aujourd'hui</button>
|
||||||
|
<span id="mission-label">—</span>
|
||||||
|
<span id="dates-hint">Chargement calendrier…</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<!-- ── Slider bar ── -->
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<span id="title">USV Track</span>
|
<span id="title">USV Track</span>
|
||||||
<input type="range" id="slider" min="0" value="0">
|
<input type="range" id="slider" min="0" value="0">
|
||||||
@@ -47,7 +104,6 @@ const seamarks = L.tileLayer('https://tiles.openseamap.org/seamark/{z}/{x}/{y}.p
|
|||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
});
|
});
|
||||||
|
|
||||||
// GEBCO bathymetry (tile-based, simpler than WMS)
|
|
||||||
const gebco = L.tileLayer(
|
const gebco = L.tileLayer(
|
||||||
'https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/GEBCO_basemap_NCEI/MapServer/tile/{z}/{y}/{x}',
|
'https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/GEBCO_basemap_NCEI/MapServer/tile/{z}/{y}/{x}',
|
||||||
{
|
{
|
||||||
@@ -78,7 +134,74 @@ function makeArrowIcon(heading) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Data loading ──────────────────────────────────────────────────────────
|
// ── Calendar data ─────────────────────────────────────────────────────────
|
||||||
|
const BACKEND = 'http://192.168.0.83:8766';
|
||||||
|
let dataMissions = {}; // date → {missions, session_count}
|
||||||
|
|
||||||
|
async function loadCalendar() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${BACKEND}/api/data-dates`);
|
||||||
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
|
const json = await resp.json();
|
||||||
|
json.dates.forEach(d => {
|
||||||
|
dataMissions[d.date] = { missions: d.missions, session_count: d.session_count };
|
||||||
|
});
|
||||||
|
|
||||||
|
const availDates = Object.keys(dataMissions).sort();
|
||||||
|
const picker = document.getElementById('date-picker');
|
||||||
|
|
||||||
|
if (availDates.length > 0) {
|
||||||
|
picker.min = availDates[0];
|
||||||
|
picker.max = availDates[availDates.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight available dates via custom attribute list (CSS trick)
|
||||||
|
// We inject a datalist for browsers that support it
|
||||||
|
const dl = document.createElement('datalist');
|
||||||
|
dl.id = 'available-dates';
|
||||||
|
availDates.forEach(d => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = d;
|
||||||
|
dl.appendChild(opt);
|
||||||
|
});
|
||||||
|
document.body.appendChild(dl);
|
||||||
|
picker.setAttribute('list', 'available-dates');
|
||||||
|
|
||||||
|
document.getElementById('dates-hint').textContent =
|
||||||
|
`${availDates.length} dates avec données`;
|
||||||
|
|
||||||
|
// Select most recent date by default
|
||||||
|
if (availDates.length > 0) {
|
||||||
|
picker.value = availDates[availDates.length - 1];
|
||||||
|
onDateChange(picker.value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('dates-hint').textContent = 'Calendrier indispo: ' + e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDateChange(date) {
|
||||||
|
const label = document.getElementById('mission-label');
|
||||||
|
const info = dataMissions[date];
|
||||||
|
if (info) {
|
||||||
|
label.textContent = info.missions.join(', ') + ` (${info.session_count} session${info.session_count > 1 ? 's' : ''})`;
|
||||||
|
label.style.color = '#00e676';
|
||||||
|
} else {
|
||||||
|
label.textContent = 'Aucune donnée pour cette date';
|
||||||
|
label.style.color = '#666';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('date-picker').addEventListener('change', e => onDateChange(e.target.value));
|
||||||
|
|
||||||
|
document.getElementById('btn-today').addEventListener('click', () => {
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const picker = document.getElementById('date-picker');
|
||||||
|
picker.value = today;
|
||||||
|
onDateChange(today);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Track data loading ────────────────────────────────────────────────────
|
||||||
let points = [];
|
let points = [];
|
||||||
let trackLayer = null;
|
let trackLayer = null;
|
||||||
let marker = null;
|
let marker = null;
|
||||||
@@ -136,6 +259,8 @@ function updateInfo(idx) {
|
|||||||
`[${idx+1}/${points.length}] ${p.t} | Lat: ${p.lat.toFixed(6)} Lon: ${p.lon.toFixed(6)} | Cap: ${hdg}`;
|
`[${idx+1}/${points.length}] ${p.t} | Lat: ${p.lat.toFixed(6)} Lon: ${p.lon.toFixed(6)} | Cap: ${hdg}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Init ──────────────────────────────────────────────────────────────────
|
||||||
|
loadCalendar();
|
||||||
loadData();
|
loadData();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user