mirror of
https://github.com/azaion/ui.git
synced 2026-04-25 15:46:34 +00:00
feat(flights): integrate mission-planner into Flights page
- Port mission-planner flight planning to main app (Tailwind, react-leaflet v5, react-i18next) - Add FlightMap with click-to-add waypoints, draggable markers, polyline with arrows - Add FlightParamsPanel with action modes, waypoint list (drag-reorder), altitude chart, wind, JSON import/export - Add FlightListSidebar with create/delete and telemetry date - Add collapsible left panel with quick action mode shortcuts - Add work area / no-go zone drawing via manual mouse events (L.rectangle) - Add AltitudeDialog and JsonEditorDialog (Tailwind modals) - Add battery/time/distance calculations per waypoint segment - Add satellite/classic map toggle and mini-map on point drag - Add Camera FOV and Communication Addr fields - Add current position display under location search - Merge mission-planner translations under flights.planner.* - Gitignore .superpowers session data
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import WaypointList from './WaypointList'
|
||||
import AltitudeChart from './AltitudeChart'
|
||||
import WindEffect from './WindEffect'
|
||||
import type { FlightPoint, CalculatedPointInfo, ActionMode, WindParams } from './types'
|
||||
import type { Aircraft } from '../../types'
|
||||
|
||||
interface Props {
|
||||
points: FlightPoint[]
|
||||
calculatedPointInfo: CalculatedPointInfo[]
|
||||
aircrafts: Aircraft[]
|
||||
initialAltitude: number
|
||||
actionMode: ActionMode
|
||||
wind: WindParams
|
||||
locationInput: string
|
||||
currentPosition: { lat: number; lng: number }
|
||||
totalDistance: string
|
||||
totalTime: string
|
||||
batteryStatus: { label: string; color: string }
|
||||
onInitialAltitudeChange: (v: number) => void
|
||||
onActionModeChange: (mode: ActionMode) => void
|
||||
onWindChange: (w: WindParams) => void
|
||||
onLocationInputChange: (v: string) => void
|
||||
onLocationSearch: () => void
|
||||
onReorderPoints: (points: FlightPoint[]) => void
|
||||
onEditPoint: (point: FlightPoint) => void
|
||||
onRemovePoint: (id: string) => void
|
||||
onSave: () => void
|
||||
onUpload: () => void
|
||||
onEditJson: () => void
|
||||
onExport: () => void
|
||||
}
|
||||
|
||||
export default function FlightParamsPanel({
|
||||
points, calculatedPointInfo, aircrafts, initialAltitude, actionMode, wind,
|
||||
locationInput, currentPosition, totalDistance, totalTime, batteryStatus,
|
||||
onInitialAltitudeChange, onActionModeChange, onWindChange,
|
||||
onLocationInputChange, onLocationSearch, onReorderPoints, onEditPoint, onRemovePoint,
|
||||
onSave, onUpload, onEditJson, onExport,
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const modeBtn = (mode: ActionMode, label: string, color: 'orange' | 'green' | 'red') => {
|
||||
const active = actionMode === mode
|
||||
const colorMap = {
|
||||
orange: { border: 'border-az-orange', text: 'text-az-orange', bg: 'bg-az-orange/20', hover: 'hover:bg-az-orange/10' },
|
||||
green: { border: 'border-az-green', text: 'text-az-green', bg: 'bg-az-green/20', hover: 'hover:bg-az-green/10' },
|
||||
red: { border: 'border-az-red', text: 'text-az-red', bg: 'bg-az-red/20', hover: 'hover:bg-az-red/10' },
|
||||
}[color]
|
||||
return (
|
||||
<button
|
||||
onClick={() => onActionModeChange(mode)}
|
||||
className={`flex-1 px-2.5 py-1 rounded border text-[11px] ${colorMap.border} ${colorMap.text} ${active ? colorMap.bg : colorMap.hover}`}
|
||||
>{label}</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-2 space-y-2 text-xs overflow-y-auto flex-1">
|
||||
<div className="flex gap-1">
|
||||
{modeBtn('points', t('flights.planner.addPoints'), 'orange')}
|
||||
{modeBtn('workArea', t('flights.planner.workArea'), 'green')}
|
||||
{modeBtn('prohibitedArea', t('flights.planner.prohibitedArea'), 'red')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.location')}</label>
|
||||
<input
|
||||
value={locationInput}
|
||||
onChange={e => onLocationInputChange(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && onLocationSearch()}
|
||||
placeholder="47.242, 35.024"
|
||||
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
||||
/>
|
||||
<div className="text-az-muted text-[9px] mt-0.5">
|
||||
{t('flights.planner.currentLocation')}: {currentPosition.lat.toFixed(6)}, {currentPosition.lng.toFixed(6)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.aircraft')}</label>
|
||||
<select className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text">
|
||||
{aircrafts.map(a => <option key={a.id} value={a.id}>{a.model}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.initialAltitude')}</label>
|
||||
<input type="number" value={initialAltitude}
|
||||
onChange={e => onInitialAltitudeChange(Number(e.target.value))}
|
||||
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.cameraFov')}</label>
|
||||
<input type="text" placeholder={t('flights.planner.cameraFovPlaceholder')}
|
||||
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.commAddr')}</label>
|
||||
<input type="text" placeholder={t('flights.planner.commAddrPlaceholder')}
|
||||
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-az-muted block mb-1 text-[9px]">{t('flights.waypoints')}</label>
|
||||
<WaypointList
|
||||
points={points}
|
||||
calculatedPointInfo={calculatedPointInfo}
|
||||
onReorder={onReorderPoints}
|
||||
onEdit={onEditPoint}
|
||||
onRemove={onRemovePoint}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{points.length > 1 && (
|
||||
<div className="bg-az-header rounded px-2 py-1 flex gap-2 text-[10px]">
|
||||
<span>{totalDistance}</span>
|
||||
<span>{totalTime}</span>
|
||||
<span style={{ color: batteryStatus.color }}>{batteryStatus.label}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AltitudeChart points={points} />
|
||||
|
||||
<WindEffect wind={wind} onChange={onWindChange} />
|
||||
|
||||
<div className="flex gap-1">
|
||||
<button onClick={onSave} className="flex-1 px-2.5 py-1 rounded border border-az-green text-az-green text-[11px] hover:bg-az-green/10">
|
||||
{t('flights.planner.save')}
|
||||
</button>
|
||||
<button onClick={onUpload} className="flex-1 px-2.5 py-1 rounded border border-az-blue text-az-blue text-[11px] hover:bg-az-blue/10">
|
||||
{t('flights.planner.upload')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<button onClick={onEditJson} className="flex-1 px-2.5 py-1 rounded border border-az-muted text-az-text text-[11px] hover:border-az-text hover:text-white">
|
||||
{t('flights.planner.editAsJson')}
|
||||
</button>
|
||||
<button onClick={onExport} className="flex-1 px-2.5 py-1 rounded border border-az-muted text-az-text text-[11px] hover:border-az-text hover:text-white">
|
||||
{t('flights.planner.exportMapData')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user