Files
bodyshop/client/src/components/dms-post-form/rr-dms-post-form.jsx

243 lines
7.6 KiB
JavaScript

import { ReloadOutlined } from "@ant-design/icons";
import {
Button,
Card,
Col,
Divider,
Form,
Input,
InputNumber,
Row,
Select,
Space,
Statistic,
Tooltip,
Typography
} from "antd";
import Dinero from "dinero.js";
import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useState } from "react";
import dayjs from "../../utils/day";
/**
* RR DMS Post Form component
* Submit: "rr-export-job"
* @param bodyshop
* @param socket
* @param job
* @param logsRef
* @returns {JSX.Element}
* @constructor
*/
export default function RRPostForm({ bodyshop, socket, job, logsRef }) {
const [form] = Form.useForm();
const { t } = useTranslation();
// Advisors
const [advisors, setAdvisors] = useState([]);
const [advLoading, setAdvLoading] = useState(false);
const getAdvisorNumber = (a) => a?.advisorId;
const getAdvisorLabel = (a) => `${a?.firstName || ""} ${a?.lastName || ""}`.trim();
const fetchRrAdvisors = (refresh = false) => {
if (!socket) return;
setAdvLoading(true);
const onResult = (payload) => {
try {
const list = payload?.result ?? payload ?? [];
setAdvisors(Array.isArray(list) ? list : []);
} finally {
setAdvLoading(false);
socket.off("rr-get-advisors:result", onResult);
}
};
socket.once("rr-get-advisors:result", onResult);
socket.emit("rr-get-advisors", { departmentType: "B", refresh }, (ack) => {
if (ack?.ok) {
const list = ack.result ?? [];
setAdvisors(Array.isArray(list) ? list : []);
} else if (ack) {
console.error("Something went wrong fetching DMS Advisors");
}
setAdvLoading(false);
socket.off("rr-get-advisors:result", onResult);
});
};
useEffect(() => {
fetchRrAdvisors(false);
}, [bodyshop?.id, socket]);
const initialValues = useMemo(
() => ({
story: `${t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`.trim(),
ins_co_nm: job.ins_co_nm || "N/A",
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${job.po_number || ""}`
}).trim()}.${
job.area_of_damage?.impact1
? " " +
t("jobs.labels.dms.damageto", {
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1.padStart(2, "0")) || "UNKNOWN"
})
: ""
}`.slice(0, 239),
inservicedate: dayjs(
`${
(job.v_model_yr &&
(job.v_model_yr < 100
? job.v_model_yr >= (dayjs().year() + 1) % 100
? 1900 + parseInt(job.v_model_yr, 10)
: 2000 + parseInt(job.v_model_yr, 10)
: job.v_model_yr)) ||
2019
}-01-01`
)
}),
[job, t]
);
const handleFinish = (values) => {
if (!socket) return;
socket.emit("rr-export-job", {
bodyshopId: bodyshop?.id,
jobId: job.id,
job,
txEnvelope: values
});
logsRef?.current?.scrollIntoView({ behavior: "smooth" });
};
// Discrepancy is ignored for RR; we still show totals for operator context
const totals = socket?.allocationsSummary
? socket.allocationsSummary.reduce(
(acc, val) => ({
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost))
}),
{ totalSale: Dinero(), totalCost: Dinero() }
)
: { totalSale: Dinero(), totalCost: Dinero() };
return (
<Card title={t("jobs.labels.dms.postingform")}>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
style={{ width: "100%" }}
initialValues={initialValues}
>
<Row gutter={[16, 12]} align="bottom">
{/* Advisor + inline Refresh */}
<Col xs={24} sm={24} md={12} lg={8}>
<Form.Item label={t("jobs.fields.dms.advisor")} required>
<Space.Compact block>
<Form.Item
name="advisorNo"
noStyle
rules={[{ required: true, message: t("general.validation.required") }]}
>
<Select
style={{ flex: 1 }}
loading={advLoading}
allowClear
placeholder={t("general.actions.select", "Select...")}
popupMatchSelectWidth
options={advisors
.map((a) => {
const value = getAdvisorNumber(a);
if (value == null) return null;
return { value: String(value), label: getAdvisorLabel(a) || String(value) };
})
.filter(Boolean)}
notFoundContent={advLoading ? t("general.labels.loading") : t("general.labels.none")}
/>
</Form.Item>
<Tooltip title={t("general.actions.refresh")}>
<Button
aria-label={t("general.actions.refresh")}
icon={<ReloadOutlined />}
onClick={() => fetchRrAdvisors(true)}
loading={advLoading}
/>
</Tooltip>
</Space.Compact>
</Form.Item>
</Col>
{/* Make Override */}
<Col xs={24} sm={12} md={12} lg={8}>
<Form.Item name="makeOverride" label={t("jobs.fields.dms.make_override")}>
<Input allowClear placeholder={t("general.actions.optional")} />
</Form.Item>
</Col>
<Col xs={12} sm={8} md={6} lg={4}>
<Form.Item name="kmin" label={t("jobs.fields.kmin")} initialValue={job?.kmin} rules={[{ required: true }]}>
<InputNumber style={{ width: "100%" }} disabled />
</Form.Item>
</Col>
<Col xs={12} sm={8} md={6} lg={4}>
<Form.Item
name="kmout"
label={t("jobs.fields.kmout")}
initialValue={job?.kmout}
rules={[{ required: true }]}
>
<InputNumber style={{ width: "100%" }} disabled />
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 12]}>
<Col span={24}>
<Form.Item name="story" label={t("jobs.fields.dms.story")} rules={[{ required: true }]}>
<Input.TextArea maxLength={240} />
</Form.Item>
</Col>
</Row>
<Divider />
<Space size="large" wrap align="center" style={{ marginBottom: 16 }}>
<Statistic
title={t("jobs.fields.ded_amt")}
value={Dinero(job.job_totals.totals.custPayable.deductible).toFormat()}
/>
<Statistic
title={t("jobs.labels.total_cust_payable")}
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
/>
</Space>
{/* Validation */}
<Form.Item shouldUpdate>
{() => {
const advisorOk = !!form.getFieldValue("advisorNo");
return (
<Space size="large" wrap align="center">
<Statistic title={t("jobs.labels.subtotal")} value={totals.totalSale.toFormat()} />
<Typography.Title>=</Typography.Title>
<Button disabled={!advisorOk} htmlType="submit">
{t("jobs.actions.dms.post")}
</Button>
</Space>
);
}}
</Form.Item>
</Form>
</Card>
);
}