mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix issue
This commit is contained in:
@@ -110,6 +110,7 @@ function makeService(overrides?: {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue({ id: 'ta-1', name: 'Ahmed Bourhan' }),
|
.mockResolvedValue({ id: 'ta-1', name: 'Ahmed Bourhan' }),
|
||||||
} as never, // transit agents
|
} as never, // transit agents
|
||||||
|
{ findAll: jest.fn().mockResolvedValue([]) } as never, // contracts repository
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { In } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
ContractDocPhase,
|
ContractDocPhase,
|
||||||
isDeliveryOrderFileCode,
|
isDeliveryOrderFileCode,
|
||||||
@@ -29,6 +30,7 @@ import { ClearanceMilestoneService } from './clearance-milestone.service';
|
|||||||
import { GlOperationsService } from './gl-operations.service';
|
import { GlOperationsService } from './gl-operations.service';
|
||||||
import { GlExchangeService } from './gl-exchange.service';
|
import { GlExchangeService } from './gl-exchange.service';
|
||||||
import { TransitAgentsService } from '../transit-agents/transit-agents.service';
|
import { TransitAgentsService } from '../transit-agents/transit-agents.service';
|
||||||
|
import { ContractsRepository } from './contracts.repository';
|
||||||
import { AdviseContractDutyDto } from './dto/phased-clearance.dto';
|
import { AdviseContractDutyDto } from './dto/phased-clearance.dto';
|
||||||
import { buildWorkflowFiles, belongsOnDjClearanceQueue, belongsOnEtClearanceQueue, DJ_BOOKING_QUEUE_STATUSES, persistDeclarationUploads, persistDeliveryOrderUploads, persistDraftDeclarationUploads, persistReleaseOrderUploads, persistTransitPermitUploads, PHASED_CUSTOMS_BOOKING_QUEUE_STATUSES } from './phased-clearance.util';
|
import { buildWorkflowFiles, belongsOnDjClearanceQueue, belongsOnEtClearanceQueue, DJ_BOOKING_QUEUE_STATUSES, persistDeclarationUploads, persistDeliveryOrderUploads, persistDraftDeclarationUploads, persistReleaseOrderUploads, persistTransitPermitUploads, PHASED_CUSTOMS_BOOKING_QUEUE_STATUSES } from './phased-clearance.util';
|
||||||
|
|
||||||
@@ -155,6 +157,7 @@ export class BookingClearanceService {
|
|||||||
private readonly notifier: BookingLifecycleNotifierService,
|
private readonly notifier: BookingLifecycleNotifierService,
|
||||||
private readonly glExchangeService: GlExchangeService,
|
private readonly glExchangeService: GlExchangeService,
|
||||||
private readonly transitAgentsService: TransitAgentsService,
|
private readonly transitAgentsService: TransitAgentsService,
|
||||||
|
private readonly contractsRepository: ContractsRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async assertPhasedCustoms(booking: Booking): Promise<void> {
|
private async assertPhasedCustoms(booking: Booking): Promise<void> {
|
||||||
@@ -988,7 +991,39 @@ export class BookingClearanceService {
|
|||||||
const milestones = await this.workflowService.listMilestonesForBooking(b.id);
|
const milestones = await this.workflowService.listMilestonesForBooking(b.id);
|
||||||
if (belongsOnEtClearanceQueue(milestones)) filtered.push(b);
|
if (belongsOnEtClearanceQueue(milestones)) filtered.push(b);
|
||||||
}
|
}
|
||||||
return filtered;
|
return this.attachContractSummary(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue rows show the parent contract's reference and lane. Booking has no
|
||||||
|
* contract relation, and a bare initiated instance may not carry yards yet —
|
||||||
|
* so batch-load the contracts (with routes) and fill in what's missing:
|
||||||
|
* `contractReference` always, origin/destination yards only when the booking
|
||||||
|
* lacks them (its own route wins).
|
||||||
|
*/
|
||||||
|
private async attachContractSummary(bookings: Booking[]): Promise<Booking[]> {
|
||||||
|
const ids = [...new Set(bookings.map((b) => b.contractId).filter(Boolean))] as string[];
|
||||||
|
if (!ids.length) return bookings;
|
||||||
|
const contracts = await this.contractsRepository.findAll({
|
||||||
|
where: { id: In(ids) },
|
||||||
|
relations: { routes: { originYard: true, destinationYard: true } },
|
||||||
|
});
|
||||||
|
const byId = new Map(contracts.map((c) => [c.id, c]));
|
||||||
|
for (const b of bookings) {
|
||||||
|
const contract = b.contractId ? byId.get(b.contractId) : undefined;
|
||||||
|
if (!contract) continue;
|
||||||
|
const row = b as Booking & { contractReference?: string | null };
|
||||||
|
row.contractReference = contract.reference ?? null;
|
||||||
|
if (b.originYard && b.destinationYard) continue;
|
||||||
|
const routes = contract.routes ?? [];
|
||||||
|
const route =
|
||||||
|
routes.find((r) => r.id === b.contractRouteId) ??
|
||||||
|
(routes.length === 1 ? routes[0] : undefined);
|
||||||
|
if (!route) continue;
|
||||||
|
b.originYard = b.originYard ?? route.originYard;
|
||||||
|
b.destinationYard = b.destinationYard ?? route.destinationYard;
|
||||||
|
}
|
||||||
|
return bookings;
|
||||||
}
|
}
|
||||||
|
|
||||||
async djQueue(): Promise<Booking[]> {
|
async djQueue(): Promise<Booking[]> {
|
||||||
@@ -1008,6 +1043,6 @@ export class BookingClearanceService {
|
|||||||
filtered.push(b);
|
filtered.push(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered;
|
return this.attachContractSummary(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,47 @@ import {
|
|||||||
summarizeRequestedCargo,
|
summarizeRequestedCargo,
|
||||||
} from "@/features/clearance/requestedCargo";
|
} from "@/features/clearance/requestedCargo";
|
||||||
import { contractsService } from "@/services/contracts.service";
|
import { contractsService } from "@/services/contracts.service";
|
||||||
|
import "./contract-clearance-table.css";
|
||||||
|
|
||||||
|
/** Yards carry `label` (API) — older shapes used `name`/`code`. */
|
||||||
|
function yardLabel(
|
||||||
|
yard?: { label?: string; code?: string; name?: string } | null,
|
||||||
|
): string {
|
||||||
|
if (!yard) return "—";
|
||||||
|
return yard.label ?? yard.name ?? yard.code ?? "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Origin → Destination", wrapping past 120px as "Addis Ababa" /
|
||||||
|
* "→ Djibouti": the arrow is glued to the destination with an nbsp, and
|
||||||
|
* text wraps normally (the table's cells are otherwise nowrap) so a long
|
||||||
|
* lane never spills into the next column.
|
||||||
|
*/
|
||||||
|
function RouteLabel({
|
||||||
|
origin,
|
||||||
|
destination,
|
||||||
|
}: {
|
||||||
|
origin: string;
|
||||||
|
destination: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
size="sm"
|
||||||
|
maw={120}
|
||||||
|
lh={1.35}
|
||||||
|
style={{ whiteSpace: "normal", overflowWrap: "anywhere" }}
|
||||||
|
>
|
||||||
|
{origin}{" "}
|
||||||
|
<ArrowRight
|
||||||
|
size={13}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
style={{ display: "inline-block", verticalAlign: "-2px" }}
|
||||||
|
/>
|
||||||
|
{"\u00A0"}
|
||||||
|
{destination}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function CustomsBadge({ customs }: { customs: boolean }) {
|
function CustomsBadge({ customs }: { customs: boolean }) {
|
||||||
return customs ? (
|
return customs ? (
|
||||||
@@ -118,8 +159,8 @@ export default function ContractClearanceListPage() {
|
|||||||
id: b.id,
|
id: b.id,
|
||||||
reference: b.reference,
|
reference: b.reference,
|
||||||
customerLabel: b.company?.name ?? b.governmentInstitution ?? "—",
|
customerLabel: b.company?.name ?? b.governmentInstitution ?? "—",
|
||||||
originLabel: b.originYard?.name ?? "—",
|
originLabel: yardLabel(b.originYard),
|
||||||
destinationLabel: b.destinationYard?.name ?? "—",
|
destinationLabel: yardLabel(b.destinationYard),
|
||||||
tradeDirection: b.tradeDirection ?? "—",
|
tradeDirection: b.tradeDirection ?? "—",
|
||||||
freightType: b.freightType ?? "—",
|
freightType: b.freightType ?? "—",
|
||||||
status: b.status,
|
status: b.status,
|
||||||
@@ -430,11 +471,10 @@ function ShipmentBookingsTable({
|
|||||||
id: "route",
|
id: "route",
|
||||||
header: () => <span className={bookingTable.headerCell}>Route</span>,
|
header: () => <span className={bookingTable.headerCell}>Route</span>,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Group gap={6} wrap="nowrap">
|
<RouteLabel
|
||||||
<Text size="sm">{row.original.originLabel}</Text>
|
origin={row.original.originLabel}
|
||||||
<ArrowRight size={13} className="shrink-0 text-muted-foreground" />
|
destination={row.original.destinationLabel}
|
||||||
<Text size="sm">{row.original.destinationLabel}</Text>
|
/>
|
||||||
</Group>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -600,13 +640,13 @@ function ShipmentBookingsTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box style={{ overflowX: "auto" }} px="xs" pb="xs">
|
<Box w="100%" miw={0} style={{ overflowX: "auto" }}>
|
||||||
<DataTable<ShipmentBookingRow, unknown>
|
<DataTable<ShipmentBookingRow, unknown>
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={rows}
|
data={rows}
|
||||||
status={loading ? "loading" : error ? "error" : "success"}
|
status={loading ? "loading" : error ? "error" : "success"}
|
||||||
onRowClick={(row) => onOpen(row.id)}
|
onRowClick={(row) => onOpen(row.id)}
|
||||||
containerClassName="border-0 shadow-none bg-transparent [&_th]:max-w-[100px] [&_td]:max-w-[100px] [&_td]:break-words"
|
containerClassName="edr-clearance-table border-0 shadow-none rounded-none bg-transparent"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import { KpiStrip } from "@/components/page/KpiStrip";
|
|||||||
import { bookingTable } from "@/components/bookings/booking-ui.styles";
|
import { bookingTable } from "@/components/bookings/booking-ui.styles";
|
||||||
import { useBookingDjClearanceQueue } from "@/hooks/bookings/useBookings";
|
import { useBookingDjClearanceQueue } from "@/hooks/bookings/useBookings";
|
||||||
import type { BookingDetail } from "@/types/booking";
|
import type { BookingDetail } from "@/types/booking";
|
||||||
|
import "./contract-clearance-table.css";
|
||||||
|
|
||||||
const prettyStatus = (s?: string | null) =>
|
const prettyStatus = (s?: string | null) =>
|
||||||
(s ?? "")
|
(s ?? "")
|
||||||
@@ -214,15 +215,24 @@ function RouteCell({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Stack gap={4} py={2}>
|
<Stack gap={4} py={2}>
|
||||||
<Group gap={6} wrap="nowrap">
|
{/* Wraps past 120px as "Addis Ababa" / "→ Djibouti"; text wraps
|
||||||
<Text size="sm" fw={500}>
|
normally (cells are otherwise nowrap) so it never spills over. */}
|
||||||
{origin}
|
<Text
|
||||||
</Text>
|
size="sm"
|
||||||
<ArrowRight size={14} className="shrink-0 text-muted-foreground" />
|
fw={500}
|
||||||
<Text size="sm" fw={500}>
|
maw={120}
|
||||||
{destination}
|
lh={1.35}
|
||||||
</Text>
|
style={{ whiteSpace: "normal", overflowWrap: "anywhere" }}
|
||||||
</Group>
|
>
|
||||||
|
{origin}{" "}
|
||||||
|
<ArrowRight
|
||||||
|
size={13}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
style={{ display: "inline-block", verticalAlign: "-2px" }}
|
||||||
|
/>
|
||||||
|
{"\u00A0"}
|
||||||
|
{destination}
|
||||||
|
</Text>
|
||||||
<Group gap={8} align="center">
|
<Group gap={8} align="center">
|
||||||
<DirectionIcon direction={direction} />
|
<DirectionIcon direction={direction} />
|
||||||
<Badge size="xs" variant="default" radius="sm">
|
<Badge size="xs" variant="default" radius="sm">
|
||||||
@@ -676,7 +686,7 @@ export default function GlDjiboutiClearanceListPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ overflowX: "auto" }} px="xs" pb="xs">
|
<Box w="100%" miw={0} style={{ overflowX: "auto" }}>
|
||||||
<DataTable<ShipmentRow, unknown>
|
<DataTable<ShipmentRow, unknown>
|
||||||
columns={shipmentColumns}
|
columns={shipmentColumns}
|
||||||
data={pagedShipmentRows}
|
data={pagedShipmentRows}
|
||||||
@@ -694,7 +704,7 @@ export default function GlDjiboutiClearanceListPage() {
|
|||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
pageCount,
|
pageCount,
|
||||||
}}
|
}}
|
||||||
containerClassName="border-0 shadow-none bg-transparent [&_th]:max-w-[100px] [&_td]:max-w-[100px] [&_td]:break-words"
|
containerClassName="edr-clearance-table border-0 shadow-none rounded-none bg-transparent"
|
||||||
footer={DataTableFooter}
|
footer={DataTableFooter}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Scoped to .edr-clearance-table — the DataTable container div on the
|
||||||
|
* Document Clearance hubs (GL Ethiopia + GL Djibouti). Mirrors the portal's /bookings table
|
||||||
|
* (bookings-table.css): content-sized columns with a 100px floor, no
|
||||||
|
* truncation, horizontal scroll when the table outgrows the card, sticky
|
||||||
|
* header row and a sticky shadowed action column.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table {
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* width: max-content — the table is exactly as wide as its columns' content
|
||||||
|
* needs, never squeezed to fit the viewport; the container scrolls instead.
|
||||||
|
* min-width: 100% keeps it filling the card when content is narrow.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table table {
|
||||||
|
table-layout: auto;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 100px floor, no ceiling: cells grow to fit their text, nothing is clipped. */
|
||||||
|
.edr-clearance-table th,
|
||||||
|
.edr-clearance-table td:not([colspan]) {
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: none;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mantine Badge caps itself at max-width: 100%; inside an auto-layout table
|
||||||
|
* cell that resolves against min-content and clips the label. Let badges size
|
||||||
|
* to their text so the column grows to fit them.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table .mantine-Badge-root {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mantine Group's preventGrowOverflow caps every child at 100%/N of the cell.
|
||||||
|
* In an auto-width table cell that resolves against min-content and collapses
|
||||||
|
* the badges/text in the Type, Route and Status columns to nothing. Let group
|
||||||
|
* children size to their content; the column grows and the container scrolls.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table .mantine-Group-root > * {
|
||||||
|
max-width: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky header row. */
|
||||||
|
.edr-clearance-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sticky action column, shrunk to its content. The width overrides the inline
|
||||||
|
* width DataTable stamps from tanstack's column size — hence !important.
|
||||||
|
* `:not([colspan])` keeps the full-width error/empty rows out.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table th:last-child,
|
||||||
|
.edr-clearance-table td:last-child:not([colspan]) {
|
||||||
|
width: 1% !important;
|
||||||
|
min-width: 0;
|
||||||
|
position: sticky;
|
||||||
|
right: 0;
|
||||||
|
box-shadow: -12px 0 16px -6px rgba(16, 32, 47, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sticky cells sit above the scrolling ones, so they need their own opaque
|
||||||
|
* background or the columns underneath show through.
|
||||||
|
*/
|
||||||
|
.edr-clearance-table td:last-child:not([colspan]) {
|
||||||
|
background: #f5f8fb;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row hover uses the tailwind `hover:bg-accent` class on the <tr>. */
|
||||||
|
.edr-clearance-table tbody tr:hover td:last-child:not([colspan]) {
|
||||||
|
background: var(--accent, #f4fbf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header cell is sticky on both axes — it must outrank the body's sticky column. */
|
||||||
|
.edr-clearance-table th:last-child {
|
||||||
|
background: #f4f7fa;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
@@ -5,14 +5,20 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Group,
|
Group,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
SimpleGrid,
|
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { RefreshCw, Search, X } from "lucide-react";
|
import {
|
||||||
|
Banknote,
|
||||||
|
CircleDollarSign,
|
||||||
|
Landmark,
|
||||||
|
RefreshCw,
|
||||||
|
Search,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
@@ -22,6 +28,7 @@ import {
|
|||||||
formatMoney,
|
formatMoney,
|
||||||
humanize,
|
humanize,
|
||||||
} from "@/components/customers";
|
} from "@/components/customers";
|
||||||
|
import { KpiStrip } from "@/components/page";
|
||||||
import CreditInvoiceActions from "@/components/shipping-lines/CreditInvoiceActions";
|
import CreditInvoiceActions from "@/components/shipping-lines/CreditInvoiceActions";
|
||||||
import { useExchangeSettingsQuery } from "@/hooks/useExchangeSettings";
|
import { useExchangeSettingsQuery } from "@/hooks/useExchangeSettings";
|
||||||
import { api } from "@/services/api";
|
import { api } from "@/services/api";
|
||||||
@@ -83,7 +90,7 @@ export default function InvoicesPanel() {
|
|||||||
|
|
||||||
// Summary card: total collected (paidAmount) across every invoice matching
|
// Summary card: total collected (paidAmount) across every invoice matching
|
||||||
// the current search/status filters, not just the visible page.
|
// the current search/status filters, not just the visible page.
|
||||||
const { data: summary } = useQuery(
|
const { data: summary, isLoading: summaryLoading } = useQuery(
|
||||||
api.invoices.collectedSummary.queryOptions({
|
api.invoices.collectedSummary.queryOptions({
|
||||||
input: {
|
input: {
|
||||||
filter: { search: debouncedQuery, status: statusFilter || undefined },
|
filter: { search: debouncedQuery, status: statusFilter || undefined },
|
||||||
@@ -190,39 +197,30 @@ export default function InvoicesPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<SimpleGrid cols={{ base: 1, sm: 3 }}>
|
<KpiStrip
|
||||||
<Card withBorder radius="md" padding="md">
|
loading={summaryLoading}
|
||||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
|
items={[
|
||||||
Total collected
|
{
|
||||||
</Text>
|
label: "Total collected",
|
||||||
<Text size="xl" fw={700} c="edr-text">
|
hint: etbFromUsd !== null ? "ETB + USD" : "ETB only",
|
||||||
{etbFromUsd !== null
|
value: formatMoney(etbCollected + (etbFromUsd ?? 0), "ETB"),
|
||||||
? formatMoney(etbCollected + etbFromUsd, "ETB")
|
icon: CircleDollarSign,
|
||||||
: formatMoney(etbCollected, "ETB")}
|
color: "edr-green",
|
||||||
</Text>
|
},
|
||||||
<Text size="xs" c="dimmed">
|
{
|
||||||
{etbFromUsd !== null
|
label: "Collected in ETB",
|
||||||
? `Includes ${formatMoney(usdCollected, "USD")} converted @ ${rate} ETB/USD`
|
value: formatMoney(etbCollected, "ETB"),
|
||||||
: "USD rate unavailable — ETB collected only"}
|
icon: Banknote,
|
||||||
</Text>
|
color: "blue",
|
||||||
</Card>
|
},
|
||||||
<Card withBorder radius="md" padding="md">
|
{
|
||||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
|
label: "Collected in USD",
|
||||||
Collected — ETB only
|
value: formatMoney(usdCollected, "USD"),
|
||||||
</Text>
|
icon: Landmark,
|
||||||
<Text size="xl" fw={700} c="edr-text">
|
color: "violet",
|
||||||
{formatMoney(etbCollected, "ETB")}
|
},
|
||||||
</Text>
|
]}
|
||||||
</Card>
|
/>
|
||||||
<Card withBorder radius="md" padding="md">
|
|
||||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
|
|
||||||
Collected — USD only
|
|
||||||
</Text>
|
|
||||||
<Text size="xl" fw={700} c="edr-text">
|
|
||||||
{formatMoney(usdCollected, "USD")}
|
|
||||||
</Text>
|
|
||||||
</Card>
|
|
||||||
</SimpleGrid>
|
|
||||||
|
|
||||||
<Card p={0}>
|
<Card p={0}>
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ export function WagonCancellationCard({
|
|||||||
<SectionCard>
|
<SectionCard>
|
||||||
<Group justify="space-between" align="center" mb="sm">
|
<Group justify="space-between" align="center" mb="sm">
|
||||||
<CardTitle>Wagon Cancellation</CardTitle>
|
<CardTitle>Wagon Cancellation</CardTitle>
|
||||||
{canRequest && !openRow && !creditRow && (
|
{/* {canRequest && !openRow && !creditRow && (
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -244,7 +244,7 @@ export function WagonCancellationCard({
|
|||||||
>
|
>
|
||||||
Cancel wagons
|
Cancel wagons
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)} */}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{openRow ? (
|
{openRow ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user