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 `
`;
}
+ // ── 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 '';
}