IO-3515 set po context, update confidence UI showing

This commit is contained in:
Patrick Fic
2026-02-18 11:57:56 -08:00
parent d4bbdd7383
commit 5d53d09af9
9 changed files with 77 additions and 42 deletions

View File

@@ -5,6 +5,7 @@ import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { FaWandMagicSparkles } from "react-icons/fa6";
const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal,
@@ -19,7 +20,8 @@ function BillEnterAiScan({
form,
fileInputRef,
scanLoading,
setScanLoading
setScanLoading,
setIsAiScan
}) {
const notification = useNotification();
@@ -59,8 +61,6 @@ function BillEnterAiScan({
}
// 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);
@@ -86,11 +86,12 @@ function BillEnterAiScan({
const file = e.target.files?.[0];
if (file) {
setScanLoading(true);
setIsAiScan(true);
const formdata = new FormData();
formdata.append("billScan", file);
formdata.append("jobid", billEnterModal.context.job.id);
formdata.append("bodyshopid", bodyshop.id);
formdata.append("partsorderid", "3dd26419-a139-4399-af4e-43eeb6f0dbad");
formdata.append("partsorderid", billEnterModal.context.parts_order?.id);
//formdata.append("skipTextract", "true"); // For testing purposes
axios
.post("/ai/bill-ocr", formdata)
@@ -123,7 +124,6 @@ function BillEnterAiScan({
}
})
.catch((error) => {
console.error("*** ~ BillEnterModalContainer ~ error:", error);
setScanLoading(false);
notification.error({
title: "AI Scan Failed",
@@ -137,9 +137,9 @@ function BillEnterAiScan({
/>
<Button
onClick={() => {
console.log("Fields Object", form.getFieldsValue());
fileInputRef.current?.click();
}}
icon={<FaWandMagicSparkles />}
loading={scanLoading}
disabled={scanLoading}
>

View File

@@ -52,6 +52,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
const [loading, setLoading] = useState(false);
const [scanLoading, setScanLoading] = useState(false);
const [isAiScan, setIsAiScan] = useState(false);
const client = useApolloClient();
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
const notification = useNotification();
@@ -59,12 +60,13 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
const pollingIntervalRef = useRef(null);
const {
treatments: { Enhanced_Payroll, Imgproxy }
treatments: { Enhanced_Payroll, Imgproxy, Bill_OCR_AI }
} = useTreatmentsWithConfig({
attributes: {},
names: ["Enhanced_Payroll", "Imgproxy"],
names: ["Enhanced_Payroll", "Imgproxy", "Bill_OCR_AI"],
splitKey: bodyshop.imexshopid
});
console.log("*** ~ BillEnterModalContainer ~ Bill_OCR_AI:", Bill_OCR_AI);
const formValues = useMemo(() => {
return {
@@ -382,6 +384,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
vendorid: values.vendorid,
billlines: []
});
setIsAiScan(false);
// form.resetFields();
} else {
toggleModalVisible();
@@ -398,6 +401,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
pollingIntervalRef.current = null;
}
setScanLoading(false);
setIsAiScan(false);
toggleModalVisible();
}
};
@@ -422,6 +426,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
pollingIntervalRef.current = null;
}
setScanLoading(false);
setIsAiScan(false);
}
}, [billEnterModal.open, form, formValues]);
@@ -437,7 +442,22 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
return (
<Modal
title={t("bills.labels.new")}
title={
<Space size="large">
{t("bills.labels.new")}
{Bill_OCR_AI.treatment === "on" && (
<BillEnterAiScan
fileInputRef={fileInputRef}
form={form}
pollingIntervalRef={pollingIntervalRef}
setPollingIntervalRef={setPollingIntervalRef}
scanLoading={scanLoading}
setScanLoading={setScanLoading}
setIsAiScan={setIsAiScan}
/>
)}
</Space>
}
width={"98%"}
open={billEnterModal.open}
okText={t("general.actions.save")}
@@ -450,14 +470,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
}}
footer={
<Space>
<BillEnterAiScan
fileInputRef={fileInputRef}
form={form}
pollingIntervalRef={pollingIntervalRef}
setPollingIntervalRef={setPollingIntervalRef}
scanLoading={scanLoading}
setScanLoading={setScanLoading}
/>
<Checkbox checked={generateLabel} onChange={(e) => setGenerateLabel(e.target.checked)}>
{t("bills.labels.generatepartslabel")}
</Checkbox>
@@ -491,7 +503,11 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
}}
>
<RbacWrapper action="bills:enter">
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
<BillFormContainer
form={form}
isAiScan={isAiScan}
disableInvNumber={billEnterModal.context.disableInvNumber}
/>
</RbacWrapper>
</Form>
</Modal>

View File

@@ -43,7 +43,8 @@ export function BillFormComponent({
loadOutstandingReturns,
loadInventory,
preferredMake,
disableInHouse
disableInHouse,
isAiScan
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -452,6 +453,7 @@ export function BillFormComponent({
responsibilityCenters={responsibilityCenters}
disabled={disabled}
billEdit={billEdit}
isAiScan={isAiScan}
/>
)}
<Divider titlePlacement="left" style={{ display: billEdit ? "none" : null }}>

View File

@@ -15,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse,isAiScan }) {
const {
treatments: { Simple_Inventory }
} = useTreatmentsWithConfig({
@@ -50,6 +50,7 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
isAiScan={isAiScan}
/>
{!billEdit && <BillCmdReturnsTableComponent form={form} returnLoading={returnLoading} returnData={returnData} />}
{Simple_Inventory.treatment === "on" && (

View File

@@ -30,7 +30,8 @@ export function BillEnterModalLinesComponent({
discount,
form,
responsibilityCenters,
billEdit
billEdit,
isAiScan
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
@@ -140,6 +141,29 @@ export function BillEnterModalLinesComponent({
const columns = (remove) => {
return [
...(isAiScan
? [
{
title: t("billlines.fields.confidence"),
dataIndex: "confidence",
editable: true,
width: "5rem",
formItemProps: (field) => ({
key: `${field.index}confidence`,
name: [field.name, "confidence"],
label: t("billlines.fields.confidence")
}),
formInput: (record) => {
const confidenceValue = getFieldValue(["billlines", record.name, "confidence"]);
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<ConfidenceDisplay value={confidenceValue} />
</div>
);
}
}
]
: []),
{
title: t("billlines.fields.jobline"),
dataIndex: "joblineid",
@@ -213,25 +237,7 @@ export function BillEnterModalLinesComponent({
}),
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
},
{
title: t("billlines.fields.confidence"),
dataIndex: "confidence",
editable: true,
width: "5rem",
formItemProps: (field) => ({
key: `${field.index}confidence`,
name: [field.name, "confidence"],
label: t("billlines.fields.confidence")
}),
formInput: (record) => {
const confidenceValue = getFieldValue(["billlines", record.name, "confidence"]);
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<ConfidenceDisplay value={confidenceValue} />
</div>
);
}
},
{
title: t("billlines.fields.quantity"),
dataIndex: "quantity",

View File

@@ -187,6 +187,7 @@ export function PartsOrderListTableDrawerComponent({
actions: { refetch: refetch },
context: {
job: job,
parts_order: { id: record.id },
bill: {
vendorid: record.vendor.id,
is_credit_memo: record.return,

View File

@@ -162,6 +162,7 @@ export function PartsOrderListTableComponent({
actions: { refetch: refetch },
context: {
job: job,
parts_order: { id: record.id },
bill: {
vendorid: record.vendor.id,
is_credit_memo: record.return,

View File

@@ -10,8 +10,14 @@ const { Option } = Select;
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone, ref }) => {
const [option, setOption] = useState(value);
// Sync internal state when value prop changes (e.g., from form.setFieldsValue)
useEffect(() => {
if (value !== option) {
setOption(value);
}
}, [value]);
useEffect(() => {
console.log("*** ~ VendorSearchSelect ~ USEEFFECT:", value, option);
if (value !== option && onChange) {
if (value && !option) {
onChange(value);

View File

@@ -4,4 +4,6 @@ Required Infrastructure setup
3. Created 2 roles for SNS. The textract role is the right one, the other was created manually based on incorrect instructions.
TODO:
* Create a rome bucket for uploads, or move to the regular spot.
* Create a rome bucket for uploads, or move to the regular spot.
* How to implement this across environments.
* How to prevent polling for a job that may have errored.