Backend:
- DELETE /api/v1/scans/{id} — 204 on success, 404 if missing,
409 if pending/running; CASCADE removes routes and flights
Frontend (api.ts):
- scanApi.delete(id)
Frontend (ScanDetails.tsx):
- Re-run button: derives window_months from stored dates, detects
country vs airports mode via comma in scan.country, creates new
scan and navigates to it; disabled while scan is active
- Delete button: inline two-step confirm (no modal), navigates to
dashboard on success; disabled while scan is active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
3.2 KiB
TypeScript
148 lines
3.2 KiB
TypeScript
import axios from 'axios';
|
|
|
|
const api = axios.create({
|
|
baseURL: '/api/v1',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
// Types
|
|
export interface Scan {
|
|
id: number;
|
|
origin: string;
|
|
country: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
created_at: string;
|
|
updated_at: string;
|
|
total_routes: number;
|
|
routes_scanned: number;
|
|
total_flights: number;
|
|
error_message?: string;
|
|
seat_class: string;
|
|
adults: number;
|
|
}
|
|
|
|
export interface Route {
|
|
id: number;
|
|
scan_id: number;
|
|
destination: string;
|
|
destination_name: string;
|
|
destination_city?: string;
|
|
flight_count: number;
|
|
airlines: string[];
|
|
min_price?: number;
|
|
max_price?: number;
|
|
avg_price?: number;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Flight {
|
|
id: number;
|
|
scan_id: number;
|
|
destination: string;
|
|
date: string;
|
|
airline?: string;
|
|
departure_time?: string;
|
|
arrival_time?: string;
|
|
price?: number;
|
|
stops: number;
|
|
}
|
|
|
|
export interface Airport {
|
|
iata: string;
|
|
name: string;
|
|
city: string;
|
|
country: string;
|
|
}
|
|
|
|
export interface LogEntry {
|
|
timestamp: string;
|
|
level: string;
|
|
message: string;
|
|
module?: string;
|
|
function?: string;
|
|
line?: number;
|
|
}
|
|
|
|
export interface PaginatedResponse<T> {
|
|
data: T[];
|
|
pagination: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
pages: number;
|
|
has_next: boolean;
|
|
has_prev: boolean;
|
|
};
|
|
}
|
|
|
|
export interface CreateScanRequest {
|
|
origin: string;
|
|
country?: string; // Optional: provide either country or destinations
|
|
destinations?: string[]; // Optional: provide either country or destinations
|
|
start_date?: string;
|
|
end_date?: string;
|
|
window_months?: number;
|
|
seat_class?: 'economy' | 'premium' | 'business' | 'first';
|
|
adults?: number;
|
|
}
|
|
|
|
export interface CreateScanResponse {
|
|
status: string;
|
|
id: number;
|
|
scan: Scan;
|
|
}
|
|
|
|
// API functions
|
|
export const scanApi = {
|
|
list: (page = 1, limit = 20, status?: string) => {
|
|
const params: any = { page, limit };
|
|
if (status) params.status = status;
|
|
return api.get<PaginatedResponse<Scan>>('/scans', { params });
|
|
},
|
|
|
|
get: (id: number) => {
|
|
return api.get<Scan>(`/scans/${id}`);
|
|
},
|
|
|
|
create: (data: CreateScanRequest) => {
|
|
return api.post<CreateScanResponse>('/scans', data);
|
|
},
|
|
|
|
getRoutes: (id: number, page = 1, limit = 20) => {
|
|
return api.get<PaginatedResponse<Route>>(`/scans/${id}/routes`, {
|
|
params: { page, limit }
|
|
});
|
|
},
|
|
|
|
getFlights: (id: number, destination?: string, page = 1, limit = 50) => {
|
|
const params: Record<string, unknown> = { page, limit };
|
|
if (destination) params.destination = destination;
|
|
return api.get<PaginatedResponse<Flight>>(`/scans/${id}/flights`, { params });
|
|
},
|
|
|
|
delete: (id: number) => api.delete(`/scans/${id}`),
|
|
};
|
|
|
|
export const airportApi = {
|
|
search: (query: string, page = 1, limit = 20) => {
|
|
return api.get<PaginatedResponse<Airport>>('/airports', {
|
|
params: { q: query, page, limit }
|
|
});
|
|
},
|
|
};
|
|
|
|
export const logApi = {
|
|
list: (page = 1, limit = 50, level?: string, search?: string) => {
|
|
const params: any = { page, limit };
|
|
if (level) params.level = level;
|
|
if (search) params.search = search;
|
|
return api.get<PaginatedResponse<LogEntry>>('/logs', { params });
|
|
},
|
|
};
|
|
|
|
export default api;
|