IO-2932-Scheduling-Lag-on-AIO:

Bump React-Big-Calendar

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-09-17 12:54:19 -04:00
parent cd0a08a7be
commit 8f118937f3
4 changed files with 408 additions and 373 deletions

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, { useCallback, useMemo } from "react";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -11,55 +11,61 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ScheduleEventColor({ bodyshop, event }) { export function ScheduleEventColor({ bodyshop, event }) {
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const { t } = useTranslation(); const { t } = useTranslation();
const onClick = async ({ key }) => { const onClick = useCallback(
const result = await updateAppointment({ async ({ key }) => {
variables: { const result = await updateAppointment({
appid: event.id, variables: {
app: { color: key === "null" ? null : key } appid: event.id,
} app: { color: key === "null" ? null : key }
}); }
if (!!!result.errors) {
notification["success"]({ message: t("appointments.successes.saved") });
} else {
notification["error"]({
message: t("appointments.errors.saving", {
error: JSON.stringify(result.errors)
})
}); });
if (!result.errors) {
notification.success({ message: t("appointments.successes.saved") });
} else {
notification.error({
message: t("appointments.errors.saving", {
error: JSON.stringify(result.errors)
})
});
}
},
[event.id, t, updateAppointment]
);
const selectedColor = useMemo(() => {
if (event.color && bodyshop.appt_colors) {
const colorObj = bodyshop.appt_colors.find((color) => color.color.hex === event.color);
return colorObj?.label;
} }
}; return null;
}, [event.color, bodyshop.appt_colors]);
const selectedColor = const menu = useMemo(
event.color && () => ({
bodyshop.appt_colors && defaultSelectedKeys: [event.color],
bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]?.label; onClick: onClick,
items: [
const menu = { ...(bodyshop.appt_colors || []).map((color) => ({
defaultSelectedKeys: [event.color], key: color.color.hex,
onClick: onClick, label: color.label,
items: [ style: { color: color.color.hex }
...(bodyshop.appt_colors || []).map((color) => ({ })),
key: color.color.hex, { type: "divider" },
label: color.label, { key: "null", label: t("general.actions.clear") }
style: { color: color.color.hex } ]
})), }),
{ type: "divider" }, [bodyshop.appt_colors, event.color, onClick, t]
{ key: "null", label: t("general.actions.clear") } );
]
};
return ( return (
<Dropdown menu={menu}> <Dropdown menu={menu}>
<a href=" #" onClick={(e) => e.preventDefault()}> <a href="#" onClick={(e) => e.preventDefault()}>
{selectedColor} {selectedColor}
<DownOutlined /> <DownOutlined />
</a> </a>
@@ -67,4 +73,4 @@ export function ScheduleEventColor({ bodyshop, event }) {
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventColor); export default connect(mapStateToProps)(React.memo(ScheduleEventColor));

View File

@@ -3,7 +3,7 @@ import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select,
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState, useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next"; 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";
@@ -27,6 +27,7 @@ import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
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)),
@@ -44,301 +45,322 @@ export function ScheduleEventComponent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const history = useNavigate(); const navigate = useNavigate();
const searchParams = queryString.parse(useLocation().search); const location = useLocation();
const searchParams = queryString.parse(location.search);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [title, setTitle] = useState(event.title); const [title, setTitle] = useState(event.title);
const blockContent = (
<Space direction="vertical" wrap>
<Input
value={title}
onChange={(e) => setTitle(e.currentTarget.value)}
onBlur={async () => {
await updateAppointment({
variables: {
appid: event.id,
app: {
title: title
}
},
optimisticResponse: {
update_appointments: {
__typename: "appointments_mutation_response",
returning: [
{
...event,
title: title,
__typename: "appointments"
}
]
}
}
});
}}
/>
<Button onClick={() => handleCancel({ id: event.id })} disabled={event.arrived}> const handleTitleBlur = useCallback(async () => {
{t("appointments.actions.unblock")} await updateAppointment({
</Button> variables: {
</Space> appid: event.id,
app: {
title: title
}
},
optimisticResponse: {
update_appointments: {
__typename: "appointments_mutation_response",
returning: [
{
...event,
title: title,
__typename: "appointments"
}
]
}
}
});
}, [updateAppointment, event, title]);
const handleUnblock = useCallback(() => {
handleCancel({ id: event.id });
}, [handleCancel, event.id]);
const handlePreviewClick = useCallback(() => {
navigate({
search: queryString.stringify({
...searchParams,
selected: event.job.id
})
});
}, [navigate, searchParams, event.job.id]);
const handleSendEmailReminder = useCallback(() => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id }
},
{
to: event.job && event.job.ownr_ea,
subject: Template.subject
},
"e",
event.job && event.job.id
);
}, [event.job]);
const handleSendSMSReminder = useCallback(() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: dayjs(event.start).format("MM/DD/YYYY"),
time: dayjs(event.start).format("HH:mm a")
})
);
setOpen(false);
} else {
notification.error({
message: t("messaging.error.invalidphone")
});
}
}, [event.job, openChatByPhone, setMessage, t, bodyshop.shopname, event.start, setOpen]);
const reminderMenuItems = useMemo(
() => [
{
key: "email",
label: t("general.labels.email"),
disabled: event.arrived,
onClick: handleSendEmailReminder
},
{
key: "sms",
label: t("general.labels.sms"),
disabled: event.arrived || !bodyshop.messagingservicesid,
onClick: handleSendSMSReminder
}
],
[t, event.arrived, handleSendEmailReminder, handleSendSMSReminder, bodyshop.messagingservicesid]
); );
const popoverContent = ( const reminderMenu = useMemo(() => ({ items: reminderMenuItems }), [reminderMenuItems]);
<div style={{ maxWidth: "40vw" }}>
{!event.isintake ? (
<Space>
<strong>{event.title}</strong>
<ScheduleEventColor event={event} />
</Space>
) : (
<Space>
<strong>
<OwnerNameDisplay ownerObject={event.job} />
</strong>
<span style={{ margin: 4 }}>
{`${(event.job && event.job.v_model_yr) || ""} ${
(event.job && event.job.v_make_desc) || ""
} ${(event.job && event.job.v_model_desc) || ""}`}
</span>
<ScheduleEventColor event={event} />
</Space>
)}
{event.job ? ( const handleCancelFormFinish = useCallback(
<div> ({ lost_sale_reason }) => {
<DataLabel label={t("jobs.fields.ro_number")}>{(event.job && event.job.ro_number) || ""}</DataLabel> handleCancel({ id: event.id, lost_sale_reason });
<DataLabel label={t("jobs.fields.clm_total")}> },
<CurrencyFormatter>{(event.job && event.job.clm_total) || ""}</CurrencyFormatter> [handleCancel, event.id]
</DataLabel> );
<DataLabel hideIfNull label={t("jobs.fields.ins_co_nm")}>
{(event.job && event.job.ins_co_nm) || ""}
</DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.clm_no")}>
{(event.job && event.job.clm_no) || ""}
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ea")}>{(event.job && event.job.ownr_ea) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph1")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph1} jobid={event.job.id} />
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />
</DataLabel>
<ScheduleEventNote event={event} />
</div>
) : (
<div>{event.note || ""}</div>
)}
<Divider />
<Space wrap>
{event.job ? (
<Link to={`/manage/jobs/${event.job && event.job.id}`}>
<Button>{t("appointments.actions.viewjob")}</Button>
</Link>
) : null}
{event.job ? (
<Button
onClick={() => {
history({
search: queryString.stringify({
...searchParams,
selected: event.job.id
})
});
}}
>
{t("appointments.actions.preview")}
</Button>
) : null}
{event.job ? (
<Dropdown
menu={{
items: [
{
key: "email",
label: t("general.labels.email"),
disabled: event.arrived,
onClick: () => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id }
},
{
to: event.job && event.job.ownr_ea,
subject: Template.subject
},
"e",
event.job && event.job.id
);
}
},
{
key: "sms",
label: t("general.labels.sms"),
disabled: event.arrived || !bodyshop.messagingservicesid,
onClick: () => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: dayjs(event.start).format("MM/DD/YYYY"),
time: dayjs(event.start).format("HH:mm a")
})
);
setOpen(false);
} else {
notification["error"]({
message: t("messaging.error.invalidphone")
});
}
}
}
]
}}
>
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
) : null}
{event.arrived ? (
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
) : (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr
}))}
/>
</Form.Item>
<Button htmlType="submit">{t("appointments.actions.cancel")}</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
)}
{event.isintake ? ( const handleRescheduleClick = useCallback(() => {
<Button setOpen(false);
disabled={event.arrived} setScheduleContext({
onClick={() => { actions: { refetch: refetch },
setOpen(false); context: {
setScheduleContext({ jobId: event.job.id,
actions: { refetch: refetch }, job: event.job,
context: { previousEvent: event.id,
jobId: event.job.id, color: event.color,
job: event.job, alt_transport: event.job && event.job.alt_transport,
previousEvent: event.id, note: event.note
color: event.color, }
alt_transport: event.job && event.job.alt_transport, });
note: event.note }, [setOpen, setScheduleContext, refetch, event]);
}
}); const handleOpenChange = useCallback(
}} (vis) => {
> if (!event.vacation) setOpen(vis);
{t("appointments.actions.reschedule")} },
</Button> [event.vacation]
) : ( );
<ScheduleManualEvent event={event} />
)} const blockContent = useMemo(
{event.isintake ? ( () => (
<Link <Space direction="vertical" wrap>
to={{ <Input value={title} onChange={(e) => setTitle(e.currentTarget.value)} onBlur={handleTitleBlur} />
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
search: `?appointmentId=${event.id}` <Button onClick={handleUnblock} disabled={event.arrived}>
}} {t("appointments.actions.unblock")}
> </Button>
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
</Link>
) : null}
</Space> </Space>
</div> ),
[title, handleTitleBlur, handleUnblock, event.arrived, t]
); );
const RegularEvent = event.isintake ? ( const popoverContent = useMemo(
<Space () => (
wrap <div style={{ maxWidth: "40vw" }}>
size="small" {!event.isintake ? (
style={{ <Space>
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color <strong>{event.title}</strong>
}} <ScheduleEventColor event={event} />
> </Space>
{event.note && <AlertFilled className="production-alert" />} ) : (
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong> <Space>
<strong>
<OwnerNameDisplay ownerObject={event.job} />
</strong>
<span style={{ margin: 4 }}>
{`${(event.job && event.job.v_model_yr) || ""} ${
(event.job && event.job.v_make_desc) || ""
} ${(event.job && event.job.v_model_desc) || ""}`}
</span>
<ScheduleEventColor event={event} />
</Space>
)}
<OwnerNameDisplay ownerObject={event.job} /> {event.job ? (
<div>
<DataLabel label={t("jobs.fields.ro_number")}>{(event.job && event.job.ro_number) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.clm_total")}>
<CurrencyFormatter>{(event.job && event.job.clm_total) || ""}</CurrencyFormatter>
</DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.ins_co_nm")}>
{(event.job && event.job.ins_co_nm) || ""}
</DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.clm_no")}>
{(event.job && event.job.clm_no) || ""}
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ea")}>{(event.job && event.job.ownr_ea) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph1")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph1} jobid={event.job.id} />
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />
</DataLabel>
<ScheduleEventNote event={event} />
</div>
) : (
<div>{event.note || ""}</div>
)}
<Divider />
<Space wrap>
{event.job ? (
<Link to={`/manage/jobs/${event.job && event.job.id}`}>
<Button>{t("appointments.actions.viewjob")}</Button>
</Link>
) : null}
{event.job ? <Button onClick={handlePreviewClick}>{t("appointments.actions.preview")}</Button> : null}
{event.job ? (
<Dropdown menu={reminderMenu}>
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
) : null}
{event.arrived ? (
<Button disabled={event.arrived}>{t("appointments.actions.cancel")}</Button>
) : (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form layout="vertical" onFinish={handleCancelFormFinish}>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true
}
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr
}))}
/>
</Form.Item>
<Button htmlType="submit">{t("appointments.actions.cancel")}</Button>
</Form>
}
>
<Button disabled={event.arrived}>{t("appointments.actions.cancel")}</Button>
</Popover>
)}
{`${(event.job && event.job.v_model_yr) || ""} ${ {event.isintake ? (
(event.job && event.job.v_make_desc) || "" <Button disabled={event.arrived} onClick={handleRescheduleClick}>
} ${(event.job && event.job.v_model_desc) || ""}`} {t("appointments.actions.reschedule")}
</Button>
) : (
<ScheduleManualEvent event={event} />
)}
{event.isintake ? (
<Link
to={{
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
search: `?appointmentId=${event.id}`
}}
>
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
</Link>
) : null}
</Space>
</div>
),
[
event,
t,
handlePreviewClick,
reminderMenu,
bodyshop.md_lost_sale_reasons,
handleCancelFormFinish,
handleRescheduleClick
]
);
{`(${(event.job && event.job.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${ const RegularEvent = useMemo(
(event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0" () =>
})`} event.isintake ? (
<Space
wrap
size="small"
style={{
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
}}
>
{event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>} <OwnerNameDisplay ownerObject={event.job} />
</Space>
) : ( {`${(event.job && event.job.v_model_yr) || ""} ${
<div (event.job && event.job.v_make_desc) || ""
style={{ } ${(event.job && event.job.v_model_desc) || ""}`}
height: "100%",
width: "100%", {`(${(event.job && event.job.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color (event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0"
}} })`}
>
<strong>{`${event.title || ""}`}</strong> {event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
</div> </Space>
) : (
<div
style={{
height: "100%",
width: "100%",
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
}}
>
<strong>{`${event.title || ""}`}</strong>
</div>
),
[event, t]
); );
return ( return (
<Popover <Popover
open={open} open={open}
onOpenChange={(vis) => !event.vacation && setOpen(vis)} onOpenChange={handleOpenChange}
trigger="click" trigger="click"
content={event.block ? blockContent : popoverContent} content={event.block ? blockContent : popoverContent}
style={{ style={{
height: "100%", height: "100%",
width: "100%", width: "100%",
backgroundColor: event.color && event.color.hex ? event.color.hex : event.color backgroundColor: event.color && event.color.hex ? event.color.hex : event.color
}} }}
> >
@@ -347,4 +369,4 @@ export function ScheduleEventComponent({
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventComponent); export default connect(mapStateToProps, mapDispatchToProps)(React.memo(ScheduleEventComponent));

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import React from "react"; import React, { useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -10,64 +10,70 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ScheduleEventComponent from "./schedule-event.component"; import ScheduleEventComponent from "./schedule-event.component";
export default function ScheduleEventContainer({ bodyshop, event, refetch }) { function ScheduleEventContainer({ bodyshop, event, refetch }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const handleCancel = async ({ id, lost_sale_reason }) => {
logImEXEvent("schedule_cancel_appt");
const cancelAppt = await cancelAppointment({ const handleCancel = useCallback(
variables: { appid: event.id } async ({ id, lost_sale_reason }) => {
}); logImEXEvent("schedule_cancel_appt");
notification["success"]({
message: t("appointments.successes.canceled")
});
if (!!cancelAppt.errors) { const cancelAppt = await cancelAppointment({
notification["error"]({ variables: { appid: event.id }
message: t("appointments.errors.canceling", {
message: JSON.stringify(cancelAppt.errors)
})
}); });
return;
}
if (event.job) { if (!cancelAppt.errors) {
const jobUpdate = await updateJob({ notification.success({
variables: { message: t("appointments.successes.canceled")
jobId: event.job.id, });
job: { } else {
date_scheduled: null, notification.error({
scheduled_in: null, message: t("appointments.errors.canceling", {
scheduled_completion: null, message: JSON.stringify(cancelAppt.errors)
lost_sale_reason,
date_lost_sale: new Date(),
status: bodyshop.md_ro_statuses.default_imported
}
}
});
if (!jobUpdate.errors) {
dispatch(
insertAuditTrail({
jobid: event.job.id,
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
type: "appointmentcancel"
})
);
}
if (!!jobUpdate.errors) {
notification["error"]({
message: t("jobs.errors.updating", {
message: JSON.stringify(jobUpdate.errors)
}) })
}); });
return; return;
} }
}
if (refetch) refetch(); if (event.job) {
}; const jobUpdate = await updateJob({
variables: {
jobId: event.job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
lost_sale_reason,
date_lost_sale: new Date(),
status: bodyshop.md_ro_statuses.default_imported
}
}
});
if (!jobUpdate.errors) {
dispatch(
insertAuditTrail({
jobid: event.job.id,
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
type: "appointmentcancel"
})
);
} else {
notification.error({
message: t("jobs.errors.updating", {
message: JSON.stringify(jobUpdate.errors)
})
});
return;
}
}
if (refetch) refetch();
},
[cancelAppointment, event.id, event.job, updateJob, bodyshop.md_ro_statuses.default_imported, dispatch, t, refetch]
);
return <ScheduleEventComponent event={event} refetch={refetch} handleCancel={handleCancel} />; return <ScheduleEventComponent event={event} refetch={refetch} handleCancel={handleCancel} />;
} }
export default React.memo(ScheduleEventContainer);

View File

@@ -1,7 +1,7 @@
import { EditFilled, SaveFilled } from "@ant-design/icons"; import { EditFilled, SaveFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Input, notification, Space } from "antd"; import { Button, Input, notification, Space } from "antd";
import React, { useState } from "react"; import React, { useState, useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -12,9 +12,6 @@ import DataLabel from "../data-label/data-label.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ScheduleEventNote({ event }) { export function ScheduleEventNote({ event }) {
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
@@ -23,9 +20,9 @@ export function ScheduleEventNote({ event }) {
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const { t } = useTranslation(); const { t } = useTranslation();
const toggleEdit = async () => { const toggleEdit = useCallback(async () => {
if (editing) { if (editing) {
//Await the update // Await the update
setLoading(true); setLoading(true);
const result = await updateAppointment({ const result = await updateAppointment({
variables: { variables: {
@@ -34,10 +31,10 @@ export function ScheduleEventNote({ event }) {
} }
}); });
if (!!!result.errors) { if (!result.errors) {
// notification["success"]({ message: t("appointments.successes.saved") }); // notification.success({ message: t("appointments.successes.saved") });
} else { } else {
notification["error"]({ notification.error({
message: t("jobs.errors.saving", { message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors) error: JSON.stringify(result.errors)
}) })
@@ -45,11 +42,15 @@ export function ScheduleEventNote({ event }) {
} }
setEditing(false); setEditing(false);
setLoading(false);
} else { } else {
setEditing(true); setEditing(true);
} }
setLoading(false); }, [editing, note, updateAppointment, event.id, t]);
};
const handleNoteChange = useCallback((e) => {
setNote(e.target.value);
}, []);
return ( return (
<DataLabel label={t("appointments.fields.note")}> <DataLabel label={t("appointments.fields.note")}>
@@ -57,7 +58,7 @@ export function ScheduleEventNote({ event }) {
{!editing ? ( {!editing ? (
event.note || "" event.note || ""
) : ( ) : (
<Input.TextArea rows={3} value={note} onChange={(e) => setNote(e.target.value)} style={{ maxWidth: "8vw" }} /> <Input.TextArea rows={3} value={note} onChange={handleNoteChange} style={{ maxWidth: "8vw" }} />
)} )}
<Button onClick={toggleEdit} loading={loading}> <Button onClick={toggleEdit} loading={loading}>
{editing ? <SaveFilled /> : <EditFilled />} {editing ? <SaveFilled /> : <EditFilled />}
@@ -67,4 +68,4 @@ export function ScheduleEventNote({ event }) {
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventNote); export default connect(mapStateToProps)(React.memo(ScheduleEventNote));