157 lines
5.2 KiB
JavaScript
157 lines
5.2 KiB
JavaScript
import { Alert, Button, Card, Typography } from "antd";
|
|
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
import { SyncOutlined } from "@ant-design/icons";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { connect } from "react-redux";
|
|
import { createStructuredSelector } from "reselect";
|
|
import Dinero from "dinero.js";
|
|
import { DMS_MAP } from "../../utils/dmsUtils";
|
|
|
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
import { pageLimit } from "../../utils/config";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop
|
|
});
|
|
const mapDispatchToProps = () => ({});
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummary);
|
|
|
|
/**
|
|
* DMS Allocations Summary component
|
|
* @param mode
|
|
* @param socket
|
|
* @param bodyshop
|
|
* @param jobId
|
|
* @param title
|
|
* @param onAllocationsChange
|
|
* @returns {JSX.Element}
|
|
* @constructor
|
|
*/
|
|
export function DmsAllocationsSummary({ mode, socket, bodyshop, jobId, title, onAllocationsChange }) {
|
|
const { t } = useTranslation();
|
|
const [allocationsSummary, setAllocationsSummary] = useState([]);
|
|
const socketRef = useRef(socket);
|
|
|
|
useEffect(() => {
|
|
socketRef.current = socket;
|
|
}, [socket]);
|
|
|
|
// Resolve event name by mode (PBS reuses the CDK event per existing behavior)
|
|
const allocationsEvent =
|
|
mode === DMS_MAP.reynolds
|
|
? "rr-calculate-allocations"
|
|
: mode === DMS_MAP.fortellis
|
|
? "fortellis-calculate-allocations"
|
|
: /* "cdk" | "pbs" (legacy) */ "cdk-calculate-allocations";
|
|
|
|
const fetchAllocations = useCallback(() => {
|
|
if (!socket || !jobId || !mode) return;
|
|
|
|
try {
|
|
socket.emit(allocationsEvent, jobId, (ack) => {
|
|
const list = Array.isArray(ack) ? ack : [];
|
|
setAllocationsSummary(list);
|
|
// Preserve side-channel used by the post form for discrepancy checks
|
|
socketRef.current.allocationsSummary = list;
|
|
if (onAllocationsChange) onAllocationsChange(list);
|
|
});
|
|
} catch {
|
|
// Best-effort; leave table empty on error
|
|
setAllocationsSummary([]);
|
|
if (socketRef.current) {
|
|
socketRef.current.allocationsSummary = [];
|
|
}
|
|
if (onAllocationsChange) {
|
|
onAllocationsChange([]);
|
|
}
|
|
}
|
|
}, [socket, jobId, mode, allocationsEvent]);
|
|
|
|
// Initial + whenever mode/socket/jobId changes
|
|
useEffect(() => {
|
|
fetchAllocations();
|
|
}, [fetchAllocations]);
|
|
|
|
const columns = [
|
|
{ title: t("jobs.fields.dms.center"), dataIndex: "center", key: "center" },
|
|
{
|
|
title: t("jobs.fields.dms.sale"),
|
|
dataIndex: "sale",
|
|
key: "sale",
|
|
render: (_text, record) => Dinero(record.sale).toFormat()
|
|
},
|
|
{
|
|
title: t("jobs.fields.dms.cost"),
|
|
dataIndex: "cost",
|
|
key: "cost",
|
|
render: (_text, record) => Dinero(record.cost).toFormat()
|
|
},
|
|
{
|
|
title: t("jobs.fields.dms.sale_dms_acctnumber"),
|
|
dataIndex: "sale_dms_acctnumber",
|
|
key: "sale_dms_acctnumber",
|
|
render: (_text, record) => record.profitCenter?.dms_acctnumber
|
|
},
|
|
{
|
|
title: t("jobs.fields.dms.cost_dms_acctnumber"),
|
|
dataIndex: "cost_dms_acctnumber",
|
|
key: "cost_dms_acctnumber",
|
|
render: (_text, record) => record.costCenter?.dms_acctnumber
|
|
},
|
|
{
|
|
title: t("jobs.fields.dms.dms_wip_acctnumber"),
|
|
dataIndex: "dms_wip_acctnumber",
|
|
key: "dms_wip_acctnumber",
|
|
render: (_text, record) => record.costCenter?.dms_wip_acctnumber
|
|
}
|
|
];
|
|
|
|
return (
|
|
<Card
|
|
title={title}
|
|
extra={<Button onClick={fetchAllocations} aria-label={t("general.actions.refresh")} icon={<SyncOutlined />} />}
|
|
>
|
|
{bodyshop.pbs_configuration?.disablebillwip && (
|
|
<Alert type="warning" title={t("jobs.labels.dms.disablebillwip")} />
|
|
)}
|
|
|
|
<ResponsiveTable
|
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
|
columns={columns}
|
|
mobileColumnKeys={["center", "sale", "cost", "sale_dms_acctnumber"]}
|
|
rowKey="center"
|
|
dataSource={allocationsSummary}
|
|
locale={{ emptyText: t("dms.labels.refreshallocations") }}
|
|
scroll={{ x: true }}
|
|
summary={() => {
|
|
const totals = allocationsSummary?.reduce(
|
|
(acc, val) => ({
|
|
totalSale: acc.totalSale.add(Dinero(val.sale)),
|
|
totalCost: acc.totalCost.add(Dinero(val.cost))
|
|
}),
|
|
{ totalSale: Dinero(), totalCost: Dinero() }
|
|
) || { totalSale: Dinero(), totalCost: Dinero() };
|
|
|
|
const hasNonZeroSaleTotal = totals.totalSale.getAmount() !== 0;
|
|
|
|
return (
|
|
<ResponsiveTable.Summary.Row>
|
|
<ResponsiveTable.Summary.Cell>
|
|
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
|
</ResponsiveTable.Summary.Cell>
|
|
<ResponsiveTable.Summary.Cell>
|
|
{hasNonZeroSaleTotal ? totals.totalSale.toFormat() : null}
|
|
</ResponsiveTable.Summary.Cell>
|
|
<ResponsiveTable.Summary.Cell />
|
|
<ResponsiveTable.Summary.Cell />
|
|
<ResponsiveTable.Summary.Cell />
|
|
</ResponsiveTable.Summary.Row>
|
|
);
|
|
}}
|
|
/>
|
|
</Card>
|
|
);
|
|
}
|