- reapply proper prettier formatting.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -6,7 +6,7 @@ import {connect} from "react-redux";
|
|||||||
import {createStructuredSelector} from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import {DateFormatter} from "../../utils/DateFormatter";
|
import {DateFormatter} from "../../utils/DateFormatter";
|
||||||
import {alphaSort, dateSort} from "../../utils/sorters";
|
import {alphaSort, dateSort} from "../../utils/sorters";
|
||||||
@@ -22,7 +22,10 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "billEnter"
|
||||||
|
})),
|
||||||
setReconciliationContext: (context) => dispatch(setModalContext({
|
setReconciliationContext: (context) => dispatch(setModalContext({
|
||||||
context: context,
|
context: context,
|
||||||
modal: "reconciliation"
|
modal: "reconciliation"
|
||||||
@@ -40,7 +43,7 @@ export function BillsListTableComponent({
|
|||||||
setReconciliationContext,
|
setReconciliationContext,
|
||||||
setTaskUpsertContext,
|
setTaskUpsertContext,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {}
|
sortedInfo: {}
|
||||||
@@ -51,14 +54,14 @@ export function BillsListTableComponent({
|
|||||||
|
|
||||||
const Templates = TemplateList("bill");
|
const Templates = TemplateList("bill");
|
||||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||||
const { refetch } = billsQuery;
|
const {refetch} = billsQuery;
|
||||||
|
|
||||||
|
|
||||||
const recordActions = (record, showView = false) => (
|
const recordActions = (record, showView = false) => (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{showView && (
|
{showView && (
|
||||||
<Button onClick={() => handleOnRowClick(record)}>
|
<Button onClick={() => handleOnRowClick(record)}>
|
||||||
<EditFilled />
|
<EditFilled/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button title={t('tasks.buttons.create')} onClick={() => {
|
<Button title={t('tasks.buttons.create')} onClick={() => {
|
||||||
@@ -71,9 +74,9 @@ export function BillsListTableComponent({
|
|||||||
}}>
|
}}>
|
||||||
<FaTasks/>
|
<FaTasks/>
|
||||||
</Button>
|
</Button>
|
||||||
<BillDeleteButton bill={record} jobid={job.id} />
|
<BillDeleteButton bill={record} jobid={job.id}/>
|
||||||
<BillDetailEditReturnComponent
|
<BillDetailEditReturnComponent
|
||||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
data={{bills_by_pk: {...record, jobid: job.id}}}
|
||||||
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
|
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -81,9 +84,9 @@ export function BillsListTableComponent({
|
|||||||
<PrintWrapperComponent
|
<PrintWrapperComponent
|
||||||
templateObject={{
|
templateObject={{
|
||||||
name: Templates.inhouse_invoice.key,
|
name: Templates.inhouse_invoice.key,
|
||||||
variables: { id: record.id }
|
variables: {id: record.id}
|
||||||
}}
|
}}
|
||||||
messageObject={{ subject: Templates.inhouse_invoice.subject }}
|
messageObject={{subject: Templates.inhouse_invoice.subject}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -126,7 +129,7 @@ export function BillsListTableComponent({
|
|||||||
key: "is_credit_memo",
|
key: "is_credit_memo",
|
||||||
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
|
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
|
||||||
sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox checked={record.is_credit_memo} />
|
render: (text, record) => <Checkbox checked={record.is_credit_memo}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("bills.fields.exported"),
|
title: t("bills.fields.exported"),
|
||||||
@@ -134,7 +137,7 @@ export function BillsListTableComponent({
|
|||||||
key: "exported",
|
key: "exported",
|
||||||
sorter: (a, b) => a.exported - b.exported,
|
sorter: (a, b) => a.exported - b.exported,
|
||||||
sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox checked={record.exported} />
|
render: (text, record) => <Checkbox checked={record.exported}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
@@ -145,18 +148,18 @@ export function BillsListTableComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredBills = bills
|
const filteredBills = bills
|
||||||
? searchText === ""
|
? searchText === ""
|
||||||
? bills
|
? bills
|
||||||
: bills.filter(
|
: bills.filter(
|
||||||
(b) =>
|
(b) =>
|
||||||
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
|
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,14 +168,14 @@ export function BillsListTableComponent({
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined/>
|
||||||
</Button>
|
</Button>
|
||||||
{job && job.converted ? (
|
{job && job.converted ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: billsQuery.refetch },
|
actions: {refetch: billsQuery.refetch},
|
||||||
context: {
|
context: {
|
||||||
job
|
job
|
||||||
}
|
}
|
||||||
@@ -184,7 +187,7 @@ export function BillsListTableComponent({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setReconciliationContext({
|
setReconciliationContext({
|
||||||
actions: { refetch: billsQuery.refetch },
|
actions: {refetch: billsQuery.refetch},
|
||||||
context: {
|
context: {
|
||||||
job,
|
job,
|
||||||
bills: (billsQuery.data && billsQuery.data.bills) || []
|
bills: (billsQuery.data && billsQuery.data.bills) || []
|
||||||
|
|||||||
@@ -26,26 +26,35 @@ import Icon, {
|
|||||||
UnorderedListOutlined,
|
UnorderedListOutlined,
|
||||||
UserOutlined
|
UserOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import { Layout, Menu, Switch, Tooltip } from "antd";
|
import {Layout, Menu, Switch, Tooltip} from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { BsKanban } from "react-icons/bs";
|
import {BsKanban} from "react-icons/bs";
|
||||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
import {
|
||||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
FaCalendarAlt,
|
||||||
import { IoBusinessOutline } from "react-icons/io5";
|
FaCarCrash,
|
||||||
import { RiSurveyLine } from "react-icons/ri";
|
FaCreditCard,
|
||||||
import { connect } from "react-redux";
|
FaFileInvoiceDollar,
|
||||||
import { Link } from "react-router-dom";
|
FaTasks
|
||||||
import { createStructuredSelector } from "reselect";
|
} from "react-icons/fa";
|
||||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
import {GiPayMoney, GiPlayerTime, GiSettingsKnobs} from "react-icons/gi";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import {IoBusinessOutline} from "react-icons/io5";
|
||||||
import { signOutStart } from "../../redux/user/user.actions";
|
import {RiSurveyLine} from "react-icons/ri";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import {connect} from "react-redux";
|
||||||
import { FiLogOut } from "react-icons/fi";
|
import {Link} from "react-router-dom";
|
||||||
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
|
import {createStructuredSelector} from "reselect";
|
||||||
|
import {
|
||||||
|
selectRecentItems,
|
||||||
|
selectSelectedHeader
|
||||||
|
} from "../../redux/application/application.selectors";
|
||||||
|
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||||
|
import {signOutStart} from "../../redux/user/user.actions";
|
||||||
|
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||||
|
import {FiLogOut} from "react-icons/fi";
|
||||||
|
import {checkBeta, handleBeta, setBeta} from "../../utils/betaHandler";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -55,38 +64,53 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
context: context,
|
||||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
modal: "billEnter"
|
||||||
setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })),
|
})),
|
||||||
|
setTimeTicketContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "timeTicket"
|
||||||
|
})),
|
||||||
|
setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
|
||||||
|
setReportCenterContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "reportCenter"
|
||||||
|
})),
|
||||||
signOutStart: () => dispatch(signOutStart()),
|
signOutStart: () => dispatch(signOutStart()),
|
||||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
setCardPaymentContext: (context) => dispatch(setModalContext({
|
||||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context: context, modal: 'taskUpsert'})),
|
context: context,
|
||||||
|
modal: "cardPayment"
|
||||||
|
})),
|
||||||
|
setTaskUpsertContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: 'taskUpsert'
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
function Header({
|
function Header({
|
||||||
handleMenuClick,
|
handleMenuClick,
|
||||||
currentUser,
|
currentUser,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
selectedHeader,
|
selectedHeader,
|
||||||
signOutStart,
|
signOutStart,
|
||||||
setBillEnterContext,
|
setBillEnterContext,
|
||||||
setTimeTicketContext,
|
setTimeTicketContext,
|
||||||
setPaymentContext,
|
setPaymentContext,
|
||||||
setReportCenterContext,
|
setReportCenterContext,
|
||||||
recentItems,
|
recentItems,
|
||||||
setCardPaymentContext,
|
setCardPaymentContext,
|
||||||
setTaskUpsertContext,
|
setTaskUpsertContext,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
treatments: { ImEXPay, DmsAp, Simple_Inventory }
|
treatments: {ImEXPay, DmsAp, Simple_Inventory}
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||||
splitKey: bodyshop && bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isBeta = checkBeta();
|
const isBeta = checkBeta();
|
||||||
@@ -105,18 +129,18 @@ function Header({
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "bills", bodyshop })
|
promanager: HasFeatureAccess({featureName: "bills", bodyshop})
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
accountingChildren.push(
|
accountingChildren.push(
|
||||||
{
|
{
|
||||||
key: "bills",
|
key: "bills",
|
||||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
icon: <Icon component={FaFileInvoiceDollar}/>,
|
||||||
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "enterbills",
|
key: "enterbills",
|
||||||
icon: <Icon component={GiPayMoney} />,
|
icon: <Icon component={GiPayMoney}/>,
|
||||||
label: t("menus.header.enterbills"),
|
label: t("menus.header.enterbills"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
@@ -135,7 +159,7 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "inventory",
|
key: "inventory",
|
||||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
icon: <Icon component={FaFileInvoiceDollar}/>,
|
||||||
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -144,7 +168,7 @@ function Header({
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "payments", bodyshop })
|
promanager: HasFeatureAccess({featureName: "payments", bodyshop})
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
accountingChildren.push(
|
accountingChildren.push(
|
||||||
@@ -153,12 +177,12 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "allpayments",
|
key: "allpayments",
|
||||||
icon: <BankFilled />,
|
icon: <BankFilled/>,
|
||||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "enterpayments",
|
key: "enterpayments",
|
||||||
icon: <Icon component={FaCreditCard} />,
|
icon: <Icon component={FaCreditCard}/>,
|
||||||
label: t("menus.header.enterpayment"),
|
label: t("menus.header.enterpayment"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
@@ -173,7 +197,7 @@ function Header({
|
|||||||
if (ImEXPay.treatment === "on") {
|
if (ImEXPay.treatment === "on") {
|
||||||
accountingChildren.push({
|
accountingChildren.push({
|
||||||
key: "entercardpayments",
|
key: "entercardpayments",
|
||||||
icon: <Icon component={FaCreditCard} />,
|
icon: <Icon component={FaCreditCard}/>,
|
||||||
label: t("menus.header.entercardpayment"),
|
label: t("menus.header.entercardpayment"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setCardPaymentContext({
|
setCardPaymentContext({
|
||||||
@@ -188,7 +212,7 @@ function Header({
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
accountingChildren.push(
|
accountingChildren.push(
|
||||||
@@ -197,7 +221,7 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "timetickets",
|
key: "timetickets",
|
||||||
icon: <FieldTimeOutlined />,
|
icon: <FieldTimeOutlined/>,
|
||||||
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -205,14 +229,14 @@ function Header({
|
|||||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||||
accountingChildren.push({
|
accountingChildren.push({
|
||||||
key: "ttapprovals",
|
key: "ttapprovals",
|
||||||
icon: <FieldTimeOutlined />,
|
icon: <FieldTimeOutlined/>,
|
||||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
accountingChildren.push(
|
accountingChildren.push(
|
||||||
{
|
{
|
||||||
key: "entertimetickets",
|
key: "entertimetickets",
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
icon: <Icon component={GiPlayerTime}/>,
|
||||||
label: t("menus.header.entertimeticket"),
|
label: t("menus.header.entertimeticket"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
@@ -234,7 +258,8 @@ function Header({
|
|||||||
const accountingExportChildren = [
|
const accountingExportChildren = [
|
||||||
{
|
{
|
||||||
key: "receivables",
|
key: "receivables",
|
||||||
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
|
label: <Link
|
||||||
|
to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -266,12 +291,12 @@ function Header({
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "export", bodyshop })
|
promanager: HasFeatureAccess({featureName: "export", bodyshop})
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
accountingChildren.push({
|
accountingChildren.push({
|
||||||
key: "accountingexport",
|
key: "accountingexport",
|
||||||
icon: <ExportOutlined />,
|
icon: <ExportOutlined/>,
|
||||||
label: t("menus.header.export"),
|
label: t("menus.header.export"),
|
||||||
children: accountingExportChildren
|
children: accountingExportChildren
|
||||||
});
|
});
|
||||||
@@ -280,44 +305,44 @@ function Header({
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
key: "home",
|
key: "home",
|
||||||
icon: <HomeFilled />,
|
icon: <HomeFilled/>,
|
||||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "schedule",
|
key: "schedule",
|
||||||
icon: <Icon component={FaCalendarAlt} />,
|
icon: <Icon component={FaCalendarAlt}/>,
|
||||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "jobssubmenu",
|
key: "jobssubmenu",
|
||||||
id: "header-jobs",
|
id: "header-jobs",
|
||||||
icon: <Icon component={FaCarCrash} />,
|
icon: <Icon component={FaCarCrash}/>,
|
||||||
label: t("menus.header.jobs"),
|
label: t("menus.header.jobs"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "activejobs",
|
key: "activejobs",
|
||||||
icon: <FileFilled />,
|
icon: <FileFilled/>,
|
||||||
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "readyjobs",
|
key: "readyjobs",
|
||||||
icon: <CheckCircleOutlined />,
|
icon: <CheckCircleOutlined/>,
|
||||||
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "parts-queue",
|
key: "parts-queue",
|
||||||
icon: <ToolFilled />,
|
icon: <ToolFilled/>,
|
||||||
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "availablejobs",
|
key: "availablejobs",
|
||||||
id: "header-jobs-available",
|
id: "header-jobs-available",
|
||||||
icon: <ImportOutlined />,
|
icon: <ImportOutlined/>,
|
||||||
label: <Link to="/manage/available">{t("menus.header.availablejobs")}</Link>
|
label: <Link to="/manage/available">{t("menus.header.availablejobs")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "newjob",
|
key: "newjob",
|
||||||
icon: <FileAddOutlined />,
|
icon: <FileAddOutlined/>,
|
||||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -325,7 +350,7 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "alljobs",
|
key: "alljobs",
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined/>,
|
||||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -333,54 +358,54 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "productionlist",
|
key: "productionlist",
|
||||||
icon: <ScheduleOutlined />,
|
icon: <ScheduleOutlined/>,
|
||||||
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "visualboard", bodyshop })
|
promanager: HasFeatureAccess({featureName: "visualboard", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "productionboard",
|
key: "productionboard",
|
||||||
icon: <Icon component={BsKanban} />,
|
icon: <Icon component={BsKanban}/>,
|
||||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "scoreboard", bodyshop })
|
promanager: HasFeatureAccess({featureName: "scoreboard", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
type: "divider"
|
type: "divider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "scoreboard",
|
key: "scoreboard",
|
||||||
icon: <LineChartOutlined />,
|
icon: <LineChartOutlined/>,
|
||||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "customers",
|
key: "customers",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined/>,
|
||||||
label: t("menus.header.customers"),
|
label: t("menus.header.customers"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "owners",
|
key: "owners",
|
||||||
icon: <TeamOutlined />,
|
icon: <TeamOutlined/>,
|
||||||
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "vehicles",
|
key: "vehicles",
|
||||||
icon: <CarFilled />,
|
icon: <CarFilled/>,
|
||||||
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -391,63 +416,65 @@ function Header({
|
|||||||
promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }),
|
promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }),
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "ccs",
|
key: "ccs",
|
||||||
icon: <CarFilled />,
|
icon: <CarFilled/>,
|
||||||
label: t("menus.header.courtesycars"),
|
label: t("menus.header.courtesycars"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "courtesycarsall",
|
key: "courtesycarsall",
|
||||||
icon: <CarFilled />,
|
icon: <CarFilled/>,
|
||||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "contracts",
|
key: "contracts",
|
||||||
icon: <FileFilled />,
|
icon: <FileFilled/>,
|
||||||
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
label: <Link
|
||||||
},
|
to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
||||||
{
|
},
|
||||||
key: "newcontract",
|
{
|
||||||
icon: <FileAddFilled />,
|
key: "newcontract",
|
||||||
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
icon: <FileAddFilled/>,
|
||||||
}
|
label: <Link
|
||||||
]
|
to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|
||||||
...(accountingChildren.length > 0
|
...(accountingChildren.length > 0
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "accounting",
|
key: "accounting",
|
||||||
icon: <DollarCircleFilled />,
|
icon: <DollarCircleFilled/>,
|
||||||
label: t("menus.header.accounting"),
|
label: t("menus.header.accounting"),
|
||||||
children: accountingChildren
|
children: accountingChildren
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
key: "phonebook",
|
key: "phonebook",
|
||||||
icon: <PhoneOutlined />,
|
icon: <PhoneOutlined/>,
|
||||||
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "media", bodyshop })
|
promanager: HasFeatureAccess({featureName: "media", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "temporarydocs",
|
key: "temporarydocs",
|
||||||
icon: <PaperClipOutlined />,
|
icon: <PaperClipOutlined/>,
|
||||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
key: 'tasks',
|
key: 'tasks',
|
||||||
id: 'tasks',
|
id: 'tasks',
|
||||||
icon: <FaTasks />,
|
icon: <FaTasks/>,
|
||||||
label: t('menus.header.tasks'),
|
label: t('menus.header.tasks'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -475,22 +502,22 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'shopsubmenu',
|
key: 'shopsubmenu',
|
||||||
icon: <SettingOutlined />,
|
icon: <SettingOutlined/>,
|
||||||
label: t("menus.header.shop"),
|
label: t("menus.header.shop"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "shop",
|
key: "shop",
|
||||||
icon: <Icon component={GiSettingsKnobs} />,
|
icon: <Icon component={GiSettingsKnobs}/>,
|
||||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "dashboard",
|
key: "dashboard",
|
||||||
icon: <DashboardFilled />,
|
icon: <DashboardFilled/>,
|
||||||
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "reportcenter",
|
key: "reportcenter",
|
||||||
icon: <BarChartOutlined />,
|
icon: <BarChartOutlined/>,
|
||||||
label: t("menus.header.reportcenter"),
|
label: t("menus.header.reportcenter"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setReportCenterContext({
|
setReportCenterContext({
|
||||||
@@ -501,21 +528,21 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "shop-vendors",
|
key: "shop-vendors",
|
||||||
icon: <Icon component={IoBusinessOutline} />,
|
icon: <Icon component={IoBusinessOutline}/>,
|
||||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "csi", bodyshop })
|
promanager: HasFeatureAccess({featureName: "csi", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "shop-csi",
|
key: "shop-csi",
|
||||||
icon: <Icon component={RiSurveyLine} />,
|
icon: <Icon component={RiSurveyLine}/>,
|
||||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -525,14 +552,14 @@ function Header({
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "signout",
|
key: "signout",
|
||||||
icon: <Icon component={FiLogOut} />,
|
icon: <Icon component={FiLogOut}/>,
|
||||||
danger: true,
|
danger: true,
|
||||||
label: t("user.actions.signout"),
|
label: t("user.actions.signout"),
|
||||||
onClick: () => signOutStart()
|
onClick: () => signOutStart()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "help",
|
key: "help",
|
||||||
icon: <Icon component={QuestionCircleFilled} />,
|
icon: <Icon component={QuestionCircleFilled}/>,
|
||||||
label: t("menus.header.help"),
|
label: t("menus.header.help"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.open(
|
window.open(
|
||||||
@@ -558,19 +585,19 @@ function Header({
|
|||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "shiftclock",
|
key: "shiftclock",
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
icon: <Icon component={GiPlayerTime}/>,
|
||||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
key: "profile",
|
key: "profile",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined/>,
|
||||||
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
@@ -604,7 +631,7 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "recent",
|
key: "recent",
|
||||||
icon: <ClockCircleFilled />,
|
icon: <ClockCircleFilled/>,
|
||||||
children: recentItems.map((i, idx) => ({
|
children: recentItems.map((i, idx) => ({
|
||||||
key: idx,
|
key: idx,
|
||||||
label: <Link to={i.url}>{i.label}</Link>
|
label: <Link to={i.url}>{i.label}</Link>
|
||||||
@@ -618,7 +645,7 @@ function Header({
|
|||||||
imex: () => {
|
imex: () => {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: "beta-switch",
|
key: "beta-switch",
|
||||||
style: { marginLeft: "auto" },
|
style: {marginLeft: "auto"},
|
||||||
label: (
|
label: (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={`A more modern ${InstanceRenderManager({
|
title={`A more modern ${InstanceRenderManager({
|
||||||
@@ -627,9 +654,9 @@ function Header({
|
|||||||
promanager: t("titles.promanager")
|
promanager: t("titles.promanager")
|
||||||
})} is ready for you to try! You can switch back at any time.`}
|
})} is ready for you to try! You can switch back at any time.`}
|
||||||
>
|
>
|
||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined/>
|
||||||
<span style={{ marginRight: 8 }}>Try the new app</span>
|
<span style={{marginRight: 8}}>Try the new app</span>
|
||||||
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
|
<Switch checked={betaSwitch} onChange={betaSwitchChange}/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import {useQuery} from "@apollo/client";
|
||||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
|
import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import {DateFormatter} from "../../utils/DateFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||||
import {
|
import {QUERY_JOBLINE_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
|
||||||
QUERY_JOB_TASKS_PAGINATED,
|
|
||||||
QUERY_JOBLINE_TASKS_PAGINATED
|
|
||||||
} from "../../graphql/tasks.queries.js";
|
|
||||||
import TaskListContainer from "../task-list/task-list.container.jsx";
|
import TaskListContainer from "../task-list/task-list.container.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -26,9 +23,9 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
||||||
|
|
||||||
export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
|
const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
variables: {
|
variables: {
|
||||||
@@ -36,8 +33,8 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) return <Skeleton />;
|
if (loading) return <Skeleton/>;
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
@@ -47,23 +44,23 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
|||||||
items={
|
items={
|
||||||
data.parts_order_lines.length > 0
|
data.parts_order_lines.length > 0
|
||||||
? data.parts_order_lines.map((line) => ({
|
? data.parts_order_lines.map((line) => ({
|
||||||
key: line.id,
|
key: line.id,
|
||||||
children: (
|
children: (
|
||||||
<Space split={<Divider type="vertical" />} wrap>
|
<Space split={<Divider type="vertical"/>} wrap>
|
||||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||||
{line.parts_order.order_number}
|
{line.parts_order.order_number}
|
||||||
</Link>
|
</Link>
|
||||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||||
{line.parts_order.vendor.name}
|
{line.parts_order.vendor.name}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: "no-orders",
|
key: "no-orders",
|
||||||
children: t("parts_orders.labels.notyetordered")
|
children: t("parts_orders.labels.notyetordered")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
</Col>
|
</Col>
|
||||||
@@ -73,39 +70,39 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
|||||||
items={
|
items={
|
||||||
data.billlines.length > 0
|
data.billlines.length > 0
|
||||||
? data.billlines.map((line) => ({
|
? data.billlines.map((line) => ({
|
||||||
key: line.id,
|
key: line.id,
|
||||||
children: (
|
children: (
|
||||||
<Row wrap>
|
<Row wrap>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||||
{line.bill.invoice_number}
|
{line.bill.invoice_number}
|
||||||
</Link>
|
</Link>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<span>
|
<span>
|
||||||
{`${t("billlines.fields.actual_price")}: `}
|
{`${t("billlines.fields.actual_price")}: `}
|
||||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<span>
|
<span>
|
||||||
{`${t("billlines.fields.actual_cost")}: `}
|
{`${t("billlines.fields.actual_cost")}: `}
|
||||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: "no-orders",
|
key: "no-orders",
|
||||||
children: t("parts_orders.labels.notyetordered")
|
children: t("parts_orders.labels.notyetordered")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -115,27 +112,31 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
|||||||
items={
|
items={
|
||||||
data.parts_dispatch_lines.length > 0
|
data.parts_dispatch_lines.length > 0
|
||||||
? data.parts_dispatch_lines.map((line) => ({
|
? data.parts_dispatch_lines.map((line) => ({
|
||||||
key: line.id,
|
key: line.id,
|
||||||
children: (
|
children: (
|
||||||
<Space split={<Divider type="vertical" />} wrap>
|
<Space split={<Divider type="vertical"/>} wrap>
|
||||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
<Link
|
||||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||||
<Space>
|
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
<Space>
|
||||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||||
</Space>
|
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
</Space>
|
||||||
}))
|
)
|
||||||
|
}))
|
||||||
: {
|
: {
|
||||||
key: "dispatch-lines",
|
key: "dispatch-lines",
|
||||||
children: t("parts_orders.labels.notyetordered")
|
children: t("parts_orders.labels.notyetordered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={24} lg={24}>
|
<Col md={24} lg={24}>
|
||||||
<TaskListContainer currentUser={currentUser} bodyshop={bodyshop} parentJobId={jobid} relationshipType={'joblineid'} relationshipId={jobline.id} query={QUERY_JOBLINE_TASKS_PAGINATED} titleTranslation='tasks.titles.job_tasks'/>
|
<TaskListContainer currentUser={currentUser} bodyshop={bodyshop} parentJobId={jobid}
|
||||||
|
relationshipType={'joblineid'} relationshipId={jobline.id}
|
||||||
|
query={QUERY_JOBLINE_TASKS_PAGINATED}
|
||||||
|
titleTranslation='tasks.titles.job_tasks'/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ export function JobLinesComponent({
|
|||||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
disableInvNumber: true,
|
disableInvNumber: true,
|
||||||
job: { id: job.id },
|
job: { id: job.id },
|
||||||
@@ -465,7 +465,7 @@ export function JobLinesComponent({
|
|||||||
vendorid: bodyshop.inhousevendorid,
|
vendorid: bodyshop.inhousevendorid,
|
||||||
invoice_number: "ih",
|
invoice_number: "ih",
|
||||||
isinhouse: true,
|
isinhouse: true,
|
||||||
date: new dayjs(),
|
date: dayjs(),
|
||||||
total: 0,
|
total: 0,
|
||||||
billlines: selectedLines.map((p) => {
|
billlines: selectedLines.map((p) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
// date: new dayjs(),
|
// date: dayjs(),
|
||||||
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
||||||
// painthrs: Math.round(v.painthrs * 10) / 10,
|
// painthrs: Math.round(v.painthrs * 10) / 10,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,51 @@
|
|||||||
import { DownCircleFilled } from "@ant-design/icons";
|
import {DownCircleFilled} from "@ant-design/icons";
|
||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import {useApolloClient, useMutation} from "@apollo/client";
|
||||||
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
import {
|
||||||
import React, { useMemo, useState } from "react";
|
Button,
|
||||||
import { useTranslation } from "react-i18next";
|
Card,
|
||||||
import { connect } from "react-redux";
|
Dropdown,
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
Form,
|
||||||
import { createStructuredSelector } from "reselect";
|
Input,
|
||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
Modal,
|
||||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
notification,
|
||||||
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
|
Popconfirm,
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
Popover,
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
Select,
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
Space
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
} from "antd";
|
||||||
|
import React, {useMemo, useState} from "react";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {Link, useNavigate} from "react-router-dom";
|
||||||
|
import {createStructuredSelector} from "reselect";
|
||||||
|
import {auth, logImEXEvent} from "../../firebase/firebase.utils";
|
||||||
|
import {
|
||||||
|
CANCEL_APPOINTMENTS_BY_JOB_ID,
|
||||||
|
INSERT_MANUAL_APPT
|
||||||
|
} from "../../graphql/appointments.queries";
|
||||||
|
import {DELETE_JOB, UPDATE_JOB, VOID_JOB} from "../../graphql/jobs.queries";
|
||||||
|
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||||
|
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||||
|
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||||
|
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
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 axios from "axios";
|
import axios from "axios";
|
||||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
import {setEmailOptions} from "../../redux/email/email.actions";
|
||||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
import {openChatByPhone, setMessage} from "../../redux/messaging/messaging.actions";
|
||||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
import {GET_CURRENT_QUESTIONSET_ID, INSERT_CSI} from "../../graphql/csi.queries";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import {TemplateList} from "../../utils/TemplateConstants";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
import JobsDetailHeaderActionsToggleProduction
|
||||||
|
from "./jobs-detail-header-actions.toggle-production";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -38,38 +54,57 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
setScheduleContext: (context) => dispatch(setModalContext({context: context, modal: "schedule"})),
|
||||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
context: context,
|
||||||
setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
modal: "billEnter"
|
||||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
})),
|
||||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
|
setJobCostingContext: (context) => dispatch(setModalContext({
|
||||||
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
context: context,
|
||||||
|
modal: "jobCosting"
|
||||||
|
})),
|
||||||
|
setTimeTicketContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "timeTicket"
|
||||||
|
})),
|
||||||
|
setCardPaymentContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "cardPayment"
|
||||||
|
})),
|
||||||
|
insertAuditTrail: ({jobid, operation, type}) => dispatch(insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})),
|
||||||
|
setTimeTicketTaskContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "timeTicketTask"
|
||||||
|
})),
|
||||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||||
setMessage: (text) => dispatch(setMessage(text))
|
setMessage: (text) => dispatch(setMessage(text))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
job,
|
job,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
currentUser,
|
currentUser,
|
||||||
refetch,
|
refetch,
|
||||||
setScheduleContext,
|
setScheduleContext,
|
||||||
setBillEnterContext,
|
setBillEnterContext,
|
||||||
setPaymentContext,
|
setPaymentContext,
|
||||||
setJobCostingContext,
|
setJobCostingContext,
|
||||||
jobRO,
|
jobRO,
|
||||||
setTimeTicketContext,
|
setTimeTicketContext,
|
||||||
setCardPaymentContext,
|
setCardPaymentContext,
|
||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
setEmailOptions,
|
setEmailOptions,
|
||||||
openChatByPhone,
|
openChatByPhone,
|
||||||
setMessage,
|
setMessage,
|
||||||
setTimeTicketTaskContext
|
setTimeTicketTaskContext
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -83,7 +118,7 @@ export function JobsDetailHeaderActions({
|
|||||||
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
|
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { ImEXPay }
|
treatments: {ImEXPay}
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["ImEXPay"],
|
names: ["ImEXPay"],
|
||||||
@@ -117,7 +152,7 @@ export function JobsDetailHeaderActions({
|
|||||||
DuplicateJob(
|
DuplicateJob(
|
||||||
client,
|
client,
|
||||||
job.id,
|
job.id,
|
||||||
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
|
{defaultOpenStatus: bodyshop.md_ro_statuses.default_imported},
|
||||||
(newJobId) => {
|
(newJobId) => {
|
||||||
history(`/manage/jobs/${newJobId}`);
|
history(`/manage/jobs/${newJobId}`);
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
@@ -128,7 +163,7 @@ export function JobsDetailHeaderActions({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleDuplicateConfirm = () =>
|
const handleDuplicateConfirm = () =>
|
||||||
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => {
|
DuplicateJob(client, job.id, {defaultOpenStatus: bodyshop.md_ro_statuses.default_imported}, (newJobId) => {
|
||||||
history(`/manage/jobs/${newJobId}`);
|
history(`/manage/jobs/${newJobId}`);
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("jobs.successes.duplicated")
|
message: t("jobs.successes.duplicated")
|
||||||
@@ -142,7 +177,7 @@ export function JobsDetailHeaderActions({
|
|||||||
try {
|
try {
|
||||||
insertAppointment({
|
insertAppointment({
|
||||||
variables: {
|
variables: {
|
||||||
apt: { ...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id }
|
apt: {...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id}
|
||||||
},
|
},
|
||||||
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
|
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
|
||||||
});
|
});
|
||||||
@@ -159,7 +194,7 @@ export function JobsDetailHeaderActions({
|
|||||||
|
|
||||||
const handleDeleteJob = async () => {
|
const handleDeleteJob = async () => {
|
||||||
//delete the job.
|
//delete the job.
|
||||||
const result = await deleteJob({ variables: { id: job.id } });
|
const result = await deleteJob({variables: {id: job.id}});
|
||||||
|
|
||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
@@ -220,7 +255,7 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("csi.successes.created") });
|
notification["success"]({message: t("csi.successes.created")});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("csi.errors.creating", {
|
message: t("csi.errors.creating", {
|
||||||
@@ -375,7 +410,7 @@ export function JobsDetailHeaderActions({
|
|||||||
try {
|
try {
|
||||||
QbXmlResponse = await axios.post(
|
QbXmlResponse = await axios.post(
|
||||||
"/accounting/qbxml/receivables",
|
"/accounting/qbxml/receivables",
|
||||||
{ jobIds: [job.id], custDataOnly: true },
|
{jobIds: [job.id], custDataOnly: true},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
|
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
|
||||||
@@ -484,7 +519,7 @@ export function JobsDetailHeaderActions({
|
|||||||
setIsCancelScheduleModalVisible(false);
|
setIsCancelScheduleModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLostSaleFinish = async ({ lost_sale_reason }) => {
|
const handleLostSaleFinish = async ({lost_sale_reason}) => {
|
||||||
const jobUpdate = await cancelAllAppointments({
|
const jobUpdate = await cancelAllAppointments({
|
||||||
variables: {
|
variables: {
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
@@ -524,10 +559,10 @@ export function JobsDetailHeaderActions({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("appointments.fields.note")} name="note">
|
<Form.Item label={t("appointments.fields.note")} name="note">
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("appointments.fields.start")}
|
label={t("appointments.fields.start")}
|
||||||
@@ -542,7 +577,7 @@ export function JobsDetailHeaderActions({
|
|||||||
<FormDateTimePickerComponent
|
<FormDateTimePickerComponent
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const start = form.getFieldValue("start");
|
const start = form.getFieldValue("start");
|
||||||
form.setFieldsValue({ end: start.add(30, "minutes") });
|
form.setFieldsValue({end: start.add(30, "minutes")});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -554,10 +589,10 @@ export function JobsDetailHeaderActions({
|
|||||||
required: true
|
required: true
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
({ getFieldValue }) => ({
|
({getFieldValue}) => ({
|
||||||
async validator(rule, value) {
|
async validator(rule, value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
const { start } = form.getFieldsValue();
|
const {start} = form.getFieldsValue();
|
||||||
if (dayjs(start).isAfter(dayjs(value))) {
|
if (dayjs(start).isAfter(dayjs(value))) {
|
||||||
return Promise.reject(t("employees.labels.endmustbeafterstart"));
|
return Promise.reject(t("employees.labels.endmustbeafterstart"));
|
||||||
} else {
|
} else {
|
||||||
@@ -570,7 +605,7 @@ export function JobsDetailHeaderActions({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDateTimePickerComponent />
|
<FormDateTimePickerComponent/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||||
<Select>
|
<Select>
|
||||||
@@ -601,7 +636,7 @@ export function JobsDetailHeaderActions({
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
logImEXEvent("job_header_schedule");
|
logImEXEvent("job_header_schedule");
|
||||||
setScheduleContext({
|
setScheduleContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch},
|
||||||
context: {
|
context: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: job,
|
job: job,
|
||||||
@@ -645,7 +680,8 @@ export function JobsDetailHeaderActions({
|
|||||||
{
|
{
|
||||||
key: "checklist",
|
key: "checklist",
|
||||||
disabled: !job.converted,
|
disabled: !job.converted,
|
||||||
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
|
label: <Link
|
||||||
|
to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
rome: "USE_IMEX",
|
rome: "USE_IMEX",
|
||||||
@@ -653,35 +689,35 @@ export function JobsDetailHeaderActions({
|
|||||||
{
|
{
|
||||||
key: "toggleproduction",
|
key: "toggleproduction",
|
||||||
disabled: !job.converted || jobRO,
|
disabled: !job.converted || jobRO,
|
||||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
|
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch}/>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: "USE_IMEX",
|
rome: "USE_IMEX",
|
||||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "entertimetickets",
|
key: "entertimetickets",
|
||||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||||
label: t("timetickets.actions.enter"),
|
label: t("timetickets.actions.enter"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
logImEXEvent("job_header_enter_time_ticekts");
|
logImEXEvent("job_header_enter_time_ticekts");
|
||||||
|
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
created_by: currentUser.displayName
|
created_by: currentUser.displayName
|
||||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||||
: currentUser.email
|
: currentUser.email
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
]
|
||||||
: [])
|
: [])
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -692,7 +728,7 @@ export function JobsDetailHeaderActions({
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTimeTicketTaskContext({
|
setTimeTicketTaskContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: { jobid: job.id }
|
context: {jobid: job.id}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
label: t("timetickets.actions.claimtasks")
|
label: t("timetickets.actions.claimtasks")
|
||||||
@@ -708,7 +744,7 @@ export function JobsDetailHeaderActions({
|
|||||||
|
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: { jobid: job.id }
|
context: {jobid: job.id}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -723,18 +759,18 @@ export function JobsDetailHeaderActions({
|
|||||||
|
|
||||||
setCardPaymentContext({
|
setCardPaymentContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: { jobid: job.id }
|
context: {jobid: job.id}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasFeatureAccess({ featureName: "courtesycars" })) {
|
if (HasFeatureAccess({featureName: "courtesycars"})) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: "cccontract",
|
key: "cccontract",
|
||||||
disabled: jobRO || !job.converted,
|
disabled: jobRO || !job.converted,
|
||||||
label: (
|
label: (
|
||||||
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
|
<Link state={{jobId: job.id}} to="/manage/courtesycars/contracts/new">
|
||||||
{t("menus.jobsactions.newcccontract")}
|
{t("menus.jobsactions.newcccontract")}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
@@ -744,17 +780,17 @@ export function JobsDetailHeaderActions({
|
|||||||
menuItems.push(
|
menuItems.push(
|
||||||
job.inproduction
|
job.inproduction
|
||||||
? {
|
? {
|
||||||
key: "removefromproduction",
|
key: "removefromproduction",
|
||||||
disabled: !job.converted,
|
disabled: !job.converted,
|
||||||
label: t("jobs.actions.removefromproduction"),
|
label: t("jobs.actions.removefromproduction"),
|
||||||
onClick: () => AddToProduction(client, job.id, refetch, true)
|
onClick: () => AddToProduction(client, job.id, refetch, true)
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
key: "addtoproduction",
|
key: "addtoproduction",
|
||||||
disabled: !job.converted,
|
disabled: !job.converted,
|
||||||
label: t("jobs.actions.addtoproduction"),
|
label: t("jobs.actions.addtoproduction"),
|
||||||
onClick: () => AddToProduction(client, job.id, refetch)
|
onClick: () => AddToProduction(client, job.id, refetch)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
@@ -810,25 +846,25 @@ export function JobsDetailHeaderActions({
|
|||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "bills", bodyshop })
|
promanager: HasFeatureAccess({featureName: "bills", bodyshop})
|
||||||
})
|
})
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "postbills",
|
key: "postbills",
|
||||||
disabled: !job.converted,
|
disabled: !job.converted,
|
||||||
label: t("jobs.actions.postbills"),
|
label: t("jobs.actions.postbills"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
logImEXEvent("job_header_enter_bills");
|
logImEXEvent("job_header_enter_bills");
|
||||||
|
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch},
|
||||||
context: {
|
context: {
|
||||||
job: job
|
job: job
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -839,7 +875,7 @@ export function JobsDetailHeaderActions({
|
|||||||
const result = await updateJob({
|
const result = await updateJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: { queued_for_parts: true }
|
job: {queued_for_parts: true}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -889,7 +925,7 @@ export function JobsDetailHeaderActions({
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: true,
|
rome: true,
|
||||||
promanager: HasFeatureAccess({ featureName: "export", bodyshop })
|
promanager: HasFeatureAccess({featureName: "export", bodyshop})
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
@@ -900,7 +936,7 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasFeatureAccess({ featureName: "csi", bodyshop })) {
|
if (HasFeatureAccess({featureName: "csi", bodyshop})) {
|
||||||
const children = [
|
const children = [
|
||||||
{
|
{
|
||||||
key: "email",
|
key: "email",
|
||||||
@@ -930,20 +966,20 @@ export function JobsDetailHeaderActions({
|
|||||||
...job.csiinvites.map((item, idx) => {
|
...job.csiinvites.map((item, idx) => {
|
||||||
return item.completedon
|
return item.completedon
|
||||||
? {
|
? {
|
||||||
key: idx,
|
key: idx,
|
||||||
label: (
|
label: (
|
||||||
<Link to={`/manage/shop/csi?responseid=${item.id}`}>
|
<Link to={`/manage/shop/csi?responseid=${item.id}`}>
|
||||||
<DateTimeFormatter>{item.completedon}</DateTimeFormatter>
|
<DateTimeFormatter>{item.completedon}</DateTimeFormatter>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
key: idx,
|
key: idx,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`);
|
navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`);
|
||||||
},
|
},
|
||||||
label: t("general.actions.copylink")
|
label: t("general.actions.copylink")
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -963,7 +999,7 @@ export function JobsDetailHeaderActions({
|
|||||||
logImEXEvent("job_header_job_costing");
|
logImEXEvent("job_header_job_costing");
|
||||||
|
|
||||||
setJobCostingContext({
|
setJobCostingContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch},
|
||||||
context: {
|
context: {
|
||||||
jobId: job.id
|
jobId: job.id
|
||||||
}
|
}
|
||||||
@@ -1073,10 +1109,10 @@ export function JobsDetailHeaderActions({
|
|||||||
<Dropdown menu={menu} trigger={["click"]} key="changestatus">
|
<Dropdown menu={menu} trigger={["click"]} key="changestatus">
|
||||||
<Button>
|
<Button>
|
||||||
<span>{t("general.labels.actions")}</span>
|
<span>{t("general.labels.actions")}</span>
|
||||||
<DownCircleFilled />
|
<DownCircleFilled/>
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Popover content={popOverContent} open={visibility} />
|
<Popover content={popOverContent} open={visibility}/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import {SyncOutlined} from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
import {Button, Card, Input, Space, Table, Typography} from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, {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 { Link, useLocation, useNavigate } from "react-router-dom";
|
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { pageLimit } from "../../utils/config";
|
import {pageLimit} from "../../utils/config";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -23,15 +23,15 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const [openSearchResults, setOpenSearchResults] = useState([]);
|
const [openSearchResults, setOpenSearchResults] = useState([]);
|
||||||
const [searchLoading, setSearchLoading] = useState(false);
|
const [searchLoading, setSearchLoading] = useState(false);
|
||||||
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
|
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
|
||||||
const { page, sortcolumn, sortorder } = search;
|
const {page, sortcolumn, sortorder} = search;
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
@@ -54,11 +54,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
<Link to={"/manage/owners/" + record.ownerid}>
|
<Link to={"/manage/owners/" + record.ownerid}>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record}/>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record}/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
key: "ownr_ph1",
|
key: "ownr_ph1",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ownr_ph2"),
|
title: t("jobs.fields.ownr_ph2"),
|
||||||
@@ -77,7 +77,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
key: "ownr_ph2",
|
key: "ownr_ph2",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
@@ -92,7 +92,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
},
|
},
|
||||||
filteredValue: filter?.status || null,
|
filteredValue: filter?.status || null,
|
||||||
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
||||||
return { text: s, value: [s] };
|
return {text: s, value: [s]};
|
||||||
}),
|
}),
|
||||||
onFilter: (value, record) => value.includes(record.status)
|
onFilter: (value, record) => value.includes(record.status)
|
||||||
},
|
},
|
||||||
@@ -178,7 +178,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
delete search.statusFilters;
|
delete search.statusFilters;
|
||||||
}
|
}
|
||||||
setFilter(filters);
|
setFilter(filters);
|
||||||
history({ search: queryString.stringify(search) });
|
history({search: queryString.stringify(search)});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -210,13 +210,13 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
{search.search && (
|
{search.search && (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={4}>
|
<Typography.Title level={4}>
|
||||||
{t("general.labels.searchresults", { search: search.search })}
|
{t("general.labels.searchresults", {search: search.search})}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
delete search.search;
|
delete search.search;
|
||||||
delete search.page;
|
delete search.page;
|
||||||
history({ search: queryString.stringify(search) });
|
history({search: queryString.stringify(search)});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("general.actions.clear")}
|
{t("general.actions.clear")}
|
||||||
@@ -224,13 +224,13 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined/>
|
||||||
</Button>
|
</Button>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={search.search || t("general.labels.search")}
|
placeholder={search.search || t("general.labels.search")}
|
||||||
onSearch={(value) => {
|
onSearch={(value) => {
|
||||||
search.search = value;
|
search.search = value;
|
||||||
history({ search: queryString.stringify(search) });
|
history({search: queryString.stringify(search)});
|
||||||
searchJobs(value);
|
searchJobs(value);
|
||||||
}}
|
}}
|
||||||
loading={loading || searchLoading}
|
loading={loading || searchLoading}
|
||||||
@@ -244,15 +244,15 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
pagination={
|
pagination={
|
||||||
search?.search
|
search?.search
|
||||||
? {
|
? {
|
||||||
pageSize: pageLimit,
|
pageSize: pageLimit,
|
||||||
showSizeChanger: false
|
showSizeChanger: false
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
pageSize: pageLimit,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(page || 1),
|
||||||
total: total,
|
total: total,
|
||||||
showSizeChanger: false
|
showSizeChanger: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons";
|
import {
|
||||||
import { useQuery } from "@apollo/client";
|
BranchesOutlined,
|
||||||
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
ExclamationCircleFilled,
|
||||||
|
PauseCircleOutlined,
|
||||||
|
SyncOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import {useQuery} from "@apollo/client";
|
||||||
|
import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { 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 { Link, useLocation, useNavigate } from "react-router-dom";
|
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import {onlyUnique} from "../../utils/arrayHelper";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
import {alphaSort, statusSort} from "../../utils/sorters";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
import {setJoyRideSteps} from "../../redux/application/application.actions";
|
||||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
import {OwnerNameDisplayFunction} from "./../owner-name-display/owner-name-display.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -28,13 +33,13 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsList({ bodyshop, setJoyRideSteps }) {
|
export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const { selected } = searchParams;
|
const {selected} = searchParams;
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
.slice(-1)[0];
|
.slice(-1)[0];
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||||
variables: {
|
variables: {
|
||||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"]
|
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"]
|
||||||
},
|
},
|
||||||
@@ -42,36 +47,36 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
const [state, setState] = useState({ sortedInfo: {} });
|
const [state, setState] = useState({sortedInfo: {}});
|
||||||
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
|
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|
||||||
const jobs = data
|
const jobs = data
|
||||||
? searchText === ""
|
? searchText === ""
|
||||||
? data.jobs
|
? data.jobs
|
||||||
: data.jobs.filter(
|
: data.jobs.filter(
|
||||||
(j) =>
|
(j) =>
|
||||||
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.comments || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.comments || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, sortedInfo: sorter });
|
setState({...state, sortedInfo: sorter});
|
||||||
setFilter(filters);
|
setFilter(filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,12 +107,12 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
<Space>
|
<Space>
|
||||||
{record.ro_number || t("general.labels.na")}
|
{record.ro_number || t("general.labels.na")}
|
||||||
{record.production_vars && record.production_vars.alert ? (
|
{record.production_vars && record.production_vars.alert ? (
|
||||||
<ExclamationCircleFilled className="production-alert" />
|
<ExclamationCircleFilled className="production-alert"/>
|
||||||
) : null}
|
) : null}
|
||||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
{record.suspended && <PauseCircleOutlined style={{color: "orangered"}}/>}
|
||||||
{record.iouparent && (
|
{record.iouparent && (
|
||||||
<Tooltip title={t("jobs.labels.iou")}>
|
<Tooltip title={t("jobs.labels.iou")}>
|
||||||
<BranchesOutlined style={{ color: "orangered" }} />
|
<BranchesOutlined style={{color: "orangered"}}/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -126,11 +131,11 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record}/>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record}/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -141,7 +146,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
key: "ownr_ph1",
|
key: "ownr_ph1",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ownr_ph2"),
|
title: t("jobs.fields.ownr_ph2"),
|
||||||
@@ -149,7 +154,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
key: "ownr_ph2",
|
key: "ownr_ph2",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id}/>
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -344,7 +349,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined/>
|
||||||
</Button>
|
</Button>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
@@ -359,7 +364,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ defaultPageSize: 50 }}
|
pagination={{defaultPageSize: 50}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {logImEXEvent} from "../../firebase/firebase.utils";
|
|||||||
import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries";
|
import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries";
|
||||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import {DateFormatter} from "../../utils/DateFormatter";
|
import {DateFormatter} from "../../utils/DateFormatter";
|
||||||
import {alphaSort} from "../../utils/sorters";
|
import {alphaSort} from "../../utils/sorters";
|
||||||
@@ -33,11 +33,13 @@ import {FaTasks} from "react-icons/fa";
|
|||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "billEnter"
|
||||||
|
})),
|
||||||
setPartsReceiveContext: (context) => dispatch(setModalContext({
|
setPartsReceiveContext: (context) => dispatch(setModalContext({
|
||||||
context: context,
|
context: context,
|
||||||
modal: "partsReceive"
|
modal: "partsReceive"
|
||||||
@@ -54,8 +56,7 @@ export function PartsOrderListTableComponent({
|
|||||||
handleOnRowClick,
|
handleOnRowClick,
|
||||||
setPartsReceiveContext,
|
setPartsReceiveContext,
|
||||||
setTaskUpsertContext,
|
setTaskUpsertContext,
|
||||||
currentUser
|
}) {
|
||||||
}) {
|
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
.slice(-1)[0];
|
.slice(-1)[0];
|
||||||
@@ -70,9 +71,9 @@ export function PartsOrderListTableComponent({
|
|||||||
};
|
};
|
||||||
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
|
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
|
||||||
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
||||||
const Templates = TemplateList("partsorder", { job });
|
const Templates = TemplateList("partsorder", {job});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {}
|
sortedInfo: {}
|
||||||
});
|
});
|
||||||
@@ -83,13 +84,13 @@ export function PartsOrderListTableComponent({
|
|||||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||||
|
|
||||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||||
const { refetch } = billsQuery;
|
const {refetch} = billsQuery;
|
||||||
|
|
||||||
const recordActions = (record, showView = false) => (
|
const recordActions = (record, showView = false) => (
|
||||||
<Space direction='horizontal' wrap>
|
<Space direction='horizontal' wrap>
|
||||||
{showView && (
|
{showView && (
|
||||||
<Button onClick={() => handleOnRowClick(record)}>
|
<Button onClick={() => handleOnRowClick(record)}>
|
||||||
<EyeFilled />
|
<EyeFilled/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ export function PartsOrderListTableComponent({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
logImEXEvent("parts_order_receive_bill");
|
logImEXEvent("parts_order_receive_bill");
|
||||||
setPartsReceiveContext({
|
setPartsReceiveContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch},
|
||||||
context: {
|
context: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: job,
|
job: job,
|
||||||
@@ -135,11 +136,11 @@ export function PartsOrderListTableComponent({
|
|||||||
//Delete the parts return.!
|
//Delete the parts return.!
|
||||||
|
|
||||||
await deletePartsOrder({
|
await deletePartsOrder({
|
||||||
variables: { partsOrderId: record.id },
|
variables: {partsOrderId: record.id},
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.modify({
|
cache.modify({
|
||||||
fields: {
|
fields: {
|
||||||
parts_orders(existingPartsOrders, { readField }) {
|
parts_orders(existingPartsOrders, {readField}) {
|
||||||
return existingPartsOrders.filter((billref) => record.id !== readField("id", billref));
|
return existingPartsOrders.filter((billref) => record.id !== readField("id", billref));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,7 @@ export function PartsOrderListTableComponent({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button disabled={jobRO}>
|
<Button disabled={jobRO}>
|
||||||
<DeleteFilled />
|
<DeleteFilled/>
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
|
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
|
||||||
@@ -159,7 +160,7 @@ export function PartsOrderListTableComponent({
|
|||||||
logImEXEvent("parts_order_receive_bill");
|
logImEXEvent("parts_order_receive_bill");
|
||||||
|
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: {refetch: refetch},
|
||||||
context: {
|
context: {
|
||||||
job: job,
|
job: job,
|
||||||
bill: {
|
bill: {
|
||||||
@@ -179,7 +180,7 @@ export function PartsOrderListTableComponent({
|
|||||||
? pol.jobline.part_type
|
? pol.jobline.part_type
|
||||||
: null
|
: null
|
||||||
: responsibilityCenters.defaults &&
|
: responsibilityCenters.defaults &&
|
||||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -194,7 +195,7 @@ export function PartsOrderListTableComponent({
|
|||||||
<PrintWrapper
|
<PrintWrapper
|
||||||
templateObject={{
|
templateObject={{
|
||||||
name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key,
|
name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key,
|
||||||
variables: { id: record.id }
|
variables: {id: record.id}
|
||||||
}}
|
}}
|
||||||
messageObject={{
|
messageObject={{
|
||||||
subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject,
|
subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject,
|
||||||
@@ -235,7 +236,7 @@ export function PartsOrderListTableComponent({
|
|||||||
key: "return",
|
key: "return",
|
||||||
sorter: (a, b) => a.return - b.return,
|
sorter: (a, b) => a.return - b.return,
|
||||||
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox checked={record.return} />
|
render: (text, record) => <Checkbox checked={record.return}/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.deliver_by"),
|
title: t("parts_orders.fields.deliver_by"),
|
||||||
@@ -259,7 +260,7 @@ export function PartsOrderListTableComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
||||||
@@ -290,15 +291,15 @@ export function PartsOrderListTableComponent({
|
|||||||
},
|
},
|
||||||
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.cost"),
|
title: t("parts_orders.fields.cost"),
|
||||||
dataIndex: "cost",
|
dataIndex: "cost",
|
||||||
key: "cost",
|
key: "cost",
|
||||||
sorter: (a, b) => a.cost - b.cost,
|
sorter: (a, b) => a.cost - b.cost,
|
||||||
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
|
||||||
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
|
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.part_type"),
|
title: t("parts_orders.fields.part_type"),
|
||||||
@@ -326,19 +327,19 @@ export function PartsOrderListTableComponent({
|
|||||||
|
|
||||||
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.cm_received"),
|
title: t("parts_orders.fields.cm_received"),
|
||||||
dataIndex: "cm_received",
|
dataIndex: "cm_received",
|
||||||
key: "cm_received",
|
key: "cm_received",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<PartsOrderCmReceived
|
<PartsOrderCmReceived
|
||||||
orderLineId={record.id}
|
orderLineId={record.id}
|
||||||
checked={record.cm_received}
|
checked={record.cm_received}
|
||||||
partsorderid={selectedPartsOrderRecord.id}
|
partsorderid={selectedPartsOrderRecord.id}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.backordered_on"),
|
title: t("parts_orders.fields.backordered_on"),
|
||||||
@@ -387,7 +388,8 @@ export function PartsOrderListTableComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
|
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`}
|
||||||
|
extra={recordActions(record)}/>
|
||||||
<Table
|
<Table
|
||||||
scroll={{
|
scroll={{
|
||||||
x: true //y: "50rem"
|
x: true //y: "50rem"
|
||||||
@@ -397,7 +399,7 @@ export function PartsOrderListTableComponent({
|
|||||||
dataSource={record.parts_order_lines}
|
dataSource={record.parts_order_lines}
|
||||||
/>
|
/>
|
||||||
<DataLabel label={t("parts_orders.fields.comments")}>
|
<DataLabel label={t("parts_orders.fields.comments")}>
|
||||||
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
|
<div style={{whiteSpace: "pre"}}>{record.comments}</div>
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -407,10 +409,10 @@ export function PartsOrderListTableComponent({
|
|||||||
? searchText === ""
|
? searchText === ""
|
||||||
? parts_orders
|
? parts_orders
|
||||||
: parts_orders.filter(
|
: parts_orders.filter(
|
||||||
(b) =>
|
(b) =>
|
||||||
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
|
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -419,7 +421,7 @@ export function PartsOrderListTableComponent({
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined/>
|
||||||
</Button>
|
</Button>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
@@ -432,7 +434,7 @@ export function PartsOrderListTableComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PartsReceiveModalContainer />
|
<PartsReceiveModalContainer/>
|
||||||
<Drawer
|
<Drawer
|
||||||
placement="right"
|
placement="right"
|
||||||
onClose={() => handleOnRowClick(null)}
|
onClose={() => handleOnRowClick(null)}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function PartsOrderModalContainer({
|
|||||||
vendorid: bodyshop.inhousevendorid,
|
vendorid: bodyshop.inhousevendorid,
|
||||||
invoice_number: "ih",
|
invoice_number: "ih",
|
||||||
isinhouse: true,
|
isinhouse: true,
|
||||||
date: new dayjs(),
|
date: dayjs(),
|
||||||
total: 0,
|
total: 0,
|
||||||
billlines: values.parts_order_lines.data.map((p) => {
|
billlines: values.parts_order_lines.data.map((p) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const DueDateRecord = ({dueDate}) => {
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const PriorityLabel = ({priority}) => {
|
const PriorityLabel = ({priority}) => {
|
||||||
switch(priority) {
|
switch (priority) {
|
||||||
case 1:
|
case 1:
|
||||||
return <div>
|
return <div>
|
||||||
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
|
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
|
||||||
@@ -89,7 +89,7 @@ function TaskListComponent({
|
|||||||
toggleDeletedStatus,
|
toggleDeletedStatus,
|
||||||
relationshipType,
|
relationshipType,
|
||||||
relationshipId,
|
relationshipId,
|
||||||
onlyMine ,
|
onlyMine,
|
||||||
parentJobId
|
parentJobId
|
||||||
}) {
|
}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
@@ -108,7 +108,7 @@ function TaskListComponent({
|
|||||||
|
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const columns = [];
|
const columns = [];
|
||||||
|
|
||||||
if (!onlyMine) {
|
if (!onlyMine) {
|
||||||
columns.push(
|
columns.push(
|
||||||
{
|
{
|
||||||
@@ -118,14 +118,14 @@ function TaskListComponent({
|
|||||||
width: '8%',
|
width: '8%',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to);
|
const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to);
|
||||||
return employee ? `${ employee.first_name} ${ employee.last_name}` : t("general.labels.na");
|
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.push(
|
columns.push(
|
||||||
{
|
{
|
||||||
title: t("tasks.fields.job.ro_number"),
|
title: t("tasks.fields.job.ro_number"),
|
||||||
@@ -134,7 +134,8 @@ function TaskListComponent({
|
|||||||
width: '8%',
|
width: '8%',
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.job
|
record.job
|
||||||
? <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
? <Link
|
||||||
|
to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||||
: t("general.labels.na")
|
: t("general.labels.na")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -151,7 +152,8 @@ function TaskListComponent({
|
|||||||
width: '8%',
|
width: '8%',
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.parts_order
|
record.parts_order
|
||||||
? <Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
? <Link
|
||||||
|
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||||
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
||||||
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
||||||
: t("general.labels.na")}
|
: t("general.labels.na")}
|
||||||
@@ -186,7 +188,7 @@ function TaskListComponent({
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: sortcolumn === "due_date" && sortorder,
|
sortOrder: sortcolumn === "due_date" && sortorder,
|
||||||
width: '8%',
|
width: '8%',
|
||||||
render: (text, record) => <DueDateRecord dueDate={record.due_date} />,
|
render: (text, record) => <DueDateRecord dueDate={record.due_date}/>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("tasks.fields.remind_at"),
|
title: t("tasks.fields.remind_at"),
|
||||||
@@ -195,7 +197,7 @@ function TaskListComponent({
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: sortcolumn === "remind_at" && sortorder,
|
sortOrder: sortcolumn === "remind_at" && sortorder,
|
||||||
width: '8%',
|
width: '8%',
|
||||||
render: (text, record) => <DueDateRecord dueDate={record.remind_at} />,
|
render: (text, record) => <DueDateRecord dueDate={record.remind_at}/>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("tasks.fields.priority"),
|
title: t("tasks.fields.priority"),
|
||||||
@@ -267,9 +269,9 @@ function TaskListComponent({
|
|||||||
}
|
}
|
||||||
history({search: queryString.stringify(search)});
|
history({search: queryString.stringify(search)});
|
||||||
};
|
};
|
||||||
|
|
||||||
const expandableRow = (record) => {
|
const expandableRow = (record) => {
|
||||||
return <Card title={t('tasks.fields.description')} size='small'>
|
return <Card title={t('tasks.fields.description')} size='small'>
|
||||||
{record.description}
|
{record.description}
|
||||||
</Card>
|
</Card>
|
||||||
};
|
};
|
||||||
@@ -289,7 +291,7 @@ function TaskListComponent({
|
|||||||
checked={mine === "true"}
|
checked={mine === "true"}
|
||||||
onChange={(value) => handleSwitchChange('mine', value)}
|
onChange={(value) => handleSwitchChange('mine', value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Switch
|
<Switch
|
||||||
checkedChildren={<CheckCircleFilled/>}
|
checkedChildren={<CheckCircleFilled/>}
|
||||||
unCheckedChildren={<CheckCircleOutlined/>}
|
unCheckedChildren={<CheckCircleOutlined/>}
|
||||||
|
|||||||
@@ -12,7 +12,16 @@ import TaskListComponent from "./task-list.component.jsx";
|
|||||||
import {notification} from "antd";
|
import {notification} from "antd";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
export default function TaskListContainer({bodyshop, titleTranslation ,query, relationshipType, relationshipId, currentUser, onlyMine, parentJobId}) {
|
export default function TaskListContainer({
|
||||||
|
bodyshop,
|
||||||
|
titleTranslation,
|
||||||
|
query,
|
||||||
|
relationshipType,
|
||||||
|
relationshipId,
|
||||||
|
currentUser,
|
||||||
|
onlyMine,
|
||||||
|
parentJobId
|
||||||
|
}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams;
|
const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams;
|
||||||
@@ -26,7 +35,7 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
|
|||||||
[relationshipType]: relationshipId,
|
[relationshipType]: relationshipId,
|
||||||
deleted: deleted === 'true',
|
deleted: deleted === 'true',
|
||||||
completed: completed === "true",
|
completed: completed === "true",
|
||||||
assigned_to: mine === "true" ? currentUser.email: undefined, // replace currentUserID with the actual ID of the current user
|
assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
|
||||||
offset: page ? (page - 1) * pageLimit : 0,
|
offset: page ? (page - 1) * pageLimit : 0,
|
||||||
limit: pageLimit,
|
limit: pageLimit,
|
||||||
order: [
|
order: [
|
||||||
@@ -56,7 +65,7 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
|
|||||||
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
||||||
};
|
};
|
||||||
}, [refetch]);
|
}, [refetch]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle task completed mutation
|
* Toggle task completed mutation
|
||||||
*/
|
*/
|
||||||
@@ -81,8 +90,8 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
|
|||||||
// refetch().catch((e) => {
|
// refetch().catch((e) => {
|
||||||
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
||||||
// });
|
// });
|
||||||
window.dispatchEvent( new CustomEvent('taskUpdated', {
|
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||||
detail: { message: 'A task has been completed.' },
|
detail: {message: 'A task has been completed.'},
|
||||||
}));
|
}));
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("tasks.successes.completed"),
|
message: t("tasks.successes.completed"),
|
||||||
@@ -115,8 +124,8 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
|
|||||||
deleted_at: deleted_at
|
deleted_at: deleted_at
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.dispatchEvent( new CustomEvent('taskUpdated', {
|
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||||
detail: { message: 'A task has been deleted.' },
|
detail: {message: 'A task has been deleted.'},
|
||||||
}));
|
}));
|
||||||
// refetch().catch((e) => {
|
// refetch().catch((e) => {
|
||||||
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export function TaskUpsertModalContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loading: taskLoading,
|
loading: taskLoading,
|
||||||
error: taskError,
|
error: taskError,
|
||||||
data: taskData
|
data: taskData
|
||||||
} = useQuery(QUERY_GET_TASK_BY_ID, {
|
} = useQuery(QUERY_GET_TASK_BY_ID, {
|
||||||
variables: {id: taskId},
|
variables: {id: taskId},
|
||||||
@@ -139,7 +139,7 @@ export function TaskUpsertModalContainer({
|
|||||||
ReplyTo: {
|
ReplyTo: {
|
||||||
Email: 'noreply@imex.online'
|
Email: 'noreply@imex.online'
|
||||||
},
|
},
|
||||||
to: values.assigned_to,
|
to: values.assigned_to,
|
||||||
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
||||||
templateStrings: {
|
templateStrings: {
|
||||||
header: values.title,
|
header: values.title,
|
||||||
@@ -189,7 +189,7 @@ export function TaskUpsertModalContainer({
|
|||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
})).data.insert_tasks.returning[0].id;
|
})).data.insert_tasks.returning[0].id;
|
||||||
|
|
||||||
if (refetch) await refetch();
|
if (refetch) await refetch();
|
||||||
|
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -255,7 +255,8 @@ export function TaskUpsertModalContainer({
|
|||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||||
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)} error={error} data={data}
|
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)}
|
||||||
|
error={error} data={data}
|
||||||
selectedJobId={selectedJobId}
|
selectedJobId={selectedJobId}
|
||||||
setSelectedJobId={setSelectedJobId}
|
setSelectedJobId={setSelectedJobId}
|
||||||
selectedJobDetails={selectedJobDetails}/>
|
selectedJobDetails={selectedJobDetails}/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { gql } from "@apollo/client";
|
import {gql} from "@apollo/client";
|
||||||
|
|
||||||
export const INSERT_NEW_BILL = gql`
|
export const INSERT_NEW_BILL = gql`
|
||||||
mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) {
|
mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { gql } from "@apollo/client";
|
import {gql} from "@apollo/client";
|
||||||
|
|
||||||
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
|
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
|
||||||
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
||||||
@@ -2049,12 +2049,12 @@ export const generate_UPDATE_JOB_KANBAN = (
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
return gql`
|
return gql`
|
||||||
mutation UPDATE_JOB_KANBAN {
|
mutation UPDATE_JOB_KANBAN {
|
||||||
${oldChildId ? oldChildQuery : ""}
|
${oldChildId ? oldChildQuery : ""}
|
||||||
${movedId ? movedQuery : ""}
|
${movedId ? movedQuery : ""}
|
||||||
${newChildId ? newChildQuery : ""}
|
${newChildId ? newChildQuery : ""}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QUERY_JOB_LBR_ADJUSTMENTS = gql`
|
export const QUERY_JOB_LBR_ADJUSTMENTS = gql`
|
||||||
|
|||||||
@@ -386,28 +386,28 @@ export const MUTATION_INSERT_NEW_TASK = gql`
|
|||||||
* @type {DocumentNode}
|
* @type {DocumentNode}
|
||||||
*/
|
*/
|
||||||
export const MUTATION_UPDATE_TASK = gql`
|
export const MUTATION_UPDATE_TASK = gql`
|
||||||
mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
|
mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
|
||||||
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
|
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
|
||||||
returning {
|
returning {
|
||||||
id
|
id
|
||||||
created_at
|
created_at
|
||||||
updated_at
|
updated_at
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
deleted
|
deleted
|
||||||
deleted_at
|
deleted_at
|
||||||
due_date
|
due_date
|
||||||
created_by
|
created_by
|
||||||
assigned_to
|
assigned_to
|
||||||
completed
|
completed
|
||||||
completed_at
|
completed_at
|
||||||
remind_at
|
remind_at
|
||||||
priority
|
priority
|
||||||
jobid
|
jobid
|
||||||
joblineid
|
joblineid
|
||||||
partsorderid
|
partsorderid
|
||||||
billid
|
billid
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function JobsDetailPage({
|
|||||||
job,
|
job,
|
||||||
mutationUpdateJob,
|
mutationUpdateJob,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
currentUser,
|
currentUser,
|
||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
refetch
|
refetch
|
||||||
}) {
|
}) {
|
||||||
@@ -120,7 +120,7 @@ export function JobsDetailPage({
|
|||||||
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
||||||
};
|
};
|
||||||
}, [refetch]);
|
}, [refetch]);
|
||||||
|
|
||||||
//useKeyboardSaveShortcut(form.submit);
|
//useKeyboardSaveShortcut(form.submit);
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
@@ -406,15 +406,19 @@ export function JobsDetailPage({
|
|||||||
key: 'tasks',
|
key: 'tasks',
|
||||||
icon: <FaTasks/>,
|
icon: <FaTasks/>,
|
||||||
label: <Space direction='horizontal'>
|
label: <Space direction='horizontal'>
|
||||||
{t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />}
|
{t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 &&
|
||||||
|
<Badge count={job.tasks_aggregate.aggregate.count}/>}
|
||||||
</Space>,
|
</Space>,
|
||||||
children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop} relationshipType={'jobid'} relationshipId={job.id} query={QUERY_JOB_TASKS_PAGINATED} titleTranslation='tasks.titles.job_tasks'/>
|
children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop}
|
||||||
|
relationshipType={'jobid'} relationshipId={job.id}
|
||||||
|
query={QUERY_JOB_TASKS_PAGINATED}
|
||||||
|
titleTranslation='tasks.titles.job_tasks'/>
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FloatButton, Layout, Spin, Collapse, Button, Space, Tag } from "antd";
|
import {Button, Collapse, FloatButton, Layout, Space, Spin, Tag} from "antd";
|
||||||
// import preval from "preval.macro";
|
// import preval from "preval.macro";
|
||||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
@@ -20,8 +20,8 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
|
|||||||
import PrintCenterModalContainer
|
import PrintCenterModalContainer
|
||||||
from "../../components/print-center-modal/print-center-modal.container";
|
from "../../components/print-center-modal/print-center-modal.container";
|
||||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
||||||
import { requestForToken } from "../../firebase/firebase.utils";
|
import {requestForToken} from "../../firebase/firebase.utils";
|
||||||
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
import {selectBodyshop, selectInstanceConflict} from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||||
import {setJoyRideFinished} from "../../redux/application/application.actions.js";
|
import {setJoyRideFinished} from "../../redux/application/application.actions.js";
|
||||||
@@ -106,7 +106,7 @@ const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
|
|||||||
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
|
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
|
||||||
|
|
||||||
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
|
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
|
||||||
const { Content, Footer } = Layout;
|
const {Content, Footer} = Layout;
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
conflict: selectInstanceConflict,
|
conflict: selectInstanceConflict,
|
||||||
@@ -119,8 +119,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
|
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
|
export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [chatVisible] = useState(false);
|
const [chatVisible] = useState(false);
|
||||||
const [tours, setTours] = useState([]);
|
const [tours, setTours] = useState([]);
|
||||||
|
|
||||||
@@ -156,115 +156,115 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PaymentModalContainer />
|
<PaymentModalContainer/>
|
||||||
|
|
||||||
<CardPaymentModalContainer />
|
<CardPaymentModalContainer/>
|
||||||
<TaskUpsertModalContainer/>
|
<TaskUpsertModalContainer/>
|
||||||
<BreadCrumbs />
|
<BreadCrumbs/>
|
||||||
<BillEnterModalContainer />
|
<BillEnterModalContainer/>
|
||||||
<JobCostingModal />
|
<JobCostingModal/>
|
||||||
<ReportCenterModal />
|
<ReportCenterModal/>
|
||||||
<EmailOverlayContainer />
|
<EmailOverlayContainer/>
|
||||||
<TimeTicketModalContainer />
|
<TimeTicketModalContainer/>
|
||||||
<TimeTicketModalTask />
|
<TimeTicketModalTask/>
|
||||||
<PrintCenterModalContainer />
|
<PrintCenterModalContainer/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/_test" element={<TestComponent />} />
|
<Route path="/_test" element={<TestComponent/>}/>
|
||||||
<Route path="/" element={<ManageRootPage />} />
|
<Route path="/" element={<ManageRootPage/>}/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs"
|
path="/jobs"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsPage />
|
<JobsPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId/intake"
|
path="/jobs/:jobId/intake"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobIntake />
|
<JobIntake/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId/deliver"
|
path="/jobs/:jobId/deliver"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobDeliver />
|
<JobDeliver/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId/checklist"
|
path="/jobs/:jobId/checklist"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobChecklistView />
|
<JobChecklistView/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId/close"
|
path="/jobs/:jobId/close"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsClose />
|
<JobsClose/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId/admin"
|
path="/jobs/:jobId/admin"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsAdmin />
|
<JobsAdmin/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/all"
|
path="/jobs/all"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<AllJobs />
|
<AllJobs/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/ready"
|
path="/jobs/ready"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ReadyJobs />
|
<ReadyJobs/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/new"
|
path="/jobs/new"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsCreateContainerPage />
|
<JobsCreateContainerPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/jobs/:jobId"
|
path="/jobs/:jobId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsDetailPage />
|
<JobsDetailPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/temporarydocs/"
|
path="/temporarydocs/"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<TempDocs />
|
<TempDocs/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/tasks/mytasks'
|
path='/tasks/mytasks'
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin/>}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<MyTasksPage/>
|
<MyTasksPage/>
|
||||||
</Suspense>}
|
</Suspense>}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/tasks/alltasks'
|
path='/tasks/alltasks'
|
||||||
@@ -276,144 +276,144 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
<Route
|
<Route
|
||||||
path="/inventory/"
|
path="/inventory/"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<InventoryListPage />
|
<InventoryListPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/"
|
path="/courtesycars/"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<CourtesyCarsPage />
|
<CourtesyCarsPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/new"
|
path="/courtesycars/new"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<CourtesyCarCreateContainer />
|
<CourtesyCarCreateContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/contracts"
|
path="/courtesycars/contracts"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ContractsList />
|
<ContractsList/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/contracts/new"
|
path="/courtesycars/contracts/new"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ContractCreatePage />
|
<ContractCreatePage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/contracts/:contractId"
|
path="/courtesycars/contracts/:contractId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ContractDetailPage />
|
<ContractDetailPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/courtesycars/:ccId"
|
path="/courtesycars/:ccId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<CourtesyCarDetailContainer />
|
<CourtesyCarDetailContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/profile"
|
path="/profile"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ProfilePage />
|
<ProfilePage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/vehicles"
|
path="/vehicles"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<VehiclesContainer />
|
<VehiclesContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/production/list"
|
path="/production/list"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ProductionListPage />
|
<ProductionListPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/production/board"
|
path="/production/board"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ProductionBoardPage />
|
<ProductionBoardPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/vehicles/:vehId"
|
path="/vehicles/:vehId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<VehiclesDetailContainer />
|
<VehiclesDetailContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/bills"
|
path="/bills"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<BillsListPage />
|
<BillsListPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/owners"
|
path="/owners"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<OwnersContainer />
|
<OwnersContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/owners/:ownerId"
|
path="/owners/:ownerId"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<OwnersDetailContainer />
|
<OwnersDetailContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/schedule"
|
path="/schedule"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ScheduleContainer />
|
<ScheduleContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/available"
|
path="/available"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<JobsAvailablePage />
|
<JobsAvailablePage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/shop"
|
path="/shop"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ShopPage />
|
<ShopPage/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -426,16 +426,16 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
<Route
|
<Route
|
||||||
path="/shop/vendors"
|
path="/shop/vendors"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ShopVendorPageContainer />
|
<ShopVendorPageContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/shop/csi"
|
path="/shop/csi"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ShopCsiPageContainer />
|
<ShopCsiPageContainer/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -443,8 +443,8 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
<Route
|
<Route
|
||||||
path="/accounting/qbo"
|
path="/accounting/qbo"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<AccountingQboCallback />
|
<AccountingQboCallback/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -452,56 +452,56 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
<Route
|
<Route
|
||||||
path="/accounting/receivables"
|
path="/accounting/receivables"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<AccountingReceivables />
|
<AccountingReceivables/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/accounting/payables"
|
path="/accounting/payables"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<AccountingPayables />
|
<AccountingPayables/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/accounting/payments"
|
path="/accounting/payments"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<AccountingPayments />
|
<AccountingPayments/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/accounting/exportlogs"
|
path="/accounting/exportlogs"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ExportLogs />
|
<ExportLogs/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/ttapprovals"
|
path="/ttapprovals"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<TtApprovals />
|
<TtApprovals/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/partsqueue"
|
path="/partsqueue"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<PartsQueue />
|
<PartsQueue/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/phonebook"
|
path="/phonebook"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<Phonebook />
|
<Phonebook/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -509,65 +509,65 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
<Route
|
<Route
|
||||||
path="/payments"
|
path="/payments"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<PaymentsAll />
|
<PaymentsAll/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/shiftclock"
|
path="/shiftclock"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<ShiftClock />
|
<ShiftClock/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/scoreboard"
|
path="/scoreboard"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<Scoreboard />
|
<Scoreboard/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/timetickets"
|
path="/timetickets"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<TimeTicketsAll />
|
<TimeTicketsAll/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/help"
|
path="/help"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<Help />
|
<Help/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/emailtest" element={<EmailTest />} />
|
<Route path="/emailtest" element={<EmailTest/>}/>
|
||||||
<Route
|
<Route
|
||||||
path="/dashboard"
|
path="/dashboard"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<Dashboard />
|
<Dashboard/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/dms"
|
path="/dms"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<Dms />
|
<Dms/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/dmsap"
|
path="/dmsap"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={<Spin/>}>
|
||||||
<DmsPayables />
|
<DmsPayables/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -577,16 +577,16 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
|
|
||||||
let PageContent;
|
let PageContent;
|
||||||
|
|
||||||
if (conflict) PageContent = <ConflictComponent />;
|
if (conflict) PageContent = <ConflictComponent/>;
|
||||||
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
|
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent/>;
|
||||||
else PageContent = AppRouteTable;
|
else PageContent = AppRouteTable;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible}/>
|
||||||
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
<Layout style={{minHeight: "100vh"}} className="layout-container">
|
||||||
<UpdateAlert />
|
<UpdateAlert/>
|
||||||
<HeaderContainer />
|
<HeaderContainer/>
|
||||||
<Content className="content-container">
|
<Content className="content-container">
|
||||||
<Joyride
|
<Joyride
|
||||||
debug
|
debug
|
||||||
@@ -603,12 +603,12 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<PartnerPingComponent />
|
<PartnerPingComponent/>
|
||||||
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
|
<Sentry.ErrorBoundary fallback={<ErrorBoundary/>} showDialog>
|
||||||
{PageContent}
|
{PageContent}
|
||||||
</Sentry.ErrorBoundary>
|
</Sentry.ErrorBoundary>
|
||||||
|
|
||||||
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
|
<FloatButton.BackTop style={{right: 100, bottom: 25}}/>
|
||||||
</Content>
|
</Content>
|
||||||
<Footer>
|
<Footer>
|
||||||
<div
|
<div
|
||||||
@@ -620,7 +620,7 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
margin: "1rem 0rem"
|
margin: "1rem 0rem"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{display: "flex"}}>
|
||||||
<div>
|
<div>
|
||||||
{`${InstanceRenderManager({
|
{`${InstanceRenderManager({
|
||||||
imex: t("titles.imexonline"),
|
imex: t("titles.imexonline"),
|
||||||
@@ -628,9 +628,9 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
promanager: t("titles.promanager")
|
promanager: t("titles.promanager")
|
||||||
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
||||||
</div>
|
</div>
|
||||||
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
|
<div id="noticeable-widget" style={{marginLeft: "1rem"}}/>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
<Link to="/disclaimer" target="_blank" style={{color: "#ccc"}}>
|
||||||
Disclaimer & Notices
|
Disclaimer & Notices
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -647,7 +647,8 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
|||||||
Get Tours
|
Get Tours
|
||||||
</Button>
|
</Button>
|
||||||
{tours.map((tour) => (
|
{tours.map((tour) => (
|
||||||
<Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
|
<Tag key={tour.id}
|
||||||
|
onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
|
||||||
{tour.name}
|
{tour.name}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ export function MyTasksPageContainer({
|
|||||||
}, [setTaskUpsertContext]);
|
}, [setTaskUpsertContext]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser} bodyshop={bodyshop}/>
|
<TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser}
|
||||||
|
bodyshop={bodyshop}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, set
|
|||||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser} bodyshop={bodyshop}/>
|
<TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser}
|
||||||
|
bodyshop={bodyshop}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const TaskPageTypes = {
|
export const TaskPageTypes = {
|
||||||
MY_TASKS: 'myTasks',
|
MY_TASKS: 'myTasks',
|
||||||
ALL_TASKS: 'allTasks',
|
ALL_TASKS: 'allTasks',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskPageTypes;
|
export default TaskPageTypes;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@ const moment = require("moment");
|
|||||||
|
|
||||||
// TODO use InstanceDynamicManager on the footer default to prevent the word IMEX from being hardcoded
|
// TODO use InstanceDynamicManager on the footer default to prevent the word IMEX from being hardcoded
|
||||||
|
|
||||||
const generateEmailTemplate = (strings) => {
|
const generateEmailTemplate = (strings) => {
|
||||||
|
|
||||||
let now = () =>moment().format('MM/DD/YYYY @ hh:mm a');
|
let now = () => moment().format('MM/DD/YYYY @ hh:mm a');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ require("dotenv").config({
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
let nodemailer = require("nodemailer");
|
let nodemailer = require("nodemailer");
|
||||||
let aws = require("@aws-sdk/client-ses");
|
let aws = require("@aws-sdk/client-ses");
|
||||||
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
let {defaultProvider} = require("@aws-sdk/credential-provider-node");
|
||||||
const InstanceManager = require("../utils/instanceMgr").default;
|
const InstanceManager = require("../utils/instanceMgr").default;
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
const {isObject} = require("lodash");
|
const {isObject} = require("lodash");
|
||||||
const generateEmailTemplate = require('./generateTemplate');
|
const generateEmailTemplate = require('./generateTemplate');
|
||||||
|
|
||||||
const ses = new aws.SES({
|
const ses = new aws.SES({
|
||||||
// The key apiVersion is no longer supported in v3, and can be removed.
|
// The key apiVersion is no longer supported in v3, and can be removed.
|
||||||
@@ -25,10 +25,10 @@ const ses = new aws.SES({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
let transporter = nodemailer.createTransport({
|
||||||
SES: { ses, aws }
|
SES: {ses, aws}
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.sendServerEmail = async function ({ subject, text }) {
|
exports.sendServerEmail = async function ({subject, text}) {
|
||||||
if (process.env.NODE_ENV === undefined) return;
|
if (process.env.NODE_ENV === undefined) return;
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
@@ -60,7 +60,7 @@ exports.sendServerEmail = async function ({ subject, text }) {
|
|||||||
logger.log("server-email-failure", "error", null, null, error);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
exports.sendTaskEmail = async function ({to, subject, text, attachments}) {
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
{
|
{
|
||||||
@@ -125,12 +125,12 @@ exports.sendEmail = async (req, res) => {
|
|||||||
attachments:
|
attachments:
|
||||||
[
|
[
|
||||||
...((req.body.attachments &&
|
...((req.body.attachments &&
|
||||||
req.body.attachments.map((a) => {
|
req.body.attachments.map((a) => {
|
||||||
return {
|
return {
|
||||||
filename: a.filename,
|
filename: a.filename,
|
||||||
path: a.path
|
path: a.path
|
||||||
};
|
};
|
||||||
})) ||
|
})) ||
|
||||||
[]),
|
[]),
|
||||||
...downloadedMedia.map((a) => {
|
...downloadedMedia.map((a) => {
|
||||||
return {
|
return {
|
||||||
@@ -157,7 +157,7 @@ exports.sendEmail = async (req, res) => {
|
|||||||
replyTo: req.body.ReplyTo.Email,
|
replyTo: req.body.ReplyTo.Email,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
templateStrings: req.body.templateStrings
|
templateStrings: req.body.templateStrings
|
||||||
// info,
|
// info,
|
||||||
});
|
});
|
||||||
@@ -186,14 +186,14 @@ exports.sendEmail = async (req, res) => {
|
|||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
bodyshopid: req.body.bodyshopid
|
bodyshopid: req.body.bodyshopid
|
||||||
});
|
});
|
||||||
res.status(500).json({ success: false, error: err });
|
res.status(500).json({success: false, error: err});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getImage(imageUrl) {
|
async function getImage(imageUrl) {
|
||||||
let image = await axios.get(imageUrl, { responseType: "arraybuffer" });
|
let image = await axios.get(imageUrl, {responseType: "arraybuffer"});
|
||||||
let raw = Buffer.from(image.data).toString("base64");
|
let raw = Buffer.from(image.data).toString("base64");
|
||||||
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
||||||
}
|
}
|
||||||
@@ -281,10 +281,10 @@ exports.emailBounce = async function (req, res) {
|
|||||||
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
||||||
|
|
||||||
${body.bounce?.bouncedRecipients.map(
|
${body.bounce?.bouncedRecipients.map(
|
||||||
(r) =>
|
(r) =>
|
||||||
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user