xpeditis2.0/apps/frontend/src/lib/api/bookings.ts
David e6b9b42f6c
Some checks failed
CI/CD Pipeline - Xpeditis PreProd / Backend - Build & Test (push) Failing after 5m51s
CI/CD Pipeline - Xpeditis PreProd / Backend - Docker Build & Push (push) Has been skipped
CI/CD Pipeline - Xpeditis PreProd / Frontend - Build & Test (push) Successful in 10m57s
CI/CD Pipeline - Xpeditis PreProd / Frontend - Docker Build & Push (push) Failing after 12m28s
CI/CD Pipeline - Xpeditis PreProd / Deploy to PreProd Server (push) Has been skipped
CI/CD Pipeline - Xpeditis PreProd / Run Smoke Tests (push) Has been skipped
fix
2025-11-13 00:15:45 +01:00

290 lines
7.8 KiB
TypeScript

/**
* Bookings API
*
* Endpoints for managing container bookings
*/
import { get, post, patch, upload } from './client';
import type {
CreateBookingRequest,
BookingResponse,
BookingListResponse,
SuccessResponse,
} from '@/types/api';
// TODO: These types should be moved to @/types/api.ts
export interface BookingSearchRequest {
query?: string;
status?: string;
originPort?: string;
destinationPort?: string;
startDate?: string;
endDate?: string;
organizationId?: string;
}
export interface BookingSearchResponse {
bookings: BookingResponse[];
total: number;
}
export interface UpdateBookingStatusRequest {
status: string;
notes?: string;
}
/**
* CSV Booking types
*/
export interface CsvBookingResponse {
id: string;
bookingId: string;
carrierName: string;
carrierEmail: string;
origin: string;
destination: string;
volumeCBM: number;
weightKG: number;
palletCount: number;
priceUSD: number;
priceEUR: number;
primaryCurrency: string;
transitDays: number;
containerType: string;
status: 'PENDING' | 'ACCEPTED' | 'REJECTED';
documents: Array<{
type: string;
fileName: string;
url: string;
}>;
notes?: string;
confirmationToken: string;
emailSentAt?: string;
acceptedAt?: string;
rejectedAt?: string;
createdAt: string;
updatedAt: string;
}
export interface CsvBookingListResponse {
bookings: CsvBookingResponse[]; // Changed from 'items' to match backend response
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface CsvBookingStatsResponse {
total: number;
pending: number;
accepted: number;
rejected: number;
}
/**
* Create a new booking
* POST /api/v1/bookings
*/
export async function createBooking(data: CreateBookingRequest): Promise<BookingResponse> {
return post<BookingResponse>('/api/v1/bookings', data);
}
/**
* Get booking by ID
* GET /api/v1/bookings/:id
*/
export async function getBooking(id: string): Promise<BookingResponse> {
return get<BookingResponse>(`/api/v1/bookings/${id}`);
}
/**
* Get booking by booking number
* GET /api/v1/bookings/number/:bookingNumber
*/
export async function getBookingByNumber(bookingNumber: string): Promise<BookingResponse> {
return get<BookingResponse>(`/api/v1/bookings/number/${bookingNumber}`);
}
/**
* List bookings with pagination
* GET /api/v1/bookings?page=1&limit=20&status=CONFIRMED&organizationId=xxx
*/
export async function listBookings(params?: {
page?: number;
limit?: number;
status?: string;
organizationId?: string;
}): Promise<BookingListResponse> {
const queryParams = new URLSearchParams();
if (params?.page) queryParams.append('page', params.page.toString());
if (params?.limit) queryParams.append('limit', params.limit.toString());
if (params?.status) queryParams.append('status', params.status);
if (params?.organizationId) queryParams.append('organizationId', params.organizationId);
const queryString = queryParams.toString();
return get<BookingListResponse>(`/api/v1/bookings${queryString ? `?${queryString}` : ''}`);
}
/**
* Fuzzy search bookings
* GET /api/v1/bookings/search?q=WCM-2024&limit=10
*/
export async function fuzzySearchBookings(params: {
q: string;
limit?: number;
}): Promise<BookingSearchResponse> {
const queryParams = new URLSearchParams();
queryParams.append('q', params.q);
if (params.limit) queryParams.append('limit', params.limit.toString());
return get<BookingSearchResponse>(`/api/v1/bookings/search?${queryParams.toString()}`);
}
/**
* Advanced search bookings
* POST /api/v1/bookings/search/advanced
*/
export async function advancedSearchBookings(
data: BookingSearchRequest
): Promise<BookingSearchResponse> {
return post<BookingSearchResponse>('/api/v1/bookings/search/advanced', data);
}
/**
* Export bookings (CSV/PDF)
* GET /api/v1/bookings/export?format=csv&status=CONFIRMED
* Returns blob for download
*/
export async function exportBookings(params: {
format: 'csv' | 'pdf';
status?: string;
organizationId?: string;
startDate?: string;
endDate?: string;
}): Promise<Blob> {
const queryParams = new URLSearchParams();
queryParams.append('format', params.format);
if (params.status) queryParams.append('status', params.status);
if (params.organizationId) queryParams.append('organizationId', params.organizationId);
if (params.startDate) queryParams.append('startDate', params.startDate);
if (params.endDate) queryParams.append('endDate', params.endDate);
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bookings/export?${queryParams.toString()}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${
typeof window !== 'undefined' ? localStorage.getItem('access_token') : ''
}`,
},
}
);
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
}
return response.blob();
}
/**
* Update booking status
* PATCH /api/v1/bookings/:id/status
*/
export async function updateBookingStatus(
id: string,
data: UpdateBookingStatusRequest
): Promise<SuccessResponse> {
return patch<SuccessResponse>(`/api/v1/bookings/${id}/status`, data);
}
// ============================================================================
// CSV BOOKINGS API
// ============================================================================
/**
* Create a new CSV booking with document uploads
* POST /api/v1/csv-bookings
*
* Uses multipart/form-data for file uploads
*/
export async function createCsvBooking(formData: FormData): Promise<CsvBookingResponse> {
return upload<CsvBookingResponse>('/api/v1/csv-bookings', formData);
}
/**
* Get CSV booking by ID
* GET /api/v1/csv-bookings/:id
*/
export async function getCsvBooking(id: string): Promise<CsvBookingResponse> {
return get<CsvBookingResponse>(`/api/v1/csv-bookings/${id}`);
}
/**
* List CSV bookings with pagination and filters
* GET /api/v1/csv-bookings?page=1&limit=20&status=PENDING
*/
export async function listCsvBookings(params?: {
page?: number;
limit?: number;
status?: 'PENDING' | 'ACCEPTED' | 'REJECTED';
startDate?: string;
endDate?: string;
}): Promise<CsvBookingListResponse> {
const queryParams = new URLSearchParams();
if (params?.page) queryParams.append('page', params.page.toString());
if (params?.limit) queryParams.append('limit', params.limit.toString());
if (params?.status) queryParams.append('status', params.status);
if (params?.startDate) queryParams.append('startDate', params.startDate);
if (params?.endDate) queryParams.append('endDate', params.endDate);
const queryString = queryParams.toString();
return get<CsvBookingListResponse>(
`/api/v1/csv-bookings${queryString ? `?${queryString}` : ''}`
);
}
/**
* Get CSV booking statistics for current user
* GET /api/v1/csv-bookings/stats
*/
export async function getCsvBookingStats(): Promise<CsvBookingStatsResponse> {
return get<CsvBookingStatsResponse>('/api/v1/csv-bookings/stats');
}
/**
* Cancel a pending CSV booking
* PATCH /api/v1/csv-bookings/:id/cancel
*/
export async function cancelCsvBooking(id: string): Promise<SuccessResponse> {
return patch<SuccessResponse>(`/api/v1/csv-bookings/${id}/cancel`, {});
}
/**
* Accept a CSV booking (public endpoint, no auth required)
* POST /api/v1/csv-bookings/:token/accept
*/
export async function acceptCsvBooking(token: string): Promise<CsvBookingResponse> {
return post<CsvBookingResponse>(
`/api/v1/csv-bookings/${token}/accept`,
{},
false // includeAuth = false
);
}
/**
* Reject a CSV booking with reason (public endpoint, no auth required)
* POST /api/v1/csv-bookings/:token/reject
*/
export async function rejectCsvBooking(
token: string,
reason?: string
): Promise<CsvBookingResponse> {
return post<CsvBookingResponse>(
`/api/v1/csv-bookings/${token}/reject`,
{ reason },
false // includeAuth = false
);
}