IO-3515 add client side polling for now, cost centers.
This commit is contained in:
@@ -51,10 +51,12 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
|
||||
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [scanLoading, setScanLoading] = useState(false);
|
||||
const client = useApolloClient();
|
||||
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
||||
const notification = useNotification();
|
||||
const fileInputRef = useRef(null);
|
||||
const pollingIntervalRef = useRef(null);
|
||||
|
||||
const {
|
||||
treatments: { Enhanced_Payroll, Imgproxy }
|
||||
@@ -390,6 +392,12 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
const handleCancel = () => {
|
||||
const r = window.confirm(t("general.labels.cancel"));
|
||||
if (r === true) {
|
||||
// Clean up polling on cancel
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
setScanLoading(false);
|
||||
toggleModalVisible();
|
||||
}
|
||||
};
|
||||
@@ -398,14 +406,82 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
if (enterAgain) form.submit();
|
||||
}, [enterAgain, form]);
|
||||
|
||||
// Polling function for multipage PDF status
|
||||
const pollJobStatus = async (jobId) => {
|
||||
try {
|
||||
const { data } = await axios.get(`/ai/bill-ocr/status/${jobId}`);
|
||||
|
||||
if (data.status === 'COMPLETED') {
|
||||
// Stop polling
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
setScanLoading(false);
|
||||
|
||||
// Update form with the extracted data
|
||||
if (data.data && data.data.billForm) {
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
notification.success({
|
||||
title: "AI Scan Complete",
|
||||
message: "Invoice data has been extracted successfully"
|
||||
});
|
||||
}
|
||||
} else if (data.status === 'FAILED') {
|
||||
// Stop polling on failure
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
setScanLoading(false);
|
||||
|
||||
notification.error({
|
||||
title: "AI Scan Failed",
|
||||
message: data.error || "Failed to process the invoice"
|
||||
});
|
||||
}
|
||||
// If status is IN_PROGRESS, continue polling
|
||||
} catch (error) {
|
||||
console.error("Error polling job status:", error);
|
||||
|
||||
// Stop polling on error
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
setScanLoading(false);
|
||||
|
||||
notification.error({
|
||||
title: "AI Scan Error",
|
||||
message: error.response?.data?.message || error.message || "Failed to check scan status"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (billEnterModal.open) {
|
||||
form.setFieldsValue(formValues);
|
||||
} else {
|
||||
form.resetFields();
|
||||
// Clean up polling on modal close
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
setScanLoading(false);
|
||||
}
|
||||
}, [billEnterModal.open, form, formValues]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("bills.labels.new")}
|
||||
@@ -429,6 +505,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setScanLoading(true);
|
||||
const formdata = new FormData();
|
||||
formdata.append("billScan", file);
|
||||
formdata.append("jobid", billEnterModal.context.job.id);
|
||||
@@ -437,38 +514,54 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
//formdata.append("skipTextract", "true"); // For testing purposes
|
||||
axios
|
||||
.post("/ai/bill-ocr", formdata)
|
||||
.then(({ data }) => {
|
||||
console.log("*** ~ BillEnterModalContainer ~ response:", data.data.billForm);
|
||||
//Stored in data.data
|
||||
.then(({ data, status }) => {
|
||||
if (status === 202) {
|
||||
// Multipage PDF - start polling
|
||||
notification.info({
|
||||
title: "Processing Invoice",
|
||||
message: "This is a multipage document. Processing may take a few moments..."
|
||||
});
|
||||
|
||||
// Start polling every 3 seconds
|
||||
pollingIntervalRef.current = setInterval(() => {
|
||||
pollJobStatus(data.jobId);
|
||||
}, 3000);
|
||||
|
||||
// Initial poll
|
||||
pollJobStatus(data.jobId);
|
||||
} else if (status === 200) {
|
||||
// Single page - immediate response
|
||||
setScanLoading(false);
|
||||
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
notification.success({
|
||||
title: "AI Scan Complete",
|
||||
message: "Invoice data has been extracted successfully"
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("*** ~ BillEnterModalContainer ~ error:", error);
|
||||
setScanLoading(false);
|
||||
notification.error({
|
||||
title: "AI Scan Failed",
|
||||
message: error.response?.data?.message || error.message || "Failed to process invoice"
|
||||
});
|
||||
});
|
||||
}
|
||||
// Reset the input so the same file can be selected again
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.setFieldsValue({ vendorid: "72634cde-8dfa-457c-8c04-08621e712d67" });
|
||||
form.setFieldsValue({ vendorid: "72634cde-8dfa-457c-8c04-08621e712d67" });
|
||||
setTimeout(() => {
|
||||
console.log("Form Values", form.getFieldsValue());
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
Test form
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log("Fields Object", form.getFieldsValue());
|
||||
fileInputRef.current?.click();
|
||||
}}
|
||||
loading={scanLoading}
|
||||
disabled={scanLoading}
|
||||
>
|
||||
AI Scan (1 page only for now)
|
||||
{scanLoading ? "Processing Invoice..." : "AI Scan"}
|
||||
</Button>
|
||||
<Checkbox checked={generateLabel} onChange={(e) => setGenerateLabel(e.target.checked)}>
|
||||
{t("bills.labels.generatepartslabel")}
|
||||
|
||||
@@ -210,6 +210,18 @@ export function BillEnterModalLinesComponent({
|
||||
}),
|
||||
formInput: () => <Input.TextArea disabled={disabled} autoSize />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.confidence"),
|
||||
dataIndex: "confidence",
|
||||
editable: true,
|
||||
width: "4rem",
|
||||
formItemProps: (field) => ({
|
||||
key: `${field.index}confidence`,
|
||||
name: [field.name, "confidence"],
|
||||
label: t("billlines.fields.confidence")
|
||||
}),
|
||||
formInput: () => <Input disabled={disabled} />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.quantity"),
|
||||
dataIndex: "quantity",
|
||||
|
||||
Reference in New Issue
Block a user