type fixes

This commit is contained in:
ghost2023
2026-06-16 15:13:11 +03:00
parent e0e6020020
commit cf3db76bb1
14 changed files with 284 additions and 387 deletions

View File

@@ -1,6 +1,6 @@
import { Fragment } from "react"; import { Fragment } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { ChevronRight, Home } from "lucide-react"; import { ChevronRight } from "lucide-react";
export interface BreadcrumbItem { export interface BreadcrumbItem {
label: string; label: string;
@@ -34,10 +34,7 @@ export default function Breadcrumbs({ items }: BreadcrumbsProps) {
<ChevronRight className="mx-2 h-4 w-4 text-slate-300" /> <ChevronRight className="mx-2 h-4 w-4 text-slate-300" />
{item.href && !isLast ? ( {item.href && !isLast ? (
<Link <Link to={item.href} className="transition hover:text-[#10B981]">
to={item.href}
className="transition hover:text-[#10B981]"
>
{item.label} {item.label}
</Link> </Link>
) : ( ) : (

View File

@@ -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<HTMLFormElement>) => {
event.preventDefault();
onSubmit({
reference,
customerId,
scheduledDate,
totalAmount: Number(totalAmount),
});
};
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
<FormField
label="Reference"
value={reference}
onChange={(e) => setReference(e.target.value)}
required
/>
<FormField
label="Customer ID"
value={customerId}
onChange={(e) => setCustomerId(e.target.value)}
required
/>
<FormField
label="Scheduled date"
type="date"
value={scheduledDate}
onChange={(e) => setScheduledDate(e.target.value)}
required
/>
<FormField
label="Total amount"
type="number"
value={totalAmount}
onChange={(e) => setTotalAmount(e.target.value)}
min="0"
/>
<Button type="submit" isLoading={isSubmitting}>
Create booking
</Button>
</form>
);
};
export default BookingForm;

View File

@@ -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<HTMLFormElement>) => {
event.preventDefault();
onSubmit({
bookingId,
trackingNumber,
cargoType,
weightKg: Number(weightKg),
originStation,
destinationStation,
});
};
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
<FormField
label="Booking ID"
value={bookingId}
onChange={(e) => setBookingId(e.target.value)}
required
/>
<FormField
label="Tracking #"
value={trackingNumber}
onChange={(e) => setTrackingNumber(e.target.value)}
required
/>
<FormField
label="Cargo type"
value={cargoType}
onChange={(e) => setCargoType(e.target.value)}
/>
<FormField
label="Weight (kg)"
type="number"
value={weightKg}
onChange={(e) => setWeightKg(e.target.value)}
min="0"
/>
<FormField
label="Origin station"
value={originStation}
onChange={(e) => setOriginStation(e.target.value)}
required
/>
<FormField
label="Destination station"
value={destinationStation}
onChange={(e) => setDestinationStation(e.target.value)}
required
/>
<Button type="submit" isLoading={isSubmitting}>
Create consignment
</Button>
</form>
);
};
export default ConsignmentForm;

View File

@@ -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<Freight.IConsignment>[] = [
{ 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) => (
<Table
columns={columns}
data={consignments}
rowKey={(row) => row.id}
emptyMessage="No consignments yet"
/>
);
export default ConsignmentTable;

View File

@@ -2,7 +2,6 @@ import { useQuery } from "@tanstack/react-query";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/services/api"; import { api } from "@/services/api";
import { Loader2, AlertCircle } from "lucide-react"; import { Loader2, AlertCircle } from "lucide-react";
import * as React from "react";
import { import {
Select, Select,
@@ -64,11 +63,7 @@ export function DynamicSelect({
const options = [...data.children].sort((a, b) => a.order - b.order); const options = [...data.children].sort((a, b) => a.order - b.order);
return ( return (
<Select <Select value={value} onValueChange={onValueChange} disabled={disabled}>
value={value}
onValueChange={onValueChange}
disabled={disabled}
>
<SelectTrigger className={cn("w-full", className)}> <SelectTrigger className={cn("w-full", className)}>
<SelectValue placeholder={placeholder ?? `Select ${data.label}`} /> <SelectValue placeholder={placeholder ?? `Select ${data.label}`} />
</SelectTrigger> </SelectTrigger>

View File

@@ -17,7 +17,7 @@ const TrackingTimeline = ({ events }: TrackingTimelineProps) => {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center gap-2 text-sm font-medium text-gray-900"> <div className="flex items-center gap-2 text-sm font-medium text-gray-900">
<span>{event.location}</span> <span>{event.location}</span>
<Badge tone="info">{event.status}</Badge> <Badge>{event.status}</Badge>
</div> </div>
<time className="text-xs text-gray-500"> <time className="text-xs text-gray-500">
{new Date(event.occurredAt).toLocaleString()} {new Date(event.occurredAt).toLocaleString()}

View File

@@ -1,8 +1,7 @@
export * from './table'; export * from "./table";
export * from './badge'; export * from "./badge";
export * from './button'; export * from "./button";
export * from './dialog'; export * from "./dialog";
export * from './input'; export * from "./input";
export * from './label'; export * from "./label";
export * from './textarea'; export * from "./textarea";
export * from './Breadcrumbs';

View File

@@ -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),
});

View File

@@ -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),
});

View File

@@ -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 }),
});
};

View File

@@ -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 { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { import {
@@ -8,7 +19,6 @@ import {
CheckCircle2, CheckCircle2,
ChevronLeft, ChevronLeft,
FileText, FileText,
Loader2,
UploadCloud, UploadCloud,
User, User,
} from "lucide-react"; } from "lucide-react";
@@ -32,7 +42,10 @@ const onboardingSchema = z.object({
companyLocation: z.string().min(1, "Location is required"), companyLocation: z.string().min(1, "Location is required"),
companyAddress: z.string().min(1, "Address is required"), companyAddress: z.string().min(1, "Address is required"),
tinNumber: z.string().length(10, "TIN must be exactly 10 digits"), 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"), fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
contactPersonName: z.string().min(1, "Contact person name is required"), contactPersonName: z.string().min(1, "Contact person name is required"),
contactPersonPhone: z.string().min(1, "Contact person phone is required"), contactPersonPhone: z.string().min(1, "Contact person phone is required"),
@@ -52,8 +65,26 @@ const onboardingSchema = z.object({
type FormData = z.infer<typeof onboardingSchema>; type FormData = z.infer<typeof onboardingSchema>;
const stepFields: Record<CompanyStep, (keyof FormData)[]> = { const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
company: ["companyName", "companyEmail", "companyPhone", "companyPhoneCountryCode", "companyLocation", "companyAddress", "tinNumber", "vatNumber", "fanNumber"], company: [
personnel: ["contactPersonName", "contactPersonPhone", "contactPersonPhoneCountryCode", "generalManagerName", "generalManagerEmail", "generalManagerPhone", "generalManagerPhoneCountryCode"], "companyName",
"companyEmail",
"companyPhone",
"companyPhoneCountryCode",
"companyLocation",
"companyAddress",
"tinNumber",
"vatNumber",
"fanNumber",
],
personnel: [
"contactPersonName",
"contactPersonPhone",
"contactPersonPhoneCountryCode",
"generalManagerName",
"generalManagerEmail",
"generalManagerPhone",
"generalManagerPhoneCountryCode",
],
poa: [], poa: [],
documents: [], documents: [],
confirm: [], confirm: [],
@@ -76,7 +107,10 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload {
generalManagerEmail: data.generalManagerEmail, generalManagerEmail: data.generalManagerEmail,
generalManagerPhone: `${data.generalManagerPhoneCountryCode}${data.generalManagerPhone}`, generalManagerPhone: `${data.generalManagerPhoneCountryCode}${data.generalManagerPhone}`,
poaName: data.poaName || undefined, 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, poaAddress: data.poaAddress || undefined,
poaEmail: data.poaEmail || undefined, poaEmail: data.poaEmail || undefined,
poaLocation: data.poaLocation || undefined, poaLocation: data.poaLocation || undefined,
@@ -102,22 +136,50 @@ export default function CompanyProfileForm({
onBack: () => void; onBack: () => void;
}) { }) {
const [step, setStep] = useState<CompanyStep>("company"); const [step, setStep] = useState<CompanyStep>("company");
const [internalFiles, setInternalFiles] = useState<Record<string, File | File[] | null>>({}); const [internalFiles, setInternalFiles] = useState<
Record<string, File | File[] | null>
>({});
const documentFiles = controlledFiles ?? internalFiles; const documentFiles = controlledFiles ?? internalFiles;
const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles;
const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( 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<FormData>({ const {
register,
handleSubmit,
trigger,
watch,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(onboardingSchema), resolver: zodResolver(onboardingSchema),
defaultValues: { defaultValues: {
companyName: "", companyEmail: "", companyPhone: "", companyPhoneCountryCode: "+251", companyName: "",
companyLocation: "", companyAddress: "", tinNumber: "", vatNumber: "", fanNumber: "", companyEmail: "",
contactPersonName: "", contactPersonPhone: "", contactPersonPhoneCountryCode: "+251", companyPhone: "",
generalManagerName: "", generalManagerEmail: "", generalManagerPhone: "", generalManagerPhoneCountryCode: "+251", companyPhoneCountryCode: "+251",
poaName: "", poaPhone: "", poaPhoneCountryCode: "+251", poaAddress: "", poaEmail: "", poaLocation: "", 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 totalSteps = 5;
const nextStep = async () => { const nextStep = async () => {
if (step === "poa") { setStep("documents"); return; } if (step === "poa") {
if (step === "documents") { setStep("confirm"); return; } setStep("documents");
if (step === "confirm") { handleSubmit((data) => onSubmit(buildPayload(data, user)))(); return; } return;
}
if (step === "documents") {
setStep("confirm");
return;
}
if (step === "confirm") {
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
return;
}
const isValid = await trigger(stepFields[step]); const isValid = await trigger(stepFields[step]);
if (!isValid) return; if (!isValid) return;
setStep(step === "company" ? "personnel" : "poa"); setStep(step === "company" ? "personnel" : "poa");
@@ -158,7 +229,13 @@ export default function CompanyProfileForm({
confirm: `Step 5 of ${totalSteps} — Review & Confirm`, 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); const currentIdx = stepOrder.indexOf(step);
return ( return (
@@ -175,13 +252,24 @@ export default function CompanyProfileForm({
Change account type Change account type
</Button> </Button>
<Group justify="space-between" align="center" className="relative max-w-lg mx-auto px-2"> <Group
justify="space-between"
align="center"
className="relative max-w-lg mx-auto px-2"
>
<Box className="absolute top-1/2 left-2 right-2 h-0.5 bg-edr-border -translate-y-1/2 z-0" /> <Box className="absolute top-1/2 left-2 right-2 h-0.5 bg-edr-border -translate-y-1/2 z-0" />
{STEPS.map(({ key, icon }, i) => { {STEPS.map(({ key, icon }, i) => {
const done = i < currentIdx; const done = i < currentIdx;
const active = i === currentIdx; const active = i === currentIdx;
return done || active ? ( return done || active ? (
<ThemeIcon key={key} size={40} radius="xl" variant="filled" color="edr-green" className="relative z-10"> <ThemeIcon
key={key}
size={40}
radius="xl"
variant="filled"
color="edr-green"
className="relative z-10"
>
{done ? <CheckCircle2 size={18} /> : icon} {done ? <CheckCircle2 size={18} /> : icon}
</ThemeIcon> </ThemeIcon>
) : ( ) : (
@@ -223,7 +311,10 @@ export default function CompanyProfileForm({
/> />
<PhoneInput <PhoneInput
countryCode={{ ...register("companyPhoneCountryCode") }} countryCode={{ ...register("companyPhoneCountryCode") }}
phone={{ ...register("companyPhone"), placeholder: "912345678" }} phone={{
...register("companyPhone"),
placeholder: "912345678",
}}
countryCodeError={errors.companyPhoneCountryCode} countryCodeError={errors.companyPhoneCountryCode}
phoneError={errors.companyPhone} phoneError={errors.companyPhone}
label="Company Phone" label="Company Phone"
@@ -271,7 +362,9 @@ export default function CompanyProfileForm({
{step === "personnel" && ( {step === "personnel" && (
<> <>
<Text fw={600} size="sm" c="edr-text">Contact Person</Text> <Text fw={600} size="sm" c="edr-text">
Contact Person
</Text>
<SimpleGrid cols={2} spacing="md"> <SimpleGrid cols={2} spacing="md">
<TextInput <TextInput
label="Name" label="Name"
@@ -281,7 +374,10 @@ export default function CompanyProfileForm({
/> />
<PhoneInput <PhoneInput
countryCode={{ ...register("contactPersonPhoneCountryCode") }} countryCode={{ ...register("contactPersonPhoneCountryCode") }}
phone={{ ...register("contactPersonPhone"), placeholder: "912345678" }} phone={{
...register("contactPersonPhone"),
placeholder: "912345678",
}}
countryCodeError={errors.contactPersonPhoneCountryCode} countryCodeError={errors.contactPersonPhoneCountryCode}
phoneError={errors.contactPersonPhone} phoneError={errors.contactPersonPhone}
label="Phone" label="Phone"
@@ -290,7 +386,9 @@ export default function CompanyProfileForm({
<Divider color="edr-border" /> <Divider color="edr-border" />
<Text fw={600} size="sm" c="edr-text">General Manager</Text> <Text fw={600} size="sm" c="edr-text">
General Manager
</Text>
<TextInput <TextInput
label="Name" label="Name"
placeholder="Abebe Bikila" placeholder="Abebe Bikila"
@@ -306,8 +404,13 @@ export default function CompanyProfileForm({
{...register("generalManagerEmail")} {...register("generalManagerEmail")}
/> />
<PhoneInput <PhoneInput
countryCode={{ ...register("generalManagerPhoneCountryCode") }} countryCode={{
phone={{ ...register("generalManagerPhone"), placeholder: "912345678" }} ...register("generalManagerPhoneCountryCode"),
}}
phone={{
...register("generalManagerPhone"),
placeholder: "912345678",
}}
countryCodeError={errors.generalManagerPhoneCountryCode} countryCodeError={errors.generalManagerPhoneCountryCode}
phoneError={errors.generalManagerPhone} phoneError={errors.generalManagerPhone}
label="Phone" label="Phone"
@@ -319,7 +422,8 @@ export default function CompanyProfileForm({
{step === "poa" && ( {step === "poa" && (
<> <>
<Text size="sm" c="edr-muted"> <Text size="sm" c="edr-muted">
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.
</Text> </Text>
<TextInput <TextInput
label="PoA Name" label="PoA Name"
@@ -371,51 +475,126 @@ export default function CompanyProfileForm({
No document requirements found for your account type. No document requirements found for your account type.
</Text> </Text>
) : ( ) : (
<SmartFileInput file={uploadSetting} value={documentFiles} onChange={setDocumentFiles} /> <SmartFileInput
file={uploadSetting}
value={documentFiles}
onChange={setDocumentFiles}
/>
)} )}
</> </>
)} )}
{step === "confirm" && ( {step === "confirm" && (
<Box p={16} className="rounded-2xl border border-edr-border bg-edr-card"> <Box
<Text fw={600} c="edr-text">Review your registration</Text> p={16}
className="rounded-2xl border border-edr-border bg-edr-card"
>
<Text fw={600} c="edr-text">
Review your registration
</Text>
<Text size="sm" c="edr-muted" mt={4} mb="md"> <Text size="sm" c="edr-muted" mt={4} mb="md">
Confirm the company details below before saving. Confirm the company details below before saving.
</Text> </Text>
<SimpleGrid cols={2} spacing="sm"> <SimpleGrid cols={2} spacing="sm">
<ReviewRow label="Company name" value={formValues.companyName} /> <ReviewRow
<ReviewRow label="Company email" value={formValues.companyEmail} /> label="Company name"
<ReviewRow label="Company phone" value={formValues.companyPhone} /> value={formValues.companyName}
<ReviewRow label="Location" value={formValues.companyLocation} /> />
<ReviewRow
label="Company email"
value={formValues.companyEmail}
/>
<ReviewRow
label="Company phone"
value={formValues.companyPhone}
/>
<ReviewRow
label="Location"
value={formValues.companyLocation}
/>
<ReviewRow label="Address" value={formValues.companyAddress} /> <ReviewRow label="Address" value={formValues.companyAddress} />
<ReviewRow label="TIN" value={formValues.tinNumber} /> <ReviewRow label="TIN" value={formValues.tinNumber} />
<ReviewRow label="VAT" value={formValues.vatNumber} /> <ReviewRow label="VAT" value={formValues.vatNumber} />
<ReviewRow label="FAN" value={formValues.fanNumber} /> <ReviewRow label="FAN" value={formValues.fanNumber} />
<ReviewRow label="Contact person" value={formValues.contactPersonName} /> <ReviewRow
<ReviewRow label="Contact phone" value={`${formValues.contactPersonPhoneCountryCode}${formValues.contactPersonPhone}`} /> label="Contact person"
<ReviewRow label="General manager" value={formValues.generalManagerName} /> value={formValues.contactPersonName}
<ReviewRow label="GM email" value={formValues.generalManagerEmail} /> />
<ReviewRow label="GM phone" value={`${formValues.generalManagerPhoneCountryCode}${formValues.generalManagerPhone}`} /> <ReviewRow
<ReviewRow label="PoA name" value={formValues.poaName || undefined} /> label="Contact phone"
<ReviewRow label="PoA phone" value={formValues.poaPhone && formValues.poaPhoneCountryCode ? `${formValues.poaPhoneCountryCode}${formValues.poaPhone}` : undefined} /> value={`${formValues.contactPersonPhoneCountryCode}${formValues.contactPersonPhone}`}
<ReviewRow label="PoA email" value={formValues.poaEmail || undefined} /> />
<ReviewRow label="PoA location" value={formValues.poaLocation || undefined} /> <ReviewRow
label="General manager"
value={formValues.generalManagerName}
/>
<ReviewRow
label="GM email"
value={formValues.generalManagerEmail}
/>
<ReviewRow
label="GM phone"
value={`${formValues.generalManagerPhoneCountryCode}${formValues.generalManagerPhone}`}
/>
<ReviewRow
label="PoA name"
value={formValues.poaName || undefined}
/>
<ReviewRow
label="PoA phone"
value={
formValues.poaPhone && formValues.poaPhoneCountryCode
? `${formValues.poaPhoneCountryCode}${formValues.poaPhone}`
: undefined
}
/>
<ReviewRow
label="PoA email"
value={formValues.poaEmail || undefined}
/>
<ReviewRow
label="PoA location"
value={formValues.poaLocation || undefined}
/>
</SimpleGrid> </SimpleGrid>
</Box> </Box>
)} )}
<Group justify="space-between" pt="xs"> <Group justify="space-between" pt="xs">
<Button variant="default" onClick={prevStep} leftSection={<ArrowLeft size={16} />}> <Button
{step === "company" ? "Change Type" : step === "confirm" ? "Back to Documents" : "Back"} variant="default"
onClick={prevStep}
leftSection={<ArrowLeft size={16} />}
>
{step === "company"
? "Change Type"
: step === "confirm"
? "Back to Documents"
: "Back"}
</Button> </Button>
<Button <Button
color="edr-green" color="edr-green"
onClick={step === "confirm" ? handleSubmit((data) => onSubmit(buildPayload(data, user))) : nextStep} onClick={
disabled={isPending || (step === "documents" && !hasDocuments && loadingDocuments)} step === "confirm"
? handleSubmit((data) => onSubmit(buildPayload(data, user)))
: nextStep
}
disabled={
isPending ||
(step === "documents" && !hasDocuments && loadingDocuments)
}
loading={isPending} loading={isPending}
rightSection={!isPending && step !== "confirm" && step !== "documents" ? <ArrowRight size={16} /> : undefined} rightSection={
!isPending && step !== "confirm" && step !== "documents" ? (
<ArrowRight size={16} />
) : undefined
}
> >
{step === "documents" ? "Continue" : step === "confirm" ? "Submit Registration" : "Next Step"} {step === "documents"
? "Continue"
: step === "confirm"
? "Submit Registration"
: "Next Step"}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
@@ -427,10 +606,20 @@ export default function CompanyProfileForm({
function ReviewRow({ label, value }: { label: string; value?: string | null }) { function ReviewRow({ label, value }: { label: string; value?: string | null }) {
return ( return (
<Box p={12} className="rounded-xl bg-edr-bg"> <Box p={12} className="rounded-xl bg-edr-bg">
<Text size="xs" fw={600} c="edr-muted" className="uppercase tracking-wide"> <Text
size="xs"
fw={600}
c="edr-muted"
className="uppercase tracking-wide"
>
{label} {label}
</Text> </Text>
<Text size="sm" fw={500} c={value?.trim() ? "edr-text" : "edr-muted"} mt={4}> <Text
size="sm"
fw={500}
c={value?.trim() ? "edr-text" : "edr-muted"}
mt={4}
>
{value?.trim() ? value : "Not provided"} {value?.trim() ? value : "Not provided"}
</Text> </Text>
</Box> </Box>

View File

@@ -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 (
<div className="max-w-lg">
<h1 className="mb-4 text-2xl font-semibold text-gray-900">New booking</h1>
<BookingForm
onSubmit={mutation.mutate}
isSubmitting={mutation.isPending}
/>
</div>
);
};
export default CreateBookingPage;

View File

@@ -10,7 +10,11 @@ import {
} from "./schema"; } from "./schema";
import { SelectField, StepHeader, StepLabel } from "./shared"; import { SelectField, StepHeader, StepLabel } from "./shared";
type BookingForm = UseFormReturn<BookingFormInputValues, any, BookingFormValues>; type BookingForm = UseFormReturn<
BookingFormInputValues,
any,
BookingFormValues
>;
export function Step4Route({ export function Step4Route({
form, form,
@@ -31,44 +35,43 @@ export function Step4Route({
const shippingLineOptions = useMemo(() => { const shippingLineOptions = useMemo(() => {
if (!referenceData?.shipping_line) return []; 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]); }, [referenceData]);
const originData = useMemo( const originData = useMemo(() => {
() => { return yardOptions
return yardOptions.filter((o) => o.value !== destinationYard).filter((o) => { .filter((o) => o.value !== destinationYard)
const dest = referenceData?.yard.find((y) => y.id === destinationYard); .filter((o) => {
if(!dest) return true; const dest = referenceData?.yard.find((y) => y.id === destinationYard);
if (!dest) return true;
const origin = referenceData?.yard.find((y) => y.id === o.value); 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 // 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; return true;
}); });
}, }, [yardOptions, destinationYard]);
[yardOptions, originYard], 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<string, string> = { const directionStyle: Record<string, string> = {
export: "bg-sky-50 text-sky-800 border-sky-200", export: "bg-sky-50 text-sky-800 border-sky-200",

View File

@@ -8,7 +8,6 @@ import {
List, List,
MapPin, MapPin,
MoreHorizontal, MoreHorizontal,
Plus,
Search, Search,
Train, Train,
Trash2, Trash2,