Spaces:
Sleeping
Sleeping
File size: 6,848 Bytes
6ef6359 87dbbc6 6ef6359 d936242 6ef6359 d936242 f62fc81 d936242 6ef6359 d936242 6ef6359 d936242 97ef31f d936242 97ef31f d936242 97ef31f d936242 97ef31f d936242 97ef31f d936242 6ef6359 d936242 97ef31f d936242 6ef6359 d936242 6ef6359 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | import React from 'react';
import { MAP_CENTER } from '../utils/geo';
export default function FlightRoster({ flights, onSelectFlight, gameState, sendWSMessage, hoveredWaypoint }) {
return (
<div className="sidebar-panel" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ flex: 1, overflowY: 'auto' }}>
{gameState && (
<div style={{ marginBottom: '24px', paddingBottom: '16px', borderBottom: '1px solid #eee' }}>
<h3 style={{ fontSize: '0.75rem', color: '#555', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Environment</h3>
<div style={{ fontSize: '0.85rem', marginTop: '12px', display: 'flex', flexDirection: 'column', gap: '8px', color: '#1a1a1a' }}>
{gameState.current_task && <div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Task:</span> <span style={{ fontWeight: '600' }}>{gameState.current_task}</span></div>}
{typeof gameState.step === 'number' && <div style={{ display: 'flex', justifyContent: 'space-between' }}><span>RL Step:</span> <span style={{ fontWeight: '600' }}>{gameState.step}</span></div>}
{typeof gameState.reward === 'number' && <div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Reward:</span> <span style={{ fontWeight: '600' }}>{gameState.reward.toFixed(4)}</span></div>}
{typeof gameState.cumulative_reward === 'number' && <div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Cumulative:</span> <span style={{ fontWeight: '600' }}>{gameState.cumulative_reward.toFixed(4)}</span></div>}
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Sim Time:</span> <span style={{ fontWeight: '600' }}>{gameState.simulation_time?.toFixed(1)}s</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Wind:</span> <span style={{ fontWeight: '600' }}>{gameState.wind_heading}° @ {gameState.wind_speed}kts</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Active RWY:</span> <span style={{ color: '#28a745', fontWeight: 'bold' }}>{(gameState.active_runways && gameState.active_runways.length > 0) ? gameState.active_runways.join(', ') : 'NONE'}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span>Time Scale:</span> <span style={{ color: '#007bff', fontWeight: 'bold' }}>{gameState.time_scale}x</span></div>
</div>
</div>
)}
<h3 style={{ fontSize: '0.75rem', color: '#555', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Active Flights ({flights.length})</h3>
<div style={{ marginTop: '16px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
{flights.map((flight) => (
<div
key={flight.callsign}
className="flight-item"
style={{
position: 'relative',
padding: '12px',
background: '#fcfcfc',
border: '1px solid #dee2e6',
borderRadius: '8px',
cursor: 'pointer',
transition: 'all 0.2s',
boxShadow: '0 2px 4px rgba(0,0,0,0.02)'
}}
onClick={() => onSelectFlight(flight)}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<strong style={{ color: '#1a1a1a', fontSize: '0.95rem' }}>{flight.callsign} <span style={{ fontWeight: 'normal', color: '#6c757d', fontSize: '0.75rem' }}>{flight.type}</span></strong>
<button
title="Remove Aircraft"
style={{
background: 'none',
border: 'none',
color: '#dc3545',
cursor: 'pointer',
fontSize: '1.2rem',
padding: '0 4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: 0.7
}}
onMouseOver={(e) => e.target.style.opacity = 1}
onMouseOut={(e) => e.target.style.opacity = 0.7}
onClick={(e) => {
e.stopPropagation();
if (sendWSMessage) {
sendWSMessage('delete_aircraft', { callsign: flight.callsign });
}
}}
>×</button>
</div>
<div style={{ fontSize: '0.8rem', color: '#495057', marginTop: '6px' }}>
Alt: {flight.altitude}ft | Spd: {flight.speed}kts
</div>
<div style={{ fontSize: '0.75rem', color: flight.state === 'APPROACH' ? '#28a745' : '#6c757d', marginTop: '4px', fontWeight: '500' }}>
{flight.state}
</div>
</div>
))}
</div>
</div>
{/* Waypoint Inspector (Bottom Sticky) */}
<div style={{
marginTop: 'auto',
paddingTop: '16px',
borderTop: '2px solid #007bff',
background: hoveredWaypoint ? '#f0f7ff' : '#fafafa',
padding: '16px',
borderRadius: '8px',
transition: 'all 0.3s ease'
}}>
<h3 style={{ fontSize: '0.75rem', color: '#0056b3', textTransform: 'uppercase', letterSpacing: '0.1em', margin: 0 }}>Waypoint Inspector</h3>
{hoveredWaypoint ? (
<div style={{ marginTop: '12px', fontSize: '0.85rem', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
<div style={{ gridColumn: 'span 2', fontWeight: 'bold', fontSize: '1rem', color: '#333' }}>{hoveredWaypoint.name}</div>
<div style={{ color: '#666' }}>Target Alt:</div> <div style={{ fontWeight: '600' }}>{hoveredWaypoint.target_alt} ft</div>
<div style={{ color: '#666' }}>Target Spd:</div> <div style={{ fontWeight: '600' }}>{hoveredWaypoint.target_speed} kts</div>
<div style={{ color: '#666' }}>Position X:</div> <div style={{ fontWeight: '600' }}>{hoveredWaypoint.x.toFixed(2)} km</div>
<div style={{ color: '#666' }}>Position Y:</div> <div style={{ fontWeight: '600' }}>{hoveredWaypoint.y.toFixed(2)} km</div>
{hoveredWaypoint.is_iaf && (
<div style={{ gridColumn: 'span 2', marginTop: '4px', color: '#4B0082', fontWeight: 'bold', fontSize: '0.7rem' }}>
✦ FINAL APPROACH FIX (IAF)
</div>
)}
</div>
) : (
<div style={{ marginTop: '12px', fontSize: '0.8rem', color: '#999', fontStyle: 'italic' }}>
Hover over a waypoint on the map for details...
</div>
)}
</div>
</div>
);
}
|