Merged in release/2025-01-31 (pull request #2089)

Release/2025 01 31
This commit is contained in:
Dave Richer
2025-01-30 17:12:44 +00:00
10 changed files with 128 additions and 66 deletions

View File

@@ -62,7 +62,17 @@
t = d.getElementsByTagName("script")[0]; t = d.getElementsByTagName("script")[0];
t.parentNode.insertBefore(s, t); t.parentNode.insertBefore(s, t);
</script> </script>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<% } %> <% } %>
<script> <script>
!(function () { !(function () {

View File

@@ -1,10 +1,10 @@
import * as Sentry from "@sentry/react";
import { Button, Col, Collapse, Result, Row, Space } from "antd"; import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react"; import React from "react";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import * as Sentry from "@sentry/react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -38,28 +38,23 @@ class ErrorBoundary extends React.Component {
} }
handleErrorSubmit = () => { handleErrorSubmit = () => {
InstanceRenderManager({ window.$crisp.push([
executeFunction: true, "do",
args: [], "message:send",
imex: () => { [
window.$crisp.push([ "text",
"do", `I hit the following error: \n\n
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n ${this.state.error.message}\n\n
${this.state.error.stack}\n\n ${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${ URL:${window.location} as ${this.props.currentUser.email} for ${
this.props.bodyshop && this.props.bodyshop.name this.props.bodyshop && this.props.bodyshop.name
} }
` `
] ]
]); ]);
window.$crisp.push(["do", "chat:open"]);
window.$crisp.push(["do", "chat:open"]);
}
});
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue** // const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
// ---- // ----

View File

@@ -656,14 +656,7 @@ function Header({
icon: <Icon component={QuestionCircleFilled} />, icon: <Icon component={QuestionCircleFilled} />,
label: t("menus.header.help"), label: t("menus.header.help"),
onClick: () => { onClick: () => {
window.open( window.open("https://help.imex.online/", "_blank");
InstanceRenderManager({
imex: "https://help.imex.online/",
rome: "https://rometech.com//"
}),
"_blank"
);
} }
}, },
...(InstanceRenderManager({ ...(InstanceRenderManager({

View File

@@ -4,11 +4,12 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space } from "antd"; import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space } from "antd";
import axios from "axios"; import axios from "axios";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import React, { useContext, useMemo, useState } from "react"; import { useContext, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries"; import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
@@ -30,7 +31,6 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -121,6 +121,7 @@ export function JobsDetailHeaderActions({
const history = useNavigate(); const history = useNavigate();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [dropdownOpen, setDropdownOpen] = useState(false);
const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false); const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false);
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
const [deleteJob] = useMutation(DELETE_JOB); const [deleteJob] = useMutation(DELETE_JOB);
@@ -718,7 +719,13 @@ export function JobsDetailHeaderActions({
key: "toggleproduction", key: "toggleproduction",
id: "job-actions-toggleproduction", id: "job-actions-toggleproduction",
disabled: !job.converted || jobRO, disabled: !job.converted || jobRO,
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} /> label: (
<JobsDetailHeaderActionsToggleProduction
job={job}
refetch={refetch}
closeParentMenu={() => setDropdownOpen(false)}
/>
)
}, },
{ {
@@ -1148,7 +1155,7 @@ export function JobsDetailHeaderActions({
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
<Dropdown menu={menu} trigger={["click"]} key="changestatus"> <Dropdown menu={menu} trigger={["click"]} key="changestatus" open={dropdownOpen} onOpenChange={setDropdownOpen}>
<Button> <Button>
<span>{t("general.labels.actions")}</span> <span>{t("general.labels.actions")}</span>
<DownCircleFilled /> <DownCircleFilled />

View File

@@ -1,11 +1,11 @@
import { useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Form, Popover, Space } from "antd"; import { Button, Form, Popover, Space } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import React, { useEffect, useState } from "react"; import { useEffect, useState } 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";
import { JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries"; import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -23,16 +23,40 @@ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })) insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation }))
}); });
export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO, insertAuditTrail }) { export function JobsDetailHeaderActionsToggleProduction({
bodyshop,
job,
jobRO,
refetch,
closeParentMenu,
insertAuditTrail
}) {
const [scenario, setScenario] = useState(false); const [scenario, setScenario] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [popOverVisible, setPopOverVisible] = useState(false);
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE); const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const notification = useNotification(); const notification = useNotification();
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
variables: { id: job.id },
onCompleted: (data) => {
if (data?.jobs_by_pk) {
form.setFieldsValue({
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
scheduled_completion: data.jobs_by_pk.scheduled_completion,
actual_completion: data.jobs_by_pk.actual_completion,
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
actual_delivery: data.jobs_by_pk.actual_delivery
});
}
},
fetchPolicy: "network-only"
});
useEffect(() => { useEffect(() => {
//Figure out what scenario were in, populate accodingly //Figure out what scenario were in, populate accordingly
if (job && bodyshop) { if (job && bodyshop) {
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) { if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
setScenario("pre"); setScenario("pre");
@@ -76,24 +100,16 @@ export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO,
DateTimeFormatterFunction(values.actual_completion) DateTimeFormatterFunction(values.actual_completion)
) )
}); });
setPopOverVisible(false);
closeParentMenu();
refetch();
} }
setLoading(false); setLoading(false);
}; };
const popMenu = ( const popMenu = (
<div onClick={(e) => e.stopPropagation()}> <div onClick={(e) => e.stopPropagation()}>
<Form <Form layout="vertical" form={form} onFinish={handleConvert}>
layout="vertical"
form={form}
onFinish={handleConvert}
initialValues={{
actual_in: dayjs(),
scheduled_completion: job.scheduled_completion,
actual_completion: job.actual_completion,
scheduled_deliver: job.scheduled_deliver,
actual_delivery: job.actual_delivery
}}
>
{scenario === "pre" && ( {scenario === "pre" && (
<> <>
<Form.Item <Form.Item
@@ -158,7 +174,12 @@ export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO,
return ( return (
<Popover //open={open} <Popover //open={open}
content={popMenu} content={popMenu}
onClick={(e) => e.stopPropagation()} open={popOverVisible}
onOpenChange={setPopOverVisible}
onClick={(e) => {
getJobDetails();
e.stopPropagation();
}}
getPopupContainer={(trigger) => trigger.parentNode} getPopupContainer={(trigger) => trigger.parentNode}
trigger="click" trigger="click"
> >

View File

@@ -1,14 +1,14 @@
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { Button, Form, Popover, Space } from "antd"; import { Button, Form, Popover, Space } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useMemo, useState } from "react"; import { useMemo, useState } 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";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries"; import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -134,7 +134,7 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages, callback
]} ]}
name={"jobid"} name={"jobid"}
> >
<JobSearchSelect /> <JobSearchSelect notExported={false} notInvoiced={false} />
</Form.Item> </Form.Item>
</Form> </Form>
<Space> <Space>

View File

@@ -1,5 +1,5 @@
import { Button, Form, Popover, Space } from "antd"; import { Button, Form, Popover, Space } from "antd";
import React, { useState } from "react"; import { useState } 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";
@@ -60,7 +60,7 @@ export function JobsDocumentsLocalGalleryReassign({ bodyshop, jobid, allMedia, g
]} ]}
name={"jobid"} name={"jobid"}
> >
<JobSearchSelect /> <JobSearchSelect notExported={false} notInvoiced={false}/>
</Form.Item> </Form.Item>
</Form> </Form>
<Space> <Space>

View File

@@ -2553,3 +2553,16 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
} }
} }
`; `;
export const GET_JOB_BY_PK_QUICK_INTAKE = gql`
query GET_JOB_BY_PK_QUICK_INTAKE($id: uuid!) {
jobs_by_pk(id: $id) {
id
actual_in
scheduled_completion
actual_completion
scheduled_delivery
actual_delivery
}
}
`;

View File

@@ -1,4 +1,5 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled, PrinterFilled } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-layout";
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { import {
Alert, Alert,
@@ -16,32 +17,31 @@ import {
Switch, Switch,
Typography Typography
} from "antd"; } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
// import { useNavigate } from 'react-router-dom'; // import { useNavigate } from 'react-router-dom';
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import dayjs from "../../utils/day";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component"; import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component"; import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component"; import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component"; import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component"; import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries"; import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions.js";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container"; import dayjs from "../../utils/day";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -49,10 +49,17 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "printCenter"
})
)
}); });
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) { export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, setPrintCenterContext }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const client = useApolloClient(); const client = useApolloClient();
@@ -171,7 +178,6 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
extra={ extra={
<Space> <Space>
<JobsCloseAutoAllocate joblines={job.joblines} form={form} disabled={!!job.date_exported || jobRO} /> <JobsCloseAutoAllocate joblines={job.joblines} form={form} disabled={!!job.date_exported || jobRO} />
<Popconfirm <Popconfirm
onConfirm={() => form.submit()} onConfirm={() => form.submit()}
disabled={jobRO} disabled={jobRO}
@@ -188,6 +194,21 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button> <Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
</Link> </Link>
)} )}
<Button
onClick={() => {
setPrintCenterContext({
context: {
id: job.id,
job: job,
type: "job"
}
});
}}
key="printing"
icon={<PrinterFilled />}
>
{t("jobs.actions.printCenter")}
</Button>
<JobsScoreboardAdd job={job} disabled={false} /> <JobsScoreboardAdd job={job} disabled={false} />
</Space> </Space>
} }

View File

@@ -26,6 +26,7 @@ import {
updateCurrentUser updateCurrentUser
} from "../../firebase/firebase.utils"; } from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries"; import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import cleanAxios from "../../utils/CleanAxios";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -47,7 +48,6 @@ import {
validatePasswordResetSuccess validatePasswordResetSuccess
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
import cleanAxios from "../../utils/CleanAxios";
const fpPromise = FingerprintJS.load(); const fpPromise = FingerprintJS.load();
@@ -234,16 +234,18 @@ export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email); LogRocket.identify(payload.email);
try { try {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
InstanceRenderManager({ InstanceRenderManager({
executeFunction: true, executeFunction: true,
args: [], args: [],
imex: () => { imex: () => {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]); window.$crisp.push(["set", "session:segments", [["imex"]]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
}, },
rome: () => { rome: () => {
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email); window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
window.$zoho.salesiq.visitor.email(payload.email); window.$zoho.salesiq.visitor.email(payload.email);
window.$crisp.push(["set", "session:segments", [["rome"]]]);
} }
}); });
} catch (error) { } catch (error) {