Files
maps-saas/apps/web/src/main.tsx
T

192 lines
6.0 KiB
TypeScript

import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom/client'
import maplibregl from 'maplibre-gl'
import App from './App.tsx'
import CompareView from './pages/CompareView'
import IntelligenceDashboard from './pages/IntelligenceDashboard'
import { ExecutiveShowcase } from './pages/ExecutiveShowcase'
import { TacticalDefenseView } from './pages/TacticalDefenseView'
import './index.css'
maplibregl.setRTLTextPlugin(
'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js',
(err) => { if (err) console.error(err); },
true
);
// Lightweight hash router
const NAV = [
{ hash: '#tactical', label: 'المنظومة التكتيكية العسكرية', icon: '🎖️' },
{ hash: '#map', label: 'الخريطة والطقس', icon: '🗺️' },
{ hash: '#review', label: 'تدقيق الطرق', icon: '🔍' },
{ hash: '#compare', label: 'المقارنة المرجعية', icon: '⚖️' },
{ hash: '#executive', label: 'العرض الاستراتيجي', icon: '🏛️' },
]
function ViewNav({ hash }: { hash: string }) {
const currentHash = hash || '#tactical'
return (
<div style={{
position: 'fixed',
top: 12,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 9999,
display: 'flex',
alignItems: 'center',
gap: 4,
background: 'rgba(15, 23, 42, 0.92)',
backdropFilter: 'blur(20px)',
border: '1px solid rgba(255, 255, 255, 0.18)',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.55)',
borderRadius: 999,
padding: '4px 6px',
fontFamily: "'IBM Plex Sans Arabic', Inter, system-ui, sans-serif",
direction: 'rtl',
maxWidth: 'calc(100vw - 24px)',
overflowX: 'auto'
}}>
{NAV.map(n => {
const active = currentHash === n.hash
return (
<a key={n.hash} href={n.hash}
style={{
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
color: active ? '#fff' : '#94a3b8',
background: active ? 'linear-gradient(135deg, #6366f1, #4f46e5)' : 'transparent',
boxShadow: active ? '0 2px 8px rgba(99, 102, 241, 0.4)' : 'none',
padding: '6px 14px',
borderRadius: 999,
display: 'flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.15s ease'
}}>
<span>{n.icon}</span>
<span>{n.label}</span>
</a>
)
})}
</div>
)
}
class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean; error: any }> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: any) {
return { hasError: true, error };
}
componentDidCatch(error: any, errorInfo: any) {
console.error('App UI Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
background: '#0f172a',
color: '#fff',
fontFamily: 'system-ui, sans-serif',
direction: 'rtl',
padding: 24,
textAlign: 'center'
}}>
<div style={{ fontSize: '3rem', marginBottom: 16 }}>⚠️</div>
<h2 style={{ fontSize: '1.4rem', fontWeight: 700, marginBottom: 8 }}>حدث خطأ أثناء تحميل الواجهة</h2>
<p style={{ color: '#94a3b8', fontSize: '0.9rem', maxWidth: 450, marginBottom: 20 }}>
{this.state.error?.message || 'تعذر تحميل أحد مكونات الخريطة'}
</p>
<button
onClick={() => { window.location.hash = '#map'; window.location.reload(); }}
style={{
padding: '10px 24px',
background: '#6366f1',
color: '#fff',
border: 'none',
borderRadius: 8,
fontWeight: 700,
cursor: 'pointer',
fontSize: '0.95rem'
}}
>
إعادة تحميل الخريطة الأساسية
</button>
</div>
);
}
return this.props.children;
}
}
function Root() {
const [hash, setHash] = useState(window.location.hash || '#tactical');
useEffect(() => {
const on = () => {
const h = window.location.hash || '#tactical';
setHash(h);
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 50);
};
window.addEventListener('hashchange', on);
return () => window.removeEventListener('hashchange', on);
}, []);
const isTactical = hash === '#tactical' || hash === '#military';
const isExecutive = hash === '#executive' || hash === '#pitch';
const isCompare = hash === '#compare';
const isReview = hash === '#review';
const isMap = !isTactical && !isExecutive && !isCompare && !isReview;
return (
<ErrorBoundary>
<ViewNav hash={hash} />
{isTactical && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<TacticalDefenseView />
</div>
)}
{isExecutive && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<ExecutiveShowcase />
</div>
)}
{isCompare && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<CompareView />
</div>
)}
{isReview && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<IntelligenceDashboard />
</div>
)}
{isMap && (
<div style={{ width: '100vw', height: '100vh', position: 'absolute', top: 0, left: 0 }}>
<App />
</div>
)}
</ErrorBoundary>
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(<Root />);