feat(tactical): add comprehensive Terrain Study (دراسة الأرض) with natural/manmade obstacles and OAKOC intelligence report
This commit is contained in:
@@ -23,20 +23,28 @@ import {
|
||||
MapPin,
|
||||
Target,
|
||||
LocateFixed,
|
||||
Move
|
||||
Move,
|
||||
Building,
|
||||
TreePine,
|
||||
Printer,
|
||||
FileText,
|
||||
Compass,
|
||||
Check
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
calculateLineOfSight,
|
||||
calculateRadialViewshed,
|
||||
calculateMinefieldAnalysis,
|
||||
calculateTerrainStudy,
|
||||
LineOfSightResult,
|
||||
Viewshed360Result,
|
||||
MinefieldAnalysisResult,
|
||||
TerrainStudyResult,
|
||||
calculateDistance,
|
||||
calculateAzimuth
|
||||
} from '../utils/elevationService';
|
||||
|
||||
type TacticalMode = 'los' | 'viewshed' | 'artillery' | 'minefield' | 'hlz' | 'symbols' | 'study';
|
||||
type TacticalMode = 'terrain' | 'los' | 'viewshed' | 'artillery' | 'minefield' | 'hlz' | 'symbols' | 'study';
|
||||
|
||||
interface TacticalSymbolItem {
|
||||
id: string;
|
||||
@@ -67,11 +75,18 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const mapContainer = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<maplibregl.Map | null>(null);
|
||||
|
||||
const [mode, setMode] = useState<TacticalMode>('los');
|
||||
const [mode, setMode] = useState<TacticalMode>('terrain');
|
||||
const [cursorPos, setCursorPos] = useState({ lat: 31.95, lng: 35.93, elev: 850 });
|
||||
const [activePlacement, setActivePlacement] = useState<string | null>(null);
|
||||
|
||||
// Line of Sight State
|
||||
// 1. Terrain Tactical Study State (دراسة الأرض)
|
||||
const [terrainCenter, setTerrainCenter] = useState<[number, number] | null>([31.9539, 35.9106]);
|
||||
const [terrainRadius, setTerrainRadius] = useState<number>(4000);
|
||||
const [terrainResult, setTerrainResult] = useState<TerrainStudyResult | null>(null);
|
||||
const [terrainLoading, setTerrainLoading] = useState<boolean>(false);
|
||||
const [copiedReport, setCopiedReport] = useState<boolean>(false);
|
||||
|
||||
// 2. Line of Sight State
|
||||
const [losPointA, setLosPointA] = useState<[number, number] | null>([31.9539, 35.9106]);
|
||||
const [losPointB, setLosPointB] = useState<[number, number] | null>([32.0125, 35.8540]);
|
||||
const [losObsHeight, setLosObsHeight] = useState<number>(2);
|
||||
@@ -79,14 +94,14 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const [losResult, setLosResult] = useState<LineOfSightResult | null>(null);
|
||||
const [losLoading, setLosLoading] = useState<boolean>(false);
|
||||
|
||||
// Viewshed State
|
||||
// 3. Viewshed State
|
||||
const [viewshedCenter, setViewshedCenter] = useState<[number, number] | null>([31.9800, 35.8800]);
|
||||
const [viewshedRadius, setViewshedRadius] = useState<number>(5000);
|
||||
const [viewshedHeight, setViewshedHeight] = useState<number>(10);
|
||||
const [viewshedResult, setViewshedResult] = useState<Viewshed360Result | null>(null);
|
||||
const [viewshedLoading, setViewshedLoading] = useState<boolean>(false);
|
||||
|
||||
// Artillery Fire Mission State
|
||||
// 4. Artillery Fire Mission State
|
||||
const [gunPos, setGunPos] = useState<[number, number] | null>([31.9300, 35.9100]);
|
||||
const [targetPos, setTargetPos] = useState<[number, number] | null>([32.0400, 35.8200]);
|
||||
const [caliber, setCaliber] = useState<string>('155mm Howitzer (M109)');
|
||||
@@ -94,19 +109,19 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const [artilleryResult, setArtilleryResult] = useState<any | null>(null);
|
||||
const [artilleryLoading, setArtilleryLoading] = useState<boolean>(false);
|
||||
|
||||
// Minefield Analysis State
|
||||
// 5. Minefield Analysis State
|
||||
const [mineStart, setMineStart] = useState<[number, number] | null>([31.9700, 35.8600]);
|
||||
const [mineEnd, setMineEnd] = useState<[number, number] | null>([31.9950, 35.8750]);
|
||||
const [mineDensity, setMineDensity] = useState<'standard' | 'dense' | 'light'>('standard');
|
||||
const [mineResult, setMineResult] = useState<MinefieldAnalysisResult | null>(null);
|
||||
const [mineLoading, setMineLoading] = useState<boolean>(false);
|
||||
|
||||
// HLZ State
|
||||
// 6. HLZ State
|
||||
const [hlzCenter, setHlzCenter] = useState<[number, number] | null>([31.9600, 35.9000]);
|
||||
const [hlzResult, setHlzResult] = useState<any | null>(null);
|
||||
const [hlzLoading, setHlzLoading] = useState<boolean>(false);
|
||||
|
||||
// Tactical Symbols State
|
||||
// 7. Tactical Symbols State
|
||||
const [symbols, setSymbols] = useState<TacticalSymbolItem[]>(DEFAULT_SYMBOLS);
|
||||
const [selectedSymbolType, setSelectedSymbolType] = useState<string>('friendly');
|
||||
const [newSymbolName, setNewSymbolName] = useState<string>('');
|
||||
@@ -120,6 +135,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const activePlacementRef = useRef(activePlacement);
|
||||
activePlacementRef.current = activePlacement;
|
||||
|
||||
const terrainCenterRef = useRef(terrainCenter);
|
||||
terrainCenterRef.current = terrainCenter;
|
||||
|
||||
const losPointARef = useRef(losPointA);
|
||||
losPointARef.current = losPointA;
|
||||
|
||||
@@ -203,6 +221,8 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
// Sync Draggable Markers to Map
|
||||
useEffect(() => {
|
||||
if (!map.current) return;
|
||||
// Terrain Study Center
|
||||
updateMarker('terrain-c', terrainCenter, '⛰️ مركز دراسة الأرض', '#6366f1', (pos) => setTerrainCenter(pos));
|
||||
// LOS
|
||||
updateMarker('los-a', losPointA, '📍 راصد A', '#0284c7', (pos) => setLosPointA(pos));
|
||||
updateMarker('los-b', losPointB, '🎯 هدف B', '#d97706', (pos) => setLosPointB(pos));
|
||||
@@ -216,7 +236,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
updateMarker('mine-e', mineEnd, '⛔ نهاية حقل الألغام', '#991b1b', (pos) => setMineEnd(pos));
|
||||
// HLZ
|
||||
updateMarker('hlz-c', hlzCenter, '🚁 مركز HLZ', '#059669', (pos) => setHlzCenter(pos));
|
||||
}, [losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter]);
|
||||
}, [terrainCenter, losPointA, losPointB, viewshedCenter, gunPos, targetPos, mineStart, mineEnd, hlzCenter]);
|
||||
|
||||
// Map Initialization
|
||||
useEffect(() => {
|
||||
@@ -256,6 +276,11 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
const currentMode = modeRef.current;
|
||||
|
||||
// 1. Explicit Active Placement from Buttons
|
||||
if (placement === 'terrain-center') {
|
||||
setTerrainCenter([clickedLat, clickedLng]);
|
||||
setActivePlacement(null);
|
||||
return;
|
||||
}
|
||||
if (placement === 'los-a') {
|
||||
setLosPointA([clickedLat, clickedLng]);
|
||||
setActivePlacement(null);
|
||||
@@ -298,7 +323,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
}
|
||||
|
||||
// 2. Default Context-Aware Click by Active Mode
|
||||
if (currentMode === 'los') {
|
||||
if (currentMode === 'terrain') {
|
||||
setTerrainCenter([clickedLat, clickedLng]);
|
||||
} else if (currentMode === 'los') {
|
||||
if (!losPointARef.current) {
|
||||
setLosPointA([clickedLat, clickedLng]);
|
||||
} else if (!losPointBRef.current) {
|
||||
@@ -352,6 +379,33 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
});
|
||||
|
||||
initialMap.on('load', () => {
|
||||
// 0. Terrain Study Circle Layer
|
||||
initialMap.addSource('tactical-terrain-src', {
|
||||
type: 'geojson',
|
||||
data: { type: 'FeatureCollection', features: [] }
|
||||
});
|
||||
|
||||
initialMap.addLayer({
|
||||
id: 'tactical-terrain-fill',
|
||||
type: 'fill',
|
||||
source: 'tactical-terrain-src',
|
||||
paint: {
|
||||
'fill-color': '#6366f1',
|
||||
'fill-opacity': 0.15
|
||||
}
|
||||
});
|
||||
|
||||
initialMap.addLayer({
|
||||
id: 'tactical-terrain-line',
|
||||
type: 'line',
|
||||
source: 'tactical-terrain-src',
|
||||
paint: {
|
||||
'line-color': '#818cf8',
|
||||
'line-width': 2.5,
|
||||
'line-dasharray': [3, 2]
|
||||
}
|
||||
});
|
||||
|
||||
// 1. Line of Sight Layer
|
||||
initialMap.addSource('tactical-los-src', {
|
||||
type: 'geojson',
|
||||
@@ -493,6 +547,50 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Run Terrain Tactical Study
|
||||
const runTerrainStudy = () => {
|
||||
if (!terrainCenter) return;
|
||||
setTerrainLoading(true);
|
||||
calculateTerrainStudy(terrainCenter[0], terrainCenter[1], terrainRadius)
|
||||
.then((res) => {
|
||||
setTerrainResult(res);
|
||||
setTerrainLoading(false);
|
||||
|
||||
// Draw sector boundary polygon on map
|
||||
if (map.current && map.current.getSource('tactical-terrain-src')) {
|
||||
const points = 48;
|
||||
const coords: [number, number][] = [];
|
||||
const R_earth = 6371000;
|
||||
for (let i = 0; i <= points; i++) {
|
||||
const angle = (i / points) * 2 * Math.PI;
|
||||
const dLat = (terrainRadius / R_earth) * (180 / Math.PI) * Math.cos(angle);
|
||||
const dLng = (terrainRadius / (R_earth * Math.cos((terrainCenter[0] * Math.PI) / 180))) * (180 / Math.PI) * Math.sin(angle);
|
||||
coords.push([terrainCenter[1] + dLng, terrainCenter[0] + dLat]);
|
||||
}
|
||||
|
||||
(map.current.getSource('tactical-terrain-src') as maplibregl.GeoJSONSource).setData({
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [coords]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => setTerrainLoading(false));
|
||||
};
|
||||
|
||||
// Auto-run initial terrain study on mount
|
||||
useEffect(() => {
|
||||
runTerrainStudy();
|
||||
}, [terrainCenter, terrainRadius]);
|
||||
|
||||
// Update Line of Sight Calculations & Map Layer
|
||||
useEffect(() => {
|
||||
if (!losPointA || !losPointB) {
|
||||
@@ -700,10 +798,10 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
essential: true
|
||||
});
|
||||
|
||||
// Dynamically re-anchor operational points into this region so the user is not stuck with old coordinates
|
||||
const lat = sector.lat;
|
||||
const lng = sector.lng;
|
||||
|
||||
setTerrainCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]);
|
||||
setLosPointA([Number((lat - 0.015).toFixed(5)), Number((lng - 0.015).toFixed(5))]);
|
||||
setLosPointB([Number((lat + 0.025).toFixed(5)), Number((lng + 0.025).toFixed(5))]);
|
||||
setViewshedCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]);
|
||||
@@ -714,6 +812,38 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
setHlzCenter([Number(lat.toFixed(5)), Number(lng.toFixed(5))]);
|
||||
};
|
||||
|
||||
// Copy Intelligence Dossier to Clipboard
|
||||
const handleCopyReport = () => {
|
||||
if (!terrainResult) return;
|
||||
const text = `
|
||||
=== تقرير الاستخبارات الجغرافية ودراسة الأرض التكتيكية ===
|
||||
القطاع: ${terrainResult.sectorName}
|
||||
الإحداثيات: ${terrainResult.centerLat}, ${terrainResult.centerLng}
|
||||
نصف القطر: ${terrainResult.radiusMeters / 1000} كم | المساحة: ${terrainResult.areaKm2} كم²
|
||||
طبيعة الأرض: ${terrainResult.terrainClassificationAr}
|
||||
حالة الحركة والمناورة: ${terrainResult.mobilityStatusAr}
|
||||
المناسيب: أعلى قمة ${terrainResult.maxElevation}م | أخفض نقطة ${terrainResult.minElevation}م | الفارق التضاريسي ${terrainResult.reliefMeters}م
|
||||
الانحدار: متوسط ${terrainResult.avgSlopeDegrees}° | أقصى انحدار ${terrainResult.maxSlopeDegrees}°
|
||||
|
||||
[الموانع الطبيعية]:
|
||||
${terrainResult.naturalObstacles.map(o => `- ${o.name}: ${o.description}`).join('\n')}
|
||||
|
||||
[الموانع الصناعية والبنية التحتية]:
|
||||
${terrainResult.manMadeObstacles.map(m => `- ${m.name}: ${m.description} (${m.tacticalNote})`).join('\n')}
|
||||
|
||||
[المناطق المأهولة والعمران]:
|
||||
- الكثافة: ${terrainResult.urbanAndDemographics.densityAr}
|
||||
- التجمعات: ${terrainResult.urbanAndDemographics.settlements.join('، ')}
|
||||
- بيئة قتال المباني: ${terrainResult.urbanAndDemographics.moutComplexity}
|
||||
|
||||
[التوصيات التكتيكية]:
|
||||
${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n')}
|
||||
`;
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedReport(true);
|
||||
setTimeout(() => setCopiedReport(false), 2500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100vw',
|
||||
@@ -767,6 +897,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
{/* Tactical Modes Selector */}
|
||||
<div style={{ display: 'flex', gap: 4, background: 'rgba(255, 255, 255, 0.05)', padding: 3, borderRadius: 10, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||
{[
|
||||
{ id: 'terrain', label: 'دراسة الأرض', icon: <Mountain size={13} /> },
|
||||
{ id: 'los', label: 'تبادل الرؤية (LOS)', icon: <Eye size={13} /> },
|
||||
{ id: 'viewshed', label: 'كشف 360°', icon: <Radio size={13} /> },
|
||||
{ id: 'artillery', label: 'موقع المدفعية', icon: <Flame size={13} /> },
|
||||
@@ -853,7 +984,7 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
|
||||
{/* Left Side Tactical Control Deck */}
|
||||
<aside style={{
|
||||
width: 410,
|
||||
width: 440,
|
||||
background: 'rgba(15, 23, 42, 0.96)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderLeft: '1px solid rgba(255, 255, 255, 0.12)',
|
||||
@@ -909,7 +1040,273 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
<span>يمكنك <strong>سحب أي علامة مباشرة بالفأرة</strong> على الخريطة لتغيير موقعها وحساب النتائج فوراً.</span>
|
||||
</div>
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 0: TERRAIN TACTICAL STUDY (دراسة الأرض والموانع والعمران) */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'terrain' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<h3 style={{ margin: 0, fontSize: '1.05rem', fontWeight: 800, color: '#818cf8', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Mountain size={18} /> دراسة الأرض والاستخبارات الجغرافية
|
||||
</h3>
|
||||
<p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#94a3b8' }}>
|
||||
تحليل طبيعة الأرض، الموانع الطبيعية والصناعية، والمناطق المأهولة
|
||||
</p>
|
||||
</div>
|
||||
{terrainResult && (
|
||||
<button
|
||||
onClick={handleCopyReport}
|
||||
style={{
|
||||
background: copiedReport ? 'rgba(34, 197, 94, 0.2)' : 'rgba(255,255,255,0.08)',
|
||||
border: copiedReport ? '1px solid #22c55e' : '1px solid rgba(255,255,255,0.15)',
|
||||
color: copiedReport ? '#4ade80' : '#cbd5e1',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 6,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4
|
||||
}}
|
||||
>
|
||||
{copiedReport ? <Check size={12} /> : <FileText size={12} />}
|
||||
<span>{copiedReport ? 'تم نسخ التقرير' : 'نسخ التقرير'}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Terrain Center Coordinate Card */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.25)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
|
||||
<span style={{ fontSize: '0.78rem', fontWeight: 800, color: '#a5b4fc' }}>📍 مركز قطاع دراسة الأرض:</span>
|
||||
<button
|
||||
onClick={() => setActivePlacement('terrain-center')}
|
||||
style={{
|
||||
background: activePlacement === 'terrain-center' ? '#4f46e5' : 'rgba(99, 102, 241, 0.15)',
|
||||
border: '1px solid #6366f1',
|
||||
color: '#ffffff',
|
||||
padding: '3px 8px',
|
||||
borderRadius: 6,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{activePlacement === 'terrain-center' ? '🎯 انقر على الخريطة...' : '📍 التقاط من الخريطة'}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
|
||||
<div>
|
||||
<label style={{ fontSize: '0.65rem', color: '#94a3b8' }}>خط العرض (Lat):</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={terrainCenter ? terrainCenter[0] : ''}
|
||||
onChange={e => setTerrainCenter([Number(e.target.value), terrainCenter ? terrainCenter[1] : 35.9])}
|
||||
style={{ width: '100%', background: '#0f172a', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: '0.78rem' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '0.65rem', color: '#94a3b8' }}>خط الطول (Lng):</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={terrainCenter ? terrainCenter[1] : ''}
|
||||
onChange={e => setTerrainCenter([terrainCenter ? terrainCenter[0] : 31.9, Number(e.target.value)])}
|
||||
style={{ width: '100%', background: '#0f172a', border: '1px solid rgba(255,255,255,0.2)', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: '0.78rem' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Radius Slider */}
|
||||
<div>
|
||||
<label style={{ fontSize: '0.72rem', color: '#94a3b8', display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
|
||||
<span>نطاق دراسة القطاع:</span>
|
||||
<strong style={{ color: '#a5b4fc' }}>{(terrainRadius / 1000).toFixed(1)} كم (المساحة: {(Math.PI * Math.pow(terrainRadius / 1000, 2)).toFixed(1)} كم²)</strong>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min={1000}
|
||||
max={12000}
|
||||
step={500}
|
||||
value={terrainRadius}
|
||||
onChange={e => setTerrainRadius(Number(e.target.value))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={runTerrainStudy}
|
||||
disabled={terrainLoading}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #4f46e5, #6366f1)',
|
||||
border: 'none',
|
||||
color: '#fff',
|
||||
padding: '9px',
|
||||
borderRadius: 8,
|
||||
fontSize: '0.82rem',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 6
|
||||
}}
|
||||
>
|
||||
<Mountain size={15} />
|
||||
<span>{terrainLoading ? 'جاري تحليل التضاريس والموانع...' : 'تحديث دراسة الأرض والتقرير التكتيكي'}</span>
|
||||
</button>
|
||||
|
||||
{/* Terrain Intelligence Dossier Output */}
|
||||
{terrainResult && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 4 }}>
|
||||
|
||||
{/* Top Mobility & Classification Badge */}
|
||||
<div style={{
|
||||
background: terrainResult.mobilityStatus === 'GO'
|
||||
? 'rgba(34, 197, 94, 0.15)'
|
||||
: terrainResult.mobilityStatus === 'SLOW-GO'
|
||||
? 'rgba(234, 179, 8, 0.15)'
|
||||
: 'rgba(239, 68, 68, 0.15)',
|
||||
border: terrainResult.mobilityStatus === 'GO'
|
||||
? '1px solid #22c55e'
|
||||
: terrainResult.mobilityStatus === 'SLOW-GO'
|
||||
? '1px solid #eab308'
|
||||
: '1px solid #ef4444',
|
||||
padding: '10px 12px',
|
||||
borderRadius: 8,
|
||||
}}>
|
||||
<div style={{ fontSize: '0.72rem', color: '#94a3b8', marginBottom: 2 }}>{terrainResult.sectorName}</div>
|
||||
<div style={{ fontSize: '0.92rem', fontWeight: 800, color: '#f8fafc', marginBottom: 4 }}>
|
||||
{terrainResult.terrainClassificationAr}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '0.78rem',
|
||||
fontWeight: 700,
|
||||
color: terrainResult.mobilityStatus === 'GO' ? '#4ade80' : terrainResult.mobilityStatus === 'SLOW-GO' ? '#fde047' : '#f87171'
|
||||
}}>
|
||||
⚡ {terrainResult.mobilityStatusAr}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Elevation & Slope Metrics Grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6 }}>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>أعلى قمة</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#38bdf8' }}>{terrainResult.maxElevation}م</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>أخفض نقطة</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#38bdf8' }}>{terrainResult.minElevation}م</div>
|
||||
</div>
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 7, borderRadius: 6, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '0.62rem', color: '#94a3b8' }}>الفارق التضاريسي</div>
|
||||
<div style={{ fontSize: '0.9rem', fontWeight: 800, color: '#fb923c' }}>{terrainResult.reliefMeters}م</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slope Distribution Bar */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8 }}>
|
||||
<div style={{ fontSize: '0.72rem', fontWeight: 700, color: '#cbd5e1', marginBottom: 6, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>توزيع درجات الانحدار (Slope Analysis):</span>
|
||||
<span style={{ color: '#94a3b8' }}>متوسط: {terrainResult.avgSlopeDegrees}°</span>
|
||||
</div>
|
||||
<div style={{ height: 10, width: '100%', display: 'flex', borderRadius: 5, overflow: 'hidden', marginBottom: 6 }}>
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.flatPercentage}%`, background: '#22c55e' }} title={`منبسط: ${terrainResult.slopeDistribution.flatPercentage}%`} />
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.moderatePercentage}%`, background: '#eab308' }} title={`متوسط: ${terrainResult.slopeDistribution.moderatePercentage}%`} />
|
||||
<div style={{ width: `${terrainResult.slopeDistribution.steepPercentage}%`, background: '#ef4444' }} title={`شديد الوعورة: ${terrainResult.slopeDistribution.steepPercentage}%`} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.68rem', color: '#94a3b8' }}>
|
||||
<span style={{ color: '#4ade80' }}>■ منبسط (<5°): {terrainResult.slopeDistribution.flatPercentage}%</span>
|
||||
<span style={{ color: '#facc15' }}>■ متوسط (5-15°): {terrainResult.slopeDistribution.moderatePercentage}%</span>
|
||||
<span style={{ color: '#f87171' }}>■ وعر (>15°): {terrainResult.slopeDistribution.steepPercentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 1: NATURAL OBSTACLES */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(239, 68, 68, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#f87171', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<AlertOctagon size={14} /> 1. الموانع الطبيعية (Natural Obstacles):
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{terrainResult.naturalObstacles.map((obs, idx) => (
|
||||
<div key={idx} style={{ background: 'rgba(0,0,0,0.2)', padding: '6px 8px', borderRadius: 6, fontSize: '0.72rem' }}>
|
||||
<div style={{ fontWeight: 700, color: '#fca5a5' }}>• {obs.name} ({obs.type})</div>
|
||||
<div style={{ color: '#cbd5e1', marginTop: 2 }}>{obs.description}</div>
|
||||
<div style={{ color: '#94a3b8', fontSize: '0.68rem', marginTop: 2 }}><strong>الأثر التكتيكي:</strong> {obs.impact}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 2: MAN-MADE OBSTACLES & INFRASTRUCTURE */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(245, 158, 11, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#f59e0b', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Building size={14} /> 2. الموانع الصناعية والبنية التحتية (Man-Made Features):
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{terrainResult.manMadeObstacles.map((obs, idx) => (
|
||||
<div key={idx} style={{ background: 'rgba(0,0,0,0.2)', padding: '6px 8px', borderRadius: 6, fontSize: '0.72rem' }}>
|
||||
<div style={{ fontWeight: 700, color: '#fde047' }}>• {obs.name}</div>
|
||||
<div style={{ color: '#cbd5e1', marginTop: 2 }}>{obs.description}</div>
|
||||
<div style={{ color: '#94a3b8', fontSize: '0.68rem', marginTop: 2 }}><strong>ملاحظة تكتيكية:</strong> {obs.tacticalNote}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 3: URBAN AREAS & DEMOGRAPHICS */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(56, 189, 248, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#38bdf8', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Layers size={14} /> 3. المناطق المأهولة بالسكان والعمران:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: '0.72rem', color: '#cbd5e1' }}>
|
||||
<div><strong>الكثافة السكانية:</strong> {terrainResult.urbanAndDemographics.densityAr}</div>
|
||||
<div><strong>التجمعات والقرى الرئيسية:</strong> {terrainResult.urbanAndDemographics.settlements.join('، ')}</div>
|
||||
<div><strong>قتال المناطق المبنية (MOUT):</strong> {terrainResult.urbanAndDemographics.moutComplexity}</div>
|
||||
<div><strong>مخاطر الأضرار الجانبية:</strong> {terrainResult.urbanAndDemographics.collateralDamageRisk}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 4: SOIL & CONCEALMENT */}
|
||||
<div style={{ background: 'rgba(255,255,255,0.04)', padding: 10, borderRadius: 8, border: '1px solid rgba(16, 185, 129, 0.2)' }}>
|
||||
<div style={{ fontSize: '0.78rem', fontWeight: 800, color: '#10b981', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<TreePine size={14} /> 4. التربة وإمكانات الإخفاء والتمويه:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: '0.72rem', color: '#cbd5e1' }}>
|
||||
<div><strong>نوع التربة:</strong> {terrainResult.landCoverAndCover.soilType}</div>
|
||||
<div><strong>صلاحية السير للآليات:</strong> {terrainResult.landCoverAndCover.trafficability}</div>
|
||||
<div><strong>الإخفاء من الرصد الأرضي:</strong> {terrainResult.landCoverAndCover.concealmentRating}</div>
|
||||
<div><strong>الانكشاف للرصد الجوي:</strong> {terrainResult.landCoverAndCover.airObservationExposure}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 5: COMMANDER'S TACTICAL RECOMMENDATIONS (OAKOC) */}
|
||||
<div style={{ background: 'linear-gradient(135deg, rgba(79, 70, 229, 0.15), rgba(99, 102, 241, 0.15))', padding: 12, borderRadius: 8, border: '1px solid #6366f1' }}>
|
||||
<div style={{ fontSize: '0.82rem', fontWeight: 800, color: '#a5b4fc', marginBottom: 8, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Award size={16} /> 5. التقييم التكتيكي للقائد والتوصيات الميدانية:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: '0.72rem', color: '#f8fafc', lineHeight: 1.45 }}>
|
||||
{terrainResult.tacticalRecommendations.map((rec, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: 6 }}>
|
||||
<span style={{ color: '#818cf8', fontWeight: 700 }}>{i + 1}.</span>
|
||||
<span>{rec}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 1: LINE OF SIGHT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'los' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1080,7 +1477,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 2: 360 VIEWSHED */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'viewshed' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1198,7 +1597,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 3: ARTILLERY FIRE MISSION */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'artillery' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1366,7 +1767,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 4: MINEFIELD ANALYSIS */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'minefield' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1543,7 +1946,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 5: HLZ ASSESSMENT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'hlz' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1637,7 +2042,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 6: TACTICAL SYMBOLS & DEPLOYMENT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'symbols' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -1787,7 +2194,9 @@ export const TacticalDefenseView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========================================================================= */}
|
||||
{/* MODE 7: TACTICAL FEASIBILITY STUDY & ORGANIZATION REPORT */}
|
||||
{/* ========================================================================= */}
|
||||
{mode === 'study' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ paddingBottom: 8, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
|
||||
Reference in New Issue
Block a user