316 lines
9.8 KiB
TypeScript
316 lines
9.8 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 with Sovereign Executive Styling
|
|
const NAV = [
|
|
{ hash: '#tactical', label: 'المنظومة التكتيكية العسكرية', icon: '🎖️' },
|
|
{ hash: '#map', label: 'الخريطة والطقس والملاحة', icon: '🗺️' },
|
|
{ hash: '#compare', label: 'مقارنة العمالقة والأسعار', icon: '⚖️' },
|
|
{ hash: '#review', label: 'تدقيق الطرق الذكي', icon: '🔍' },
|
|
]
|
|
|
|
function MasterHeader({ hash }: { hash: string }) {
|
|
const currentHash = hash || '#tactical'
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
|
|
const toggleFullscreen = () => {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
setIsFullscreen(true);
|
|
} else {
|
|
document.exitFullscreen().catch(() => {});
|
|
setIsFullscreen(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<header style={{
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: 52,
|
|
zIndex: 9999,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
padding: '0 16px',
|
|
background: 'rgba(9, 14, 26, 0.94)',
|
|
backdropFilter: 'blur(24px)',
|
|
WebkitBackdropFilter: 'blur(24px)',
|
|
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
|
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
|
|
fontFamily: "'IBM Plex Sans Arabic', Inter, system-ui, sans-serif",
|
|
direction: 'rtl',
|
|
userSelect: 'none'
|
|
}}>
|
|
{/* Right Section: Brand & Sovereign Security Clearance */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<a href="#tactical" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
<div style={{
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 9,
|
|
background: 'linear-gradient(135deg, #0071e3, #6366f1)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
boxShadow: '0 0 14px rgba(0, 113, 227, 0.5)'
|
|
}}>
|
|
<span style={{ fontSize: 16 }}>⚡</span>
|
|
</div>
|
|
<div>
|
|
<div style={{
|
|
color: '#ffffff',
|
|
fontSize: '0.85rem',
|
|
fontWeight: 800,
|
|
letterSpacing: '-0.02em',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 6
|
|
}}>
|
|
<span>منظومة انطلاق المكانية</span>
|
|
<span style={{
|
|
fontSize: '9.5px',
|
|
background: 'rgba(0, 113, 227, 0.25)',
|
|
border: '1px solid rgba(0, 113, 227, 0.5)',
|
|
color: '#38bdf8',
|
|
padding: '1px 5px',
|
|
borderRadius: 4,
|
|
fontWeight: 700
|
|
}}>
|
|
Sovereign Cloud v3.2
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
{/* Center Section: Master Navigation Segmented Controller */}
|
|
<nav style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 3,
|
|
background: 'rgba(255, 255, 255, 0.05)',
|
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
padding: '3px 4px',
|
|
borderRadius: 12,
|
|
boxShadow: 'inset 0 1px 4px rgba(0, 0, 0, 0.3)'
|
|
}}>
|
|
{NAV.map(n => {
|
|
const active = currentHash === n.hash
|
|
return (
|
|
<a key={n.hash} href={n.hash}
|
|
style={{
|
|
textDecoration: 'none',
|
|
fontSize: 12,
|
|
fontWeight: active ? 800 : 600,
|
|
color: active ? '#ffffff' : '#94a3b8',
|
|
background: active
|
|
? 'linear-gradient(135deg, #0071e3, #4f46e5)'
|
|
: 'transparent',
|
|
boxShadow: active ? '0 2px 10px rgba(0, 113, 227, 0.45)' : 'none',
|
|
padding: '6px 12px',
|
|
borderRadius: 9,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
transition: 'all 0.15s ease'
|
|
}}>
|
|
<span style={{ fontSize: 13 }}>{n.icon}</span>
|
|
<span>{n.label}</span>
|
|
</a>
|
|
)
|
|
})}
|
|
</nav>
|
|
|
|
{/* Left Section: Live Sovereign Telemetry & Fullscreen */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<div style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
background: 'rgba(34, 197, 94, 0.12)',
|
|
border: '1px solid rgba(34, 197, 94, 0.3)',
|
|
color: '#4ade80',
|
|
padding: '4px 10px',
|
|
borderRadius: 8,
|
|
fontSize: '0.72rem',
|
|
fontWeight: 700
|
|
}}>
|
|
<span style={{
|
|
display: 'inline-block',
|
|
width: 7,
|
|
height: 7,
|
|
borderRadius: '50%',
|
|
background: '#22c55e',
|
|
boxShadow: '0 0 8px #22c55e'
|
|
}} />
|
|
<span>السحابة السيادية متصلة</span>
|
|
</div>
|
|
|
|
<button
|
|
onClick={toggleFullscreen}
|
|
title="ملء الشاشة"
|
|
style={{
|
|
background: 'rgba(255, 255, 255, 0.06)',
|
|
border: '1px solid rgba(255, 255, 255, 0.12)',
|
|
color: '#cbd5e1',
|
|
padding: '5px 9px',
|
|
borderRadius: 8,
|
|
fontSize: '0.75rem',
|
|
cursor: 'pointer',
|
|
fontWeight: 600,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 4
|
|
}}
|
|
>
|
|
<span>{isFullscreen ? '🗗' : '🗖'}</span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
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: '#090e1a',
|
|
color: '#ffffff',
|
|
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: '#0071e3',
|
|
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' || hash === '' || hash === '#siro';
|
|
const isExecutive = hash === '#showcase' || hash === '#pitch';
|
|
const isCompare = hash === '#compare';
|
|
const isReview = hash === '#review';
|
|
const isMap = hash === '#map';
|
|
|
|
return (
|
|
<ErrorBoundary>
|
|
<MasterHeader hash={hash} />
|
|
|
|
<main style={{
|
|
position: 'absolute',
|
|
top: 52,
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
width: '100vw',
|
|
height: 'calc(100vh - 52px)',
|
|
overflow: 'hidden'
|
|
}}>
|
|
{isTactical && (
|
|
<div style={{ width: '100%', height: '100%' }}>
|
|
<TacticalDefenseView />
|
|
</div>
|
|
)}
|
|
|
|
{isExecutive && (
|
|
<div style={{ width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden' }}>
|
|
<ExecutiveShowcase />
|
|
</div>
|
|
)}
|
|
|
|
{isCompare && (
|
|
<div style={{ width: '100%', height: '100%' }}>
|
|
<CompareView />
|
|
</div>
|
|
)}
|
|
|
|
{isReview && (
|
|
<div style={{ width: '100%', height: '100%' }}>
|
|
<IntelligenceDashboard />
|
|
</div>
|
|
)}
|
|
|
|
{isMap && (
|
|
<div style={{ width: '100%', height: '100%' }}>
|
|
<App />
|
|
</div>
|
|
)}
|
|
</main>
|
|
</ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')!).render(<Root />);
|