diff --git a/index.html b/index.html index 2ebcaa5..2777553 100644 --- a/index.html +++ b/index.html @@ -221,6 +221,11 @@ Place Camera 400ft Ahead + + + + +
+
+

+ 🛡️ + Admin: Manage Placed Cameras +

+ 0 Placed +
+

+ Manage and remove any cameras you placed or tested. The 1,439 official municipal transparency portals remain permanent and protected. +

+
+ +
+ +
@@ -839,6 +864,17 @@ Inspect Official Transparency Portal & Policy Records ↗ + + + diff --git a/js/auth.js b/js/auth.js index bfa42ec..7e001c0 100644 --- a/js/auth.js +++ b/js/auth.js @@ -155,8 +155,24 @@ function removeReport(reportId) { communityReports = communityReports.filter(r => r.id !== reportId); if (cameraMetadata[reportId]) delete cameraMetadata[reportId]; - saveReports(); - saveMetadata(); + localStorage.setItem(STORAGE_KEY_REPORTS, JSON.stringify(communityReports)); + localStorage.setItem(STORAGE_KEY_METADATA, JSON.stringify(cameraMetadata)); + window.dispatchEvent(new CustomEvent('flock_reports_updated', { detail: communityReports })); + return true; + } + + function clearAllPlacedReports() { + communityReports = []; + localStorage.setItem(STORAGE_KEY_REPORTS, JSON.stringify([])); + // Clean up metadata for custom placed pins + Object.keys(cameraMetadata).forEach(id => { + if (id.startsWith('rpt-') || id.startsWith('usr-pin-') || id.startsWith('test-') || id.startsWith('cam-test-')) { + delete cameraMetadata[id]; + } + }); + localStorage.setItem(STORAGE_KEY_METADATA, JSON.stringify(cameraMetadata)); + window.dispatchEvent(new CustomEvent('flock_reports_updated', { detail: [] })); + return true; } function saveReports() { @@ -369,6 +385,7 @@ getReports, addReport, removeReport, + clearAllPlacedReports, clearUnverifiedReports, getCameraMeta, confirmCamera, diff --git a/js/radar.js b/js/radar.js index ee49c50..72c31c9 100644 --- a/js/radar.js +++ b/js/radar.js @@ -198,7 +198,15 @@ btnReportRemovePhoto: document.getElementById('btn-report-remove-photo'), // Native Camera / File Input - cameraPhotoFileInput: document.getElementById('camera-photo-file-input') + cameraPhotoFileInput: document.getElementById('camera-photo-file-input'), + + // Admin Placed Camera Controls + camAdminControlBox: document.getElementById('cam-admin-control-box'), + btnAdminDeleteCam: document.getElementById('btn-admin-delete-cam'), + adminPlacedCount: document.getElementById('admin-placed-count'), + adminPlacedCamsList: document.getElementById('admin-placed-cams-list'), + btnAdminClearAllPlaced: document.getElementById('btn-admin-clear-all-placed'), + btnAdminManagePins: document.getElementById('btn-admin-manage-pins') }; // --- INITIALIZATION --- @@ -410,6 +418,11 @@ Inspect Agency Records & Sharing ↗ ` : ''} + ${(cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-')) ? ` + + ` : ''} `; @@ -1250,6 +1263,8 @@ document.getElementById('login-vehicle').value = user.vehicle; } + renderAdminPlacedList(); + if (typeof el.loginModal.showModal === 'function') { el.loginModal.showModal(); } else { @@ -1384,6 +1399,12 @@ } } + // Admin Delete Control: ONLY visible for custom placed pins, NEVER for official scraped municipal portals + const isPlaced = cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-'); + if (el.camAdminControlBox) { + el.camAdminControlBox.style.display = isPlaced ? 'block' : 'none'; + } + // Show modal if (el.cameraDetailModal) { if (typeof el.cameraDetailModal.showModal === 'function') { @@ -1404,6 +1425,107 @@ } } + // --- ADMIN PLACED CAMERA MANAGEMENT --- + function renderAdminPlacedList() { + if (!el.adminPlacedCamsList) return; + const reports = window.FlockAuth ? window.FlockAuth.getReports() : []; + + // Find all placed cameras from active radar memory (including test cameras) + const placedCams = allCameras.filter(c => + c.isCommunity || + c.id.startsWith('rpt-') || + c.id.startsWith('usr-pin-') || + c.id.startsWith('cam-test-') || + c.id.startsWith('test-') + ); + + // Combine into unique map + const combinedMap = new Map(); + reports.forEach(r => { + combinedMap.set(r.id, { + id: r.id, + name: r.intersection || r.type || 'Placed Camera', + lat: r.lat, + lng: r.lng, + dateStr: new Date(r.timestamp || Date.now()).toLocaleDateString(), + photoUrl: r.photoUrl || null + }); + }); + placedCams.forEach(c => { + if (!combinedMap.has(c.id)) { + combinedMap.set(c.id, { + id: c.id, + name: c.name || 'Test Placed Camera', + lat: c.lat, + lng: c.lng, + dateStr: 'Active Session', + photoUrl: null + }); + } + }); + + const items = Array.from(combinedMap.values()); + if (el.adminPlacedCount) el.adminPlacedCount.textContent = `${items.length} Placed`; + + if (items.length === 0) { + el.adminPlacedCamsList.innerHTML = ` +
+ No custom placed cameras. All active radar cameras are official municipal portals. +
+ `; + if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'none'; + return; + } + + if (el.btnAdminClearAllPlaced) el.btnAdminClearAllPlaced.style.display = 'block'; + + el.adminPlacedCamsList.innerHTML = items.map(r => ` +
+
+ ${r.photoUrl ? ` + Thumbnail + ` : ` + 📍 + `} +
+ ${escapeHtml(r.name)} + ${r.lat.toFixed(4)}, ${r.lng.toFixed(4)} • ${r.dateStr} +
+
+ +
+ `).join(''); + } + + window.adminDeleteCamera = function(camId) { + // SECURITY GUARD: Protect official scraped municipal transparency portals + if (camId.startsWith('portal-') || (!camId.startsWith('rpt-') && !camId.startsWith('usr-pin-') && !camId.startsWith('test-') && !camId.startsWith('cam-test-'))) { + const targetCam = allCameras.find(c => c.id === camId); + if (targetCam && !targetCam.isCommunity) { + alert("Protected Municipal Portal: Official city and police department transparency records cannot be deleted. Only custom placed cameras can be removed."); + return; + } + } + + const cam = allCameras.find(c => c.id === camId) || { name: 'Placed Camera', id: camId }; + if (!confirm(`Admin Action: Permanently delete placed camera "${cam.name || cam.id}" from radar?`)) { + return; + } + + if (window.FlockAuth) { + window.FlockAuth.removeReport(camId); + } + allCameras = allCameras.filter(c => c.id !== camId); + renderCameraZones(); + renderAdminPlacedList(); + closeCameraDetailModal(); + playLogConfirmationChirp(); + speakVoiceAlert("Placed camera deleted from radar."); + showToast("🗑️ Placed camera permanently removed."); + }; + // --- CONTEST CAMERA MODAL --- function openContestModal(cam) { activeModalCamera = cam; @@ -1615,6 +1737,14 @@ }); } + // Admin Delete Camera Button (Detail Modal) + if (el.btnAdminDeleteCam) { + el.btnAdminDeleteCam.addEventListener('click', () => { + if (!activeModalCamera) return; + window.adminDeleteCamera(activeModalCamera.id); + }); + } + // Contest Modal Events if (el.contestCloseBtn) el.contestCloseBtn.addEventListener('click', closeContestModal); if (el.btnContestAddPhoto) el.btnContestAddPhoto.addEventListener('click', triggerPhotoCaptureForContest); @@ -1723,6 +1853,42 @@ closeLoginModal(); }); + // Admin Clear All Placed Pins Button (Keeps 1,439 real municipal portals) + if (el.btnAdminClearAllPlaced) { + el.btnAdminClearAllPlaced.addEventListener('click', () => { + if (!confirm("Admin Action: Clear ALL custom placed pins and radar test markers? (The 1,439 official municipal portals will remain permanent and protected).")) { + return; + } + if (window.FlockAuth) { + window.FlockAuth.clearAllPlacedReports(); + } + allCameras = allCameras.filter(c => !c.isCommunity && !c.id.startsWith('rpt-') && !c.id.startsWith('usr-pin-') && !c.id.startsWith('test-') && !c.id.startsWith('cam-test-')); + renderCameraZones(); + renderAdminPlacedList(); + playLogConfirmationChirp(); + speakVoiceAlert("All custom placed pins cleared from radar."); + showToast("🗑️ All custom placed pins cleared."); + }); + } + + // Admin Placed Pins Shortcut in Map Top Controls + if (el.btnAdminManagePins) { + el.btnAdminManagePins.addEventListener('click', () => { + openLoginModal(); + setTimeout(() => { + const adminSec = document.querySelector('.admin-profile-section'); + if (adminSec) adminSec.scrollIntoView({ behavior: 'smooth' }); + }, 120); + }); + } + + // Sync radar when reports updated + window.addEventListener('flock_reports_updated', () => { + loadAllCameraData(); + renderCameraZones(); + renderAdminPlacedList(); + }); + // Report Form Submit el.reportForm.addEventListener('submit', e => { e.preventDefault();