mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(bookings): Integrate booking list and detail pages with backend API
This commit is contained in:
48
apps/edr-freight-web/backoffice/src/hooks/useBookings.ts
Normal file
48
apps/edr-freight-web/backoffice/src/hooks/useBookings.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { api } from "@/services/api";
|
||||
import type { BookingListFilter } from "@/services/bookings.service";
|
||||
|
||||
export const useBookingList = (filter?: BookingListFilter) => {
|
||||
const input = { filter };
|
||||
return {
|
||||
queryKey: api.bookings.list.queryKey(input),
|
||||
queryFn: () => api.bookings.list.call(input),
|
||||
};
|
||||
};
|
||||
|
||||
export const useBooking = (id: string) => ({
|
||||
queryKey: api.bookings.getById.queryKey({ id }),
|
||||
queryFn: () => api.bookings.getById.call({ id }),
|
||||
enabled: Boolean(id),
|
||||
});
|
||||
|
||||
export const useUpdateBookingStatus = () => {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
id,
|
||||
action,
|
||||
reason,
|
||||
}: {
|
||||
id: string;
|
||||
action: string;
|
||||
reason?: string;
|
||||
}) => api.bookings.updateStatus.call({ id, action, reason }),
|
||||
onSuccess: (_data, { id }) => {
|
||||
qc.invalidateQueries({ queryKey: api.bookings.list.queryKey() });
|
||||
qc.invalidateQueries({
|
||||
queryKey: api.bookings.getById.queryKey({ id }),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteBooking = () => {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.bookings.remove.call({ id }),
|
||||
onSuccess: () =>
|
||||
qc.invalidateQueries({ queryKey: api.bookings.list.queryKey() }),
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
@@ -27,14 +27,9 @@ import {
|
||||
|
||||
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getBookingRequestById,
|
||||
getBookingRequests,
|
||||
saveBookingRequestsToStorage,
|
||||
updateBookingRequestStatus,
|
||||
BOOKING_STATUSES,
|
||||
type BookingRequest,
|
||||
} from "./booking-requests.mock";
|
||||
import { api } from "@/services/api";
|
||||
import { useUpdateBookingStatus } from "@/hooks/useBookings";
|
||||
import { mapBookingToRequest, BOOKING_STATUSES } from "./booking-requests.mock";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -233,9 +228,16 @@ const STATUS_CONFIG: Record<
|
||||
export default function BookingRequestDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [booking, setBooking] = useState<BookingRequest | undefined>(
|
||||
id ? getBookingRequestById(id) : undefined,
|
||||
|
||||
const { data: bookingData } = useQuery(
|
||||
api.bookings.getById.queryOptions({
|
||||
input: { id: id ?? "" },
|
||||
enabled: Boolean(id),
|
||||
}),
|
||||
);
|
||||
const updateStatus = useUpdateBookingStatus();
|
||||
|
||||
const booking = bookingData ? mapBookingToRequest(bookingData) : undefined;
|
||||
|
||||
if (!booking) {
|
||||
return (
|
||||
@@ -270,20 +272,20 @@ export default function BookingRequestDetailPage() {
|
||||
booking.status,
|
||||
);
|
||||
|
||||
const isPending = updateStatus.isPending;
|
||||
|
||||
function handleApprove() {
|
||||
if (!booking) return;
|
||||
const nextStatus =
|
||||
const action =
|
||||
booking.status === "RFQ_SUBMITTED"
|
||||
? ("QUOTATION_SENT" as const)
|
||||
: ("APPROVED" as const);
|
||||
updateBookingRequestStatus(booking.id, nextStatus);
|
||||
setBooking(getBookingRequestById(booking.id));
|
||||
? "SEND_QUOTATION"
|
||||
: "APPROVE";
|
||||
updateStatus.mutate({ id: booking.id, action });
|
||||
}
|
||||
|
||||
function handleReject() {
|
||||
if (!booking) return;
|
||||
updateBookingRequestStatus(booking.id, "CANCELLED");
|
||||
setBooking(getBookingRequestById(booking.id));
|
||||
updateStatus.mutate({ id: booking.id, action: "CANCEL", reason: "Cancelled by backoffice" });
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
@@ -18,10 +19,11 @@ import {
|
||||
|
||||
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/services/api";
|
||||
import {
|
||||
getBookingRequests,
|
||||
BOOKING_STATUSES,
|
||||
type BookingRequest,
|
||||
mapBookingToRequest,
|
||||
} from "./booking-requests.mock";
|
||||
import {
|
||||
DataTable,
|
||||
@@ -153,7 +155,13 @@ export default function BookingRequestsPage() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||
|
||||
const bookingRequests = useMemo(() => getBookingRequests(), []);
|
||||
const { data: bookingData } = useQuery(
|
||||
api.bookings.list.queryOptions({ input: { filter: { page: pagination.pageIndex + 1, pageSize: pagination.pageSize } } }),
|
||||
);
|
||||
const bookingRequests = useMemo(
|
||||
() => (bookingData?.items ?? []).map(mapBookingToRequest),
|
||||
[bookingData],
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = query.trim().toLowerCase();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
export interface BookingRequest {
|
||||
id: string;
|
||||
reference: string;
|
||||
@@ -131,6 +133,36 @@ export function updateBookingRequestStatus(id: string, newStatus: (typeof BOOKIN
|
||||
saveBookingRequestsToStorage(requests);
|
||||
}
|
||||
|
||||
export function mapBookingToRequest(booking: Freight.IBooking): BookingRequest {
|
||||
return {
|
||||
id: booking.id,
|
||||
reference: booking.reference,
|
||||
customer: booking.customerId,
|
||||
status: booking.status as BookingRequest["status"],
|
||||
scheduledDate: booking.scheduledDate,
|
||||
totalAmount: booking.totalAmount,
|
||||
paymentStatus: booking.paymentStatus,
|
||||
contractType: booking.contractType,
|
||||
serviceType:
|
||||
booking.serviceType === "RAIL_ONLY" ? "RAIL" : (booking.serviceType as string),
|
||||
tradeDirection: booking.tradeDirection as string,
|
||||
originYard: booking.originStation,
|
||||
destinationYard: booking.destinationStation,
|
||||
cargoType: booking.freightType ?? booking.freightSubtype ?? "",
|
||||
cargoTotalWeightVgm: booking.cargoTotalWeightVgm,
|
||||
isHazardous: booking.isHazardous,
|
||||
paymentCurrency: booking.paymentCurrency,
|
||||
priorityScore: booking.priorityScore,
|
||||
firstMilePickupAddress: booking.firstMilePickupAddress ?? null,
|
||||
lastMileDeliveryAddress: booking.lastMileDeliveryAddress ?? null,
|
||||
shippingLine: null,
|
||||
pnrCode: null,
|
||||
createdBy: booking.customerId,
|
||||
createdAt: booking.createdAt,
|
||||
updatedAt: booking.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function getBookingRequests(): BookingRequest[] {
|
||||
if (typeof window === "undefined" || !window.localStorage) {
|
||||
return INITIAL_REQUESTS;
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
ruleEngineService,
|
||||
RuleEngineListParams,
|
||||
} from "./ruleEngine/ruleEngine.service";
|
||||
import { bookingsService, BookingListFilter } from "./bookings.service";
|
||||
import type { Freight, PaginatedResponse } from "@edr/types";
|
||||
|
||||
export const api = {
|
||||
fileUploadSettings: {
|
||||
@@ -220,4 +222,30 @@ export const api = {
|
||||
() => ruleEngineService.getApprovalChain(),
|
||||
),
|
||||
},
|
||||
|
||||
bookings: {
|
||||
list: endpoint<
|
||||
{ filter?: BookingListFilter },
|
||||
PaginatedResponse<Freight.IBooking>
|
||||
>("bookings", "list", ({ filter }) => bookingsService.list(filter)),
|
||||
|
||||
getById: endpoint<{ id: string }, Freight.IBooking>(
|
||||
"bookings",
|
||||
"getById",
|
||||
({ id }) => bookingsService.getById(id),
|
||||
),
|
||||
|
||||
updateStatus: endpoint<
|
||||
{ id: string; action: string; reason?: string },
|
||||
Freight.IBooking
|
||||
>("bookings", "updateStatus", ({ id, action, reason }) =>
|
||||
bookingsService.updateStatus(id, { action, reason }),
|
||||
),
|
||||
|
||||
remove: endpoint<{ id: string }, void>(
|
||||
"bookings",
|
||||
"remove",
|
||||
({ id }) => bookingsService.remove(id),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { Freight, PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { api as client } from "../auth/http";
|
||||
import { unwrap } from "@/utils/endpoint";
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
|
||||
const BASE = URL_CONSTANTS.BOOKINGS.BASE;
|
||||
|
||||
export interface BookingListFilter {
|
||||
status?: string;
|
||||
customerId?: string;
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: "ASC" | "DESC";
|
||||
}
|
||||
|
||||
export const bookingsService = {
|
||||
list: async (
|
||||
filter?: BookingListFilter,
|
||||
): Promise<PaginatedResponse<Freight.IBooking>> => {
|
||||
const response = await client.get<PaginatedResponse<Freight.IBooking>>(
|
||||
BASE,
|
||||
{ params: filter },
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
getById: async (id: string): Promise<Freight.IBooking> => {
|
||||
const response = await client.get<Freight.IBooking>(
|
||||
URL_CONSTANTS.BOOKINGS.BY_ID(id),
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
updateStatus: async (
|
||||
id: string,
|
||||
payload: { action: string; reason?: string },
|
||||
): Promise<Freight.IBooking> => {
|
||||
const response = await client.patch<Freight.IBooking>(
|
||||
`${URL_CONSTANTS.BOOKINGS.BY_ID(id)}/status`,
|
||||
payload,
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
remove: async (id: string): Promise<void> => {
|
||||
await client.delete(URL_CONSTANTS.BOOKINGS.BY_ID(id));
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user