Merge pull request #738 from Tria-plc/freight_feature/usermanagement

fix issue
This commit is contained in:
marshal
2026-07-16 16:00:07 +03:00
committed by GitHub
6 changed files with 160 additions and 12 deletions

View File

@@ -13,3 +13,6 @@ export function fileViewUrl(fileId: string, download = false): string {
const base = `${API_BASE_URL}/api/files/${fileId}`;
return download ? `${base}?download=1` : base;
}

View File

@@ -5,6 +5,7 @@ import {
Alert,
Badge,
Box,
Button,
Grid,
Group,
Loader,
@@ -21,10 +22,18 @@ import {
CheckCircle2,
Clock,
PackageCheck,
RefreshCw,
ShieldCheck,
} from "lucide-react";
import { Link } from "react-router-dom";
import { useAuth } from "@/auth/useAuth";
import {
FREIGHT_PERMS,
hasPermission,
isDjiboutiGl,
} from "@/lib/permissions";
import { ClearanceOpsTabs } from "@/components/contracts/ClearanceOpsTabs";
import { PageContainer } from "@/components/page/PageContainer";
import { PageHeader } from "@/components/page/PageHeader";
@@ -44,6 +53,7 @@ import {
export default function ContractClearanceDetailPage() {
const { id } = useParams<{ id: string }>();
const { view, viewer } = useFileViewer();
const { user } = useAuth();
const { data: contract, refetch: refetchContract } = useContractDetail(id);
const {
@@ -106,6 +116,16 @@ export default function ContractClearanceDetailPage() {
const reviewReadOnly = shipmentLocked;
const queriesLocked = Boolean(clearance?.preClearanceFinalized);
const bookingHref = `/dashboard/contracts/${id}/create-booking`;
// The GL-created booking expired unpaid — the slot is free again and GL
// rebooks on the customer's behalf (customs bookings are never self-booked).
const bookingExpired = clearance?.linkedBookingStatus === "EXPIRED";
const canRebook =
bookingExpired &&
hasPermission(user, FREIGHT_PERMS.contracts.createBooking) &&
!isDjiboutiGl(user);
const rebookHref = linkedBookingId
? `${bookingHref}?copyFrom=${linkedBookingId}`
: bookingHref;
const { data: bookingMilestones, refetch: refetchBookingMilestones } =
useBookingMilestones(linkedBookingId);
@@ -165,7 +185,16 @@ export default function ContractClearanceDetailPage() {
{ label: reference },
]}
meta={
bookingAlreadyCreated ? (
bookingExpired ? (
<Badge
variant="light"
color="orange"
radius="sm"
leftSection={<RefreshCw size={13} />}
>
Payment expired rebook
</Badge>
) : bookingAlreadyCreated ? (
<Badge
variant="light"
color="blue"
@@ -211,7 +240,49 @@ export default function ContractClearanceDetailPage() {
it can actually create the booking without checking the schedule board. */}
{id ? <GlUpcomingWindowsSection contractId={id} /> : null}
{bookingAlreadyCreated ? (
{bookingExpired ? (
<Alert
color="orange"
radius="md"
icon={<RefreshCw size={16} />}
title="Booking expired — payment not received"
>
<Stack gap="sm" align="flex-start">
<Text size="sm">
The customer did not pay before the deadline, so the booking
expired and its train slot was released. The contract slot is
free again GL Ethiopia can rebook on the customer&apos;s
behalf without re-running clearance.
{linkedBookingId ? (
<>
{" "}
<Text
component={Link}
to={`/dashboard/bookings/${linkedBookingId}/clearance`}
inherit
fw={600}
c="orange.8"
>
View expired booking
</Text>
</>
) : null}
</Text>
{canRebook ? (
<Button
component={Link}
to={rebookHref}
color="grape"
radius="md"
size="sm"
leftSection={<RefreshCw size={15} />}
>
Rebook for customer
</Button>
) : null}
</Stack>
</Alert>
) : bookingAlreadyCreated ? (
<Alert
color="blue"
radius="md"

View File

@@ -92,6 +92,10 @@ interface ClearanceRow {
ready: boolean;
/** true once GL Ethiopia created the shipment booking. */
bookingCreated: boolean;
/** true when the created booking EXPIRED unpaid — GL must rebook. */
paymentExpired: boolean;
/** The expired booking, so rebook can copy its cargo. */
expiredBookingId: string | null;
}
function yardLabel(
@@ -145,6 +149,13 @@ function toClearanceRow(contract: Freight.IContract): ClearanceRow {
status: contract.status,
ready: contract.status === "CLEARANCE_READY_FOR_BOOKING",
bookingCreated: contract.status === "ACTIVE_SHIPMENT_IN_PROGRESS",
paymentExpired:
contract.status === "ACTIVE_SHIPMENT_IN_PROGRESS" &&
contract.latestCycleBookingStatus === "EXPIRED",
expiredBookingId:
contract.latestCycleBookingStatus === "EXPIRED"
? (contract.latestCycleBookingId ?? null)
: null,
};
}
@@ -186,6 +197,24 @@ function DirectionIcon({ direction }: { direction: string }) {
}
function StatusBadge({ row }: { row: ClearanceRow }) {
if (row.paymentExpired) {
return (
<Tooltip
label="The customer did not pay in time — the booking expired. GL rebooks on the customer's behalf."
withArrow
>
<Badge
size="sm"
variant="light"
color="orange"
radius="sm"
leftSection={<RefreshCw size={12} />}
>
Payment expired
</Badge>
</Tooltip>
);
}
if (row.bookingCreated) {
return (
<Tooltip label="GL Ethiopia created the shipment booking" withArrow>
@@ -519,6 +548,27 @@ export default function ContractClearanceListPage() {
Create booking
</Button>
</Group>
) : row.original.paymentExpired && canCreateBooking ? (
<Group justify="flex-end" pr="xs">
<Button
size="compact-sm"
color="grape"
radius="md"
leftSection={<RefreshCw size={14} />}
onClick={(e) => {
e.stopPropagation();
navigate(
`/dashboard/contracts/${row.original.id}/create-booking${
row.original.expiredBookingId
? `?copyFrom=${row.original.expiredBookingId}`
: ""
}`,
);
}}
>
Rebook
</Button>
</Group>
) : (
<Group justify="flex-end" pr="xs">
<ChevronRight size={16} className="text-muted-foreground" />