fix(web): position map source switcher below navbar and fix missing Flame icon import in ExecutiveShowcase
This commit is contained in:
@@ -22,7 +22,9 @@ import {
|
||||
Plane,
|
||||
Radar,
|
||||
Sparkles,
|
||||
ExternalLink
|
||||
ExternalLink,
|
||||
Flame,
|
||||
ShieldAlert
|
||||
} from 'lucide-react';
|
||||
|
||||
export const ExecutiveShowcase: React.FC = () => {
|
||||
|
||||
@@ -19,17 +19,17 @@ type RefKey =
|
||||
| 'osm';
|
||||
|
||||
const REFS: Record<RefKey, { label: string; tiles: string; attribution: string; maxzoom: number }> = {
|
||||
'google-hybrid': { label: '🛰️ Google Hybrid', tiles: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'google-topo': { label: '🏔️ Google Topo', tiles: 'https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'soviet-military': { label: '🇷🇺 Soviet Military Topo', tiles: 'https://maps.marshruty.ru/ml.ashx?x={x}&y={y}&z={z}&i=1&al=1', attribution: '© Soviet Military Topo 1:100k', maxzoom: 15 },
|
||||
'opentopo': { label: '🧭 OpenTopoMap', tiles: 'https://tile.opentopomap.org/{z}/{x}/{y}.png', attribution: '© OpenTopoMap', maxzoom: 17 },
|
||||
'esri-clarity': { label: '✨ Esri Clarity HD', tiles: 'https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'google-sat': { label: '🛰️ Google Sat', tiles: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'google-streets': { label: '🗺️ Google Streets', tiles: 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'esri-sat': { label: '🛰️ Esri Satellite', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri, Maxar', maxzoom: 19 },
|
||||
'esri-topo': { label: '🏔️ Esri Topo', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'esri-streets': { label: '🗺️ Esri Streets', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'osm': { label: '🧭 OSM Standard', tiles: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', attribution: '© OpenStreetMap', maxzoom: 19 },
|
||||
'google-hybrid': { label: '🛰️ Google Hybrid', tiles: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'google-topo': { label: '🏔️ Google Topo', tiles: 'https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'soviet-military': { label: '🇷🇺 Soviet Military Topo', tiles: 'https://maps.marshruty.ru/ml.ashx?x={x}&y={y}&z={z}&i=1&al=1', attribution: '© Soviet Military Topo 1:100k', maxzoom: 15 },
|
||||
'opentopo': { label: '🧭 OpenTopoMap', tiles: 'https://tile.opentopomap.org/{z}/{x}/{y}.png', attribution: '© OpenTopoMap', maxzoom: 17 },
|
||||
'esri-clarity': { label: '✨ Esri Clarity HD', tiles: 'https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'google-sat': { label: '🛰️ Google Sat', tiles: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'google-streets': { label: '🗺️ Google Streets', tiles: 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', attribution: '© Google', maxzoom: 20 },
|
||||
'esri-sat': { label: '🛰️ Esri Satellite', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri, Maxar', maxzoom: 19 },
|
||||
'esri-topo': { label: '🏔️ Esri Topo', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'esri-streets': { label: '🗺️ Esri Streets', tiles: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', attribution: '© Esri', maxzoom: 19 },
|
||||
'osm': { label: '🧭 OSM Standard', tiles: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', attribution: '© OpenStreetMap', maxzoom: 19 },
|
||||
};
|
||||
|
||||
const API = (import.meta as any).env.VITE_API_URL || '/api';
|
||||
@@ -42,7 +42,7 @@ const rasterStyle = (ref: any): any => ({
|
||||
layers: [{ id: 'ref', type: 'raster', source: 'ref' }],
|
||||
});
|
||||
|
||||
const DEFAULT_SYSTEM_KEY = (import.meta as any).env.VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
|
||||
const DEFAULT_SYSTEM_KEY = (import.meta as any).env.VITE_API_KEY;
|
||||
|
||||
const IntelligenceDashboard: React.FC = () => {
|
||||
const [stats, setStats] = useState<any>(null);
|
||||
@@ -95,7 +95,7 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
[point.x - radius, point.y - radius],
|
||||
[point.x + radius, point.y + radius]
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
const features = map.queryRenderedFeatures(bbox).filter(f => {
|
||||
const id = f.layer?.id || '';
|
||||
@@ -109,22 +109,22 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
const geom = f.geometry;
|
||||
if (!geom) continue;
|
||||
|
||||
const lines: [number, number][][] = geom.type === 'LineString'
|
||||
const lines: [number, number][][] = geom.type === 'LineString'
|
||||
? [geom.coordinates as [number, number][]]
|
||||
: geom.type === 'MultiLineString'
|
||||
? (geom.coordinates as [number, number][][])
|
||||
: [];
|
||||
? (geom.coordinates as [number, number][][])
|
||||
: [];
|
||||
|
||||
for (const line of lines) {
|
||||
for (let i = 0; i < line.length - 1; i++) {
|
||||
const p1 = map.project(line[i] as any);
|
||||
const p2 = map.project(line[i + 1] as any);
|
||||
|
||||
|
||||
const dx = p2.x - p1.x;
|
||||
const dy = p2.y - p1.y;
|
||||
const lenSq = dx * dx + dy * dy;
|
||||
if (lenSq === 0) continue;
|
||||
|
||||
|
||||
const t = Math.max(0, Math.min(1, ((point.x - p1.x) * dx + (point.y - p1.y) * dy) / lenSq));
|
||||
const closePx = { x: p1.x + t * dx, y: p1.y + t * dy };
|
||||
const distPx = Math.hypot(point.x - closePx.x, point.y - closePx.y);
|
||||
@@ -308,14 +308,14 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
const nameLine = p.name ? `<b>${p.name}</b><br/>` : '';
|
||||
const evidence = isOvt ? `${p.uniqueDriverCount ?? 0} trace pts corroborate` : `${p.uniqueDriverCount || 1} drivers`;
|
||||
if (popupRef.current) popupRef.current.remove();
|
||||
|
||||
|
||||
const popupDiv = document.createElement('div');
|
||||
popupDiv.style.font = '12px sans-serif';
|
||||
popupDiv.style.color = '#1e293b';
|
||||
popupDiv.style.lineHeight = '1.6';
|
||||
popupDiv.innerHTML = `${nameLine}<b>${title}</b><br/>Conf: <b style="color:${cc(p.confidence)}">${Math.round(p.confidence * 100)}%</b><br/>${Math.round(p.lengthMeters || 0)}m | ${evidence}
|
||||
<br/><button id="btn-del-${p.id}" style="margin-top:8px;background:#ef4444;color:white;border:none;border-radius:4px;padding:4px 8px;cursor:pointer;">🗑️ Delete Road</button>`;
|
||||
|
||||
|
||||
popupDiv.querySelector(`#btn-del-${p.id}`)?.addEventListener('click', () => {
|
||||
deleteRoad(p.id, isAppr ? 'approved' : 'candidate');
|
||||
popupRef.current?.remove();
|
||||
@@ -501,12 +501,12 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
setTimeout(() => setMsg(''), 5000);
|
||||
};
|
||||
|
||||
const cancelDrawing = () => {
|
||||
pointsRef.current = [];
|
||||
redrawDrawing();
|
||||
const cancelDrawing = () => {
|
||||
pointsRef.current = [];
|
||||
redrawDrawing();
|
||||
if (leftMapRef.current) updateSnapGuide(leftMapRef.current, null);
|
||||
if (rightMapRef.current) updateSnapGuide(rightMapRef.current, null);
|
||||
setDrawing(false);
|
||||
setDrawing(false);
|
||||
};
|
||||
|
||||
if (needsAuth) {
|
||||
@@ -691,8 +691,8 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
{/* Left Map: Our Data */}
|
||||
<div style={{ flex: 1, position: 'relative', borderRight: '2px solid #0f172a' }}>
|
||||
<div ref={leftDiv} style={{ width: '100%', height: '100%' }} />
|
||||
<div style={{ position: 'absolute', top: 10, left: 10, zIndex: 2, background: 'rgba(15,23,42,0.85)', border: '1px solid #334155', padding: '4px 10px', borderRadius: '20px', fontSize: '0.72rem', fontWeight: 600, pointerEvents: 'none' }}>
|
||||
Our Map (Intaleq)
|
||||
<div style={{ position: 'absolute', top: 62, left: 16, zIndex: 30, background: 'rgba(15,23,42,0.92)', backdropFilter: 'blur(8px)', border: '1px solid #334155', padding: '6px 14px', borderRadius: '20px', fontSize: '0.8rem', fontWeight: 700, color: '#38bdf8', boxShadow: '0 4px 12px rgba(0,0,0,0.4)', pointerEvents: 'none' }}>
|
||||
🗺️ خريطتنا السيادية (Intaleq Map)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -700,9 +700,9 @@ const IntelligenceDashboard: React.FC = () => {
|
||||
<div style={{ flex: 1, position: 'relative' }}>
|
||||
<div ref={rightDiv} style={{ width: '100%', height: '100%' }} />
|
||||
|
||||
<div style={{ position: 'absolute', top: 10, left: 10, right: 280, zIndex: 2, display: 'flex', gap: '6px', overflowX: 'auto', paddingBottom: '4px' }}>
|
||||
<div style={{ position: 'absolute', top: 62, left: 16, right: 20, zIndex: 30, display: 'flex', gap: '8px', overflowX: 'auto', paddingBottom: '4px' }}>
|
||||
{(Object.keys(REFS) as Array<keyof typeof REFS>).map(k => (
|
||||
<button key={k} onClick={() => setRefKey(k)} style={{ background: refKey === k ? 'rgba(99,102,241,0.95)' : 'rgba(15,23,42,0.85)', border: '1px solid #334155', color: refKey === k ? 'white' : '#e2e8f0', padding: '4px 10px', borderRadius: '20px', fontSize: '0.72rem', fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0 }}>
|
||||
<button key={k} onClick={() => setRefKey(k)} style={{ background: refKey === k ? 'linear-gradient(135deg, #6366f1, #3b82f6)' : 'rgba(15,23,42,0.92)', backdropFilter: 'blur(8px)', border: refKey === k ? '1px solid #818cf8' : '1px solid #334155', color: refKey === k ? 'white' : '#cbd5e1', padding: '6px 14px', borderRadius: '20px', fontSize: '0.78rem', fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0, boxShadow: '0 4px 12px rgba(0,0,0,0.3)', transition: 'all 0.15s ease' }}>
|
||||
{REFS[k].label}
|
||||
</button>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user