Files
bodyshop/client/src/components/parts-receive-modal/parts-receive-modal.container.jsx
2026-01-20 11:37:10 -05:00

150 lines
4.8 KiB
JavaScript

import { useMutation } from "@apollo/client/react";
import { Form, Modal } from "antd";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { RECEIVE_PARTS_LINE } from "../../graphql/jobs-lines.queries";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPartsReceive } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import PartsReceiveModalComponent from "./parts-receive-modal.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partsReceiveModal: selectPartsReceive
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("partsReceive"))
});
export function PartsReceiveModalContainer({ partsReceiveModal, toggleModalVisible, bodyshop }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const notification = useNotification();
const { open, context, actions } = partsReceiveModal;
const { partsorderlines } = context;
const { refetch } = actions;
const [form] = Form.useForm();
const [receivePartsLine] = useMutation(RECEIVE_PARTS_LINE);
const handleFinish = async (values) => {
logImEXEvent("parts_order_receive");
setLoading(true);
try {
const submittedLines = values.partsorderlines ?? [];
// Preserve ids from modal context, merge editable fields from form submission (e.g. location)
const mergedLines = (partsorderlines ?? []).map((ctxLine, idx) => ({
...ctxLine,
...submittedLines[idx]
}));
// Optional: hard guard to catch the exact failure early with a better message
const missing = mergedLines
.map((l, idx) => ({
idx,
orderLineId: l?.id,
jobLineId: l?.joblineid // adjust if your ctx uses job_line_id instead
}))
.filter((x) => !x.orderLineId || !x.jobLineId);
if (missing.length) {
notification.error({
title: t("parts_orders.errors.creating"),
description: `Missing required ids for lines: ${missing
.map((m) => `#${m.idx + 1} (orderLineId=${m.orderLineId}, jobLineId=${m.jobLineId})`)
.join(", ")}`
});
return;
}
const results = await Promise.allSettled(
mergedLines.map((li) =>
receivePartsLine({
variables: {
lineId: li.joblineid,
line: {
location: li.location,
status: bodyshop.md_order_statuses.default_received || "Received*"
},
orderLineId: li.id,
orderLine: {
status: bodyshop.md_order_statuses.default_received || "Received*"
}
},
// Ensures GraphQL errors come back on the result when possible (instead of only throwing)
errorPolicy: "all"
})
)
);
const errors = [];
results.forEach((r, idx) => {
if (r.status === "rejected") {
errors.push({ idx, message: r.reason?.message ?? String(r.reason) });
return;
}
if (r.value?.errors?.length) {
errors.push({
idx,
message: r.value.errors.map((e) => e.message).join(" | ")
});
}
});
if (errors.length) {
errors.forEach((e) =>
notification.error({
title: t("parts_orders.errors.creating"),
description: `Line ${e.idx + 1}: ${e.message}`
})
);
return; // keep modal open so user can retry
}
notification.success({ title: t("parts_orders.successes.received") });
if (refetch) refetch();
toggleModalVisible();
} finally {
setLoading(false);
}
};
const initialValues = {
partsorderlines: partsorderlines
};
useEffect(() => {
if (open && !!partsorderlines) {
form.resetFields();
}
}, [open, partsorderlines, form]);
return (
<Modal
open={open}
title={t("parts_orders.labels.receive")}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{ loading: loading }}
destroyOnHidden
forceRender
width="50%"
>
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish} initialValues={initialValues}>
<PartsReceiveModalComponent form={form} />
</Form>
</Modal>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(PartsReceiveModalContainer);