- Merge client update into test-beta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,61 +1,65 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Dropdown, Menu, notification } from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {Dropdown, notification} from "antd";
|
||||
import {DownOutlined} from "@ant-design/icons";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function JobAltTransportChange({ bodyshop, job }) {
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const { t } = useTranslation();
|
||||
export function JobAltTransportChange({bodyshop, job}) {
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const {t} = useTranslation();
|
||||
|
||||
const onClick = async ({ key }) => {
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: { alt_transport: key === "null" ? null : key },
|
||||
},
|
||||
});
|
||||
const onClick = async ({key}) => {
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {alt_transport: key === "null" ? null : key},
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("appointments.successes.saved") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
const menu = (
|
||||
<Menu selectedKeys={[job && job.alt_transport]} onClick={onClick}>
|
||||
{bodyshop.appt_alt_transport &&
|
||||
bodyshop.appt_alt_transport.map((alt) => (
|
||||
<Menu.Item key={alt}>{alt}</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("appointments.successes.saved") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const menu = {
|
||||
items: [
|
||||
...(bodyshop.appt_alt_transport || []).map((alt) => ({
|
||||
key: alt,
|
||||
label: alt,
|
||||
})),
|
||||
{key: "null", label: t("general.actions.clear")},
|
||||
],
|
||||
onClick: onClick,
|
||||
defaultSelectedKeys: [job && job.alt_transport]
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobAltTransportChange);
|
||||
|
||||
@@ -1,69 +1,72 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Dropdown, Menu, notification } from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {Dropdown, notification} from "antd";
|
||||
import {DownOutlined} from "@ant-design/icons";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ScheduleEventColor({ bodyshop, event }) {
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const { t } = useTranslation();
|
||||
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 },
|
||||
},
|
||||
});
|
||||
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),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({message: t("appointments.successes.saved")});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("appointments.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectedColor =
|
||||
event.color &&
|
||||
bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]
|
||||
?.label;
|
||||
const selectedColor =
|
||||
event.color &&
|
||||
bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]
|
||||
?.label;
|
||||
|
||||
const menu = (
|
||||
<Menu selectedKeys={[event.color]} onClick={onClick}>
|
||||
{bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.map((color) => (
|
||||
<Menu.Item style={{ color: color.color.hex }} key={color.color.hex}>
|
||||
{color.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{selectedColor}
|
||||
<DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
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")},
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{selectedColor}
|
||||
<DownOutlined/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventColor);
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
import { AlertFilled } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
Space,
|
||||
} from "antd";
|
||||
import {AlertFilled} from "@ant-design/icons";
|
||||
import {Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space,} from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import moment from "moment";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import {GenerateDocument} from "../../utils/RenderTemplate";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
@@ -35,357 +21,358 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
|
||||
import ScheduleAtChange from "./job-at-change.component";
|
||||
import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
setScheduleContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "schedule"})),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
});
|
||||
|
||||
export function ScheduleEventComponent({
|
||||
bodyshop,
|
||||
setMessage,
|
||||
openChatByPhone,
|
||||
event,
|
||||
refetch,
|
||||
handleCancel,
|
||||
setScheduleContext,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const history = useHistory();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
bodyshop,
|
||||
setMessage,
|
||||
openChatByPhone,
|
||||
event,
|
||||
refetch,
|
||||
handleCancel,
|
||||
setScheduleContext,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const history = useNavigate();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
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}
|
||||
>
|
||||
{t("appointments.actions.unblock")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
<div style={{ maxWidth: "40vw" }}>
|
||||
{!event.isintake ? (
|
||||
<Space>
|
||||
<strong>{event.title}</strong>
|
||||
<ScheduleEventColor event={event} />
|
||||
<Button
|
||||
onClick={() => handleCancel({id: event.id})}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.unblock")}
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<strong>
|
||||
<OwnerNameDisplay ownerObject={event.job} />
|
||||
</strong>
|
||||
<span style={{ margin: 4 }}>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
<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_make_desc) || ""
|
||||
} ${(event.job && event.job.v_model_desc) || ""}`}
|
||||
</span>
|
||||
<ScheduleEventColor event={event} />
|
||||
</Space>
|
||||
)}
|
||||
<ScheduleEventColor event={event}/>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{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} />
|
||||
{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={() => {
|
||||
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 ? (
|
||||
<Button
|
||||
disabled={event.arrived}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setScheduleContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
jobId: event.job.id,
|
||||
job: event.job,
|
||||
previousEvent: event.id,
|
||||
color: event.color,
|
||||
alt_transport: event.job && event.job.alt_transport,
|
||||
note: event.note,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
) : (
|
||||
<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.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
selected: event.job.id,
|
||||
}),
|
||||
});
|
||||
);
|
||||
|
||||
const RegularEvent = event.isintake ? (
|
||||
<Space
|
||||
wrap
|
||||
size="small"
|
||||
style={{
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
}}
|
||||
>
|
||||
{t("appointments.actions.preview")}
|
||||
</Button>
|
||||
) : null}
|
||||
{event.job ? (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
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
|
||||
);
|
||||
}}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("general.labels.email")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
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: moment(event.start).format("MM/DD/YYYY"),
|
||||
time: moment(event.start).format("HH:mm a"),
|
||||
})
|
||||
);
|
||||
setVisible(false);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("messaging.error.invalidphone"),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={event.arrived || !bodyshop.messagingservicesid}
|
||||
>
|
||||
{t("general.labels.sms")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
{event.arrived ? (
|
||||
<Button
|
||||
// onClick={() => handleCancel(event.id)}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
) : (
|
||||
<Popover
|
||||
>
|
||||
{event.note && <AlertFilled className="production-alert"/>}
|
||||
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
|
||||
|
||||
<OwnerNameDisplay ownerObject={event.job}/>
|
||||
|
||||
{`${(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.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${
|
||||
(event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0"
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && (
|
||||
<div style={{margin: ".1rem"}}>{event.job.alt_transport}</div>
|
||||
)}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
}}
|
||||
>
|
||||
<strong>{`${event.title || ""}`}</strong>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={(vis) => !event.vacation && setOpen(vis)}
|
||||
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>
|
||||
)}
|
||||
content={event.block ? blockContent : popoverContent}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
|
||||
{event.isintake ? (
|
||||
<Button
|
||||
disabled={event.arrived}
|
||||
onClick={() => {
|
||||
setVisible(false);
|
||||
setScheduleContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
jobId: event.job.id,
|
||||
job: event.job,
|
||||
previousEvent: event.id,
|
||||
color: event.color,
|
||||
alt_transport: event.job && event.job.alt_transport,
|
||||
note: event.note,
|
||||
},
|
||||
});
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
|
||||
const RegularEvent = 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>
|
||||
|
||||
<OwnerNameDisplay ownerObject={event.job} />
|
||||
|
||||
{`${(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.labhrs.aggregate.sum.mod_lb_hrs) || "0"} / ${
|
||||
(event.job && event.job.larhrs.aggregate.sum.mod_lb_hrs) || "0"
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && (
|
||||
<div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>
|
||||
)}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
}}
|
||||
>
|
||||
<strong>{`${event.title || ""}`}</strong>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
visible={visible}
|
||||
onVisibleChange={(vis) => !event.vacation && setVisible(vis)}
|
||||
trigger="click"
|
||||
content={event.block ? blockContent : popoverContent}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
|
||||
backgroundColor:
|
||||
event.color && event.color.hex ? event.color.hex : event.color,
|
||||
}}
|
||||
>
|
||||
{RegularEvent}
|
||||
</Popover>
|
||||
);
|
||||
>
|
||||
{RegularEvent}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScheduleEventComponent);
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { notification } from "antd";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {notification} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||
import {CANCEL_APPOINTMENT_BY_ID} from "../../graphql/appointments.queries";
|
||||
import {UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import ScheduleEventComponent from "./schedule-event.component";
|
||||
|
||||
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const handleCancel = async ({ id, lost_sale_reason }) => {
|
||||
logImEXEvent("schedule_cancel_appt");
|
||||
export default function ScheduleEventContainer({bodyshop, event, refetch}) {
|
||||
const dispatch = useDispatch();
|
||||
const {t} = useTranslation();
|
||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const handleCancel = async ({id, lost_sale_reason}) => {
|
||||
logImEXEvent("schedule_cancel_appt");
|
||||
|
||||
const cancelAppt = await cancelAppointment({
|
||||
variables: { appid: event.id },
|
||||
});
|
||||
notification["success"]({
|
||||
message: t("appointments.successes.canceled"),
|
||||
});
|
||||
|
||||
if (!!cancelAppt.errors) {
|
||||
notification["error"]({
|
||||
message: t("appointments.errors.canceling", {
|
||||
message: JSON.stringify(cancelAppt.errors),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!!jobUpdate.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
message: JSON.stringify(jobUpdate.errors),
|
||||
}),
|
||||
const cancelAppt = await cancelAppointment({
|
||||
variables: {appid: event.id},
|
||||
});
|
||||
notification["success"]({
|
||||
message: t("appointments.successes.canceled"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (refetch) refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<ScheduleEventComponent
|
||||
event={event}
|
||||
refetch={refetch}
|
||||
handleCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
if (!!cancelAppt.errors) {
|
||||
notification["error"]({
|
||||
message: t("appointments.errors.canceling", {
|
||||
message: JSON.stringify(cancelAppt.errors),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!!jobUpdate.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
message: JSON.stringify(jobUpdate.errors),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (refetch) refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<ScheduleEventComponent
|
||||
event={event}
|
||||
refetch={refetch}
|
||||
handleCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,74 +1,75 @@
|
||||
import { EditFilled, SaveFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Input, notification, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {EditFilled, SaveFilled} from "@ant-design/icons";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Input, notification, Space} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {UPDATE_APPOINTMENT} from "../../graphql/appointments.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ScheduleEventNote({ event }) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [note, setNote] = useState(event.note || "");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const { t } = useTranslation();
|
||||
export function ScheduleEventNote({event}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [note, setNote] = useState(event.note || "");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const {t} = useTranslation();
|
||||
|
||||
const toggleEdit = async () => {
|
||||
if (editing) {
|
||||
//Await the update
|
||||
setLoading(true);
|
||||
const result = await updateAppointment({
|
||||
variables: {
|
||||
appid: event.id,
|
||||
app: { note },
|
||||
},
|
||||
});
|
||||
const toggleEdit = async () => {
|
||||
if (editing) {
|
||||
//Await the update
|
||||
setLoading(true);
|
||||
const result = await updateAppointment({
|
||||
variables: {
|
||||
appid: event.id,
|
||||
app: {note},
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("appointments.successes.saved") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("appointments.successes.saved") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
setEditing(false);
|
||||
} else {
|
||||
setEditing(true);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
setEditing(false);
|
||||
} else {
|
||||
setEditing(true);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataLabel label={t("appointments.fields.note")}>
|
||||
<Space flex>
|
||||
{!editing ? (
|
||||
event.note || ""
|
||||
) : (
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={note}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
style={{ maxWidth: "8vw" }}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={toggleEdit} loading={loading}>
|
||||
{editing ? <SaveFilled /> : <EditFilled />}
|
||||
</Button>
|
||||
</Space>
|
||||
</DataLabel>
|
||||
);
|
||||
return (
|
||||
<DataLabel label={t("appointments.fields.note")}>
|
||||
<Space>
|
||||
{!editing ? (
|
||||
event.note || ""
|
||||
) : (
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={note}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
style={{maxWidth: "8vw"}}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={toggleEdit} loading={loading}>
|
||||
{editing ? <SaveFilled/> : <EditFilled/>}
|
||||
</Button>
|
||||
</Space>
|
||||
</DataLabel>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventNote);
|
||||
|
||||
Reference in New Issue
Block a user