Files
bodyshop/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx

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>
);
}