diff --git a/static/index.html b/static/index.html index 12e0248..1454650 100644 --- a/static/index.html +++ b/static/index.html @@ -1020,6 +1020,21 @@ .no-print { display: none !important; } .stop-num { background: #333 !important; } } + + /* GPS Map Editor */ + .gps-lock-btn { + font-size: 16px; cursor: pointer; user-select: none; + padding: 2px 6px; border-radius: 4px; transition: background 0.15s; + float: right; line-height: 1; + } + .gps-lock-btn:hover { background: rgba(255,255,255,0.08); } + .gps-lock-btn.unlocked { background: var(--amber-bg); border-radius: 4px; } + #detailGpsMap { + z-index: 1; overflow: hidden; + } + #detailGpsMap .leaflet-container { + border-radius: 8px; + } @@ -1267,6 +1282,21 @@ + + +
Asset Details
@@ -3891,6 +3921,9 @@ photoCard.style.display = 'none'; } + // Render GPS Map Editor card (at top of detail view) + renderGpsCard(a); + // Location / Directions const hasDir = a.address || a.building_name || a.building_number || a.floor || a.room || a.trailer_number || a.walking_directions || a.map_link || a.parking_location; const dirCard = document.getElementById('detailDirections'); @@ -4053,6 +4086,188 @@ return `
${esc(label)}
${value}
`; } + // ── GPS Map Editor ────────────────────────────────────────────────── + let _gpsState = { map: null, marker: null, unlocked: false, asset: null }; + + function renderGpsCard(a) { + const card = document.getElementById('detailGpsCard'); + const mapDiv = document.getElementById('detailGpsMap'); + const infoEl = document.getElementById('detailGpsInfo'); + const coordsEl = document.getElementById('detailGpsCoords'); + const lockBtn = document.getElementById('detailGpsLockBtn'); + const saveBtn = document.getElementById('detailGpsSaveBtn'); + const statusEl = document.getElementById('detailGpsStatus'); + + // Clean up previous map instance + if (_gpsState.map) { + _gpsState.map.remove(); + _gpsState.map = null; + _gpsState.marker = null; + } + + _gpsState.asset = a; + const hasGps = a.latitude != null && a.longitude != null; + + // If no GPS, show card as unlocked and editable + _gpsState.unlocked = !hasGps; + card.style.display = 'block'; + + lockBtn.onclick = toggleGpsLock; + + if (hasGps) { + infoEl.textContent = '📍 GPS coordinates on file — click 🔒 to unlock and edit'; + lockBtn.textContent = '🔒'; + lockBtn.className = 'gps-lock-btn'; + lockBtn.title = 'GPS is locked — click to edit'; + saveBtn.style.display = 'none'; + statusEl.textContent = ''; + } else { + infoEl.textContent = '📍 No GPS data — tap the map to set coordinates'; + lockBtn.textContent = '🔓'; + lockBtn.className = 'gps-lock-btn unlocked'; + lockBtn.title = 'Editable — tap lock to disable editing'; + statusEl.textContent = 'Editable'; + } + + const defaultLat = hasGps ? a.latitude : 28.5383; + const defaultLng = hasGps ? a.longitude : -81.3792; + + // Update coords display + coordsEl.textContent = `${defaultLat.toFixed(6)}, ${defaultLng.toFixed(6)}`; + + // Initialize map next tick so the div is in DOM + requestAnimationFrame(() => { + if (!mapDiv.offsetParent && mapDiv.parentNode) { + // Map may not be visible yet — use setTimeout + setTimeout(() => initGpsEditorMap(defaultLat, defaultLng, a), 100); + } else { + initGpsEditorMap(defaultLat, defaultLng, a); + } + }); + } + + function initGpsEditorMap(lat, lng, a) { + const mapDiv = document.getElementById('detailGpsMap'); + if (!mapDiv || mapDiv.offsetHeight === 0) { + // Retry if map isn't visible yet (hidden tab, not yet scrolled) + setTimeout(() => initGpsEditorMap(lat, lng, a), 300); + return; + } + + const map = L.map(mapDiv, { + zoomControl: true, + attributionControl: false, + }).setView([lat, lng], 15); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + }).addTo(map); + + const marker = L.marker([lat, lng], { + draggable: _gpsState.unlocked, + }).addTo(map); + + marker.on('dragend', function() { + const pos = marker.getLatLng(); + const latRounded = Math.round(pos.lat * 1000000) / 1000000; + const lngRounded = Math.round(pos.lng * 1000000) / 1000000; + marker.setLatLng([latRounded, lngRounded]); + document.getElementById('detailGpsCoords').textContent = + `${latRounded.toFixed(6)}, ${lngRounded.toFixed(6)}`; + }); + + // When map is clicked (not dragging), move marker there + map.on('click', function(e) { + if (!_gpsState.unlocked) return; + const latRounded = Math.round(e.latlng.lat * 1000000) / 1000000; + const lngRounded = Math.round(e.latlng.lng * 1000000) / 1000000; + marker.setLatLng([latRounded, lngRounded]); + document.getElementById('detailGpsCoords').textContent = + `${latRounded.toFixed(6)}, ${lngRounded.toFixed(6)}`; + }); + + _gpsState.map = map; + _gpsState.marker = marker; + + // Invalidate size after a short delay to fix rendering in hidden containers + setTimeout(() => map.invalidateSize(), 350); + } + + function toggleGpsLock() { + const lockBtn = document.getElementById('detailGpsLockBtn'); + const saveBtn = document.getElementById('detailGpsSaveBtn'); + const statusEl = document.getElementById('detailGpsStatus'); + const infoEl = document.getElementById('detailGpsInfo'); + + _gpsState.unlocked = !_gpsState.unlocked; + + if (_gpsState.unlocked) { + lockBtn.textContent = '🔓'; + lockBtn.className = 'gps-lock-btn unlocked'; + lockBtn.title = 'Click to lock GPS'; + saveBtn.style.display = ''; + statusEl.textContent = 'Editable — drag the marker or tap the map'; + infoEl.textContent = '📍 Drag the marker or click the map to set GPS position'; + } else { + lockBtn.textContent = '🔒'; + lockBtn.className = 'gps-lock-btn'; + lockBtn.title = 'GPS is locked — click to edit'; + saveBtn.style.display = 'none'; + statusEl.textContent = 'Locked'; + infoEl.textContent = '📍 GPS position is locked — click 🔒 to edit'; + } + + // Update marker draggability + if (_gpsState.marker) { + _gpsState.marker.dragging[_gpsState.unlocked ? 'enable' : 'disable'](); + } + } + + async function saveGpsCoords() { + if (!_gpsState.marker || !_gpsState.asset) return; + const pos = _gpsState.marker.getLatLng(); + const lat = Math.round(pos.lat * 1000000) / 1000000; + const lng = Math.round(pos.lng * 1000000) / 1000000; + + const saveBtn = document.getElementById('detailGpsSaveBtn'); + saveBtn.textContent = '⏳ Saving...'; + saveBtn.disabled = true; + + try { + await api('/api/assets/' + _gpsState.asset.id, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ latitude: lat, longitude: lng }), + }); + showToast('✅ GPS coordinates saved!', false); + + // Update asset data and re-lock + _gpsState.asset.latitude = lat; + _gpsState.asset.longitude = lng; + + // Re-lock + _gpsState.unlocked = false; + const lockBtn = document.getElementById('detailGpsLockBtn'); + lockBtn.textContent = '🔒'; + lockBtn.className = 'gps-lock-btn'; + lockBtn.title = 'GPS is locked — click to edit'; + document.getElementById('detailGpsSaveBtn').style.display = 'none'; + document.getElementById('detailGpsStatus').textContent = 'Locked'; + document.getElementById('detailGpsInfo').textContent = '📍 GPS coordinates saved'; + if (_gpsState.marker) { + _gpsState.marker.dragging.disable(); + } + + // Refresh detail to show updated data + setTimeout(() => viewAsset(_gpsState.asset.id), 1200); + } catch (e) { + showToast('Failed to save GPS: ' + e.message, true); + } finally { + saveBtn.textContent = '💾 Save GPS'; + saveBtn.disabled = false; + } + } + function statusBadge(status) { return ''; }