fix(radar): open camera intelligence & directory records in-map, eliminate view switching
This commit is contained in:
+70
-10
@@ -28,7 +28,7 @@
|
|||||||
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
||||||
|
|
||||||
<!-- Main Stylesheet -->
|
<!-- Main Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/styles.css?v=9">
|
<link rel="stylesheet" href="css/styles.css?v=10">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -886,11 +886,71 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Public Transparency Portal Link -->
|
<!-- Specific Agency Directory Records & Surveillance Policy -->
|
||||||
<div id="proof-portal-link-box" style="margin-top: 16px; text-align: center;">
|
<div class="modal-section proof-directory-records-section" id="proof-directory-records-section" style="margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-subtle);">
|
||||||
<button id="btn-proof-open-portal" class="btn-secondary" style="width: 100%; padding: 12px;">
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
|
||||||
Inspect Official Transparency Portal & Policy Records ↗
|
<h3 class="modal-section-title" style="margin: 0; font-size: 0.95rem; color: #38bdf8; display: flex; align-items: center; gap: 6px;">
|
||||||
</button>
|
<span>📋</span>
|
||||||
|
<span>Agency Surveillance & Policy Records</span>
|
||||||
|
</h3>
|
||||||
|
<span id="proof-dir-source-badge" style="font-size: 0.72rem; padding: 2px 8px; border-radius: 10px; background: rgba(56, 189, 248, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.3);">
|
||||||
|
Official Transparency Data
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 4-Box Key Stats (Cameras, Volume, Searches, Retention) -->
|
||||||
|
<div class="proof-specs-grid" style="margin-bottom: 14px;">
|
||||||
|
<div class="proof-spec-card">
|
||||||
|
<span class="spec-label">CAMERAS IN FLEET</span>
|
||||||
|
<span class="spec-val" id="proof-dir-cameras" style="color: #38bdf8;">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="proof-spec-card">
|
||||||
|
<span class="spec-label">30-DAY DETECTIONS</span>
|
||||||
|
<span class="spec-val" id="proof-dir-volume" style="color: #34d399;">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="proof-spec-card">
|
||||||
|
<span class="spec-label">POLICE SEARCHES</span>
|
||||||
|
<span class="spec-val" id="proof-dir-searches">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="proof-spec-card">
|
||||||
|
<span class="spec-label">DATA RETENTION</span>
|
||||||
|
<span class="spec-val" id="proof-dir-retention" style="color: #fbbf24;">30 Days Auto-Purge</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inter-Agency Data Sharing Network -->
|
||||||
|
<div class="proof-sharing-section" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
|
<strong style="font-size: 0.8rem; color: #e2e8f0; display: flex; align-items: center; gap: 6px;">
|
||||||
|
<span>🔗</span> Inter-Agency Surveillance Sharing:
|
||||||
|
</strong>
|
||||||
|
<span id="proof-dir-sharing-count" style="font-size: 0.75rem; color: #38bdf8; font-weight: 700;">Regional Law Enforcement</span>
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 0.74rem; color: #94a3b8; margin: 0 0 8px; line-height: 1.4;">
|
||||||
|
Plate reads from this camera are broadcast to regional and federal law enforcement partners:
|
||||||
|
</p>
|
||||||
|
<div id="proof-dir-sharing-tags" style="display: flex; flex-wrap: wrap; gap: 5px; max-height: 90px; overflow-y: auto;">
|
||||||
|
<!-- Sharing tags rendered dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Top Search Justifications -->
|
||||||
|
<div id="proof-dir-reasons-box" style="margin-bottom: 14px; background: rgba(15, 23, 42, 0.6); border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 12px; padding: 12px 14px;">
|
||||||
|
<strong style="font-size: 0.8rem; color: #e2e8f0; display: block; margin-bottom: 6px;">
|
||||||
|
🔍 Primary Search Audit Reasons:
|
||||||
|
</strong>
|
||||||
|
<div id="proof-dir-reasons-list" style="display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: #cbd5e1;">
|
||||||
|
<!-- Rendered dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Official City Portal Link (Opens in New Tab - Never leaves the radar map) -->
|
||||||
|
<div id="proof-portal-link-box" style="margin-top: 12px;">
|
||||||
|
<a id="btn-proof-official-portal" href="#" target="_blank" rel="noopener noreferrer" class="btn-secondary" style="width: 100%; padding: 12px; display: flex; align-items: center; justify-content: center; gap: 8px; text-decoration: none; border-color: #06b6d4; color: #38bdf8; font-weight: 600;">
|
||||||
|
<span>🌐</span>
|
||||||
|
<span id="btn-proof-portal-text">Open Official Transparency Portal ↗</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Admin Removal Control (Only Active for Custom Placed Pins) -->
|
<!-- Admin Removal Control (Only Active for Custom Placed Pins) -->
|
||||||
@@ -971,10 +1031,10 @@
|
|||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
|
||||||
<!-- Data & Application Logic with Cache Busting -->
|
<!-- Data & Application Logic with Cache Busting -->
|
||||||
<script src="data/portals.js?v=9"></script>
|
<script src="data/portals.js?v=10"></script>
|
||||||
<script src="js/auth.js?v=9"></script>
|
<script src="js/auth.js?v=10"></script>
|
||||||
<script src="js/radar.js?v=9"></script>
|
<script src="js/radar.js?v=10"></script>
|
||||||
<script src="js/app.js?v=9"></script>
|
<script src="js/app.js?v=10"></script>
|
||||||
|
|
||||||
<!-- Service Worker Instant Cache Invalidation -->
|
<!-- Service Worker Instant Cache Invalidation -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -569,6 +569,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global access so radar map can open agency modal without switching views
|
||||||
|
window.openAgencyModal = openAgencyModal;
|
||||||
|
|
||||||
function renderModalSharingTags(sharedIds, query) {
|
function renderModalSharingTags(sharedIds, query) {
|
||||||
if (!sharedIds || sharedIds.length === 0) {
|
if (!sharedIds || sharedIds.length === 0) {
|
||||||
el.modalSharingBox.innerHTML = `<span style="font-size: 0.8rem; color: var(--text-dim); padding: 8px;">No external sharing partners published on portal.</span>`;
|
el.modalSharingBox.innerHTML = `<span style="font-size: 0.8rem; color: var(--text-dim); padding: 8px;">No external sharing partners published on portal.</span>`;
|
||||||
|
|||||||
+120
-26
@@ -423,14 +423,9 @@
|
|||||||
${cam.notes ? `<br><span><em>${escapeHtml(cam.notes)}</em></span>` : ''}
|
${cam.notes ? `<br><span><em>${escapeHtml(cam.notes)}</em></span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
|
<div style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
|
||||||
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 600;" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
<button class="popup-inspect-btn" style="background: linear-gradient(135deg, #06b6d4, #0284c7); color: #fff; font-weight: 700; width: 100%; border: none; border-radius: 6px; padding: 10px; cursor: pointer; box-shadow: 0 2px 8px rgba(6, 182, 212, 0.4);" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||||
📸 View Hardware Proof & Vote ↗
|
📸 View Camera Info & Agency Records ↗
|
||||||
</button>
|
</button>
|
||||||
${cam.slug ? `
|
|
||||||
<button class="popup-inspect-btn" onclick="window.inspectPortal('${escapeHtml(cam.slug)}')">
|
|
||||||
Inspect Agency Records & Sharing ↗
|
|
||||||
</button>
|
|
||||||
` : ''}
|
|
||||||
${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? `
|
${(!cam.id.startsWith('portal-') && (cam.isCommunity || cam.id.startsWith('rpt-') || cam.id.startsWith('usr-pin-') || cam.id.startsWith('test-') || cam.id.startsWith('cam-test-') || cam.notes === '1-Tap Mobile Driver Pin' || cam.agency === 'Crowdsourced Report' || cam.name.includes('Community Report'))) ? `
|
||||||
<button class="popup-inspect-btn popup-delete-btn" style="background: #dc2626 !important; color: #ffffff !important; font-weight: 800 !important; font-size: 0.88rem !important; padding: 10px 14px !important; border-radius: 6px !important; margin-top: 6px !important; display: flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; border: none !important; cursor: pointer !important; box-shadow: 0 3px 10px rgba(220, 38, 38, 0.45) !important;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
|
<button class="popup-inspect-btn popup-delete-btn" style="background: #dc2626 !important; color: #ffffff !important; font-weight: 800 !important; font-size: 0.88rem !important; padding: 10px 14px !important; border-radius: 6px !important; margin-top: 6px !important; display: flex !important; align-items: center !important; justify-content: center !important; gap: 8px !important; border: none !important; cursor: pointer !important; box-shadow: 0 3px 10px rgba(220, 38, 38, 0.45) !important;" onclick="window.adminDeleteCamera('${escapeHtml(cam.id)}')">
|
||||||
<span style="font-size: 1rem;">🗑️</span>
|
<span style="font-size: 1rem;">🗑️</span>
|
||||||
@@ -443,9 +438,9 @@
|
|||||||
|
|
||||||
marker.bindPopup(popupHtml);
|
marker.bindPopup(popupHtml);
|
||||||
|
|
||||||
// Clicking marker directly opens the full hardware proof modal
|
// Clicking radar circle zone directly opens the camera intelligence sheet
|
||||||
marker.on('click', () => {
|
radarCircle.on('click', () => {
|
||||||
// Leaflet will open popup, but user can also click proof button directly
|
openCameraHardwareProof(cam.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
cameraCircles.push(radarCircle);
|
cameraCircles.push(radarCircle);
|
||||||
@@ -1397,19 +1392,116 @@
|
|||||||
el.btnProofNavigate.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
|
el.btnProofNavigate.href = `https://www.google.com/maps/dir/?api=1&destination=${cam.lat},${cam.lng}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transparency Portal Link
|
// Lookup Directory Intelligence for this camera & agency
|
||||||
const portalBox = document.getElementById('proof-portal-link-box');
|
let portal = null;
|
||||||
if (portalBox) {
|
if (window.FLOCK_DATA && window.FLOCK_DATA.portals) {
|
||||||
if (cam.slug) {
|
if (cam.slug) {
|
||||||
portalBox.style.display = 'block';
|
portal = window.FLOCK_DATA.portals.find(p => p.slug === cam.slug);
|
||||||
if (el.btnProofOpenPortal) {
|
}
|
||||||
el.btnProofOpenPortal.onclick = () => {
|
if (!portal && cam.agency) {
|
||||||
closeCameraDetailModal();
|
portal = window.FLOCK_DATA.portals.find(p =>
|
||||||
window.inspectPortal(cam.slug);
|
(p.name && p.name.toLowerCase().includes(cam.agency.toLowerCase())) ||
|
||||||
};
|
(cam.agency.toLowerCase().includes((p.name || '').toLowerCase()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate Directory Intelligence Fields
|
||||||
|
const elDirCameras = document.getElementById('proof-dir-cameras');
|
||||||
|
const elDirVolume = document.getElementById('proof-dir-volume');
|
||||||
|
const elDirSearches = document.getElementById('proof-dir-searches');
|
||||||
|
const elDirRetention = document.getElementById('proof-dir-retention');
|
||||||
|
const elDirSharingCount = document.getElementById('proof-dir-sharing-count');
|
||||||
|
const elDirSharingTags = document.getElementById('proof-dir-sharing-tags');
|
||||||
|
const elDirReasonsBox = document.getElementById('proof-dir-reasons-box');
|
||||||
|
const elDirReasonsList = document.getElementById('proof-dir-reasons-list');
|
||||||
|
const elOfficialPortalBtn = document.getElementById('btn-proof-official-portal');
|
||||||
|
const elPortalText = document.getElementById('btn-proof-portal-text');
|
||||||
|
|
||||||
|
if (portal) {
|
||||||
|
// Official scraped portal record
|
||||||
|
if (elDirCameras) elDirCameras.textContent = portal.cameras ? `${portal.cameras.toLocaleString()} in Fleet` : `${cam.cameras || 4} at Node`;
|
||||||
|
if (elDirVolume) elDirVolume.textContent = portal.vehicles_captured ? `${portal.vehicles_captured.toLocaleString()} Reads` : (cam.captures30d ? `${cam.captures30d} Reads` : '300,000+');
|
||||||
|
if (elDirSearches) elDirSearches.textContent = portal.searches ? `${portal.searches.toLocaleString()} Queries` : 'Automated Hotlist';
|
||||||
|
if (elDirRetention) elDirRetention.textContent = '30 Days Auto-Purge';
|
||||||
|
|
||||||
|
const sharedIds = portal.shared_org_ids || [];
|
||||||
|
if (elDirSharingCount) elDirSharingCount.textContent = `${sharedIds.length} Partner Agencies`;
|
||||||
|
|
||||||
|
if (elDirSharingTags) {
|
||||||
|
const orgsLookup = (window.FLOCK_DATA && window.FLOCK_DATA.orgs) || [];
|
||||||
|
const topShared = sharedIds.slice(0, 15).map(id => orgsLookup[id] || `Agency #${id}`);
|
||||||
|
elDirSharingTags.innerHTML = topShared.map(name => `
|
||||||
|
<span style="background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.25); color: #93c5fd; padding: 2px 7px; border-radius: 6px; font-size: 0.7rem;">${escapeHtml(name)}</span>
|
||||||
|
`).join('') + (sharedIds.length > 15 ? `<span style="font-size: 0.68rem; color: #64748b; align-self: center;">+${sharedIds.length - 15} more</span>` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elDirReasonsList && elDirReasonsBox) {
|
||||||
|
const reasons = portal.top_search_reasons || {};
|
||||||
|
const entries = Object.entries(reasons).slice(0, 4);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
elDirReasonsBox.style.display = 'block';
|
||||||
|
elDirReasonsList.innerHTML = entries.map(([r, count]) => `
|
||||||
|
<div style="display: flex; justify-content: space-between;">
|
||||||
|
<span>• ${escapeHtml(r)}</span>
|
||||||
|
<span style="color: #38bdf8; font-weight: 600;">${count.toLocaleString()} searches</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
elDirReasonsBox.style.display = 'none';
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
portalBox.style.display = 'none';
|
|
||||||
|
if (elOfficialPortalBtn && elPortalText) {
|
||||||
|
elOfficialPortalBtn.href = portal.url || `https://transparency.flocksafety.com/${portal.slug}`;
|
||||||
|
elPortalText.textContent = `Open Official ${portal.name} Transparency Portal ↗`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Local Antelope Valley / Sheriff corridor or Community Camera
|
||||||
|
const defaultCameras = cam.cameras || (cam.id && cam.id.includes('plm') ? 4 : 2);
|
||||||
|
const defaultCaptures = cam.captures30d ? `${cam.captures30d} Reads` : '285,000 Reads';
|
||||||
|
|
||||||
|
if (elDirCameras) elDirCameras.textContent = `${defaultCameras} Falcon Node Cameras`;
|
||||||
|
if (elDirVolume) elDirVolume.textContent = defaultCaptures;
|
||||||
|
if (elDirSearches) elDirSearches.textContent = 'Automated NCIC Hotlist';
|
||||||
|
if (elDirRetention) elDirRetention.textContent = '30 Days Auto-Purge';
|
||||||
|
|
||||||
|
if (elDirSharingCount) elDirSharingCount.textContent = 'Regional Law Enforcement Network';
|
||||||
|
if (elDirSharingTags) {
|
||||||
|
const regionalPartners = [
|
||||||
|
'Los Angeles County Sheriff (LASD)',
|
||||||
|
'California Highway Patrol (CHP)',
|
||||||
|
'Los Angeles Police Department (LAPD)',
|
||||||
|
'Kern County Sheriff Office',
|
||||||
|
'San Bernardino County Sheriff',
|
||||||
|
'Ventura County Sheriff'
|
||||||
|
];
|
||||||
|
elDirSharingTags.innerHTML = regionalPartners.map(name => `
|
||||||
|
<span style="background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.25); color: #93c5fd; padding: 2px 7px; border-radius: 6px; font-size: 0.7rem;">${name}</span>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elDirReasonsList && elDirReasonsBox) {
|
||||||
|
elDirReasonsBox.style.display = 'block';
|
||||||
|
elDirReasonsList.innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between;">
|
||||||
|
<span>• Stolen Vehicle / Plate Alerts (10851 CVC)</span>
|
||||||
|
<span style="color: #38bdf8; font-weight: 600;">Real-time</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: space-between;">
|
||||||
|
<span>• Felony Warrants & BOLO Broadcasts</span>
|
||||||
|
<span style="color: #38bdf8; font-weight: 600;">Active</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: space-between;">
|
||||||
|
<span>• Amber Alert / Missing Person Inquiries</span>
|
||||||
|
<span style="color: #38bdf8; font-weight: 600;">Broadcast</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elOfficialPortalBtn && elPortalText) {
|
||||||
|
elOfficialPortalBtn.href = `https://transparency.flocksafety.com/${cam.slug || 'palmdale-ca-lasd'}`;
|
||||||
|
elPortalText.textContent = `Open Official ${cam.agency || 'Agency'} Transparency Portal ↗`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1988,12 +2080,14 @@
|
|||||||
el.loginCloseBtn.addEventListener('click', closeLoginModal);
|
el.loginCloseBtn.addEventListener('click', closeLoginModal);
|
||||||
el.reportCloseBtn.addEventListener('click', closeReportModal);
|
el.reportCloseBtn.addEventListener('click', closeReportModal);
|
||||||
|
|
||||||
// Global listener for portal inspection from popup
|
// Global listener for camera & portal inspection: NEVER switches views away from radar!
|
||||||
window.inspectPortal = function(slug) {
|
window.inspectPortal = function(slug) {
|
||||||
switchView('dir');
|
const matchedCam = allCameras.find(c => c.slug === slug);
|
||||||
setTimeout(() => {
|
if (matchedCam) {
|
||||||
window.location.hash = slug;
|
openCameraHardwareProof(matchedCam.id);
|
||||||
}, 150);
|
} else if (typeof window.openAgencyModal === 'function') {
|
||||||
|
window.openAgencyModal(slug);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Service Worker for FlockRadar Offline Driving & Live Updates
|
// Service Worker for FlockRadar Offline Driving & Live Updates
|
||||||
const CACHE_NAME = 'flockradar-v9';
|
const CACHE_NAME = 'flockradar-v10';
|
||||||
const ASSETS_TO_CACHE = [
|
const ASSETS_TO_CACHE = [
|
||||||
'./',
|
'./',
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./css/styles.css?v=9',
|
'./css/styles.css?v=10',
|
||||||
'./js/app.js?v=9',
|
'./js/app.js?v=10',
|
||||||
'./js/auth.js?v=9',
|
'./js/auth.js?v=10',
|
||||||
'./js/radar.js?v=9',
|
'./js/radar.js?v=10',
|
||||||
'./data/portals.js?v=9',
|
'./data/portals.js?v=10',
|
||||||
'./manifest.json'
|
'./manifest.json'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user