diff --git a/client/src/components/job-at-change/schedule-event.color.component.jsx b/client/src/components/job-at-change/schedule-event.color.component.jsx index e66a8b6d9..c8e1a57b2 100644 --- a/client/src/components/job-at-change/schedule-event.color.component.jsx +++ b/client/src/components/job-at-change/schedule-event.color.component.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useMemo } from "react"; import { useMutation } from "@apollo/client"; import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; import { useTranslation } from "react-i18next"; @@ -11,55 +11,61 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); export function ScheduleEventColor({ bodyshop, event }) { const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const { t } = useTranslation(); - const onClick = async ({ key }) => { - const result = await updateAppointment({ - variables: { - 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) - }) + const onClick = useCallback( + async ({ key }) => { + const result = await updateAppointment({ + variables: { + 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) + }) + }); + } + }, + [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 = - event.color && - bodyshop.appt_colors && - bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]?.label; - - const menu = { - defaultSelectedKeys: [event.color], - onClick: onClick, - items: [ - ...(bodyshop.appt_colors || []).map((color) => ({ - key: color.color.hex, - label: color.label, - style: { color: color.color.hex } - })), - { type: "divider" }, - { key: "null", label: t("general.actions.clear") } - ] - }; + const menu = useMemo( + () => ({ + defaultSelectedKeys: [event.color], + onClick: onClick, + items: [ + ...(bodyshop.appt_colors || []).map((color) => ({ + key: color.color.hex, + label: color.label, + style: { color: color.color.hex } + })), + { type: "divider" }, + { key: "null", label: t("general.actions.clear") } + ] + }), + [bodyshop.appt_colors, event.color, onClick, t] + ); return ( - e.preventDefault()}> + e.preventDefault()}> {selectedColor} @@ -67,4 +73,4 @@ export function ScheduleEventColor({ bodyshop, event }) { ); } -export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventColor); +export default connect(mapStateToProps)(React.memo(ScheduleEventColor)); diff --git a/client/src/components/job-at-change/schedule-event.component.jsx b/client/src/components/job-at-change/schedule-event.component.jsx index b34e90668..f3c2adc55 100644 --- a/client/src/components/job-at-change/schedule-event.component.jsx +++ b/client/src/components/job-at-change/schedule-event.component.jsx @@ -3,7 +3,7 @@ import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, import parsePhoneNumber from "libphonenumber-js"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, { useState } from "react"; +import React, { useState, useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -27,6 +27,7 @@ import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); + const mapDispatchToProps = (dispatch) => ({ setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), @@ -44,301 +45,322 @@ export function ScheduleEventComponent({ }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); - const history = useNavigate(); - const searchParams = queryString.parse(useLocation().search); + const navigate = useNavigate(); + const location = useLocation(); + const searchParams = queryString.parse(location.search); const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const [title, setTitle] = useState(event.title); - const blockContent = ( - - 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" - } - ] - } - } - }); - }} - /> - - + const handleTitleBlur = useCallback(async () => { + await updateAppointment({ + variables: { + 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 = ( -
- {!event.isintake ? ( - - {event.title} - - - ) : ( - - - - - - {`${(event.job && event.job.v_model_yr) || ""} ${ - (event.job && event.job.v_make_desc) || "" - } ${(event.job && event.job.v_model_desc) || ""}`} - - - - )} + const reminderMenu = useMemo(() => ({ items: reminderMenuItems }), [reminderMenuItems]); - {event.job ? ( -
- {(event.job && event.job.ro_number) || ""} - - {(event.job && event.job.clm_total) || ""} - - - {(event.job && event.job.ins_co_nm) || ""} - - - {(event.job && event.job.clm_no) || ""} - - {(event.job && event.job.ownr_ea) || ""} - - - - - - - - {(event.job && event.job.alt_transport) || ""} - - - -
- ) : ( -
{event.note || ""}
- )} - - - {event.job ? ( - - - - ) : null} - {event.job ? ( - - ) : null} - {event.job ? ( - { - 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") - }); - } - } - } - ] - }} - > - - - ) : null} - {event.arrived ? ( - - ) : ( - { - handleCancel({ id: event.id, lost_sale_reason }); - }} - > - - setTitle(e.currentTarget.value)} onBlur={handleTitleBlur} /> + + -
+ ), + [title, handleTitleBlur, handleUnblock, event.arrived, t] ); - const RegularEvent = event.isintake ? ( - - {event.note && } - {`${event.job.ro_number || t("general.labels.na")}`} + const popoverContent = useMemo( + () => ( +
+ {!event.isintake ? ( + + {event.title} + + + ) : ( + + + + + + {`${(event.job && event.job.v_model_yr) || ""} ${ + (event.job && event.job.v_make_desc) || "" + } ${(event.job && event.job.v_model_desc) || ""}`} + + + + )} - + {event.job ? ( +
+ {(event.job && event.job.ro_number) || ""} + + {(event.job && event.job.clm_total) || ""} + + + {(event.job && event.job.ins_co_nm) || ""} + + + {(event.job && event.job.clm_no) || ""} + + {(event.job && event.job.ownr_ea) || ""} + + + + + + + + {(event.job && event.job.alt_transport) || ""} + + + +
+ ) : ( +
{event.note || ""}
+ )} + + + {event.job ? ( + + + + ) : null} + {event.job ? : null} + {event.job ? ( + + + + ) : null} + {event.arrived ? ( + + ) : ( + + +