diff --git a/apps/edr-freight-web/portal/src/components/Breadcrumbs.tsx b/apps/edr-freight-web/portal/src/components/Breadcrumbs.tsx index 2d756b096..16c9b0f7d 100644 --- a/apps/edr-freight-web/portal/src/components/Breadcrumbs.tsx +++ b/apps/edr-freight-web/portal/src/components/Breadcrumbs.tsx @@ -1,6 +1,6 @@ import { Fragment } from "react"; import { Link } from "react-router-dom"; -import { ChevronRight, Home } from "lucide-react"; +import { ChevronRight } from "lucide-react"; export interface BreadcrumbItem { label: string; @@ -34,10 +34,7 @@ export default function Breadcrumbs({ items }: BreadcrumbsProps) { {item.href && !isLast ? ( - + {item.label} ) : ( diff --git a/apps/edr-freight-web/portal/src/components/bookings/BookingForm.tsx b/apps/edr-freight-web/portal/src/components/bookings/BookingForm.tsx deleted file mode 100644 index 44736a486..000000000 --- a/apps/edr-freight-web/portal/src/components/bookings/BookingForm.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { FormEvent, useState } from "react"; -import { Button, FormField } from "@edr/ui-common"; - -import type { CreateBookingPayload } from "../../services/bookings.service"; - -export interface BookingFormProps { - onSubmit: (payload: CreateBookingPayload) => void; - isSubmitting?: boolean; -} - -const BookingForm = ({ onSubmit, isSubmitting }: BookingFormProps) => { - const [reference, setReference] = useState(""); - const [customerId, setCustomerId] = useState(""); - const [scheduledDate, setScheduledDate] = useState(""); - const [totalAmount, setTotalAmount] = useState("0"); - - const handleSubmit = (event: FormEvent) => { - event.preventDefault(); - onSubmit({ - reference, - customerId, - scheduledDate, - totalAmount: Number(totalAmount), - }); - }; - - return ( - - setReference(e.target.value)} - required - /> - setCustomerId(e.target.value)} - required - /> - setScheduledDate(e.target.value)} - required - /> - setTotalAmount(e.target.value)} - min="0" - /> - - Create booking - - - ); -}; - -export default BookingForm; diff --git a/apps/edr-freight-web/portal/src/components/consignments/ConsignmentForm.tsx b/apps/edr-freight-web/portal/src/components/consignments/ConsignmentForm.tsx deleted file mode 100644 index 1738cb1d0..000000000 --- a/apps/edr-freight-web/portal/src/components/consignments/ConsignmentForm.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { FormEvent, useState } from "react"; -import { Button, FormField } from "@edr/ui-common"; - -export interface ConsignmentFormProps { - onSubmit: (payload: { - bookingId: string; - trackingNumber: string; - cargoType: string; - weightKg: number; - originStation: string; - destinationStation: string; - }) => void; - isSubmitting?: boolean; -} - -const ConsignmentForm = ({ onSubmit, isSubmitting }: ConsignmentFormProps) => { - const [bookingId, setBookingId] = useState(""); - const [trackingNumber, setTrackingNumber] = useState(""); - const [cargoType, setCargoType] = useState("GENERAL"); - const [weightKg, setWeightKg] = useState("0"); - const [originStation, setOriginStation] = useState(""); - const [destinationStation, setDestinationStation] = useState(""); - - const handleSubmit = (event: FormEvent) => { - event.preventDefault(); - onSubmit({ - bookingId, - trackingNumber, - cargoType, - weightKg: Number(weightKg), - originStation, - destinationStation, - }); - }; - - return ( - - setBookingId(e.target.value)} - required - /> - setTrackingNumber(e.target.value)} - required - /> - setCargoType(e.target.value)} - /> - setWeightKg(e.target.value)} - min="0" - /> - setOriginStation(e.target.value)} - required - /> - setDestinationStation(e.target.value)} - required - /> - - Create consignment - - - ); -}; - -export default ConsignmentForm; diff --git a/apps/edr-freight-web/portal/src/components/consignments/ConsignmentTable.tsx b/apps/edr-freight-web/portal/src/components/consignments/ConsignmentTable.tsx deleted file mode 100644 index cc04524bf..000000000 --- a/apps/edr-freight-web/portal/src/components/consignments/ConsignmentTable.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { Freight } from "@edr/types"; -import { Table, type TableColumn } from "@edr/ui-common"; - -export interface ConsignmentTableProps { - consignments: Freight.IConsignment[]; -} - -const columns: TableColumn[] = [ - { key: "trackingNumber", header: "Tracking #" }, - { key: "cargoType", header: "Cargo" }, - { key: "status", header: "Status" }, - { key: "originStation", header: "Origin" }, - { key: "destinationStation", header: "Destination" }, - { - key: "weightKg", - header: "Weight (kg)", - render: (row) => row.weightKg.toFixed(2), - }, -]; - -const ConsignmentTable = ({ consignments }: ConsignmentTableProps) => ( - row.id} - emptyMessage="No consignments yet" - /> -); - -export default ConsignmentTable; diff --git a/apps/edr-freight-web/portal/src/components/dynamic-select/DynamicSelect.tsx b/apps/edr-freight-web/portal/src/components/dynamic-select/DynamicSelect.tsx index 2003457f0..fa10a795e 100644 --- a/apps/edr-freight-web/portal/src/components/dynamic-select/DynamicSelect.tsx +++ b/apps/edr-freight-web/portal/src/components/dynamic-select/DynamicSelect.tsx @@ -2,7 +2,6 @@ import { useQuery } from "@tanstack/react-query"; import { cn } from "@/lib/utils"; import { api } from "@/services/api"; import { Loader2, AlertCircle } from "lucide-react"; -import * as React from "react"; import { Select, @@ -64,11 +63,7 @@ export function DynamicSelect({ const options = [...data.children].sort((a, b) => a.order - b.order); return ( - + diff --git a/apps/edr-freight-web/portal/src/components/tracking/TrackingTimeline.tsx b/apps/edr-freight-web/portal/src/components/tracking/TrackingTimeline.tsx index c167e1371..ac02239ad 100644 --- a/apps/edr-freight-web/portal/src/components/tracking/TrackingTimeline.tsx +++ b/apps/edr-freight-web/portal/src/components/tracking/TrackingTimeline.tsx @@ -17,7 +17,7 @@ const TrackingTimeline = ({ events }: TrackingTimelineProps) => { {event.location} - {event.status} + {event.status} {new Date(event.occurredAt).toLocaleString()} diff --git a/apps/edr-freight-web/portal/src/components/ui/index.ts b/apps/edr-freight-web/portal/src/components/ui/index.ts index 21dcac6e4..2dd239160 100644 --- a/apps/edr-freight-web/portal/src/components/ui/index.ts +++ b/apps/edr-freight-web/portal/src/components/ui/index.ts @@ -1,8 +1,7 @@ -export * from './table'; -export * from './badge'; -export * from './button'; -export * from './dialog'; -export * from './input'; -export * from './label'; -export * from './textarea'; -export * from './Breadcrumbs'; +export * from "./table"; +export * from "./badge"; +export * from "./button"; +export * from "./dialog"; +export * from "./input"; +export * from "./label"; +export * from "./textarea"; diff --git a/apps/edr-freight-web/portal/src/hooks/useBookings.ts b/apps/edr-freight-web/portal/src/hooks/useBookings.ts deleted file mode 100644 index 7f353d50a..000000000 --- a/apps/edr-freight-web/portal/src/hooks/useBookings.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { bookingsService } from "../services/bookings.service"; - -export const useBookings = () => - useQuery({ - queryKey: ["bookings"], - queryFn: bookingsService.list, - }); - -export const useBooking = (id: string) => - useQuery({ - queryKey: ["bookings", id], - queryFn: () => bookingsService.get(id), - enabled: Boolean(id), - }); diff --git a/apps/edr-freight-web/portal/src/hooks/useConsignments.ts b/apps/edr-freight-web/portal/src/hooks/useConsignments.ts deleted file mode 100644 index 593c6be5d..000000000 --- a/apps/edr-freight-web/portal/src/hooks/useConsignments.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { consignmentsService } from "../services/consignments.service"; - -export const useConsignments = () => - useQuery({ - queryKey: ["consignments"], - queryFn: consignmentsService.list, - }); - -export const useConsignment = (id: string) => - useQuery({ - queryKey: ["consignments", id], - queryFn: () => consignmentsService.get(id), - enabled: Boolean(id), - }); diff --git a/apps/edr-freight-web/portal/src/hooks/useCustomers.ts b/apps/edr-freight-web/portal/src/hooks/useCustomers.ts deleted file mode 100644 index ec26af310..000000000 --- a/apps/edr-freight-web/portal/src/hooks/useCustomers.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; - -import { customersService } from "@/services/customers.service"; -import type { - CreateCustomerDto, - UpdateCustomerDto, -} from "@/types/customers"; - -const KEY = ["customers"] as const; - -export const useCustomers = () => - useQuery({ - queryKey: KEY, - queryFn: customersService.list, - }); - -export const useCustomer = (id: string | undefined) => - useQuery({ - queryKey: [...KEY, "id", id], - queryFn: () => customersService.getById(id!), - enabled: Boolean(id), - }); - -export const useCreateCustomer = () => { - const qc = useQueryClient(); - return useMutation({ - mutationFn: (dto: CreateCustomerDto) => customersService.create(dto), - onSuccess: () => qc.invalidateQueries({ queryKey: KEY }), - }); -}; - -export const useUpdateCustomer = () => { - const qc = useQueryClient(); - return useMutation({ - mutationFn: ({ id, dto }: { id: string; dto: UpdateCustomerDto }) => - customersService.update(id, dto), - onSuccess: (_data, { id }) => { - qc.invalidateQueries({ queryKey: KEY }); - qc.invalidateQueries({ queryKey: [...KEY, "id", id] }); - }, - }); -}; - -export const useDeleteCustomer = () => { - const qc = useQueryClient(); - return useMutation({ - mutationFn: (id: string) => customersService.remove(id), - onSuccess: () => qc.invalidateQueries({ queryKey: KEY }), - }); -}; diff --git a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx index 80465a42f..3a26c92b2 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx @@ -1,4 +1,15 @@ -import { Box, Button, Divider, Group, Loader, SimpleGrid, Stack, Text, TextInput, ThemeIcon } from "@mantine/core"; +import { + Box, + Button, + Divider, + Group, + Loader, + SimpleGrid, + Stack, + Text, + TextInput, + ThemeIcon, +} from "@mantine/core"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; import { @@ -8,7 +19,6 @@ import { CheckCircle2, ChevronLeft, FileText, - Loader2, UploadCloud, User, } from "lucide-react"; @@ -32,7 +42,10 @@ const onboardingSchema = z.object({ companyLocation: z.string().min(1, "Location is required"), companyAddress: z.string().min(1, "Address is required"), tinNumber: z.string().length(10, "TIN must be exactly 10 digits"), - vatNumber: z.string().min(1, "VAT number is required").length(10, "VAT number must be exactly 10 digits"), + vatNumber: z + .string() + .min(1, "VAT number is required") + .length(10, "VAT number must be exactly 10 digits"), fanNumber: z.string().length(16, "FAN must be exactly 16 digits"), contactPersonName: z.string().min(1, "Contact person name is required"), contactPersonPhone: z.string().min(1, "Contact person phone is required"), @@ -52,8 +65,26 @@ const onboardingSchema = z.object({ type FormData = z.infer; const stepFields: Record = { - company: ["companyName", "companyEmail", "companyPhone", "companyPhoneCountryCode", "companyLocation", "companyAddress", "tinNumber", "vatNumber", "fanNumber"], - personnel: ["contactPersonName", "contactPersonPhone", "contactPersonPhoneCountryCode", "generalManagerName", "generalManagerEmail", "generalManagerPhone", "generalManagerPhoneCountryCode"], + company: [ + "companyName", + "companyEmail", + "companyPhone", + "companyPhoneCountryCode", + "companyLocation", + "companyAddress", + "tinNumber", + "vatNumber", + "fanNumber", + ], + personnel: [ + "contactPersonName", + "contactPersonPhone", + "contactPersonPhoneCountryCode", + "generalManagerName", + "generalManagerEmail", + "generalManagerPhone", + "generalManagerPhoneCountryCode", + ], poa: [], documents: [], confirm: [], @@ -76,7 +107,10 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { generalManagerEmail: data.generalManagerEmail, generalManagerPhone: `${data.generalManagerPhoneCountryCode}${data.generalManagerPhone}`, poaName: data.poaName || undefined, - poaPhone: data.poaPhone && data.poaPhoneCountryCode ? `${data.poaPhoneCountryCode}${data.poaPhone}` : undefined, + poaPhone: + data.poaPhone && data.poaPhoneCountryCode + ? `${data.poaPhoneCountryCode}${data.poaPhone}` + : undefined, poaAddress: data.poaAddress || undefined, poaEmail: data.poaEmail || undefined, poaLocation: data.poaLocation || undefined, @@ -102,22 +136,50 @@ export default function CompanyProfileForm({ onBack: () => void; }) { const [step, setStep] = useState("company"); - const [internalFiles, setInternalFiles] = useState>({}); + const [internalFiles, setInternalFiles] = useState< + Record + >({}); const documentFiles = controlledFiles ?? internalFiles; const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( - api.fileUploadSettings.getByCode.queryOptions({ input: { code: documentSettingCode }, refetchOnMount: false }), + api.fileUploadSettings.getByCode.queryOptions({ + input: { code: documentSettingCode }, + refetchOnMount: false, + }), ); - const { register, handleSubmit, trigger, watch, formState: { errors } } = useForm({ + const { + register, + handleSubmit, + trigger, + watch, + formState: { errors }, + } = useForm({ resolver: zodResolver(onboardingSchema), defaultValues: { - companyName: "", companyEmail: "", companyPhone: "", companyPhoneCountryCode: "+251", - companyLocation: "", companyAddress: "", tinNumber: "", vatNumber: "", fanNumber: "", - contactPersonName: "", contactPersonPhone: "", contactPersonPhoneCountryCode: "+251", - generalManagerName: "", generalManagerEmail: "", generalManagerPhone: "", generalManagerPhoneCountryCode: "+251", - poaName: "", poaPhone: "", poaPhoneCountryCode: "+251", poaAddress: "", poaEmail: "", poaLocation: "", + companyName: "", + companyEmail: "", + companyPhone: "", + companyPhoneCountryCode: "+251", + companyLocation: "", + companyAddress: "", + tinNumber: "", + vatNumber: "", + fanNumber: "", + contactPersonName: "", + contactPersonPhone: "", + contactPersonPhoneCountryCode: "+251", + generalManagerName: "", + generalManagerEmail: "", + generalManagerPhone: "", + generalManagerPhoneCountryCode: "+251", + poaName: "", + poaPhone: "", + poaPhoneCountryCode: "+251", + poaAddress: "", + poaEmail: "", + poaLocation: "", }, }); @@ -126,9 +188,18 @@ export default function CompanyProfileForm({ const totalSteps = 5; const nextStep = async () => { - if (step === "poa") { setStep("documents"); return; } - if (step === "documents") { setStep("confirm"); return; } - if (step === "confirm") { handleSubmit((data) => onSubmit(buildPayload(data, user)))(); return; } + if (step === "poa") { + setStep("documents"); + return; + } + if (step === "documents") { + setStep("confirm"); + return; + } + if (step === "confirm") { + handleSubmit((data) => onSubmit(buildPayload(data, user)))(); + return; + } const isValid = await trigger(stepFields[step]); if (!isValid) return; setStep(step === "company" ? "personnel" : "poa"); @@ -158,7 +229,13 @@ export default function CompanyProfileForm({ confirm: `Step 5 of ${totalSteps} — Review & Confirm`, }; - const stepOrder: CompanyStep[] = ["company", "personnel", "poa", "documents", "confirm"]; + const stepOrder: CompanyStep[] = [ + "company", + "personnel", + "poa", + "documents", + "confirm", + ]; const currentIdx = stepOrder.indexOf(step); return ( @@ -175,13 +252,24 @@ export default function CompanyProfileForm({ Change account type - + {STEPS.map(({ key, icon }, i) => { const done = i < currentIdx; const active = i === currentIdx; return done || active ? ( - + {done ? : icon} ) : ( @@ -223,7 +311,10 @@ export default function CompanyProfileForm({ /> - Contact Person + + Contact Person + - General Manager + + General Manager + - Power of Attorney details are optional. Fill them in if you have them, or skip to continue. + Power of Attorney details are optional. Fill them in if you have + them, or skip to continue. ) : ( - + )} > )} {step === "confirm" && ( - - Review your registration + + + Review your registration + Confirm the company details below before saving. - - - - + + + + - - - - - - - - - + + + + + + + + + )} - }> - {step === "company" ? "Change Type" : step === "confirm" ? "Back to Documents" : "Back"} + } + > + {step === "company" + ? "Change Type" + : step === "confirm" + ? "Back to Documents" + : "Back"} onSubmit(buildPayload(data, user))) : nextStep} - disabled={isPending || (step === "documents" && !hasDocuments && loadingDocuments)} + onClick={ + step === "confirm" + ? handleSubmit((data) => onSubmit(buildPayload(data, user))) + : nextStep + } + disabled={ + isPending || + (step === "documents" && !hasDocuments && loadingDocuments) + } loading={isPending} - rightSection={!isPending && step !== "confirm" && step !== "documents" ? : undefined} + rightSection={ + !isPending && step !== "confirm" && step !== "documents" ? ( + + ) : undefined + } > - {step === "documents" ? "Continue" : step === "confirm" ? "Submit Registration" : "Next Step"} + {step === "documents" + ? "Continue" + : step === "confirm" + ? "Submit Registration" + : "Next Step"} @@ -427,10 +606,20 @@ export default function CompanyProfileForm({ function ReviewRow({ label, value }: { label: string; value?: string | null }) { return ( - + {label} - + {value?.trim() ? value : "Not provided"} diff --git a/apps/edr-freight-web/portal/src/pages/bookings/CreateBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/CreateBookingPage.tsx deleted file mode 100644 index d89dfc0a1..000000000 --- a/apps/edr-freight-web/portal/src/pages/bookings/CreateBookingPage.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useNavigate } from "react-router-dom"; - -import BookingForm from "../../components/bookings/BookingForm"; -import { bookingsService } from "../../services/bookings.service"; - -const CreateBookingPage = () => { - const navigate = useNavigate(); - const queryClient = useQueryClient(); - - const mutation = useMutation({ - mutationFn: bookingsService.create, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["bookings"] }); - navigate("/bookings"); - }, - }); - - return ( - - New booking - - - ); -}; - -export default CreateBookingPage; diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx index c74b3dfd1..162516d67 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx @@ -10,7 +10,11 @@ import { } from "./schema"; import { SelectField, StepHeader, StepLabel } from "./shared"; -type BookingForm = UseFormReturn; +type BookingForm = UseFormReturn< + BookingFormInputValues, + any, + BookingFormValues +>; export function Step4Route({ form, @@ -31,44 +35,43 @@ export function Step4Route({ const shippingLineOptions = useMemo(() => { if (!referenceData?.shipping_line) return []; - return referenceData.shipping_line.map((sl) => ({ value: sl.name, label: sl.name })); + return referenceData.shipping_line.map((sl) => ({ + value: sl.name, + label: sl.name, + })); }, [referenceData]); - const originData = useMemo( - () => { - return yardOptions.filter((o) => o.value !== destinationYard).filter((o) => { - const dest = referenceData?.yard.find((y) => y.id === destinationYard); - if(!dest) return true; + const originData = useMemo(() => { + return yardOptions + .filter((o) => o.value !== destinationYard) + .filter((o) => { + const dest = referenceData?.yard.find((y) => y.id === destinationYard); + if (!dest) return true; const origin = referenceData?.yard.find((y) => y.id === o.value); - // can't go from Djibouti to Djibouti - if(dest?.country === 'Djibouti' && origin?.country == 'Djibouti') return false; - - return true; - }); - }, - [yardOptions, destinationYard], - ); - console.log({yardOptions,originYard, destinationYard}) - const destData = useMemo( - () => { - return yardOptions.filter((o) => o.value !== originYard).filter((d) => { - - - const origin = referenceData?.yard.find((y) => y.id === originYard); - if(!origin) return true; - - const dest = referenceData?.yard.find((y) => y.id === d.value); // can't go from Djibouti to Djibouti - // if(origin.country === 'Djibouti' && dest?.country == 'Djibouti') return false; + if (dest?.country === "Djibouti" && origin?.country == "Djibouti") + return false; return true; }); - }, - [yardOptions, originYard], - ); + }, [yardOptions, destinationYard]); + console.log({ yardOptions, originYard, destinationYard }); + const destData = useMemo(() => { + return yardOptions + .filter((o) => o.value !== originYard) + .filter((d) => { + const origin = referenceData?.yard.find((y) => y.id === originYard); + if (!origin) return true; - const direction = getRouteDirection(referenceData?.yard.find((y) => y.id === originYard), referenceData?.yard.find((y) => y.name === destinationYard)); + return true; + }); + }, [yardOptions, originYard]); + + const direction = getRouteDirection( + referenceData?.yard.find((y) => y.id === originYard), + referenceData?.yard.find((y) => y.name === destinationYard), + ); const directionStyle: Record = { export: "bg-sky-50 text-sky-800 border-sky-200", diff --git a/apps/edr-freight-web/portal/src/pages/tracking/TrackingPage.tsx b/apps/edr-freight-web/portal/src/pages/tracking/TrackingPage.tsx index ccd8e398c..e9f6949ed 100644 --- a/apps/edr-freight-web/portal/src/pages/tracking/TrackingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/tracking/TrackingPage.tsx @@ -8,7 +8,6 @@ import { List, MapPin, MoreHorizontal, - Plus, Search, Train, Trash2,