119 lines
3.9 KiB
JavaScript
119 lines
3.9 KiB
JavaScript
import { DislikeOutlined, LikeOutlined } from "@ant-design/icons";
|
|
import { Button, Form, Input, Radio, Space } from "antd";
|
|
import axios from "axios";
|
|
import { useState } from "react";
|
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
import { useTranslation } from "react-i18next";
|
|
import { connect } from "react-redux";
|
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
import { createStructuredSelector } from "reselect";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop
|
|
});
|
|
|
|
function BillAiFeedback({ billForm, rawAIData, bodyshop }) {
|
|
const { t } = useTranslation();
|
|
const [form] = Form.useForm();
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const notification = useNotification();
|
|
|
|
//Need to sanitize becuase we pass as form data to include the attachment.
|
|
const sanitizeBillFormValues = (value) => {
|
|
const seen = new WeakSet();
|
|
return JSON.stringify(
|
|
value,
|
|
(key, v) => {
|
|
if (key === "originFileObj") return undefined;
|
|
if (key === "thumbUrl") return undefined;
|
|
if (key === "preview") return undefined;
|
|
if (typeof v === "function") return undefined;
|
|
if (v && typeof v === "object") {
|
|
if (seen.has(v)) return "[Circular]";
|
|
seen.add(v);
|
|
}
|
|
return v;
|
|
},
|
|
0
|
|
);
|
|
};
|
|
|
|
const getAttachmentFromBillFormUpload = () => {
|
|
const uploads = billForm?.getFieldValue?.("upload") || [];
|
|
const files = uploads.map((u) => u?.originFileObj).filter(Boolean);
|
|
|
|
return (
|
|
files.find((f) => f?.type === "application/pdf") ||
|
|
files.find((f) => isString(f?.name) && f.name.toLowerCase().endsWith(".pdf")) ||
|
|
files[0] ||
|
|
null
|
|
);
|
|
};
|
|
|
|
const submitFeedback = async ({ rating, comments }) => {
|
|
setSubmitting(true);
|
|
try {
|
|
const billFormValues = billForm.getFieldsValue(true);
|
|
|
|
const formData = new FormData();
|
|
formData.append("rating", rating);
|
|
formData.append("comments", comments || "");
|
|
formData.append("billFormValues", sanitizeBillFormValues(billFormValues));
|
|
formData.append("rawAIData", sanitizeBillFormValues(rawAIData));
|
|
formData.append("shopname", bodyshop?.shopname || "");
|
|
|
|
const attachmentFile = getAttachmentFromBillFormUpload();
|
|
if (attachmentFile) {
|
|
formData.append("billPdf", attachmentFile, attachmentFile.name || "bill.pdf");
|
|
}
|
|
|
|
await axios.post("/ai/bill-feedback", formData);
|
|
|
|
notification.success({
|
|
title: "Thanks — feedback submitted"
|
|
});
|
|
form.resetFields();
|
|
} catch (error) {
|
|
notification.error({
|
|
title: "Failed to submit feedback",
|
|
description: error?.response?.data?.message || error?.message
|
|
});
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const isString = (v) => typeof v === "string";
|
|
|
|
return (
|
|
<Form form={form} onFinish={submitFeedback} requiredMark={false}>
|
|
<Space wrap align="top" size="small">
|
|
<Form.Item name="rating" label={t("bills.labels.ai.feedback_prompt")} rules={[{ required: true }]}>
|
|
<Radio.Group optionType="button" buttonStyle="solid">
|
|
<Radio.Button value="up">
|
|
<LikeOutlined />
|
|
</Radio.Button>
|
|
<Radio.Button value="down">
|
|
<DislikeOutlined />
|
|
</Radio.Button>
|
|
</Radio.Group>
|
|
</Form.Item>
|
|
|
|
<Space wrap size="small" orientation="vertical">
|
|
<Form.Item name="comments">
|
|
<Input.TextArea
|
|
rows={3}
|
|
style={{ minWidth: "400px" }}
|
|
placeholder={t("bills.labels.ai.feedback_placeholder")}
|
|
/>
|
|
</Form.Item>
|
|
<Button onClick={() => form.submit()} loading={submitting} disabled={submitting}>
|
|
{t("bills.labels.ai.submit_feedback")}
|
|
</Button>
|
|
</Space>
|
|
</Space>
|
|
</Form>
|
|
);
|
|
}
|
|
export default connect(mapStateToProps, null)(BillAiFeedback);
|