mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 09:42:53 +00:00
add clearance history and operations history endpoints, update UI components for history view
This commit is contained in:
@@ -506,4 +506,28 @@ export class ContractClearanceService {
|
||||
sortOrder: filter.sortOrder,
|
||||
});
|
||||
}
|
||||
|
||||
/** GL ET history: contracts that completed Path B clearance. */
|
||||
async history(filter: FilterContractDto): Promise<PaginatedContracts> {
|
||||
return this.contractsRepository.findAllPaginated({
|
||||
page: filter.page ?? 1,
|
||||
pageSize: filter.pageSize ?? 50,
|
||||
statuses: ['CLEARANCE_READY_FOR_BOOKING', 'ACTIVE', 'CLOSED', 'CANCELLED'],
|
||||
customsClearingEnabled: true,
|
||||
sortBy: filter.sortBy ?? 'createdAt',
|
||||
sortOrder: filter.sortOrder ?? 'DESC',
|
||||
});
|
||||
}
|
||||
|
||||
/** Operations history: contracts that completed Path A self-clearance review. */
|
||||
async opsHistory(filter: FilterContractDto): Promise<PaginatedContracts> {
|
||||
return this.contractsRepository.findAllPaginated({
|
||||
page: filter.page ?? 1,
|
||||
pageSize: filter.pageSize ?? 50,
|
||||
statuses: ['CLEARANCE_READY_FOR_BOOKING', 'ACTIVE', 'CLOSED', 'CANCELLED'],
|
||||
customsClearingEnabled: false,
|
||||
sortBy: filter.sortBy ?? 'createdAt',
|
||||
sortOrder: filter.sortOrder ?? 'DESC',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,20 @@ export class ContractsController {
|
||||
return this.clearanceService.opsFinalize(id);
|
||||
}
|
||||
|
||||
@Get('clearance/history')
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.finalizeClearance)
|
||||
@ApiOperation({ summary: 'GL ET clearance history: contracts that completed Path B clearance' })
|
||||
clearanceHistory(@Query() filter: FilterContractDto) {
|
||||
return this.clearanceService.history(filter);
|
||||
}
|
||||
|
||||
@Get('clearance/ops-history')
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.opsClearanceReview)
|
||||
@ApiOperation({ summary: 'Operations clearance history: contracts that completed Path A self-clearance' })
|
||||
opsClearanceHistory(@Query() filter: FilterContractDto) {
|
||||
return this.clearanceService.opsHistory(filter);
|
||||
}
|
||||
|
||||
// ── Booking under contract (Path A customer / Path B GL ET) ────────────────
|
||||
|
||||
@Post(':id/bookings')
|
||||
|
||||
@@ -112,9 +112,7 @@ export function ContractClearanceReviewSection({
|
||||
);
|
||||
const glDocs = useMemo(
|
||||
() =>
|
||||
(clearance?.documents ?? []).filter(
|
||||
(d) => d.uploadedBy === "gl_et" || d.uploadedBy === "gl_dj",
|
||||
),
|
||||
(clearance?.documents ?? []).filter((d) => d.uploadedBy === "gl"),
|
||||
[clearance],
|
||||
);
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ export const QUERY_KEYS = {
|
||||
clearance: (id: string) => ["contracts", "clearance", id] as const,
|
||||
clearanceQueue: (region?: string) =>
|
||||
["contracts", "clearance-queue", region ?? "ET"] as const,
|
||||
clearanceHistory: (region?: string) =>
|
||||
["contracts", "clearance-history", region ?? "ET"] as const,
|
||||
milestones: (id: string) => ["contracts", "milestones", id] as const,
|
||||
capacity: (id: string) => ["contracts", "capacity", id] as const,
|
||||
bookingMilestones: (bookingId: string) =>
|
||||
|
||||
@@ -150,6 +150,8 @@ export const URL_CONSTANTS = {
|
||||
`/contracts/${id}/clearance/ops-review`,
|
||||
OPS_CLEARANCE_FINALIZE: (id: string) =>
|
||||
`/contracts/${id}/clearance/ops-finalize`,
|
||||
CLEARANCE_HISTORY: "/contracts/clearance/history",
|
||||
OPS_CLEARANCE_HISTORY: "/contracts/clearance/ops-history",
|
||||
BOOKINGS: (id: string) => `/contracts/${id}/bookings`,
|
||||
CAPACITY: (id: string) => `/contracts/${id}/capacity`,
|
||||
MILESTONES: (id: string) => `/contracts/${id}/milestones`,
|
||||
|
||||
@@ -61,6 +61,22 @@ export function useOpsClearanceQueue(enabled = true) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useContractClearanceHistory(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("GL"),
|
||||
queryFn: () => contractsService.getClearanceHistory(),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOpsClearanceHistory(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("OPS"),
|
||||
queryFn: () => contractsService.getOpsClearanceHistory(),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useContractMilestones(id: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: QUERY_KEYS.CONTRACTS.milestones(id ?? ""),
|
||||
|
||||
@@ -15,8 +15,10 @@ import {
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
ArrowRight,
|
||||
CheckCircle,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
History,
|
||||
Inbox,
|
||||
LayoutGrid,
|
||||
RefreshCw,
|
||||
@@ -36,15 +38,20 @@ import {
|
||||
} from "@edr/ui-common";
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
import { ContractStatusBadge } from "@/components/contracts/ContractStatusBadge";
|
||||
import { PageContainer } from "@/components/page/PageContainer";
|
||||
import { PageHeader } from "@/components/page/PageHeader";
|
||||
import { KpiStrip } from "@/components/page/KpiStrip";
|
||||
import { bookingTable } from "@/components/bookings/booking-ui.styles";
|
||||
import {
|
||||
useContractClearanceHistory,
|
||||
useContractClearanceQueue,
|
||||
useOpsClearanceHistory,
|
||||
useOpsClearanceQueue,
|
||||
} from "@/hooks/contracts/useContracts";
|
||||
|
||||
type PageTab = "queue" | "history";
|
||||
|
||||
type ViewMode = "table" | "cards";
|
||||
|
||||
interface ClearanceRow {
|
||||
@@ -56,6 +63,7 @@ interface ClearanceRow {
|
||||
originLabel: string;
|
||||
destinationLabel: string;
|
||||
contractKind: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
function yardLabel(
|
||||
@@ -83,6 +91,7 @@ function toClearanceRow(contract: Freight.IContract): ClearanceRow {
|
||||
originLabel: yardLabel(first?.originYard),
|
||||
destinationLabel: yardLabel(last?.destinationYard),
|
||||
contractKind: contract.contractKind,
|
||||
status: contract.status,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,13 +128,19 @@ export default function ContractClearanceListPage({
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = useState("");
|
||||
const [view, setView] = useState<ViewMode>("table");
|
||||
const [pageTab, setPageTab] = useState<PageTab>("queue");
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
|
||||
const glQueue = useContractClearanceQueue(!opsMode);
|
||||
const opsQueue = useOpsClearanceQueue(opsMode);
|
||||
const isHistory = pageTab === "history";
|
||||
|
||||
const glQueue = useContractClearanceQueue(!opsMode && !isHistory);
|
||||
const opsQueue = useOpsClearanceQueue(opsMode && !isHistory);
|
||||
const glHistory = useContractClearanceHistory(!opsMode && isHistory);
|
||||
const opsHistory = useOpsClearanceHistory(opsMode && isHistory);
|
||||
|
||||
const { data, isLoading, isError, isFetching, refetch } = opsMode
|
||||
? opsQueue
|
||||
: glQueue;
|
||||
? (isHistory ? opsHistory : opsQueue)
|
||||
: (isHistory ? glHistory : glQueue);
|
||||
|
||||
const allRows = useMemo(
|
||||
() => (data?.items ?? []).map(toClearanceRow),
|
||||
@@ -229,11 +244,14 @@ export default function ContractClearanceListPage({
|
||||
{
|
||||
id: "status",
|
||||
header: () => <span className={bookingTable.headerCell}>Status</span>,
|
||||
cell: () => (
|
||||
<Badge size="sm" variant="light" color="edr-green" radius="sm">
|
||||
Under review
|
||||
</Badge>
|
||||
),
|
||||
cell: ({ row }) =>
|
||||
isHistory ? (
|
||||
<ContractStatusBadge status={row.original.status} />
|
||||
) : (
|
||||
<Badge size="sm" variant="light" color="edr-green" radius="sm">
|
||||
Under review
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "go",
|
||||
@@ -261,11 +279,11 @@ export default function ContractClearanceListPage({
|
||||
meta={
|
||||
<Badge
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
color={isHistory ? "gray" : "edr-green"}
|
||||
radius="sm"
|
||||
leftSection={<ShieldCheck size={13} />}
|
||||
leftSection={isHistory ? <History size={13} /> : <ShieldCheck size={13} />}
|
||||
>
|
||||
{counts.all} awaiting review
|
||||
{isHistory ? `${counts.all} completed` : `${counts.all} awaiting review`}
|
||||
</Badge>
|
||||
}
|
||||
action={
|
||||
@@ -282,28 +300,53 @@ export default function ContractClearanceListPage({
|
||||
}
|
||||
/>
|
||||
|
||||
<Group>
|
||||
<SegmentedControl
|
||||
size="sm"
|
||||
radius="md"
|
||||
value={pageTab}
|
||||
onChange={(v) => {
|
||||
setPageTab(v as PageTab);
|
||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||
}}
|
||||
data={[
|
||||
{
|
||||
value: "queue",
|
||||
label: (
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<Inbox size={15} />
|
||||
<Box visibleFrom="xs">Queue</Box>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "history",
|
||||
label: (
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<History size={15} />
|
||||
<Box visibleFrom="xs">History</Box>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<KpiStrip
|
||||
loading={isLoading}
|
||||
items={[
|
||||
{
|
||||
label: "Awaiting review",
|
||||
value: counts.all,
|
||||
icon: Inbox,
|
||||
color: "edr-green",
|
||||
},
|
||||
{
|
||||
label: "Import",
|
||||
value: counts.import,
|
||||
icon: Truck,
|
||||
color: "edr-green",
|
||||
},
|
||||
{
|
||||
label: "Export",
|
||||
value: counts.export,
|
||||
icon: ShipWheel,
|
||||
color: "gray",
|
||||
},
|
||||
]}
|
||||
items={
|
||||
isHistory
|
||||
? [
|
||||
{ label: "Total cleared", value: counts.all, icon: CheckCircle, color: "edr-green" },
|
||||
{ label: "Import", value: counts.import, icon: Truck, color: "edr-green" },
|
||||
{ label: "Export", value: counts.export, icon: ShipWheel, color: "gray" },
|
||||
]
|
||||
: [
|
||||
{ label: "Awaiting review", value: counts.all, icon: Inbox, color: "edr-green" },
|
||||
{ label: "Import", value: counts.import, icon: Truck, color: "edr-green" },
|
||||
{ label: "Export", value: counts.export, icon: ShipWheel, color: "gray" },
|
||||
]
|
||||
}
|
||||
/>
|
||||
|
||||
<Card p={0} withBorder shadow="sm" radius="lg">
|
||||
@@ -401,6 +444,7 @@ export default function ContractClearanceListPage({
|
||||
rows={pagedRows}
|
||||
loading={isLoading}
|
||||
onOpen={openDetail}
|
||||
isHistory={isHistory}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -414,10 +458,12 @@ function ClearanceCardGrid({
|
||||
rows,
|
||||
loading,
|
||||
onOpen,
|
||||
isHistory = false,
|
||||
}: {
|
||||
rows: ClearanceRow[];
|
||||
loading: boolean;
|
||||
onOpen: (id: string) => void;
|
||||
isHistory?: boolean;
|
||||
}) {
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -449,7 +495,7 @@ function ClearanceCardGrid({
|
||||
}}
|
||||
>
|
||||
{rows.map((r) => (
|
||||
<ClearanceCard key={r.id} row={r} onOpen={() => onOpen(r.id)} />
|
||||
<ClearanceCard key={r.id} row={r} onOpen={() => onOpen(r.id)} isHistory={isHistory} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
@@ -458,9 +504,11 @@ function ClearanceCardGrid({
|
||||
function ClearanceCard({
|
||||
row,
|
||||
onOpen,
|
||||
isHistory = false,
|
||||
}: {
|
||||
row: ClearanceRow;
|
||||
onOpen: () => void;
|
||||
isHistory?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Card
|
||||
@@ -497,8 +545,8 @@ function ClearanceCard({
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
<Badge size="sm" variant="light" color="edr-green" radius="sm">
|
||||
Under review
|
||||
<Badge size="sm" variant="light" color={isHistory ? "gray" : "edr-green"} radius="sm">
|
||||
{isHistory ? "Cleared" : "Under review"}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
|
||||
@@ -212,6 +212,18 @@ export const contractsService = {
|
||||
};
|
||||
},
|
||||
|
||||
getClearanceHistory: async (): Promise<PaginatedContracts> => {
|
||||
const response = await client.get<PaginatedContracts>(C.CLEARANCE_HISTORY);
|
||||
const data = unwrap(response.data);
|
||||
return { items: (data.items ?? []) as Freight.IContract[], total: data.total ?? 0 };
|
||||
},
|
||||
|
||||
getOpsClearanceHistory: async (): Promise<PaginatedContracts> => {
|
||||
const response = await client.get<PaginatedContracts>(C.OPS_CLEARANCE_HISTORY);
|
||||
const data = unwrap(response.data);
|
||||
return { items: (data.items ?? []) as Freight.IContract[], total: data.total ?? 0 };
|
||||
},
|
||||
|
||||
opsReviewClearanceDocument: (
|
||||
id: string,
|
||||
payload: { fileKey: string; status: "APPROVED" | "QUERIED"; note?: string },
|
||||
|
||||
Reference in New Issue
Block a user