feat: add smart flight tracking with AviationStack API + visual progress

- Add 20+ flight fields (terminal, gate, delays, estimated times, etc.)
- Smart polling cron with budget-aware priority queue (100 req/month)
- Tracking phases: FAR_OUT → PRE_DEPARTURE → ACTIVE → LANDED
- Visual FlightProgressBar with animated airplane between airports
- FlightCard with status dots, delay badges, expandable details
- FlightList rewrite: card-based, grouped by status, search/filter
- Dashboard: enriched flight status widget with compact progress bars
- CommandCenter: flight alerts + enriched arrivals with gate/terminal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 19:42:52 +01:00
parent 74a292ea93
commit 0f0f1cbf38
13 changed files with 1688 additions and 325 deletions

View File

@@ -0,0 +1,65 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api';
import { Flight, FlightBudget } from '@/types';
import toast from 'react-hot-toast';
export function useFlights() {
return useQuery<Flight[]>({
queryKey: ['flights'],
queryFn: async () => {
const { data } = await api.get('/flights');
return data;
},
refetchInterval: 60000, // Refresh from DB every 60s (free, no API cost)
});
}
export function useFlightBudget() {
return useQuery<FlightBudget>({
queryKey: ['flights', 'budget'],
queryFn: async () => {
const { data } = await api.get('/flights/tracking/budget');
return data;
},
refetchInterval: 300000, // Every 5 min
});
}
export function useRefreshFlight() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (flightId: string) => {
const { data } = await api.post(`/flights/${flightId}/refresh`);
return data;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['flights'] });
queryClient.invalidateQueries({ queryKey: ['flights', 'budget'] });
const status = data.status || 'unknown';
toast.success(`Flight updated: ${data.flightNumber} (${status})`);
},
onError: (error: any) => {
toast.error(error.response?.data?.message || 'Failed to refresh flight');
},
});
}
export function useRefreshActiveFlights() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
const { data } = await api.post('/flights/refresh-active');
return data;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['flights'] });
queryClient.invalidateQueries({ queryKey: ['flights', 'budget'] });
toast.success(`Refreshed ${data.refreshed} flights (${data.budgetRemaining} API calls remaining)`);
},
onError: (error: any) => {
toast.error(error.response?.data?.message || 'Failed to refresh flights');
},
});
}