mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
type fixes
This commit is contained in:
@@ -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) {
|
||||
<ChevronRight className="mx-2 h-4 w-4 text-slate-300" />
|
||||
|
||||
{item.href && !isLast ? (
|
||||
<Link
|
||||
to={item.href}
|
||||
className="transition hover:text-[#10B981]"
|
||||
>
|
||||
<Link to={item.href} className="transition hover:text-[#10B981]">
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Select value={value} onValueChange={onValueChange} disabled={disabled}>
|
||||
<SelectTrigger className={cn("w-full", className)}>
|
||||
<SelectValue placeholder={placeholder ?? `Select ${data.label}`} />
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -17,7 +17,7 @@ const TrackingTimeline = ({ events }: TrackingTimelineProps) => {
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
||||
<span>{event.location}</span>
|
||||
<Badge tone="info">{event.status}</Badge>
|
||||
<Badge>{event.status}</Badge>
|
||||
</div>
|
||||
<time className="text-xs text-gray-500">
|
||||
{new Date(event.occurredAt).toLocaleString()}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
@@ -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),
|
||||
});
|
||||
@@ -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 }),
|
||||
});
|
||||
};
|
||||
@@ -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<typeof onboardingSchema>;
|
||||
|
||||
const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
|
||||
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<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 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<FormData>({
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
trigger,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
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
|
||||
</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" />
|
||||
{STEPS.map(({ key, icon }, i) => {
|
||||
const done = i < currentIdx;
|
||||
const active = i === currentIdx;
|
||||
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}
|
||||
</ThemeIcon>
|
||||
) : (
|
||||
@@ -223,7 +311,10 @@ export default function CompanyProfileForm({
|
||||
/>
|
||||
<PhoneInput
|
||||
countryCode={{ ...register("companyPhoneCountryCode") }}
|
||||
phone={{ ...register("companyPhone"), placeholder: "912345678" }}
|
||||
phone={{
|
||||
...register("companyPhone"),
|
||||
placeholder: "912345678",
|
||||
}}
|
||||
countryCodeError={errors.companyPhoneCountryCode}
|
||||
phoneError={errors.companyPhone}
|
||||
label="Company Phone"
|
||||
@@ -271,7 +362,9 @@ export default function CompanyProfileForm({
|
||||
|
||||
{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">
|
||||
<TextInput
|
||||
label="Name"
|
||||
@@ -281,7 +374,10 @@ export default function CompanyProfileForm({
|
||||
/>
|
||||
<PhoneInput
|
||||
countryCode={{ ...register("contactPersonPhoneCountryCode") }}
|
||||
phone={{ ...register("contactPersonPhone"), placeholder: "912345678" }}
|
||||
phone={{
|
||||
...register("contactPersonPhone"),
|
||||
placeholder: "912345678",
|
||||
}}
|
||||
countryCodeError={errors.contactPersonPhoneCountryCode}
|
||||
phoneError={errors.contactPersonPhone}
|
||||
label="Phone"
|
||||
@@ -290,7 +386,9 @@ export default function CompanyProfileForm({
|
||||
|
||||
<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
|
||||
label="Name"
|
||||
placeholder="Abebe Bikila"
|
||||
@@ -306,8 +404,13 @@ export default function CompanyProfileForm({
|
||||
{...register("generalManagerEmail")}
|
||||
/>
|
||||
<PhoneInput
|
||||
countryCode={{ ...register("generalManagerPhoneCountryCode") }}
|
||||
phone={{ ...register("generalManagerPhone"), placeholder: "912345678" }}
|
||||
countryCode={{
|
||||
...register("generalManagerPhoneCountryCode"),
|
||||
}}
|
||||
phone={{
|
||||
...register("generalManagerPhone"),
|
||||
placeholder: "912345678",
|
||||
}}
|
||||
countryCodeError={errors.generalManagerPhoneCountryCode}
|
||||
phoneError={errors.generalManagerPhone}
|
||||
label="Phone"
|
||||
@@ -319,7 +422,8 @@ export default function CompanyProfileForm({
|
||||
{step === "poa" && (
|
||||
<>
|
||||
<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>
|
||||
<TextInput
|
||||
label="PoA Name"
|
||||
@@ -371,51 +475,126 @@ export default function CompanyProfileForm({
|
||||
No document requirements found for your account type.
|
||||
</Text>
|
||||
) : (
|
||||
<SmartFileInput file={uploadSetting} value={documentFiles} onChange={setDocumentFiles} />
|
||||
<SmartFileInput
|
||||
file={uploadSetting}
|
||||
value={documentFiles}
|
||||
onChange={setDocumentFiles}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "confirm" && (
|
||||
<Box p={16} className="rounded-2xl border border-edr-border bg-edr-card">
|
||||
<Text fw={600} c="edr-text">Review your registration</Text>
|
||||
<Box
|
||||
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">
|
||||
Confirm the company details below before saving.
|
||||
</Text>
|
||||
<SimpleGrid cols={2} spacing="sm">
|
||||
<ReviewRow label="Company name" value={formValues.companyName} />
|
||||
<ReviewRow label="Company email" value={formValues.companyEmail} />
|
||||
<ReviewRow label="Company phone" value={formValues.companyPhone} />
|
||||
<ReviewRow label="Location" value={formValues.companyLocation} />
|
||||
<ReviewRow
|
||||
label="Company name"
|
||||
value={formValues.companyName}
|
||||
/>
|
||||
<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="TIN" value={formValues.tinNumber} />
|
||||
<ReviewRow label="VAT" value={formValues.vatNumber} />
|
||||
<ReviewRow label="FAN" value={formValues.fanNumber} />
|
||||
<ReviewRow label="Contact person" value={formValues.contactPersonName} />
|
||||
<ReviewRow label="Contact phone" value={`${formValues.contactPersonPhoneCountryCode}${formValues.contactPersonPhone}`} />
|
||||
<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} />
|
||||
<ReviewRow
|
||||
label="Contact person"
|
||||
value={formValues.contactPersonName}
|
||||
/>
|
||||
<ReviewRow
|
||||
label="Contact phone"
|
||||
value={`${formValues.contactPersonPhoneCountryCode}${formValues.contactPersonPhone}`}
|
||||
/>
|
||||
<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>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Group justify="space-between" pt="xs">
|
||||
<Button variant="default" onClick={prevStep} leftSection={<ArrowLeft size={16} />}>
|
||||
{step === "company" ? "Change Type" : step === "confirm" ? "Back to Documents" : "Back"}
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={prevStep}
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
>
|
||||
{step === "company"
|
||||
? "Change Type"
|
||||
: step === "confirm"
|
||||
? "Back to Documents"
|
||||
: "Back"}
|
||||
</Button>
|
||||
<Button
|
||||
color="edr-green"
|
||||
onClick={step === "confirm" ? handleSubmit((data) => 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" ? <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>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -427,10 +606,20 @@ export default function CompanyProfileForm({
|
||||
function ReviewRow({ label, value }: { label: string; value?: string | null }) {
|
||||
return (
|
||||
<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}
|
||||
</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"}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -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;
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
} from "./schema";
|
||||
import { SelectField, StepHeader, StepLabel } from "./shared";
|
||||
|
||||
type BookingForm = UseFormReturn<BookingFormInputValues, any, BookingFormValues>;
|
||||
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<string, string> = {
|
||||
export: "bg-sky-50 text-sky-800 border-sky-200",
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
List,
|
||||
MapPin,
|
||||
MoreHorizontal,
|
||||
Plus,
|
||||
Search,
|
||||
Train,
|
||||
Trash2,
|
||||
|
||||
Reference in New Issue
Block a user