1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
16} from '@mui/material';
17import DeleteIcon from '@mui/icons-material/Delete';
26} from '@/stores/scenarioStore';
27import { NumberField, FileInput, Section } from './InspectorControls';
29interface PlatformComponentInspectorProps {
30 component: PlatformComponent;
35export function PlatformComponentInspector({
39}: PlatformComponentInspectorProps) {
40 const { updateItem, waveforms, timings, antennas, setPlatformRcsModel } =
41 useScenarioStore.getState();
43 // Updates are targeted using the array index in the path string
44 const handleChange = (path: string, value: unknown) =>
45 updateItem(platformId, `components.${index}.${path}`, value);
47 const renderSchedule = (
48 c: MonostaticComponent | TransmitterComponent | ReceiverComponent
50 const schedule = c.schedule || [];
52 const handleAddPeriod = () => {
53 handleChange('schedule', [...schedule, { start: 0, end: 0 }]);
56 const handleRemovePeriod = (idx: number) => {
57 const newSchedule = [...schedule];
58 newSchedule.splice(idx, 1);
59 handleChange('schedule', newSchedule);
62 const handlePeriodChange = (
64 field: keyof SchedulePeriod,
67 const newSchedule = [...schedule];
68 newSchedule[idx] = { ...newSchedule[idx], [field]: val ?? 0 };
69 handleChange('schedule', newSchedule);
73 <Section title="Operating Schedule">
74 {schedule.length === 0 && (
75 <Typography variant="body2" color="text.secondary">
76 No specific schedule defined (always active).
79 {schedule.map((period, i) => (
88 borderColor: 'divider',
95 onChange={(v) => handlePeriodChange(i, 'start', v)}
100 onChange={(v) => handlePeriodChange(i, 'end', v)}
104 onClick={() => handleRemovePeriod(i)}
107 <DeleteIcon fontSize="small" />
112 onClick={handleAddPeriod}
123 const renderCommonRadarFields = (
124 c: MonostaticComponent | TransmitterComponent | ReceiverComponent
128 label="Component Name"
132 onChange={(e) => handleChange('name', e.target.value)}
135 <FormControl fullWidth size="small" sx={{ mb: 2 }}>
136 <InputLabel>Radar Mode</InputLabel>
140 onChange={(e) => handleChange('radarType', e.target.value)}
142 <MenuItem value="pulsed">Pulsed</MenuItem>
143 <MenuItem value="cw">CW</MenuItem>
147 {'waveformId' in c && (
148 <FormControl fullWidth size="small" sx={{ mb: 2 }}>
149 <InputLabel>Waveform</InputLabel>
152 value={c.waveformId ?? ''}
154 handleChange('waveformId', e.target.value)
160 {waveforms.map((w) => (
161 <MenuItem key={w.id} value={w.id}>
169 <FormControl fullWidth size="small" sx={{ mb: 2 }}>
170 <InputLabel>Antenna</InputLabel>
173 value={c.antennaId ?? ''}
174 onChange={(e) => handleChange('antennaId', e.target.value)}
179 {antennas.map((a) => (
180 <MenuItem key={a.id} value={a.id}>
186 <FormControl fullWidth size="small" sx={{ mb: 2 }}>
187 <InputLabel>Timing Source</InputLabel>
189 label="Timing Source"
190 value={c.timingId ?? ''}
191 onChange={(e) => handleChange('timingId', e.target.value)}
196 {timings.map((t) => (
197 <MenuItem key={t.id} value={t.id}>
206 const renderReceiverFields = (
207 c: MonostaticComponent | ReceiverComponent
210 {c.radarType === 'pulsed' && (
213 label="Window Skip (s)"
214 value={c.window_skip}
215 onChange={(v) => handleChange('window_skip', v)}
218 label="Window Length (s)"
219 value={c.window_length}
220 onChange={(v) => handleChange('window_length', v)}
225 label="Noise Temperature (K)"
226 value={c.noiseTemperature}
227 onChange={(v) => handleChange('noiseTemperature', v)}
232 checked={c.noDirectPaths}
234 handleChange('noDirectPaths', e.target.checked)
238 label="Ignore Direct Paths"
243 checked={c.noPropagationLoss}
245 handleChange('noPropagationLoss', e.target.checked)
249 label="Ignore Propagation Loss"
254 switch (component.type) {
257 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
258 {renderCommonRadarFields(component)}
259 {component.radarType === 'pulsed' && (
262 value={component.prf}
263 onChange={(v) => handleChange('prf', v)}
266 {renderReceiverFields(component)}
267 {renderSchedule(component)}
272 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
273 {renderCommonRadarFields(component)}
274 {component.radarType === 'pulsed' && (
277 value={component.prf}
278 onChange={(v) => handleChange('prf', v)}
281 {renderSchedule(component)}
286 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
287 {renderCommonRadarFields(component)}
288 {component.radarType === 'pulsed' && (
291 value={component.prf}
292 onChange={(v) => handleChange('prf', v)}
295 {renderReceiverFields(component)}
296 {renderSchedule(component)}
301 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
303 label="Component Name"
306 value={component.name}
307 onChange={(e) => handleChange('name', e.target.value)}
310 label="Component Name"
313 value={component.name}
314 onChange={(e) => handleChange('name', e.target.value)}
316 <FormControl fullWidth size="small">
317 <InputLabel>RCS Type</InputLabel>
320 value={component.rcs_type}
322 handleChange('rcs_type', e.target.value)
325 <MenuItem value="isotropic">Isotropic</MenuItem>
326 <MenuItem value="file">File</MenuItem>
329 {component.rcs_type === 'isotropic' && (
331 label="RCS Value (m^2)"
332 value={component.rcs_value ?? 0}
333 onChange={(v) => handleChange('rcs_value', v)}
336 {component.rcs_type === 'file' && (
339 value={component.rcs_filename}
340 onChange={(v) => handleChange('rcs_filename', v)}
341 filters={[{ name: 'RCS Data', extensions: ['*'] }]}
345 <FormControl fullWidth size="small">
346 <InputLabel>RCS Model</InputLabel>
349 value={component.rcs_model}
355 .value as TargetComponent['rcs_model']
359 <MenuItem value="constant">Constant</MenuItem>
360 <MenuItem value="chisquare">Chi-Square</MenuItem>
361 <MenuItem value="gamma">Gamma</MenuItem>
364 {(component.rcs_model === 'chisquare' ||
365 component.rcs_model === 'gamma') && (
368 value={component.rcs_k ?? 0}
369 onChange={(v) => handleChange('rcs_k', v)}
376 <Typography color="text.secondary">
377 Unknown component type.