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>
  );
}