feature/IO-3401-Parts-Rec-Enhanced - Implement
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Popover } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays "Parts Received" summary (modeled after the Production Board List column),
|
||||
* and on click shows a popover with the Parts Status grid (existing JobPartsQueueCount UI).
|
||||
* @param bodyshop
|
||||
* @param parts
|
||||
* @param displayMode
|
||||
* @param popoverPlacement
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popoverPlacement = "top" }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const summary = useMemo(() => {
|
||||
const receivedStatus = bodyshop?.md_order_statuses?.default_received;
|
||||
|
||||
if (!Array.isArray(parts) || parts.length === 0 || !receivedStatus) {
|
||||
return { total: 0, received: 0, percentLabel: t("general.labels.na") };
|
||||
}
|
||||
|
||||
// Keep consistent with JobPartsQueueCount: exclude PAS / PASL from parts math
|
||||
const { total, received } = parts.reduce(
|
||||
(acc, val) => {
|
||||
if (val?.part_type === "PAS" || val?.part_type === "PASL") return acc;
|
||||
const count = Number(val?.count || 0);
|
||||
acc.total += count;
|
||||
|
||||
if (val?.status === receivedStatus) {
|
||||
acc.received += count;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ total: 0, received: 0 }
|
||||
);
|
||||
|
||||
const percentLabel = total > 0 ? `${Math.round((received / total) * 100)}%` : t("general.labels.na");
|
||||
return { total, received, percentLabel };
|
||||
}, [parts, bodyshop?.md_order_statuses?.default_received]);
|
||||
|
||||
const canOpen = summary.total > 0;
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(nextOpen) => {
|
||||
if (!canOpen) return;
|
||||
setOpen(nextOpen);
|
||||
},
|
||||
[canOpen]
|
||||
);
|
||||
|
||||
const displayText =
|
||||
displayMode === "compact" ? summary.percentLabel : `${summary.percentLabel} (${summary.received}/${summary.total})`;
|
||||
|
||||
// Prevent row/cell click handlers (table selection, drawer selection, etc.)
|
||||
const stop = (e) => e.stopPropagation();
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
trigger={["click"]}
|
||||
placement={popoverPlacement}
|
||||
content={
|
||||
<div onClick={stop} style={{ minWidth: 260 }}>
|
||||
<JobPartsQueueCount parts={parts} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={stop}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "19px",
|
||||
cursor: canOpen ? "pointer" : "default",
|
||||
userSelect: "none"
|
||||
}}
|
||||
title={canOpen ? t("production.labels.click_for_statuses") : undefined}
|
||||
>
|
||||
{displayText}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
JobPartsReceived.propTypes = {
|
||||
bodyshop: PropTypes.object,
|
||||
parts: PropTypes.array,
|
||||
displayMode: PropTypes.oneOf(["full", "compact"]),
|
||||
popoverPlacement: PropTypes.string
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(JobPartsReceived);
|
||||
Reference in New Issue
Block a user