mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
style: improved the review ui
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
import { useMemo, useRef, type ReactNode } from "react";
|
import { api } from "@/services/api";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import type { CreateBookingPayload } from "@/services/bookings.service";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import type { Freight } from "@edr/types";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@@ -34,12 +34,17 @@ import {
|
|||||||
Upload,
|
Upload,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { Freight } from "@edr/types";
|
import { useMemo, useRef, type ReactNode } from "react";
|
||||||
import { api } from "@/services/api";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import type { CreateBookingPayload } from "@/services/bookings.service";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
CountChip,
|
||||||
|
DocRow,
|
||||||
|
IconSquare,
|
||||||
|
} from "./BookingDetailPage/components/Documents";
|
||||||
import {
|
import {
|
||||||
BookingFormInputValues,
|
|
||||||
BOOKING_DOCS_SETTING,
|
BOOKING_DOCS_SETTING,
|
||||||
|
BookingFormInputValues,
|
||||||
bookingFormSchema,
|
bookingFormSchema,
|
||||||
getRouteDirection,
|
getRouteDirection,
|
||||||
initialBookingFormValues,
|
initialBookingFormValues,
|
||||||
@@ -48,11 +53,6 @@ import {
|
|||||||
} from "./new-booking-form/schema";
|
} from "./new-booking-form/schema";
|
||||||
import { SelectField } from "./new-booking-form/shared";
|
import { SelectField } from "./new-booking-form/shared";
|
||||||
import { Step5CargoDetails } from "./new-booking-form/steps";
|
import { Step5CargoDetails } from "./new-booking-form/steps";
|
||||||
import {
|
|
||||||
CountChip,
|
|
||||||
DocRow,
|
|
||||||
IconSquare,
|
|
||||||
} from "./BookingDetailPage/components/Documents";
|
|
||||||
|
|
||||||
function yardNameFromBooking(
|
function yardNameFromBooking(
|
||||||
yard: { label?: string; code?: string; name?: string } | undefined | null,
|
yard: { label?: string; code?: string; name?: string } | undefined | null,
|
||||||
@@ -117,8 +117,6 @@ function mapBookingToFormValues(
|
|||||||
shippingLine: (booking as any).shippingLine?.name ?? "",
|
shippingLine: (booking as any).shippingLine?.name ?? "",
|
||||||
consolidationEnabled: booking.allowConsolidation ?? false,
|
consolidationEnabled: booking.allowConsolidation ?? false,
|
||||||
notes: "",
|
notes: "",
|
||||||
// Terms were accepted at creation; editing shouldn't re-gate on them.
|
|
||||||
termsAccepted: true,
|
|
||||||
containers: [],
|
containers: [],
|
||||||
} as BookingFormInputValues;
|
} as BookingFormInputValues;
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ export const bookingFormSchema = z
|
|||||||
consolidationEnabled: z.boolean(),
|
consolidationEnabled: z.boolean(),
|
||||||
documents: z.record(z.string(), z.any()).default({}),
|
documents: z.record(z.string(), z.any()).default({}),
|
||||||
notes: z.string(),
|
notes: z.string(),
|
||||||
termsAccepted: z.boolean(),
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) =>
|
(data) =>
|
||||||
@@ -165,10 +164,6 @@ export const bookingFormSchema = z
|
|||||||
(data) => !(data.cargoType === "container" && data.containers.length === 0),
|
(data) => !(data.cargoType === "container" && data.containers.length === 0),
|
||||||
{ message: "Add at least one container.", path: ["containers"] },
|
{ message: "Add at least one container.", path: ["containers"] },
|
||||||
)
|
)
|
||||||
.refine((data) => data.termsAccepted, {
|
|
||||||
message: "Accept the freight contract terms to submit.",
|
|
||||||
path: ["termsAccepted"],
|
|
||||||
})
|
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.cargoType === "bulk") {
|
if (data.cargoType === "bulk") {
|
||||||
if (!data.cargoTypePath[0]) {
|
if (!data.cargoTypePath[0]) {
|
||||||
@@ -237,7 +232,6 @@ export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
|
|||||||
consolidationEnabled: false,
|
consolidationEnabled: false,
|
||||||
documents: {},
|
documents: {},
|
||||||
notes: "",
|
notes: "",
|
||||||
termsAccepted: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
|
export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
|
||||||
@@ -265,7 +259,7 @@ export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
|
|||||||
],
|
],
|
||||||
5: ["scheduledDate", "trainScheduleId"],
|
5: ["scheduledDate", "trainScheduleId"],
|
||||||
6: ["documents"],
|
6: ["documents"],
|
||||||
7: ["notes", "termsAccepted"],
|
7: ["notes"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ContainerConfig {
|
export interface ContainerConfig {
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Checkbox,
|
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Textarea,
|
Textarea,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { Check, Send, XCircle } from "lucide-react";
|
import { Check, Send, XCircle, FileText, Route, Package, Truck } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
BookingFormInputValues,
|
BookingFormInputValues,
|
||||||
BOOKING_DOCS_SETTING,
|
BOOKING_DOCS_SETTING,
|
||||||
@@ -54,12 +54,11 @@ export function Step8Review({
|
|||||||
abortPending?: boolean;
|
abortPending?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const values = form.watch();
|
const values = form.watch();
|
||||||
const errors = form.formState.errors;
|
|
||||||
const serviceType = referenceData?.service.find(
|
const serviceType = referenceData?.service.find(
|
||||||
(s) => s.id === values.serviceTypeId,
|
(s) => s.id === values.serviceTypeId,
|
||||||
);
|
);
|
||||||
|
|
||||||
function Row({
|
function CompactRow({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
target,
|
target,
|
||||||
@@ -69,19 +68,19 @@ export function Step8Review({
|
|||||||
target: number;
|
target: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start justify-between gap-4 py-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="10px" c="dimmed" fw={600} tt="uppercase" className="mb-1 tracking-wider">
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" fw={500} mt={2} className="truncate">
|
<Text size="sm" fw={500} className="truncate">
|
||||||
{value || "—"}
|
{value || "—"}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setStep(target)}
|
onClick={() => setStep(target)}
|
||||||
className="shrink-0 text-xs font-medium text-emerald-600 hover:underline"
|
className="shrink-0 text-10px font-medium text-emerald-600 hover:underline whitespace-nowrap ml-2 mt-2"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -89,6 +88,28 @@ export function Step8Review({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CompactCard({
|
||||||
|
icon: Icon,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card radius="md" p="sm" withBorder className="border-gray-200 bg-white hover:shadow-sm transition-shadow">
|
||||||
|
<Group gap="xs" mb="xs" wrap="nowrap">
|
||||||
|
<Box c="edr-green">{Icon}</Box>
|
||||||
|
<Text size="xs" fw={700} tt="uppercase" c="dimmed" className="tracking-wider">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Stack gap="xs">{children}</Stack>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const containerSummary =
|
const containerSummary =
|
||||||
values.cargoType === "container" && values.containers.length > 0
|
values.cargoType === "container" && values.containers.length > 0
|
||||||
? values.containers
|
? values.containers
|
||||||
@@ -122,186 +143,24 @@ export function Step8Review({
|
|||||||
return child ? `${group.name} — ${child.name}` : group.name;
|
return child ? `${group.name} — ${child.name}` : group.name;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function ReviewCard({
|
const originYardName = referenceData?.yard.find(
|
||||||
title,
|
(y) => y.id === values.originYard,
|
||||||
children,
|
)?.name ?? values.originYard;
|
||||||
}: {
|
|
||||||
title: string;
|
const destinationYardName = referenceData?.yard.find(
|
||||||
children: React.ReactNode;
|
(y) => y.id === values.destinationYard,
|
||||||
}) {
|
)?.name ?? values.destinationYard;
|
||||||
return (
|
|
||||||
<Card radius="lg" withBorder p={0} className="overflow-hidden">
|
|
||||||
<Box
|
|
||||||
px="md"
|
|
||||||
py="sm"
|
|
||||||
className="border-b border-[var(--mantine-color-gray-2)] bg-gray-50/60"
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
size="xs"
|
|
||||||
fw={600}
|
|
||||||
tt="uppercase"
|
|
||||||
c="dimmed"
|
|
||||||
className="tracking-wider"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box px="md" py="xs" className="divide-y divide-gray-100">
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<Stack gap="md">
|
||||||
<StepHeader
|
<StepHeader
|
||||||
title="Review & Submit"
|
title="Review & Submit"
|
||||||
description="Confirm your contract request before sending it for EDR staff review."
|
description="Confirm your contract request before sending it for EDR staff review."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="md">
|
{/* Pricing Card - Prominent at top */}
|
||||||
<ReviewCard title="Contract & Service">
|
|
||||||
<Row
|
|
||||||
label="Type"
|
|
||||||
value={values.contractType === "new" ? "New Contract" : "Renewal"}
|
|
||||||
target={1}
|
|
||||||
/>
|
|
||||||
<Row label="Service" value={serviceType?.name ?? ""} target={2} />
|
|
||||||
</ReviewCard>
|
|
||||||
|
|
||||||
<ReviewCard title="First & Last Mile">
|
|
||||||
<Row
|
|
||||||
label="First Mile"
|
|
||||||
value={
|
|
||||||
values.firstMile.enabled
|
|
||||||
? values.firstMile.pickUpAddress
|
|
||||||
: "Not requested"
|
|
||||||
}
|
|
||||||
target={2}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Last Mile"
|
|
||||||
value={
|
|
||||||
values.lastMile.enabled
|
|
||||||
? values.lastMile.deliveryAddress
|
|
||||||
: "Not requested"
|
|
||||||
}
|
|
||||||
target={2}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Equipment Return"
|
|
||||||
value={
|
|
||||||
values.equipmentReturn === "with_return"
|
|
||||||
? "With Return"
|
|
||||||
: "Without Return"
|
|
||||||
}
|
|
||||||
target={2}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Customs Clearing"
|
|
||||||
value={values.customsClearingEnabled ? "Enabled" : "Not requested"}
|
|
||||||
target={2}
|
|
||||||
/>
|
|
||||||
</ReviewCard>
|
|
||||||
|
|
||||||
<ReviewCard title="Route & Cargo">
|
|
||||||
<Row
|
|
||||||
label="Route"
|
|
||||||
value={`${values.originYard} → ${values.destinationYard}`}
|
|
||||||
target={3}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Workflow"
|
|
||||||
value={
|
|
||||||
direction
|
|
||||||
? direction.charAt(0).toUpperCase() + direction.slice(1)
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
target={3}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Weight (VGM)"
|
|
||||||
value={values.cargoWeight ? `${values.cargoWeight} tons` : ""}
|
|
||||||
target={4}
|
|
||||||
/>
|
|
||||||
<Row label="Cargo" value={cargoValue} target={4} />
|
|
||||||
<Row
|
|
||||||
label="Modifiers"
|
|
||||||
value={
|
|
||||||
[
|
|
||||||
values.isHazardous && "Hazardous",
|
|
||||||
values.isRefrigerated && "Refrigerated",
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(", ") || "None"
|
|
||||||
}
|
|
||||||
target={3}
|
|
||||||
/>
|
|
||||||
</ReviewCard>
|
|
||||||
|
|
||||||
<ReviewCard title="Container & Wagons">
|
|
||||||
<Row label="Containers" value={containerSummary || "—"} target={4} />
|
|
||||||
<Row
|
|
||||||
label="Total VGM"
|
|
||||||
value={totalVgm > 0 ? `${totalVgm.toFixed(1)} tons` : ""}
|
|
||||||
target={4}
|
|
||||||
/>
|
|
||||||
</ReviewCard>
|
|
||||||
|
|
||||||
<ReviewCard title="Documents">
|
|
||||||
<Row
|
|
||||||
label="Attached"
|
|
||||||
value={
|
|
||||||
docsAttached > 0
|
|
||||||
? `${docsAttached} of ${docsTotal} attached`
|
|
||||||
: "None — upload later from the booking page"
|
|
||||||
}
|
|
||||||
target={5}
|
|
||||||
/>
|
|
||||||
</ReviewCard>
|
|
||||||
</SimpleGrid>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="notes"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Textarea
|
|
||||||
{...field}
|
|
||||||
id="notes"
|
|
||||||
label="Additional Notes"
|
|
||||||
placeholder="Any special instructions or notes for EDR operations..."
|
|
||||||
rows={3}
|
|
||||||
radius="md"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="termsAccepted"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field, fieldState }) => (
|
|
||||||
<Checkbox
|
|
||||||
label={
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
I confirm the information is accurate and agree to EDR's{" "}
|
|
||||||
<Text component="span" c="edr-green" fw={500}>
|
|
||||||
freight contract terms and conditions
|
|
||||||
</Text>
|
|
||||||
.
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
checked={field.value}
|
|
||||||
onChange={(e) => field.onChange(e.currentTarget.checked)}
|
|
||||||
error={fieldState.error?.message ?? errors.termsAccepted?.message}
|
|
||||||
color="edr-green"
|
|
||||||
radius="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{pricingPhase === "generating" && (
|
{pricingPhase === "generating" && (
|
||||||
<Card radius="lg" withBorder p="lg">
|
<Card radius="lg" withBorder p="lg" className="border-edr-green border-2">
|
||||||
<Group justify="center" py="lg">
|
<Group justify="center" py="lg">
|
||||||
<Loader size="sm" />
|
<Loader size="sm" />
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
@@ -312,62 +171,198 @@ export function Step8Review({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{pricingPhase === "ready" && pricingData && (
|
{pricingPhase === "ready" && pricingData && (
|
||||||
<Card radius="lg" withBorder p="lg">
|
<Card radius="lg" withBorder p="lg" className="border-edr-green border-2 bg-gradient-to-br from-white to-emerald-50/30">
|
||||||
<Text size="sm" fw={600} tt="uppercase" c="dimmed" mb="sm">
|
<Stack gap="sm">
|
||||||
Price Estimate
|
<Text size="sm" fw={700} tt="uppercase" c="edr-green" className="tracking-wider">
|
||||||
</Text>
|
💳 Price Breakdown
|
||||||
{pricingData.lineItems.map((item) => (
|
</Text>
|
||||||
<Group key={item.code} justify="space-between" py={4}>
|
<Stack gap="xs">
|
||||||
<Text size="sm">{item.description}</Text>
|
{pricingData.lineItems.map((item) => (
|
||||||
<Text size="sm" fw={500}>
|
<Group key={item.code} justify="space-between" py={2}>
|
||||||
{item.amount.toLocaleString()} {item.currency}
|
<Text size="sm" c="dimmed">
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" fw={600}>
|
||||||
|
{item.amount.toLocaleString()} {item.currency}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
<Divider my="xs" />
|
||||||
|
<Group justify="space-between" py={2}>
|
||||||
|
<Text fw={700} size="md">
|
||||||
|
Total
|
||||||
|
</Text>
|
||||||
|
<Text fw={800} size="lg" c="edr-green">
|
||||||
|
{pricingData.totalAmount.toLocaleString()} {pricingData.currency}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
{pricingData.warnings.length > 0 && (
|
||||||
<Divider my="sm" />
|
<Text size="xs" c="orange.7" mt="xs" p="xs" className="bg-orange-50 rounded">
|
||||||
<Group justify="space-between" py={2}>
|
⚠️ {pricingData.warnings.join(", ")}
|
||||||
<Text fw={700}>Total</Text>
|
</Text>
|
||||||
<Text fw={700}>
|
)}
|
||||||
{pricingData.totalAmount.toLocaleString()} {pricingData.currency}
|
<Group mt="md">
|
||||||
</Text>
|
<Button
|
||||||
</Group>
|
color="edr-green"
|
||||||
{pricingData.warnings.length > 0 && (
|
radius="md"
|
||||||
<Text size="xs" c="orange.7" mt="sm">
|
leftSection={<Check size={16} />}
|
||||||
{pricingData.warnings.join(", ")}
|
onClick={onConfirm}
|
||||||
</Text>
|
loading={confirmPending}
|
||||||
)}
|
className="flex-1"
|
||||||
<Group mt="lg">
|
>
|
||||||
<Button
|
{confirmPending ? "Confirming…" : "Confirm"}
|
||||||
color="edr-green"
|
</Button>
|
||||||
radius="md"
|
<Button
|
||||||
leftSection={<Check size={16} />}
|
variant="outline"
|
||||||
onClick={onConfirm}
|
color="edr-green"
|
||||||
loading={confirmPending}
|
radius="md"
|
||||||
>
|
leftSection={<Send size={16} />}
|
||||||
{confirmPending ? "Confirming…" : "Confirm"}
|
onClick={onContinueLater}
|
||||||
</Button>
|
className="flex-1"
|
||||||
<Button
|
>
|
||||||
variant="outline"
|
Continue later
|
||||||
color="edr-green"
|
</Button>
|
||||||
radius="md"
|
<Button
|
||||||
leftSection={<Send size={16} />}
|
variant="outline"
|
||||||
onClick={onContinueLater}
|
color="red"
|
||||||
>
|
radius="md"
|
||||||
Continue later
|
leftSection={!abortPending ? <XCircle size={16} /> : undefined}
|
||||||
</Button>
|
onClick={onAbort}
|
||||||
<Button
|
loading={abortPending}
|
||||||
variant="outline"
|
>
|
||||||
color="red"
|
Abort
|
||||||
radius="md"
|
</Button>
|
||||||
leftSection={!abortPending ? <XCircle size={16} /> : undefined}
|
</Group>
|
||||||
onClick={onAbort}
|
</Stack>
|
||||||
loading={abortPending}
|
|
||||||
>
|
|
||||||
{abortPending ? "Aborting…" : "Abort"}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
{/* Review Details - Compact Cards Grid */}
|
||||||
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing="sm" mt="md">
|
||||||
|
<CompactCard icon={<Package size={16} />} title="Contract & Service">
|
||||||
|
<CompactRow
|
||||||
|
label="Type"
|
||||||
|
value={values.contractType === "new" ? "New Contract" : "Renewal"}
|
||||||
|
target={1}
|
||||||
|
/>
|
||||||
|
<CompactRow label="Service" value={serviceType?.name ?? ""} target={2} />
|
||||||
|
</CompactCard>
|
||||||
|
|
||||||
|
<CompactCard icon={<Route size={16} />} title="Route">
|
||||||
|
<CompactRow
|
||||||
|
label="Origin → Destination"
|
||||||
|
value={`${originYardName} → ${destinationYardName}`}
|
||||||
|
target={3}
|
||||||
|
/>
|
||||||
|
<CompactRow
|
||||||
|
label="Workflow"
|
||||||
|
value={
|
||||||
|
direction ? direction.charAt(0).toUpperCase() + direction.slice(1) : ""
|
||||||
|
}
|
||||||
|
target={3}
|
||||||
|
/>
|
||||||
|
</CompactCard>
|
||||||
|
|
||||||
|
<CompactCard icon={<Truck size={16} />} title="Logistics">
|
||||||
|
<CompactRow
|
||||||
|
label="First Mile"
|
||||||
|
value={
|
||||||
|
values.firstMile.enabled ? values.firstMile.pickUpAddress : "Not requested"
|
||||||
|
}
|
||||||
|
target={2}
|
||||||
|
/>
|
||||||
|
<CompactRow
|
||||||
|
label="Last Mile"
|
||||||
|
value={
|
||||||
|
values.lastMile.enabled ? values.lastMile.deliveryAddress : "Not requested"
|
||||||
|
}
|
||||||
|
target={2}
|
||||||
|
/>
|
||||||
|
<CompactRow
|
||||||
|
label="Equipment Return"
|
||||||
|
value={
|
||||||
|
values.equipmentReturn === "with_return" ? "With Return" : "Without Return"
|
||||||
|
}
|
||||||
|
target={2}
|
||||||
|
/>
|
||||||
|
<CompactRow
|
||||||
|
label="Customs Clearing"
|
||||||
|
value={values.customsClearingEnabled ? "Enabled" : "Not requested"}
|
||||||
|
target={2}
|
||||||
|
/>
|
||||||
|
</CompactCard>
|
||||||
|
|
||||||
|
<CompactCard icon={<Package size={16} />} title="Cargo Details">
|
||||||
|
<CompactRow
|
||||||
|
label="Weight (VGM)"
|
||||||
|
value={values.cargoWeight ? `${values.cargoWeight} tons` : ""}
|
||||||
|
target={4}
|
||||||
|
/>
|
||||||
|
<CompactRow label="Cargo Type" value={cargoValue} target={4} />
|
||||||
|
<CompactRow
|
||||||
|
label="Modifiers"
|
||||||
|
value={
|
||||||
|
[values.isHazardous && "Hazardous", values.isRefrigerated && "Refrigerated"]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ") || "None"
|
||||||
|
}
|
||||||
|
target={3}
|
||||||
|
/>
|
||||||
|
</CompactCard>
|
||||||
|
|
||||||
|
<CompactCard icon={<Package size={16} />} title="Containers">
|
||||||
|
<CompactRow
|
||||||
|
label="Count & Type"
|
||||||
|
value={containerSummary || "—"}
|
||||||
|
target={4}
|
||||||
|
/>
|
||||||
|
<CompactRow
|
||||||
|
label="Total VGM"
|
||||||
|
value={totalVgm > 0 ? `${totalVgm.toFixed(1)} tons` : "—"}
|
||||||
|
target={4}
|
||||||
|
/>
|
||||||
|
</CompactCard>
|
||||||
|
|
||||||
|
<CompactCard icon={<FileText size={16} />} title="Documents">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<Text size="10px" c="dimmed" fw={600} tt="uppercase" className="mb-1 tracking-wider">
|
||||||
|
Attached
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{docsAttached > 0
|
||||||
|
? `${docsAttached} of ${docsTotal}`
|
||||||
|
: "None"}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setStep(5)}
|
||||||
|
className="shrink-0 text-10px font-medium text-emerald-600 hover:underline whitespace-nowrap ml-2 mt-2"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</CompactCard>
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
{/* Notes */}
|
||||||
|
<Controller
|
||||||
|
name="notes"
|
||||||
|
control={form.control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Textarea
|
||||||
|
{...field}
|
||||||
|
id="notes"
|
||||||
|
label="Additional Notes"
|
||||||
|
placeholder="Any special instructions or notes for EDR operations…"
|
||||||
|
rows={2}
|
||||||
|
radius="md"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user