Files
ciaovolo/flight-comparator/frontend/src/api.ts
domverse 4926e89e46 feat: re-run and delete scan from detail page
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>
2026-02-27 16:33:45 +01:00

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;