IO-3190 Quick Intake Schedule Event
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -128,3 +128,5 @@ vitest-coverage/
|
|||||||
*.vitest.log
|
*.vitest.log
|
||||||
test-output.txt
|
test-output.txt
|
||||||
server/job/test/fixtures
|
server/job/test/fixtures
|
||||||
|
|
||||||
|
.github
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AlertFilled } from "@ant-design/icons";
|
import { AlertFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Divider, Dropdown, Form, Input, Popover, Select, Space } from "antd";
|
import { Button, Divider, Dropdown, Form, Input, Popover, Select, Space } from "antd";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
@@ -8,24 +8,30 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||||
|
import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
|
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
|
||||||
import ScheduleAtChange from "./job-at-change.component";
|
import ScheduleAtChange from "./job-at-change.component";
|
||||||
import ScheduleEventColor from "./schedule-event.color.component";
|
import ScheduleEventColor from "./schedule-event.color.component";
|
||||||
import ScheduleEventNote from "./schedule-event.note.component";
|
import ScheduleEventNote from "./schedule-event.note.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -33,7 +39,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||||
setMessage: (text) => dispatch(setMessage(text))
|
setMessage: (text) => dispatch(setMessage(text)),
|
||||||
|
insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ScheduleEventComponent({
|
export function ScheduleEventComponent({
|
||||||
@@ -43,16 +50,36 @@ export function ScheduleEventComponent({
|
|||||||
event,
|
event,
|
||||||
refetch,
|
refetch,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
setScheduleContext
|
setScheduleContext,
|
||||||
|
insertAuditTrail
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||||
|
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
|
||||||
const [title, setTitle] = useState(event.title);
|
const [title, setTitle] = useState(event.title);
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [popOverVisible, setPopOverVisible] = useState(false);
|
||||||
|
|
||||||
|
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
|
||||||
|
variables: { id: event.job.id },
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (data?.jobs_by_pk) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
|
||||||
|
scheduled_completion: data.jobs_by_pk.scheduled_completion,
|
||||||
|
actual_completion: data.jobs_by_pk.actual_completion,
|
||||||
|
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
|
||||||
|
actual_delivery: data.jobs_by_pk.actual_delivery
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
const blockContent = (
|
const blockContent = (
|
||||||
<Space direction="vertical" wrap>
|
<Space direction="vertical" wrap>
|
||||||
@@ -89,6 +116,74 @@ export function ScheduleEventComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleConvert = async (values) => {
|
||||||
|
const res = await mutationUpdateJob({
|
||||||
|
variables: {
|
||||||
|
jobId: event.job.id,
|
||||||
|
job: {
|
||||||
|
...values,
|
||||||
|
status: bodyshop.md_ro_statuses.default_arrived,
|
||||||
|
inproduction: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.errors) {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.converted")
|
||||||
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: event.job.id,
|
||||||
|
operation: AuditTrailMapping.jobintake(
|
||||||
|
res.data.update_jobs.returning[0].status,
|
||||||
|
DateTimeFormatterFunction(values.scheduled_completion)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
setPopOverVisible(false);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const popMenu = (
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Form layout="vertical" form={form} onFinish={handleConvert}>
|
||||||
|
<Form.Item
|
||||||
|
name={["actual_in"]}
|
||||||
|
label={t("jobs.fields.actual_in")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={["scheduled_completion"]}
|
||||||
|
label={t("jobs.fields.scheduled_completion")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Space wrap>
|
||||||
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const popoverContent = (
|
const popoverContent = (
|
||||||
<div style={{ maxWidth: "40vw" }}>
|
<div style={{ maxWidth: "40vw" }}>
|
||||||
{!event.isintake ? (
|
{!event.isintake ? (
|
||||||
@@ -294,7 +389,7 @@ export function ScheduleEventComponent({
|
|||||||
) : (
|
) : (
|
||||||
<ScheduleManualEvent event={event} />
|
<ScheduleManualEvent event={event} />
|
||||||
)}
|
)}
|
||||||
{event.isintake ? (
|
{event.isintake && HasFeatureAccess({ featureName: "checklist", bodyshop }) ? (
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
|
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
|
||||||
@@ -303,7 +398,21 @@ export function ScheduleEventComponent({
|
|||||||
>
|
>
|
||||||
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
|
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : (
|
||||||
|
<Popover //open={open}
|
||||||
|
content={popMenu}
|
||||||
|
open={popOverVisible}
|
||||||
|
onOpenChange={setPopOverVisible}
|
||||||
|
onClick={(e) => {
|
||||||
|
getJobDetails();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button disabled={event.arrived}>{t("jobs.actions.intake_quick")}</Button>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const AuditTrailMapping = {
|
|||||||
jobchecklist: (type, inproduction, status) =>
|
jobchecklist: (type, inproduction, status) =>
|
||||||
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
||||||
jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
||||||
jobintake: (status, email, scheduled_completion) =>
|
jobintake: (status, scheduled_completion) =>
|
||||||
i18n.t("audit_trail.messages.jobintake", { status, email, scheduled_completion }),
|
i18n.t("audit_trail.messages.jobintake", { status, scheduled_completion }),
|
||||||
jobdelivery: (status, email, actual_completion) =>
|
jobdelivery: (status, email, actual_completion) =>
|
||||||
i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }),
|
i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }),
|
||||||
jobexported: () => i18n.t("audit_trail.messages.jobexported"),
|
jobexported: () => i18n.t("audit_trail.messages.jobexported"),
|
||||||
|
|||||||
Reference in New Issue
Block a user