feat: implement manual routing sync system, update map tile URLs to HTTPS, and migrate admin session storage to localStorage

This commit is contained in:
Hamza-Ayed
2026-07-15 23:01:18 +03:00
parent 78fe6c48cc
commit c97c961c96
14 changed files with 281 additions and 18 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ import 'maplibre-gl/dist/maplibre-gl.css';
// Martin vector-tile host (serves planet_osm_*, approved_roads, …). This is a
// DIFFERENT host from where the app + its style.json are served, so it must be
// absolute — an empty default resolved to the app host, which serves no tiles.
const TILES = (import.meta as any).env.VITE_TILES_URL || 'http://188.68.36.205:3202';
const TILES = (import.meta as any).env.VITE_TILES_URL || 'https://tiles.intaleqapp.com';
type RefKey = 'google-sat' | 'esri-sat' | 'esri-streets' | 'osm';
const REFS: Record<RefKey, { label: string; tiles: string; attribution: string; maxzoom: number }> = {
+23 -7
View File
@@ -30,8 +30,8 @@ const IntelligenceDashboard: React.FC = () => {
const [msg, setMsg] = useState('');
const [tab, setTab] = useState<'summary' | 'candidates' | 'closures'>('candidates');
const [loading, setLoading] = useState(true);
const [apiKey, setApiKey] = useState(sessionStorage.getItem('map_admin_key') || '');
const [needsAuth, setNeedsAuth] = useState(!sessionStorage.getItem('map_admin_key'));
const [apiKey, setApiKey] = useState(localStorage.getItem('map_admin_key') || '');
const [needsAuth, setNeedsAuth] = useState(!localStorage.getItem('map_admin_key'));
const [sel, setSel] = useState<string | null>(null);
// Right Map State
@@ -111,8 +111,8 @@ const IntelligenceDashboard: React.FC = () => {
if (!sr.ok || !cr.ok || !clr.ok) {
if (sr.status === 401 || sr.status === 403) {
localStorage.removeItem('map_admin_key');
setNeedsAuth(true);
sessionStorage.removeItem('map_admin_key');
setApiKey('');
throw new Error('Invalid or unauthorized API Key');
}
@@ -371,6 +371,17 @@ const IntelligenceDashboard: React.FC = () => {
setTimeout(() => setMsg(''), 5000);
};
const syncRoutes = async () => {
if (!confirm('This will request a full routing rebuild on the server which takes 5-10 minutes. Continue?')) return;
try {
setMsg('Requesting route sync...');
const r = await fetch(`${API}/maps/sync-routes`, { method: 'POST', headers: { 'x-api-key': apiKey } });
if (!r.ok) throw new Error('Failed');
setMsg('✅ Routing sync requested! Check back in 5-10 min.');
} catch (e: any) { setMsg(e.message || 'Error'); }
setTimeout(() => setMsg(''), 5000);
};
const cancelDrawing = () => { pointsRef.current = []; redrawDrawing(); setDrawing(false); };
if (needsAuth) {
@@ -389,7 +400,7 @@ const IntelligenceDashboard: React.FC = () => {
if (e.key === 'Enter') {
const val = e.currentTarget.value;
if (val) {
sessionStorage.setItem('map_admin_key', val);
localStorage.setItem('map_admin_key', val);
setApiKey(val);
}
}
@@ -416,9 +427,14 @@ const IntelligenceDashboard: React.FC = () => {
<div style={{ fontSize: '0.7rem', color: '#64748b' }}>Intaleq SaaS v2 · Split Compare</div>
</div>
</div>
<button onClick={runAI} style={{ background: 'rgba(99,102,241,0.15)', border: '1px solid #6366f1', color: '#818cf8', padding: '6px 10px', borderRadius: '8px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.78rem' }}>
<RefreshCw size={13} /> Run
</button>
<div style={{ display: 'flex', gap: '6px' }}>
<button onClick={syncRoutes} style={{ background: 'rgba(16,185,129,0.15)', border: '1px solid #10b981', color: '#34d399', padding: '6px 10px', borderRadius: '8px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.78rem' }} title="Sync Approved Roads to Router">
<RefreshCw size={13} /> Sync Routes
</button>
<button onClick={runAI} style={{ background: 'rgba(99,102,241,0.15)', border: '1px solid #6366f1', color: '#818cf8', padding: '6px 10px', borderRadius: '8px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.78rem' }}>
<RefreshCw size={13} /> Run
</button>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', padding: '1rem' }}>