fix issue

This commit is contained in:
Marshal
2026-07-27 05:59:49 +00:00
parent 773456c256
commit 15d78e1d67
10 changed files with 410 additions and 36 deletions

View File

@@ -72,12 +72,19 @@ export function computeGlShipmentTotal(
(i) => i.containerSize === line.containerSize && !i.isClearance,
);
if (rate) {
// A per-wagon rate bills the wagons this line occupies — two 20ft share
// one — not the box count. Mirrors the API's base-freight line so the
// quote matches the invoice.
const billedQty =
rate.unit === "per_wagon"
? Math.ceil(qty * (line.containerSize === "40ft" ? 1 : 0.5))
: qty;
lines.push({
label: rate.label,
unitPrice: rate.unitPrice,
unit: rate.unit,
quantity: qty,
amount: rate.unitPrice * qty,
quantity: billedQty,
amount: rate.unitPrice * billedQty,
});
}
hazardTotalQty += line.hazardousQuantity;

View File

@@ -391,6 +391,12 @@ const RuleEngineFormDialog = ({
// display order) is a non-negative magnitude — reject negatives outright
// rather than letting a typed "-" reach the API.
min={isNumber ? 0 : undefined}
// Without an explicit step the browser assumes 1 and refuses to submit
// anything fractional — which blocked decimal rates, tonnages and
// distances. The API is the one that decides which of these are whole
// numbers (@IsInt on points/sizes/order, @IsNumber on money, tons, km),
// so let the field carry decimals and let a 400 catch the rest.
step={isNumber ? "any" : undefined}
disabled={field.disabled || computed !== undefined}
value={String((computed !== undefined ? computed : values[field.name]) ?? "")}
onChange={(e) => {

View File

@@ -112,6 +112,13 @@ const SORT_OPTIONS = [
{ value: "contractValidUntil:DESC", label: "Expiring latest" },
];
/** Every column is 120px wide and wraps its content instead of truncating. */
const COLUMN_WIDTH = 120;
const COLUMN_META = {
headerClassName: "whitespace-normal break-words",
cellClassName: "whitespace-normal break-words align-top",
};
/** Local start-of-day → ISO, for inclusive "from" date filters. */
function startOfDayIso(d: Date): string {
const x = new Date(d);
@@ -260,6 +267,8 @@ export default function ContractRequestsPage() {
const columns: ColumnDef<ContractListRow>[] = [
{
id: "contract",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Customer</span>,
cell: ({ row }) => {
const c = row.original;
@@ -269,10 +278,8 @@ export default function ContractRequestsPage() {
<User className="size-4" strokeWidth={1.75} />
</div>
<div className="min-w-0">
<p className="truncate font-medium text-foreground">
{c.customerLabel}
</p>
<p className="mt-0.5 flex items-center gap-1 truncate text-xs text-muted-foreground">
<p className="font-medium text-foreground">{c.customerLabel}</p>
<p className="mt-0.5 flex items-center gap-1 text-xs text-muted-foreground">
<FileText className="size-3 shrink-0 opacity-70" />
{c.reference}
</p>
@@ -283,17 +290,17 @@ export default function ContractRequestsPage() {
},
{
id: "route",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Route</span>,
cell: ({ row }) => {
const c = row.original;
return (
<div className="space-y-1 py-1">
<div className="flex items-center gap-1.5 text-sm font-medium text-foreground">
<span className="max-w-[8rem] truncate">{c.originLabel}</span>
<span>{c.originLabel}</span>
<ArrowRight className="size-3.5 shrink-0 text-muted-foreground" />
<span className="max-w-[8rem] truncate">
{c.destinationLabel}
</span>
<span>{c.destinationLabel}</span>
</div>
<div className="flex gap-1.5">
<Badge
@@ -315,8 +322,8 @@ export default function ContractRequestsPage() {
},
{
id: "status",
size: 200,
minSize: 180,
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Status</span>,
cell: ({ row }) => (
<div className="py-1">
@@ -326,18 +333,18 @@ export default function ContractRequestsPage() {
/>
</div>
),
meta: {
headerClassName: "min-w-[11rem]",
cellClassName: "min-w-[11rem]",
},
},
{
id: "approval",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Approval</span>,
cell: ({ row }) => <ContractApprovalProgressCell row={row.original} />,
},
{
id: "validity",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Validity</span>,
cell: ({ row }) => {
const c = row.original;
@@ -362,6 +369,8 @@ export default function ContractRequestsPage() {
},
{
id: "kind",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Kind</span>,
cell: ({ row }) => {
const isGeneral = row.original.contractKind === "GENERAL";
@@ -383,6 +392,8 @@ export default function ContractRequestsPage() {
},
{
id: "actions",
size: COLUMN_WIDTH,
meta: COLUMN_META,
header: () => <span className={bookingTable.headerCell}>Action</span>,
cell: ({ row }) => {
const action = getStaffRowAction(row.original);
@@ -653,7 +664,9 @@ export default function ContractRequestsPage() {
manualPagination: true,
pageCount,
}}
containerClassName="border-0 shadow-none bg-transparent"
// table-fixed makes the per-column 120px widths stick; without
// it auto-layout re-widens columns once cells wrap.
containerClassName="border-0 shadow-none bg-transparent [&_table]:table-fixed [&_table]:min-w-[840px]"
footer={DataTableFooter}
/>
</Box>