diff --git a/templates/fleet_calendar.html b/templates/fleet_calendar.html index 112b5b7..8271073 100644 --- a/templates/fleet_calendar.html +++ b/templates/fleet_calendar.html @@ -1434,6 +1434,8 @@ function toggleJobLayer(layer) { // ============================================================ // Reservation Planner // ============================================================ +let plannerSelectedSlotIdx = null; + let plannerState = { reservation_id: null, // null = creating new slots: [], // array of {unit_id: string|null, power_type: string|null, notes: string|null, location_name: string|null} @@ -1607,6 +1609,22 @@ function plannerRenderUnits() { const placeholder = document.getElementById('planner-units-placeholder'); const list = document.getElementById('planner-units-list'); + // Show/hide slot-selection hint banner + let slotHint = document.getElementById('planner-slot-hint'); + if (!slotHint) { + slotHint = document.createElement('div'); + slotHint.id = 'planner-slot-hint'; + list.parentNode.insertBefore(slotHint, list); + } + if (plannerSelectedSlotIdx !== null) { + const slotNum = plannerSelectedSlotIdx + 1; + slotHint.className = 'mb-2 px-3 py-2 rounded-lg bg-blue-50 dark:bg-blue-900/30 border border-blue-300 dark:border-blue-700 text-sm text-blue-700 dark:text-blue-300'; + slotHint.textContent = `Assigning to Loc. ${slotNum} — click a unit below`; + } else { + slotHint.className = 'hidden'; + slotHint.textContent = ''; + } + if (plannerState.allUnits.length === 0) { placeholder.classList.remove('hidden'); const start = document.getElementById('planner-start').value; @@ -1838,12 +1856,23 @@ function plannerSyncSlotsToEstimate() { plannerRenderSlots(); } +function plannerSelectSlot(idx) { + plannerSelectedSlotIdx = (plannerSelectedSlotIdx === idx) ? null : idx; + plannerRenderSlots(); + plannerRenderUnits(); +} + function plannerAssignUnit(unitId) { - const emptyIdx = plannerState.slots.findIndex(s => !s.unit_id); - if (emptyIdx >= 0) { - plannerState.slots[emptyIdx].unit_id = unitId; + if (plannerSelectedSlotIdx !== null && plannerSelectedSlotIdx < plannerState.slots.length && !plannerState.slots[plannerSelectedSlotIdx].unit_id) { + plannerState.slots[plannerSelectedSlotIdx].unit_id = unitId; + plannerSelectedSlotIdx = null; } else { - plannerState.slots.push({ unit_id: unitId, power_type: null, notes: null, location_name: null }); + const emptyIdx = plannerState.slots.findIndex(s => !s.unit_id); + if (emptyIdx >= 0) { + plannerState.slots[emptyIdx].unit_id = unitId; + } else { + plannerState.slots.push({ unit_id: unitId, power_type: null, notes: null, location_name: null }); + } } plannerRenderSlots(); plannerRenderUnits(); @@ -1879,8 +1908,13 @@ function plannerRenderSlots() { emptyMsg.classList.add('hidden'); plannerState.slots.forEach((slot, idx) => { + const isSelected = !slot.unit_id && plannerSelectedSlotIdx === idx; const row = document.createElement('div'); - row.className = 'planner-slot-row flex flex-col gap-1.5 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-slate-700/50'; + row.className = `planner-slot-row flex flex-col gap-1.5 px-3 py-2 rounded-lg border ${ + isSelected + ? 'border-blue-500 dark:border-blue-400 bg-blue-50 dark:bg-blue-900/30 ring-2 ring-blue-400 dark:ring-blue-500' + : 'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-slate-700/50' + }`; row.dataset.idx = idx; row.draggable = !!slot.unit_id; @@ -1904,11 +1938,10 @@ function plannerRenderSlots() { e.preventDefault(); row.classList.remove('ring-2', 'ring-blue-400'); if (dragSrcIdx === null || dragSrcIdx === idx) return; - // Swap unit_id and power_type only (keep location notes in place) + // Swap unit_id only — power_type stays with the location slot const srcSlot = plannerState.slots[dragSrcIdx]; const dstSlot = plannerState.slots[idx]; [srcSlot.unit_id, dstSlot.unit_id] = [dstSlot.unit_id, srcSlot.unit_id]; - [srcSlot.power_type, dstSlot.power_type] = [dstSlot.power_type, srcSlot.power_type]; dragSrcIdx = null; plannerRenderSlots(); plannerRenderUnits(); @@ -1926,6 +1959,40 @@ function plannerRenderSlots() { ? `⠿` : ``; + // Build unit info badges for filled slots + let unitInfoLine = ''; + if (slot.unit_id) { + const uData = plannerState.allUnits.find(u => u.id === slot.unit_id); + if (uData) { + const deployedBadge = uData.deployed + ? 'Deployed' + : 'Benched'; + const outForCalBadge = uData.out_for_calibration + ? 'Out for Cal' + : ''; + const calStr = uData.last_calibrated + ? new Date(uData.last_calibrated + 'T00:00:00').toLocaleDateString('en-US', {month:'short', day:'numeric', year:'numeric'}) + : 'No cal date'; + const start = document.getElementById('planner-start').value; + const end = document.getElementById('planner-end').value; + let expiryBadge = ''; + if (uData.expiry_date) { + const expiry = new Date(uData.expiry_date + 'T00:00:00'); + const jobStart = start ? new Date(start + 'T00:00:00') : null; + const jobEnd = end ? new Date(end + 'T00:00:00') : null; + const expiryStr = expiry.toLocaleDateString('en-US', {month:'short', day:'numeric', year:'numeric'}); + if (jobStart && jobEnd && expiry >= jobStart && expiry <= jobEnd) { + expiryBadge = `cal expires ${expiryStr}`; + } else if (!jobStart || !jobEnd) { + expiryBadge = `cal exp. ${expiryStr}`; + } + } else { + expiryBadge = 'No cal'; + } + unitInfoLine = `