Refactor project structure and dependencies; rename package to azaion-ui, update version to 0.0.1, and remove unused files. Introduce new routing and authentication features in App component.

This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-25 03:10:15 +02:00
parent e407308284
commit 157a33096a
112 changed files with 6530 additions and 17843 deletions
+189
View File
@@ -0,0 +1,189 @@
import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useFlight } from '../../components/FlightContext'
import { api } from '../../api/client'
import { createSSE } from '../../api/sse'
import { useResizablePanel } from '../../hooks/useResizablePanel'
import ConfirmDialog from '../../components/ConfirmDialog'
import type { Flight, Waypoint, Aircraft } from '../../types'
import FlightMap from './FlightMap'
export default function FlightsPage() {
const { t } = useTranslation()
const { flights, selectedFlight, selectFlight, refreshFlights } = useFlight()
const [mode, setMode] = useState<'params' | 'gps'>('params')
const [waypoints, setWaypoints] = useState<Waypoint[]>([])
const [aircrafts, setAircrafts] = useState<Aircraft[]>([])
const [liveGps, setLiveGps] = useState<{ lat: number; lon: number; satellites: number; status: string } | null>(null)
const [deleteId, setDeleteId] = useState<string | null>(null)
const [newName, setNewName] = useState('')
const leftPanel = useResizablePanel(200, 150, 350)
useEffect(() => {
api.get<Aircraft[]>('/api/flights/aircrafts').then(setAircrafts).catch(() => {})
}, [])
useEffect(() => {
if (!selectedFlight) { setWaypoints([]); return }
api.get<Waypoint[]>(`/api/flights/${selectedFlight.id}/waypoints`).then(setWaypoints).catch(() => {})
}, [selectedFlight])
useEffect(() => {
if (!selectedFlight || mode !== 'gps') return
return createSSE(`/api/flights/${selectedFlight.id}/live-gps`, (data: any) => setLiveGps(data))
}, [selectedFlight, mode])
const handleCreate = async () => {
if (!newName.trim()) return
await api.post('/api/flights', { name: newName.trim() })
setNewName('')
refreshFlights()
}
const handleDelete = async () => {
if (!deleteId) return
await api.delete(`/api/flights/${deleteId}`)
if (selectedFlight?.id === deleteId) selectFlight(null)
setDeleteId(null)
refreshFlights()
}
const handleAddWaypoint = async () => {
if (!selectedFlight) return
await api.post(`/api/flights/${selectedFlight.id}/waypoints`, {
name: `Point ${waypoints.length}`,
latitude: 50.45, longitude: 30.52, order: waypoints.length,
})
const wps = await api.get<Waypoint[]>(`/api/flights/${selectedFlight.id}/waypoints`)
setWaypoints(wps)
}
const handleDeleteWaypoint = async (wpId: string) => {
if (!selectedFlight) return
await api.delete(`/api/flights/${selectedFlight.id}/waypoints/${wpId}`)
setWaypoints(prev => prev.filter(w => w.id !== wpId))
}
return (
<div className="flex h-full">
{/* Flight list sidebar */}
<div style={{ width: leftPanel.width }} className="bg-az-panel border-r border-az-border flex flex-col shrink-0">
<div className="p-2 border-b border-az-border">
<div className="flex gap-1">
<input
value={newName}
onChange={e => setNewName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleCreate()}
placeholder={t('flights.create')}
className="flex-1 bg-az-bg border border-az-border rounded px-2 py-1 text-xs text-az-text outline-none"
/>
<button onClick={handleCreate} className="bg-az-orange text-white text-xs px-2 py-1 rounded">+</button>
</div>
</div>
<div className="flex-1 overflow-y-auto">
{flights.map(f => (
<div
key={f.id}
onClick={() => selectFlight(f)}
className={`px-2 py-1.5 cursor-pointer border-b border-az-border text-sm ${
selectedFlight?.id === f.id ? 'bg-az-bg text-white' : 'text-az-text hover:bg-az-bg'
}`}
>
<div className="flex items-center justify-between">
<span className="truncate">{f.name}</span>
<button onClick={e => { e.stopPropagation(); setDeleteId(f.id) }} className="text-az-muted hover:text-az-red text-xs">×</button>
</div>
<div className="text-xs text-az-muted">{new Date(f.createdDate).toLocaleDateString()}</div>
</div>
))}
</div>
</div>
{/* Resize handle */}
<div onMouseDown={leftPanel.onMouseDown} className="w-1 cursor-col-resize bg-az-border hover:bg-az-orange shrink-0" />
{/* Left params panel */}
{selectedFlight && (
<div className="w-64 bg-az-panel border-r border-az-border flex flex-col shrink-0 overflow-y-auto">
<div className="flex border-b border-az-border">
<button
onClick={() => setMode('params')}
className={`flex-1 py-1.5 text-xs ${mode === 'params' ? 'bg-az-bg text-white' : 'text-az-muted'}`}
>
{t('flights.params')}
</button>
<button
onClick={() => setMode('gps')}
className={`flex-1 py-1.5 text-xs ${mode === 'gps' ? 'bg-az-bg text-white' : 'text-az-muted'}`}
>
{t('flights.gpsDenied')}
</button>
</div>
{mode === 'params' && (
<div className="p-2 space-y-2 text-xs">
<div>
<label className="text-az-muted block mb-0.5">{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">{t('flights.height')}</label>
<input type="number" className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text" defaultValue={100} />
</div>
<div>
<div className="flex justify-between items-center mb-1">
<label className="text-az-muted">{t('flights.waypoints')}</label>
<button onClick={handleAddWaypoint} className="text-az-orange text-xs">+ Add</button>
</div>
<div className="space-y-0.5">
{waypoints.map(wp => (
<div key={wp.id} className="flex items-center justify-between bg-az-bg rounded px-1.5 py-0.5">
<span className="text-az-text">{wp.name}</span>
<button onClick={() => handleDeleteWaypoint(wp.id)} className="text-az-muted hover:text-az-red">×</button>
</div>
))}
</div>
</div>
</div>
)}
{mode === 'gps' && (
<div className="p-2 space-y-2 text-xs">
<div>
<label className="text-az-muted block mb-1">{t('flights.liveGps')}</label>
{liveGps ? (
<div className="bg-az-bg rounded p-1.5 space-y-0.5">
<div className="text-az-text">Status: <span className="text-az-green">{liveGps.status}</span></div>
<div className="text-az-text">Lat: {liveGps.lat.toFixed(6)}</div>
<div className="text-az-text">Lon: {liveGps.lon.toFixed(6)}</div>
<div className="text-az-text">Sats: {liveGps.satellites}</div>
</div>
) : (
<div className="text-az-muted">Waiting for GPS signal...</div>
)}
</div>
<button onClick={() => setMode('params')} className="text-az-orange text-xs">
{t('flights.back')}
</button>
</div>
)}
</div>
)}
{/* Map view */}
<div className="flex-1 relative">
<FlightMap waypoints={waypoints} />
</div>
<ConfirmDialog
open={!!deleteId}
title={t('common.delete')}
message="Delete this flight and all its data?"
onConfirm={handleDelete}
onCancel={() => setDeleteId(null)}
/>
</div>
)
}