refactor(bookings): Streamline new booking process, integrate useAuth for customer data, and simplify API payloads

This commit is contained in:
ghost2023
2026-05-28 16:27:22 +03:00
parent 3e77f27a96
commit 240fb505ec
6 changed files with 24 additions and 29 deletions

View File

@@ -13,7 +13,6 @@ import {
Truck, Truck,
} from "lucide-react"; } from "lucide-react";
import Breadcrumbs from "@/components/Breadcrumbs";
import DeleteBookingDialog from "./DeleteBookingDialog"; import DeleteBookingDialog from "./DeleteBookingDialog";
import { getMyBookings } from "@/lib/currentCustomer"; import { getMyBookings } from "@/lib/currentCustomer";
import { deleteBooking, type Booking, type BookingStatus } from "./bookings.mock"; import { deleteBooking, type Booking, type BookingStatus } from "./bookings.mock";
@@ -183,8 +182,6 @@ export default function MyBookings() {
return ( return (
<div className="min-h-screen p-6"> <div className="min-h-screen p-6">
<div className="space-y-6"> <div className="space-y-6">
<Breadcrumbs items={[{ label: "My Bookings" }]} />
{/* Header Section Card */} {/* Header Section Card */}
<Card className="p-6 flex-row justify-between"> <Card className="p-6 flex-row justify-between">
<div> <div>

View File

@@ -1,12 +1,11 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react"; import { CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@edr/ui-common"; import { Button } from "@edr/ui-common";
import Breadcrumbs from "@/components/Breadcrumbs"; import Breadcrumbs from "@/components/Breadcrumbs";
import { getCurrentCustomer } from "@/lib/currentCustomer";
import { api } from "@/services/api"; import { api } from "@/services/api";
import type { CreateBookingPayload } from "@/services/bookings.service"; import type { CreateBookingPayload } from "@/services/bookings.service";
import { import {
@@ -30,6 +29,7 @@ import {
Step7Documents, Step7Documents,
Step8Review, Step8Review,
} from "./new-booking-form/steps"; } from "./new-booking-form/steps";
import useAuth from "@/hooks/useAuth";
export default function NewBookingPage() { export default function NewBookingPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -37,6 +37,7 @@ export default function NewBookingPage() {
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [renewalValidating, setRenewalValidating] = useState(false); const [renewalValidating, setRenewalValidating] = useState(false);
const [renewalValid, setRenewalValid] = useState<boolean | null>(null); const [renewalValid, setRenewalValid] = useState<boolean | null>(null);
const { customer } = useAuth();
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: (payload: CreateBookingPayload) => mutationFn: (payload: CreateBookingPayload) =>
api.bookings.create.call(payload), api.bookings.create.call(payload),
@@ -129,7 +130,6 @@ export default function NewBookingPage() {
return; return;
} }
const me = getCurrentCustomer();
const reference = data.previousContractRef; const reference = data.previousContractRef;
const totalWeight = const totalWeight =
@@ -142,7 +142,7 @@ export default function NewBookingPage() {
const apiPayload = { const apiPayload = {
reference, reference,
customerId: String(me.id), customerId: customer!.id,
scheduledDate: new Date().toISOString().slice(0, 10), scheduledDate: new Date().toISOString().slice(0, 10),
totalAmount: 0, totalAmount: 0,
contractType: contractType:
@@ -194,7 +194,7 @@ export default function NewBookingPage() {
createMutation.mutate(apiPayload); createMutation.mutate(apiPayload);
}); });
if (createMutation.isSuccess && createMutation.data) { if (createMutation.isSuccess) {
return ( return (
<div className="flex min-h-screen items-center justify-center p-6"> <div className="flex min-h-screen items-center justify-center p-6">
<div className="w-full max-w-sm rounded-2xl border border-border bg-card p-10 text-center shadow-sm"> <div className="w-full max-w-sm rounded-2xl border border-border bg-card p-10 text-center shadow-sm">
@@ -207,7 +207,7 @@ export default function NewBookingPage() {
notified once approved. notified once approved.
</p> </p>
<p className="mt-4 font-mono text-sm font-semibold text-primary"> <p className="mt-4 font-mono text-sm font-semibold text-primary">
{createMutation.data.reference} {createMutation.data?.reference}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -161,7 +161,7 @@ export const bookingFormSchema = z
destinationYard: z.string(), destinationYard: z.string(),
cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoType: z.enum(["container", "bulk"], "Select a cargo type."),
cargoWeight: z.string(), cargoWeight: z.string(),
freightType: z.enum(["bulk", "break_bulk"]), freightType: z.enum(["bulk", "break_bulk"]).optional(),
bulkCommodity: z.string(), bulkCommodity: z.string(),
bulkCommodityOther: z.string(), bulkCommodityOther: z.string(),
breakBulkType: z.string(), breakBulkType: z.string(),

View File

@@ -2,7 +2,13 @@ import { Controller, type UseFormReturn } from "react-hook-form";
import { Flame, MapPin, Snowflake } from "lucide-react"; import { Flame, MapPin, Snowflake } from "lucide-react";
import { Field, SelectItem, Separator, Switch } from "@edr/ui-common"; import { Field, SelectItem, Separator, Switch } from "@edr/ui-common";
import { type BookingFormValues, getRouteDirection, STATIONS } from "./schema"; import { type BookingFormValues, getRouteDirection, STATIONS } from "./schema";
import { SelectField, SelectOptions, StepHeader, StepLabel } from "./shared"; import {
AlertBox,
SelectField,
SelectOptions,
StepHeader,
StepLabel,
} from "./shared";
import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings"; import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings";
import { DropdownOption } from "@/types/dropdownSettings"; import { DropdownOption } from "@/types/dropdownSettings";
@@ -145,12 +151,10 @@ export function Step4Route({ form }: { form: BookingForm }) {
); );
} }
function getStationOptions(options?: DropdownOption[]): DropdownOption[] { function getStationOptions(options?: DropdownOption[]): DropdownOption[] {
return [...(options ?? [])].sort((a, b) => a.order - b.order); return [...(options ?? [])].sort((a, b) => a.order - b.order);
} }
function StationSelectOptions({ function StationSelectOptions({
options, options,
excludeValue, excludeValue,
@@ -193,4 +197,4 @@ function StationSelectOptions({
))} ))}
</> </>
); );
} }

View File

@@ -78,7 +78,9 @@ export function Step5CargoDetails({
selected={cargoType === "container"} selected={cargoType === "container"}
onClick={() => { onClick={() => {
field.onChange("container"); field.onChange("container");
form.setValue("freightType", "", { shouldDirty: true }); form.setValue("freightType", undefined, {
shouldDirty: true,
});
form.setValue("cargoWeight", "", { shouldDirty: true }); form.setValue("cargoWeight", "", { shouldDirty: true });
}} }}
> >

View File

@@ -1,31 +1,23 @@
import type { Freight, PaginatedResponse } from "@edr/types"; import type { Freight, PaginatedResponse } from "@edr/types";
import { client as api } from "@/utils/api";
import { client } from "../utils/api";
export type CreateBookingPayload = Freight.CreateBookingDto; export type CreateBookingPayload = Freight.CreateBookingDto;
export const bookingsService = { export const bookingsService = {
list: async (): Promise<PaginatedResponse<Freight.IBooking>> => { list: async (): Promise<PaginatedResponse<Freight.IBooking>> => {
const { data } = await api.get("/bookings"); const { data } = await client.get("/bookings");
return data.data; return data.data;
}, },
get: async (id: string): Promise<Freight.IBooking> => { get: async (id: string): Promise<Freight.IBooking> => {
const { data } = await api.get(`/bookings/${id}`); const { data } = await client.get(`/bookings/${id}`);
return data.data; return data.data;
}, },
create: async (payload: CreateBookingPayload): Promise<Freight.IBooking> => { create: async (payload: CreateBookingPayload): Promise<Freight.IBooking> => {
const fd = new FormData(); const { data } = await client.post("/api/bookings", payload);
for (const [key, value] of Object.entries(payload)) {
if (value === undefined || value === null) continue;
if (Array.isArray(value) || typeof value === "object") {
fd.append(key, JSON.stringify(value));
} else {
fd.append(key, String(value));
}
}
const { data } = await api.post("/api/bookings", fd);
return data.data; return data.data;
}, },
remove: async (id: string): Promise<void> => { remove: async (id: string): Promise<void> => {
await api.delete(`/bookings/${id}`); await client.delete(`/bookings/${id}`);
}, },
}; };