- Merge client update into test-beta

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-18 19:20:08 -05:00
696 changed files with 92291 additions and 107075 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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}
/>
);
}

View File

@@ -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);