@@ -147,23 +147,19 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
/>
|
||||
}
|
||||
>
|
||||
{
|
||||
import.meta.env.PRODUCT_FRUITS_DISABLED !== 'true' && (
|
||||
<ProductFruits
|
||||
workspaceCode={InstanceRenderMgr({
|
||||
imex: null,
|
||||
rome: null,
|
||||
promanager: "aoJoEifvezYI0Z0P"
|
||||
})}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<ProductFruits
|
||||
workspaceCode={InstanceRenderMgr({
|
||||
imex: null,
|
||||
rome: null,
|
||||
promanager: "aoJoEifvezYI0Z0P"
|
||||
})}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
|
||||
@@ -1,49 +1,55 @@
|
||||
import {EditFilled, SyncOutlined} from "@ant-design/icons";
|
||||
import {Button, Card, Checkbox, Input, Space, Table} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {DateFormatter} from "../../utils/DateFormatter";
|
||||
import {alphaSort, dateSort} from "../../utils/sorters";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import {FaTasks} from "react-icons/fa";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})),
|
||||
setReconciliationContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "reconciliation"
|
||||
})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setReconciliationContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "reconciliation"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function BillsListTableComponent({
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setBillEnterContext,
|
||||
setReconciliationContext,
|
||||
setTaskUpsertContext,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setBillEnterContext,
|
||||
setReconciliationContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {}
|
||||
@@ -54,27 +60,29 @@ export function BillsListTableComponent({
|
||||
|
||||
const Templates = TemplateList("bill");
|
||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||
const {refetch} = billsQuery;
|
||||
|
||||
const { refetch } = billsQuery;
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
<Space wrap>
|
||||
{showView && (
|
||||
<Button onClick={() => handleOnRowClick(record)}>
|
||||
<EditFilled/>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
)}
|
||||
<Button title={t('tasks.buttons.create')} onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
billid: record.id
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<FaTasks/>
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
billid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<BillDeleteButton bill={record} jobid={job.id}/>
|
||||
<BillDeleteButton bill={record} jobid={job.id} />
|
||||
<BillDetailEditReturnComponent
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
|
||||
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
|
||||
@@ -84,9 +92,9 @@ export function BillsListTableComponent({
|
||||
<PrintWrapperComponent
|
||||
templateObject={{
|
||||
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>
|
||||
@@ -129,7 +137,7 @@ export function BillsListTableComponent({
|
||||
key: "is_credit_memo",
|
||||
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
|
||||
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"),
|
||||
@@ -137,7 +145,7 @@ export function BillsListTableComponent({
|
||||
key: "exported",
|
||||
sorter: (a, b) => a.exported - b.exported,
|
||||
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"),
|
||||
@@ -148,18 +156,18 @@ export function BillsListTableComponent({
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const filteredBills = bills
|
||||
? searchText === ""
|
||||
? bills
|
||||
: bills.filter(
|
||||
(b) =>
|
||||
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
(b) =>
|
||||
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
@@ -168,14 +176,14 @@ export function BillsListTableComponent({
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
{job && job.converted ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setBillEnterContext({
|
||||
actions: {refetch: billsQuery.refetch},
|
||||
actions: { refetch: billsQuery.refetch },
|
||||
context: {
|
||||
job
|
||||
}
|
||||
@@ -187,7 +195,7 @@ export function BillsListTableComponent({
|
||||
<Button
|
||||
onClick={() => {
|
||||
setReconciliationContext({
|
||||
actions: {refetch: billsQuery.refetch},
|
||||
actions: { refetch: billsQuery.refetch },
|
||||
context: {
|
||||
job,
|
||||
bills: (billsQuery.data && billsQuery.data.bills) || []
|
||||
|
||||
@@ -26,35 +26,26 @@ import Icon, {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Layout, Menu, Switch, Tooltip} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {BsKanban} from "react-icons/bs";
|
||||
import {
|
||||
FaCalendarAlt,
|
||||
FaCarCrash,
|
||||
FaCreditCard,
|
||||
FaFileInvoiceDollar,
|
||||
FaTasks
|
||||
} from "react-icons/fa";
|
||||
import {GiPayMoney, GiPlayerTime, GiSettingsKnobs} from "react-icons/gi";
|
||||
import {IoBusinessOutline} from "react-icons/io5";
|
||||
import {RiSurveyLine} from "react-icons/ri";
|
||||
import {connect} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
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 { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Layout, Menu, Switch, Tooltip } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsKanban } from "react-icons/bs";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import { RiSurveyLine } from "react-icons/ri";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
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 {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -64,53 +55,68 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicket"
|
||||
})),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
|
||||
setReportCenterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "reportCenter"
|
||||
})),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
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()),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: 'taskUpsert'
|
||||
})),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
function Header({
|
||||
handleMenuClick,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedHeader,
|
||||
signOutStart,
|
||||
setBillEnterContext,
|
||||
setTimeTicketContext,
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext,
|
||||
}) {
|
||||
handleMenuClick,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedHeader,
|
||||
signOutStart,
|
||||
setBillEnterContext,
|
||||
setTimeTicketContext,
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const {
|
||||
treatments: {ImEXPay, DmsAp, Simple_Inventory}
|
||||
treatments: { ImEXPay, DmsAp, Simple_Inventory }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const isBeta = checkBeta();
|
||||
@@ -129,18 +135,18 @@ function Header({
|
||||
InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "bills", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||
})
|
||||
) {
|
||||
accountingChildren.push(
|
||||
{
|
||||
key: "bills",
|
||||
icon: <Icon component={FaFileInvoiceDollar}/>,
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterbills",
|
||||
icon: <Icon component={GiPayMoney}/>,
|
||||
icon: <Icon component={GiPayMoney} />,
|
||||
label: t("menus.header.enterbills"),
|
||||
onClick: () => {
|
||||
setBillEnterContext({
|
||||
@@ -159,7 +165,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "inventory",
|
||||
icon: <Icon component={FaFileInvoiceDollar}/>,
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
||||
}
|
||||
);
|
||||
@@ -168,7 +174,7 @@ function Header({
|
||||
InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "payments", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "payments", bodyshop })
|
||||
})
|
||||
) {
|
||||
accountingChildren.push(
|
||||
@@ -177,12 +183,12 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "allpayments",
|
||||
icon: <BankFilled/>,
|
||||
icon: <BankFilled />,
|
||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterpayments",
|
||||
icon: <Icon component={FaCreditCard}/>,
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
setPaymentContext({
|
||||
@@ -197,7 +203,7 @@ function Header({
|
||||
if (ImEXPay.treatment === "on") {
|
||||
accountingChildren.push({
|
||||
key: "entercardpayments",
|
||||
icon: <Icon component={FaCreditCard}/>,
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => {
|
||||
setCardPaymentContext({
|
||||
@@ -212,7 +218,7 @@ function Header({
|
||||
InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||
})
|
||||
) {
|
||||
accountingChildren.push(
|
||||
@@ -221,7 +227,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "timetickets",
|
||||
icon: <FieldTimeOutlined/>,
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
||||
}
|
||||
);
|
||||
@@ -229,14 +235,14 @@ function Header({
|
||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||
accountingChildren.push({
|
||||
key: "ttapprovals",
|
||||
icon: <FieldTimeOutlined/>,
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||
});
|
||||
}
|
||||
accountingChildren.push(
|
||||
{
|
||||
key: "entertimetickets",
|
||||
icon: <Icon component={GiPlayerTime}/>,
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: t("menus.header.entertimeticket"),
|
||||
onClick: () => {
|
||||
setTimeTicketContext({
|
||||
@@ -258,8 +264,7 @@ function Header({
|
||||
const accountingExportChildren = [
|
||||
{
|
||||
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>
|
||||
}
|
||||
];
|
||||
|
||||
@@ -291,12 +296,12 @@ function Header({
|
||||
InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "export", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "export", bodyshop })
|
||||
})
|
||||
) {
|
||||
accountingChildren.push({
|
||||
key: "accountingexport",
|
||||
icon: <ExportOutlined/>,
|
||||
icon: <ExportOutlined />,
|
||||
label: t("menus.header.export"),
|
||||
children: accountingExportChildren
|
||||
});
|
||||
@@ -305,44 +310,44 @@ function Header({
|
||||
const menuItems = [
|
||||
{
|
||||
key: "home",
|
||||
icon: <HomeFilled/>,
|
||||
icon: <HomeFilled />,
|
||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||
},
|
||||
{
|
||||
key: "schedule",
|
||||
icon: <Icon component={FaCalendarAlt}/>,
|
||||
icon: <Icon component={FaCalendarAlt} />,
|
||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
},
|
||||
{
|
||||
key: "jobssubmenu",
|
||||
id: "header-jobs",
|
||||
icon: <Icon component={FaCarCrash}/>,
|
||||
icon: <Icon component={FaCarCrash} />,
|
||||
label: t("menus.header.jobs"),
|
||||
children: [
|
||||
{
|
||||
key: "activejobs",
|
||||
icon: <FileFilled/>,
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "readyjobs",
|
||||
icon: <CheckCircleOutlined/>,
|
||||
icon: <CheckCircleOutlined />,
|
||||
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "parts-queue",
|
||||
icon: <ToolFilled/>,
|
||||
icon: <ToolFilled />,
|
||||
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||
},
|
||||
{
|
||||
key: "availablejobs",
|
||||
id: "header-jobs-available",
|
||||
icon: <ImportOutlined/>,
|
||||
icon: <ImportOutlined />,
|
||||
label: <Link to="/manage/available">{t("menus.header.availablejobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "newjob",
|
||||
icon: <FileAddOutlined/>,
|
||||
icon: <FileAddOutlined />,
|
||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
},
|
||||
{
|
||||
@@ -350,7 +355,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "alljobs",
|
||||
icon: <UnorderedListOutlined/>,
|
||||
icon: <UnorderedListOutlined />,
|
||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
},
|
||||
{
|
||||
@@ -358,54 +363,54 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "productionlist",
|
||||
icon: <ScheduleOutlined/>,
|
||||
icon: <ScheduleOutlined />,
|
||||
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "visualboard", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "visualboard", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "productionboard",
|
||||
icon: <Icon component={BsKanban}/>,
|
||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "productionboard",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "scoreboard", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "scoreboard", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
key: "scoreboard",
|
||||
icon: <LineChartOutlined/>,
|
||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||
}
|
||||
]
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
key: "scoreboard",
|
||||
icon: <LineChartOutlined />,
|
||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "customers",
|
||||
icon: <UserOutlined/>,
|
||||
icon: <UserOutlined />,
|
||||
label: t("menus.header.customers"),
|
||||
children: [
|
||||
{
|
||||
key: "owners",
|
||||
icon: <TeamOutlined/>,
|
||||
icon: <TeamOutlined />,
|
||||
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||
},
|
||||
{
|
||||
key: "vehicles",
|
||||
icon: <CarFilled/>,
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
||||
}
|
||||
]
|
||||
@@ -416,108 +421,106 @@ function Header({
|
||||
promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }),
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "ccs",
|
||||
icon: <CarFilled/>,
|
||||
label: t("menus.header.courtesycars"),
|
||||
children: [
|
||||
{
|
||||
key: "courtesycarsall",
|
||||
icon: <CarFilled/>,
|
||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
||||
},
|
||||
{
|
||||
key: "contracts",
|
||||
icon: <FileFilled/>,
|
||||
label: <Link
|
||||
to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
||||
},
|
||||
{
|
||||
key: "newcontract",
|
||||
icon: <FileAddFilled/>,
|
||||
label: <Link
|
||||
to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "ccs",
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.courtesycars"),
|
||||
children: [
|
||||
{
|
||||
key: "courtesycarsall",
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
||||
},
|
||||
{
|
||||
key: "contracts",
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
||||
},
|
||||
{
|
||||
key: "newcontract",
|
||||
icon: <FileAddFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
...(accountingChildren.length > 0
|
||||
? [
|
||||
{
|
||||
key: "accounting",
|
||||
icon: <DollarCircleFilled/>,
|
||||
label: t("menus.header.accounting"),
|
||||
children: accountingChildren
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "accounting",
|
||||
icon: <DollarCircleFilled />,
|
||||
label: t("menus.header.accounting"),
|
||||
children: accountingChildren
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "phonebook",
|
||||
icon: <PhoneOutlined/>,
|
||||
icon: <PhoneOutlined />,
|
||||
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "media", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "media", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "temporarydocs",
|
||||
icon: <PaperClipOutlined/>,
|
||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "temporarydocs",
|
||||
icon: <PaperClipOutlined />,
|
||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'tasks',
|
||||
id: 'tasks',
|
||||
icon: <FaTasks/>,
|
||||
label: t('menus.header.tasks'),
|
||||
key: "tasks",
|
||||
id: "tasks",
|
||||
icon: <FaTasks />,
|
||||
label: t("menus.header.tasks"),
|
||||
children: [
|
||||
{
|
||||
key: 'createTask',
|
||||
icon: <PlusCircleOutlined/>,
|
||||
label: t('menus.header.create_task'),
|
||||
key: "createTask",
|
||||
icon: <PlusCircleOutlined />,
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
context: {}
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'mytasks',
|
||||
icon: <FaTasks/>,
|
||||
label: <Link to="/manage/tasks/mytasks">{t('menus.header.my_tasks')}</Link>,
|
||||
key: "mytasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/mytasks">{t("menus.header.my_tasks")}</Link>
|
||||
},
|
||||
{
|
||||
key: 'all_tasks',
|
||||
icon: <FaTasks/>,
|
||||
label: <Link to="/manage/tasks/alltasks">{t('menus.header.all_tasks')}</Link>,
|
||||
key: "all_tasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/alltasks">{t("menus.header.all_tasks")}</Link>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'shopsubmenu',
|
||||
icon: <SettingOutlined/>,
|
||||
key: "shopsubmenu",
|
||||
icon: <SettingOutlined />,
|
||||
label: t("menus.header.shop"),
|
||||
children: [
|
||||
{
|
||||
key: "shop",
|
||||
icon: <Icon component={GiSettingsKnobs}/>,
|
||||
icon: <Icon component={GiSettingsKnobs} />,
|
||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||
},
|
||||
{
|
||||
key: "dashboard",
|
||||
icon: <DashboardFilled/>,
|
||||
icon: <DashboardFilled />,
|
||||
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
},
|
||||
{
|
||||
key: "reportcenter",
|
||||
icon: <BarChartOutlined/>,
|
||||
icon: <BarChartOutlined />,
|
||||
label: t("menus.header.reportcenter"),
|
||||
onClick: () => {
|
||||
setReportCenterContext({
|
||||
@@ -528,21 +531,21 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "shop-vendors",
|
||||
icon: <Icon component={IoBusinessOutline}/>,
|
||||
icon: <Icon component={IoBusinessOutline} />,
|
||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "csi", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "csi", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "shop-csi",
|
||||
icon: <Icon component={RiSurveyLine}/>,
|
||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "shop-csi",
|
||||
icon: <Icon component={RiSurveyLine} />,
|
||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
@@ -552,14 +555,14 @@ function Header({
|
||||
children: [
|
||||
{
|
||||
key: "signout",
|
||||
icon: <Icon component={FiLogOut}/>,
|
||||
icon: <Icon component={FiLogOut} />,
|
||||
danger: true,
|
||||
label: t("user.actions.signout"),
|
||||
onClick: () => signOutStart()
|
||||
},
|
||||
{
|
||||
key: "help",
|
||||
icon: <Icon component={QuestionCircleFilled}/>,
|
||||
icon: <Icon component={QuestionCircleFilled} />,
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => {
|
||||
window.open(
|
||||
@@ -585,19 +588,19 @@ function Header({
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "shiftclock",
|
||||
icon: <Icon component={GiPlayerTime}/>,
|
||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "shiftclock",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "profile",
|
||||
icon: <UserOutlined/>,
|
||||
icon: <UserOutlined />,
|
||||
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
||||
}
|
||||
// {
|
||||
@@ -631,7 +634,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "recent",
|
||||
icon: <ClockCircleFilled/>,
|
||||
icon: <ClockCircleFilled />,
|
||||
children: recentItems.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: <Link to={i.url}>{i.label}</Link>
|
||||
@@ -645,7 +648,7 @@ function Header({
|
||||
imex: () => {
|
||||
menuItems.push({
|
||||
key: "beta-switch",
|
||||
style: {marginLeft: "auto"},
|
||||
style: { marginLeft: "auto" },
|
||||
label: (
|
||||
<Tooltip
|
||||
title={`A more modern ${InstanceRenderManager({
|
||||
@@ -654,9 +657,9 @@ function Header({
|
||||
promanager: t("titles.promanager")
|
||||
})} is ready for you to try! You can switch back at any time.`}
|
||||
>
|
||||
<InfoCircleOutlined/>
|
||||
<span style={{marginRight: 8}}>Try the new app</span>
|
||||
<Switch checked={betaSwitch} onChange={betaSwitchChange}/>
|
||||
<InfoCircleOutlined />
|
||||
<span style={{ marginRight: 8 }}>Try the new app</span>
|
||||
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
|
||||
</Tooltip>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {DateFormatter} from "../../utils/DateFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||
import {QUERY_JOBLINE_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
import TaskListContainer from "../task-list/task-list.container.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -23,9 +23,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
||||
|
||||
export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||
const {t} = useTranslation();
|
||||
const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
|
||||
const { t } = useTranslation();
|
||||
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
@@ -33,8 +33,8 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||
}
|
||||
});
|
||||
|
||||
if (loading) return <Skeleton/>;
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
if (loading) return <Skeleton />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
@@ -44,23 +44,23 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||
items={
|
||||
data.parts_order_lines.length > 0
|
||||
? data.parts_order_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical"/>} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
)
|
||||
}))
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>{" "}
|
||||
</Col>
|
||||
@@ -70,33 +70,33 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
@@ -112,31 +112,35 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
|
||||
items={
|
||||
data.parts_dispatch_lines.length > 0
|
||||
? data.parts_dispatch_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical"/>} wrap>
|
||||
<Link
|
||||
to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
</Space>
|
||||
</Space>
|
||||
)
|
||||
}))
|
||||
)
|
||||
}))
|
||||
: {
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<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>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import JobLinesExpander from "./job-lines-expander.component";
|
||||
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
|
||||
import {FaTasks} from "react-icons/fa";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -54,7 +54,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function JobLinesComponent({
|
||||
@@ -334,15 +334,18 @@ export function JobLinesComponent({
|
||||
>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
<Button title={t('tasks.buttons.create')} onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
joblineid: record.id
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<FaTasks/>
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
joblineid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
@@ -457,7 +460,7 @@ export function JobLinesComponent({
|
||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||
onClick={() => {
|
||||
setBillEnterContext({
|
||||
actions: {refetch: refetch },
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
disableInvNumber: true,
|
||||
job: { id: job.id },
|
||||
@@ -465,7 +468,7 @@ export function JobLinesComponent({
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: dayjs(),
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: selectedLines.map((p) => {
|
||||
return {
|
||||
|
||||
@@ -2,40 +2,25 @@ import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { Select, Space, Spin, Tag } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, {forwardRef, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {
|
||||
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
|
||||
SEARCH_JOBS_FOR_AUTOCOMPLETE
|
||||
} from "../../graphql/jobs.queries";
|
||||
import React, { forwardRef, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
const {Option} = Select;
|
||||
const { Option } = Select;
|
||||
|
||||
const JobSearchSelect = (
|
||||
{
|
||||
disabled,
|
||||
convertedOnly = false,
|
||||
notInvoiced = false,
|
||||
notExported = true,
|
||||
clm_no = false,
|
||||
...restProps
|
||||
},
|
||||
{ disabled, convertedOnly = false, notInvoiced = false, notExported = true, clm_no = false, ...restProps },
|
||||
ref
|
||||
) => {
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const [theOptions, setTheOptions] = useState([]);
|
||||
const [callSearch, {loading, error, data}] = useLazyQuery(SEARCH_JOBS_FOR_AUTOCOMPLETE, {});
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_JOBS_FOR_AUTOCOMPLETE, {});
|
||||
|
||||
const [
|
||||
callIdSearch,
|
||||
{
|
||||
loading: idLoading,
|
||||
error: idError,
|
||||
data: idData
|
||||
}
|
||||
] = useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery(
|
||||
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE
|
||||
);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v);
|
||||
@@ -48,10 +33,10 @@ const JobSearchSelect = (
|
||||
search: value,
|
||||
...(convertedOnly || notExported
|
||||
? {
|
||||
...(convertedOnly ? {isConverted: true} : {}),
|
||||
...(notExported ? {notExported: true} : {}),
|
||||
...(notInvoiced ? {notInvoiced: true} : {})
|
||||
}
|
||||
...(convertedOnly ? { isConverted: true } : {}),
|
||||
...(notExported ? { notExported: true } : {}),
|
||||
...(notInvoiced ? { notInvoiced: true } : {})
|
||||
}
|
||||
: {})
|
||||
}
|
||||
});
|
||||
@@ -59,7 +44,7 @@ const JobSearchSelect = (
|
||||
|
||||
useEffect(() => {
|
||||
if (restProps.value) {
|
||||
callIdSearch({variables: {id: restProps.value}}); // Sometimes results in a no-op. Not sure how to fix.
|
||||
callIdSearch({ variables: { id: restProps.value } }); // Sometimes results in a no-op. Not sure how to fix.
|
||||
}
|
||||
}, [restProps.value, callIdSearch]);
|
||||
|
||||
@@ -89,14 +74,14 @@ const JobSearchSelect = (
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
//loading={loading || idLoading}
|
||||
suffixIcon={(loading || idLoading) && <Spin/>}
|
||||
notFoundContent={loading ? <LoadingOutlined/> : null}
|
||||
suffixIcon={(loading || idLoading) && <Spin />}
|
||||
notFoundContent={loading ? <LoadingOutlined /> : null}
|
||||
{...restProps}
|
||||
>
|
||||
{theOptions
|
||||
? theOptions.map((o) => (
|
||||
<Option key={o.id} value={o.id} status={o.status}>
|
||||
<Space align="center">
|
||||
<Option key={o.id} value={o.id} status={o.status}>
|
||||
<Space align="center">
|
||||
<span>
|
||||
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
|
||||
o.ro_number || t("general.labels.na")
|
||||
@@ -104,17 +89,17 @@ const JobSearchSelect = (
|
||||
o.v_model_desc || ""
|
||||
}`}
|
||||
</span>
|
||||
<Tag>
|
||||
<strong>{o.status}</strong>
|
||||
</Tag>
|
||||
</Space>
|
||||
</Option>
|
||||
))
|
||||
<Tag>
|
||||
<strong>{o.status}</strong>
|
||||
</Tag>
|
||||
</Space>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
|
||||
{error ? <AlertComponent message={error.message} type="error"/> : null}
|
||||
{idError ? <AlertComponent message={idError.message} type="error"/> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{idError ? <AlertComponent message={idError.message} type="error" /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,6 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const data = useMemo(() => {
|
||||
|
||||
return [
|
||||
{
|
||||
key: t("jobs.labels.subtotal"),
|
||||
@@ -166,7 +165,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
bold: true
|
||||
}
|
||||
];
|
||||
// TODO: was removed by Patrick during a CI bug fix.
|
||||
// TODO: was removed by Patrick during a CI bug fix.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||
|
||||
|
||||
@@ -1,51 +1,35 @@
|
||||
import {DownCircleFilled} from "@ant-design/icons";
|
||||
import {useApolloClient, useMutation} from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
notification,
|
||||
Popconfirm,
|
||||
Popover,
|
||||
Select,
|
||||
Space
|
||||
} 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 { DownCircleFilled } from "@ant-design/icons";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } 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 RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import axios from "axios";
|
||||
import {setEmailOptions} from "../../redux/email/email.actions";
|
||||
import {openChatByPhone, setMessage} from "../../redux/messaging/messaging.actions";
|
||||
import {GET_CURRENT_QUESTIONSET_ID, INSERT_CSI} from "../../graphql/csi.queries";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
|
||||
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
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({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -54,57 +38,75 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) => dispatch(setModalContext({context: context, modal: "schedule"})),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
|
||||
setJobCostingContext: (context) => dispatch(setModalContext({
|
||||
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"
|
||||
})),
|
||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setJobCostingContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
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)),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text))
|
||||
});
|
||||
|
||||
export function JobsDetailHeaderActions({
|
||||
job,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
refetch,
|
||||
setScheduleContext,
|
||||
setBillEnterContext,
|
||||
setPaymentContext,
|
||||
setJobCostingContext,
|
||||
jobRO,
|
||||
setTimeTicketContext,
|
||||
setCardPaymentContext,
|
||||
insertAuditTrail,
|
||||
setEmailOptions,
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
setTimeTicketTaskContext
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
job,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
refetch,
|
||||
setScheduleContext,
|
||||
setBillEnterContext,
|
||||
setPaymentContext,
|
||||
setJobCostingContext,
|
||||
jobRO,
|
||||
setTimeTicketContext,
|
||||
setCardPaymentContext,
|
||||
insertAuditTrail,
|
||||
setEmailOptions,
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
setTimeTicketTaskContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
const history = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
@@ -118,7 +120,7 @@ export function JobsDetailHeaderActions({
|
||||
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
|
||||
|
||||
const {
|
||||
treatments: {ImEXPay}
|
||||
treatments: { ImEXPay }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["ImEXPay"],
|
||||
@@ -152,7 +154,7 @@ export function JobsDetailHeaderActions({
|
||||
DuplicateJob(
|
||||
client,
|
||||
job.id,
|
||||
{defaultOpenStatus: bodyshop.md_ro_statuses.default_imported},
|
||||
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
|
||||
(newJobId) => {
|
||||
history(`/manage/jobs/${newJobId}`);
|
||||
notification["success"]({
|
||||
@@ -163,7 +165,7 @@ export function JobsDetailHeaderActions({
|
||||
);
|
||||
|
||||
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}`);
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.duplicated")
|
||||
@@ -177,7 +179,7 @@ export function JobsDetailHeaderActions({
|
||||
try {
|
||||
insertAppointment({
|
||||
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"]
|
||||
});
|
||||
@@ -194,7 +196,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
const handleDeleteJob = async () => {
|
||||
//delete the job.
|
||||
const result = await deleteJob({variables: {id: job.id}});
|
||||
const result = await deleteJob({ variables: { id: job.id } });
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({
|
||||
@@ -255,7 +257,7 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({message: t("csi.successes.created")});
|
||||
notification["success"]({ message: t("csi.successes.created") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("csi.errors.creating", {
|
||||
@@ -410,7 +412,7 @@ export function JobsDetailHeaderActions({
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{jobIds: [job.id], custDataOnly: true},
|
||||
{ jobIds: [job.id], custDataOnly: true },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
|
||||
@@ -519,7 +521,7 @@ export function JobsDetailHeaderActions({
|
||||
setIsCancelScheduleModalVisible(false);
|
||||
};
|
||||
|
||||
const handleLostSaleFinish = async ({lost_sale_reason}) => {
|
||||
const handleLostSaleFinish = async ({ lost_sale_reason }) => {
|
||||
const jobUpdate = await cancelAllAppointments({
|
||||
variables: {
|
||||
jobid: job.id,
|
||||
@@ -559,10 +561,10 @@ export function JobsDetailHeaderActions({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("appointments.fields.note")} name="note">
|
||||
<Input/>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("appointments.fields.start")}
|
||||
@@ -577,7 +579,7 @@ export function JobsDetailHeaderActions({
|
||||
<FormDateTimePickerComponent
|
||||
onBlur={() => {
|
||||
const start = form.getFieldValue("start");
|
||||
form.setFieldsValue({end: start.add(30, "minutes")});
|
||||
form.setFieldsValue({ end: start.add(30, "minutes") });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -589,10 +591,10 @@ export function JobsDetailHeaderActions({
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
({ getFieldValue }) => ({
|
||||
async validator(rule, value) {
|
||||
if (value) {
|
||||
const {start} = form.getFieldsValue();
|
||||
const { start } = form.getFieldsValue();
|
||||
if (dayjs(start).isAfter(dayjs(value))) {
|
||||
return Promise.reject(t("employees.labels.endmustbeafterstart"));
|
||||
} else {
|
||||
@@ -605,7 +607,7 @@ export function JobsDetailHeaderActions({
|
||||
})
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerComponent/>
|
||||
<FormDateTimePickerComponent />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||
<Select>
|
||||
@@ -636,7 +638,7 @@ export function JobsDetailHeaderActions({
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_schedule");
|
||||
setScheduleContext({
|
||||
actions: {refetch: refetch},
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
@@ -680,8 +682,7 @@ export function JobsDetailHeaderActions({
|
||||
{
|
||||
key: "checklist",
|
||||
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",
|
||||
@@ -689,35 +690,35 @@ export function JobsDetailHeaderActions({
|
||||
{
|
||||
key: "toggleproduction",
|
||||
disabled: !job.converted || jobRO,
|
||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch}/>
|
||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
|
||||
}
|
||||
]
|
||||
}),
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: "USE_IMEX",
|
||||
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "entertimetickets",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
label: t("timetickets.actions.enter"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_time_ticekts");
|
||||
{
|
||||
key: "entertimetickets",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
label: t("timetickets.actions.enter"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_time_ticekts");
|
||||
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: job.id,
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email
|
||||
}
|
||||
});
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: job.id,
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
@@ -728,7 +729,7 @@ export function JobsDetailHeaderActions({
|
||||
onClick: () => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: {jobid: job.id}
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
},
|
||||
label: t("timetickets.actions.claimtasks")
|
||||
@@ -744,7 +745,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: {jobid: job.id}
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -759,18 +760,18 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
context: {jobid: job.id}
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (HasFeatureAccess({featureName: "courtesycars"})) {
|
||||
if (HasFeatureAccess({ featureName: "courtesycars" })) {
|
||||
menuItems.push({
|
||||
key: "cccontract",
|
||||
disabled: jobRO || !job.converted,
|
||||
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")}
|
||||
</Link>
|
||||
)
|
||||
@@ -780,17 +781,17 @@ export function JobsDetailHeaderActions({
|
||||
menuItems.push(
|
||||
job.inproduction
|
||||
? {
|
||||
key: "removefromproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.removefromproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch, true)
|
||||
}
|
||||
key: "removefromproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.removefromproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch, true)
|
||||
}
|
||||
: {
|
||||
key: "addtoproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.addtoproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch)
|
||||
}
|
||||
key: "addtoproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.addtoproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch)
|
||||
}
|
||||
);
|
||||
|
||||
menuItems.push(
|
||||
@@ -846,25 +847,25 @@ export function JobsDetailHeaderActions({
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "bills", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "postbills",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.postbills"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_bills");
|
||||
{
|
||||
key: "postbills",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.postbills"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_bills");
|
||||
|
||||
setBillEnterContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
job: job
|
||||
}
|
||||
});
|
||||
setBillEnterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
job: job
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
@@ -875,7 +876,7 @@ export function JobsDetailHeaderActions({
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {queued_for_parts: true}
|
||||
job: { queued_for_parts: true }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -925,7 +926,7 @@ export function JobsDetailHeaderActions({
|
||||
InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "export", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "export", bodyshop })
|
||||
})
|
||||
) {
|
||||
menuItems.push({
|
||||
@@ -936,7 +937,7 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
}
|
||||
|
||||
if (HasFeatureAccess({featureName: "csi", bodyshop})) {
|
||||
if (HasFeatureAccess({ featureName: "csi", bodyshop })) {
|
||||
const children = [
|
||||
{
|
||||
key: "email",
|
||||
@@ -966,20 +967,20 @@ export function JobsDetailHeaderActions({
|
||||
...job.csiinvites.map((item, idx) => {
|
||||
return item.completedon
|
||||
? {
|
||||
key: idx,
|
||||
label: (
|
||||
<Link to={`/manage/shop/csi?responseid=${item.id}`}>
|
||||
<DateTimeFormatter>{item.completedon}</DateTimeFormatter>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
key: idx,
|
||||
label: (
|
||||
<Link to={`/manage/shop/csi?responseid=${item.id}`}>
|
||||
<DateTimeFormatter>{item.completedon}</DateTimeFormatter>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
: {
|
||||
key: idx,
|
||||
onClick: () => {
|
||||
navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`);
|
||||
},
|
||||
label: t("general.actions.copylink")
|
||||
};
|
||||
key: idx,
|
||||
onClick: () => {
|
||||
navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`);
|
||||
},
|
||||
label: t("general.actions.copylink")
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -999,7 +1000,7 @@ export function JobsDetailHeaderActions({
|
||||
logImEXEvent("job_header_job_costing");
|
||||
|
||||
setJobCostingContext({
|
||||
actions: {refetch: refetch},
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
jobId: job.id
|
||||
}
|
||||
@@ -1109,10 +1110,10 @@ export function JobsDetailHeaderActions({
|
||||
<Dropdown menu={menu} trigger={["click"]} key="changestatus">
|
||||
<Button>
|
||||
<span>{t("general.labels.actions")}</span>
|
||||
<DownCircleFilled/>
|
||||
<DownCircleFilled />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Popover content={popOverContent} open={visibility}/>
|
||||
<Popover content={popOverContent} open={visibility} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {SyncOutlined} from "@ant-design/icons";
|
||||
import {Button, Card, Input, Space, Table, Typography} from "antd";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import queryString from "query-string";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
@@ -23,15 +23,15 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
//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 [openSearchResults, setOpenSearchResults] = useState([]);
|
||||
const [searchLoading, setSearchLoading] = useState(false);
|
||||
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
|
||||
const {page, sortcolumn, sortorder} = search;
|
||||
const { page, sortcolumn, sortorder } = search;
|
||||
const history = useNavigate();
|
||||
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
@@ -54,11 +54,11 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid}>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
key: "ownr_ph1",
|
||||
|
||||
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"),
|
||||
@@ -77,7 +77,7 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
key: "ownr_ph2",
|
||||
|
||||
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"),
|
||||
@@ -92,7 +92,7 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
},
|
||||
filteredValue: filter?.status || null,
|
||||
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)
|
||||
},
|
||||
@@ -178,7 +178,7 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
delete search.statusFilters;
|
||||
}
|
||||
setFilter(filters);
|
||||
history({search: queryString.stringify(search)});
|
||||
history({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -210,13 +210,13 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
{search.search && (
|
||||
<>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.searchresults", {search: search.search})}
|
||||
{t("general.labels.searchresults", { search: search.search })}
|
||||
</Typography.Title>
|
||||
<Button
|
||||
onClick={() => {
|
||||
delete search.search;
|
||||
delete search.page;
|
||||
history({search: queryString.stringify(search)});
|
||||
history({ search: queryString.stringify(search) });
|
||||
}}
|
||||
>
|
||||
{t("general.actions.clear")}
|
||||
@@ -224,13 +224,13 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
</>
|
||||
)}
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={search.search || t("general.labels.search")}
|
||||
onSearch={(value) => {
|
||||
search.search = value;
|
||||
history({search: queryString.stringify(search)});
|
||||
history({ search: queryString.stringify(search) });
|
||||
searchJobs(value);
|
||||
}}
|
||||
loading={loading || searchLoading}
|
||||
@@ -244,15 +244,15 @@ export function JobsList({bodyshop, refetch, loading, jobs, total}) {
|
||||
pagination={
|
||||
search?.search
|
||||
? {
|
||||
pageSize: pageLimit,
|
||||
showSizeChanger: false
|
||||
}
|
||||
pageSize: pageLimit,
|
||||
showSizeChanger: false
|
||||
}
|
||||
: {
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
showSizeChanger: false
|
||||
}
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
showSizeChanger: false
|
||||
}
|
||||
}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd";
|
||||
import { BranchesOutlined, 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 React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {onlyUnique} from "../../utils/arrayHelper";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {alphaSort, statusSort} from "../../utils/sorters";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {setJoyRideSteps} from "../../redux/application/application.actions";
|
||||
import {OwnerNameDisplayFunction} from "./../owner-name-display/owner-name-display.component";
|
||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -33,13 +28,13 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
||||
});
|
||||
|
||||
export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {selected} = searchParams;
|
||||
const { selected } = searchParams;
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"]
|
||||
},
|
||||
@@ -47,36 +42,36 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const [state, setState] = useState({sortedInfo: {}});
|
||||
const [state, setState] = useState({ sortedInfo: {} });
|
||||
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
|
||||
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
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
|
||||
? searchText === ""
|
||||
? data.jobs
|
||||
: data.jobs.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.comments || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
(j) =>
|
||||
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.comments || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, sortedInfo: sorter});
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
setFilter(filters);
|
||||
};
|
||||
|
||||
@@ -107,12 +102,12 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert"/>
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && <PauseCircleOutlined style={{color: "orangered"}}/>}
|
||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{color: "orangered"}}/>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
@@ -131,11 +126,11 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +141,7 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
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"),
|
||||
@@ -154,7 +149,7 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id}/>
|
||||
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
|
||||
{
|
||||
@@ -349,7 +344,7 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
})}
|
||||
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
@@ -364,7 +359,7 @@ export function JobsList({bodyshop, setJoyRideSteps}) {
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{defaultPageSize: 50}}
|
||||
pagination={{ defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
|
||||
@@ -1,62 +1,66 @@
|
||||
import {DeleteFilled, EyeFilled, SyncOutlined} from "@ant-design/icons";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table} from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
import queryString from "query-string";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||
import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {DateFormatter} from "../../utils/DateFormatter";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import PartsOrderBackorderEta
|
||||
from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
||||
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
||||
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
|
||||
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
|
||||
import PartsOrderLineBackorderButton
|
||||
from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
|
||||
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
|
||||
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
|
||||
import PrintWrapper from "../print-wrapper/print-wrapper.component";
|
||||
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
|
||||
import {FaTasks} from "react-icons/fa";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})),
|
||||
setPartsReceiveContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "partsReceive"
|
||||
})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setPartsReceiveContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "partsReceive"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function PartsOrderListTableComponent({
|
||||
setBillEnterContext,
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setPartsReceiveContext,
|
||||
setTaskUpsertContext,
|
||||
}) {
|
||||
setBillEnterContext,
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setPartsReceiveContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
@@ -71,9 +75,9 @@ export function PartsOrderListTableComponent({
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
|
||||
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({
|
||||
sortedInfo: {}
|
||||
});
|
||||
@@ -84,13 +88,13 @@ export function PartsOrderListTableComponent({
|
||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||
|
||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||
const {refetch} = billsQuery;
|
||||
const { refetch } = billsQuery;
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
<Space direction='horizontal' wrap>
|
||||
<Space direction="horizontal" wrap>
|
||||
{showView && (
|
||||
<Button onClick={() => handleOnRowClick(record)}>
|
||||
<EyeFilled/>
|
||||
<EyeFilled />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -99,7 +103,7 @@ export function PartsOrderListTableComponent({
|
||||
onClick={() => {
|
||||
logImEXEvent("parts_order_receive_bill");
|
||||
setPartsReceiveContext({
|
||||
actions: {refetch: refetch},
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
@@ -119,15 +123,18 @@ export function PartsOrderListTableComponent({
|
||||
>
|
||||
{t("parts_orders.actions.receive")}
|
||||
</Button>
|
||||
<Button title={t('tasks.buttons.create')} onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
partsorderid: record.id
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<FaTasks/>
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
partsorderid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t("parts_orders.labels.confirmdelete")}
|
||||
@@ -136,11 +143,11 @@ export function PartsOrderListTableComponent({
|
||||
//Delete the parts return.!
|
||||
|
||||
await deletePartsOrder({
|
||||
variables: {partsOrderId: record.id},
|
||||
variables: { partsOrderId: record.id },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
parts_orders(existingPartsOrders, {readField}) {
|
||||
parts_orders(existingPartsOrders, { readField }) {
|
||||
return existingPartsOrders.filter((billref) => record.id !== readField("id", billref));
|
||||
}
|
||||
}
|
||||
@@ -150,7 +157,7 @@ export function PartsOrderListTableComponent({
|
||||
}}
|
||||
>
|
||||
<Button disabled={jobRO}>
|
||||
<DeleteFilled/>
|
||||
<DeleteFilled />
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
|
||||
@@ -160,7 +167,7 @@ export function PartsOrderListTableComponent({
|
||||
logImEXEvent("parts_order_receive_bill");
|
||||
|
||||
setBillEnterContext({
|
||||
actions: {refetch: refetch},
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
job: job,
|
||||
bill: {
|
||||
@@ -180,7 +187,7 @@ export function PartsOrderListTableComponent({
|
||||
? pol.jobline.part_type
|
||||
: null
|
||||
: responsibilityCenters.defaults &&
|
||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||
: null
|
||||
};
|
||||
})
|
||||
@@ -195,7 +202,7 @@ export function PartsOrderListTableComponent({
|
||||
<PrintWrapper
|
||||
templateObject={{
|
||||
name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key,
|
||||
variables: {id: record.id}
|
||||
variables: { id: record.id }
|
||||
}}
|
||||
messageObject={{
|
||||
subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject,
|
||||
@@ -236,7 +243,7 @@ export function PartsOrderListTableComponent({
|
||||
key: "return",
|
||||
sorter: (a, b) => a.return - b.return,
|
||||
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"),
|
||||
@@ -260,7 +267,7 @@ export function PartsOrderListTableComponent({
|
||||
];
|
||||
|
||||
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);
|
||||
@@ -291,15 +298,15 @@ export function PartsOrderListTableComponent({
|
||||
},
|
||||
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
||||
? [
|
||||
{
|
||||
title: t("parts_orders.fields.cost"),
|
||||
dataIndex: "cost",
|
||||
key: "cost",
|
||||
sorter: (a, b) => a.cost - b.cost,
|
||||
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
|
||||
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
|
||||
}
|
||||
]
|
||||
{
|
||||
title: t("parts_orders.fields.cost"),
|
||||
dataIndex: "cost",
|
||||
key: "cost",
|
||||
sorter: (a, b) => a.cost - b.cost,
|
||||
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
|
||||
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: t("parts_orders.fields.part_type"),
|
||||
@@ -327,19 +334,19 @@ export function PartsOrderListTableComponent({
|
||||
|
||||
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
||||
? [
|
||||
{
|
||||
title: t("parts_orders.fields.cm_received"),
|
||||
dataIndex: "cm_received",
|
||||
key: "cm_received",
|
||||
render: (text, record) => (
|
||||
<PartsOrderCmReceived
|
||||
orderLineId={record.id}
|
||||
checked={record.cm_received}
|
||||
partsorderid={selectedPartsOrderRecord.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
{
|
||||
title: t("parts_orders.fields.cm_received"),
|
||||
dataIndex: "cm_received",
|
||||
key: "cm_received",
|
||||
render: (text, record) => (
|
||||
<PartsOrderCmReceived
|
||||
orderLineId={record.id}
|
||||
checked={record.cm_received}
|
||||
partsorderid={selectedPartsOrderRecord.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: t("parts_orders.fields.backordered_on"),
|
||||
@@ -388,8 +395,7 @@ export function PartsOrderListTableComponent({
|
||||
|
||||
return (
|
||||
<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
|
||||
scroll={{
|
||||
x: true //y: "50rem"
|
||||
@@ -399,7 +405,7 @@ export function PartsOrderListTableComponent({
|
||||
dataSource={record.parts_order_lines}
|
||||
/>
|
||||
<DataLabel label={t("parts_orders.fields.comments")}>
|
||||
<div style={{whiteSpace: "pre"}}>{record.comments}</div>
|
||||
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
|
||||
</DataLabel>
|
||||
</div>
|
||||
);
|
||||
@@ -409,10 +415,10 @@ export function PartsOrderListTableComponent({
|
||||
? searchText === ""
|
||||
? parts_orders
|
||||
: parts_orders.filter(
|
||||
(b) =>
|
||||
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
(b) =>
|
||||
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
@@ -421,7 +427,7 @@ export function PartsOrderListTableComponent({
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
@@ -434,7 +440,7 @@ export function PartsOrderListTableComponent({
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<PartsReceiveModalContainer/>
|
||||
<PartsReceiveModalContainer />
|
||||
<Drawer
|
||||
placement="right"
|
||||
onClose={() => handleOnRowClick(null)}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Button, Card, Space, Switch, Table} from "antd";
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, {useCallback, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import dayjs from "../../utils/day";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
PlusCircleFilled,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import {DateFormatter} from "../../utils/DateFormatter.jsx";
|
||||
import {connect} from 'react-redux';
|
||||
import {setModalContext} from '../../redux/modals/modals.actions';
|
||||
import { DateFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import { connect } from "react-redux";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
/**
|
||||
* Task List Component
|
||||
@@ -25,7 +25,7 @@ import {setModalContext} from '../../redux/modals/modals.actions';
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const DueDateRecord = ({dueDate}) => {
|
||||
const DueDateRecord = ({ dueDate }) => {
|
||||
if (!dueDate) return <></>;
|
||||
|
||||
const dueDateDayjs = dayjs(dueDate);
|
||||
@@ -33,11 +33,11 @@ const DueDateRecord = ({dueDate}) => {
|
||||
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
|
||||
|
||||
return (
|
||||
<div title={relativeDueDate} style={{color: isBeforeToday ? 'red' : 'black'}}>
|
||||
<div title={relativeDueDate} style={{ color: isBeforeToday ? "red" : "black" }}>
|
||||
<DateFormatter>{dueDate}</DateFormatter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Priority Label Component
|
||||
@@ -45,30 +45,38 @@ const DueDateRecord = ({dueDate}) => {
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const PriorityLabel = ({priority}) => {
|
||||
const PriorityLabel = ({ priority }) => {
|
||||
switch (priority) {
|
||||
case 1:
|
||||
return <div>
|
||||
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
High <ExclamationCircleFilled style={{ marginLeft: "5px", color: "red" }} />
|
||||
</div>
|
||||
);
|
||||
case 2:
|
||||
return <div>
|
||||
Medium <ExclamationCircleFilled style={{marginLeft: '5px', color: 'yellow'}}/>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
Medium <ExclamationCircleFilled style={{ marginLeft: "5px", color: "yellow" }} />
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
return <div>
|
||||
Low <ExclamationCircleFilled style={{marginLeft: '5px', color: 'green'}}/>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
Low <ExclamationCircleFilled style={{ marginLeft: "5px", color: "green" }} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <div>
|
||||
None <ExclamationCircleFilled style={{marginLeft: '5px', color: 'black'}}/>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
None <ExclamationCircleFilled style={{ marginLeft: "5px", color: "black" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
// Existing dispatch props...
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
@@ -78,69 +86,59 @@ const mapStateToProps = (state) => ({
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
|
||||
|
||||
function TaskListComponent({
|
||||
bodyshop,
|
||||
loading,
|
||||
tasks,
|
||||
total,
|
||||
titleTranslation,
|
||||
refetch,
|
||||
toggleCompletedStatus,
|
||||
setTaskUpsertContext,
|
||||
toggleDeletedStatus,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
showRo = true,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
bodyshop,
|
||||
loading,
|
||||
tasks,
|
||||
total,
|
||||
titleTranslation,
|
||||
refetch,
|
||||
toggleCompletedStatus,
|
||||
setTaskUpsertContext,
|
||||
toggleDeletedStatus,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
showRo = true
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
// Extract Query Params
|
||||
const {
|
||||
page,
|
||||
sortcolumn,
|
||||
sortorder,
|
||||
deleted,
|
||||
completed,
|
||||
mine
|
||||
} = search;
|
||||
const { page, sortcolumn, sortorder, deleted, completed, mine } = search;
|
||||
|
||||
const history = useNavigate();
|
||||
const columns = [];
|
||||
|
||||
if (!onlyMine) {
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
dataIndex: "assigned_to",
|
||||
key: "assigned_to",
|
||||
width: '8%',
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
||||
render: (text, record) => {
|
||||
const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to);
|
||||
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
|
||||
}
|
||||
columns.push({
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
dataIndex: "assigned_to",
|
||||
key: "assigned_to",
|
||||
width: "8%",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
||||
render: (text, record) => {
|
||||
const employee = bodyshop?.employees?.find((e) => e.user_email === record.assigned_to);
|
||||
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (showRo) {
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: '8%',
|
||||
render: (text, record) =>
|
||||
record.job
|
||||
? <Link
|
||||
to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
: t("general.labels.na")
|
||||
}
|
||||
);
|
||||
columns.push({
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
columns.push(
|
||||
@@ -148,44 +146,47 @@ function TaskListComponent({
|
||||
title: t("tasks.fields.jobline"),
|
||||
dataIndex: ["jobline", "id"],
|
||||
key: "jobline.id",
|
||||
width: '8%',
|
||||
render: (text, record) => record?.jobline?.line_desc || ''
|
||||
width: "8%",
|
||||
render: (text, record) => record?.jobline?.line_desc || ""
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.parts_order"),
|
||||
dataIndex: ["parts_order", "id"],
|
||||
key: "part_order.id",
|
||||
width: '8%',
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.parts_order
|
||||
? <Link
|
||||
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||
record.parts_order ? (
|
||||
<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.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
: ''
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.bill"),
|
||||
dataIndex: ["bill", "id"],
|
||||
key: "bill.id",
|
||||
width: '8%',
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.bill
|
||||
? <Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
record.bill ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
||||
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
: ''
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.title"),
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "title" && sortorder,
|
||||
sortOrder: sortcolumn === "title" && sortorder
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.due_date"),
|
||||
@@ -193,8 +194,8 @@ function TaskListComponent({
|
||||
key: "due_date",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "due_date" && sortorder,
|
||||
width: '8%',
|
||||
render: (text, record) => <DueDateRecord dueDate={record.due_date}/>,
|
||||
width: "8%",
|
||||
render: (text, record) => <DueDateRecord dueDate={record.due_date} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.remind_at"),
|
||||
@@ -202,8 +203,8 @@ function TaskListComponent({
|
||||
key: "remind_at",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "remind_at" && sortorder,
|
||||
width: '8%',
|
||||
render: (text, record) => <DueDateRecord dueDate={record.remind_at}/>,
|
||||
width: "8%",
|
||||
render: (text, record) => <DueDateRecord dueDate={record.remind_at} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.priority"),
|
||||
@@ -211,75 +212,82 @@ function TaskListComponent({
|
||||
key: "priority",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "priority" && sortorder,
|
||||
width: '8%',
|
||||
render: (text, record) => <PriorityLabel priority={record.priority}
|
||||
/>
|
||||
width: "8%",
|
||||
render: (text, record) => <PriorityLabel priority={record.priority} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.actions"),
|
||||
key: "toggleCompleted",
|
||||
width: '5%',
|
||||
width: "5%",
|
||||
render: (text, record) => (
|
||||
<Space direction='horizontal'>
|
||||
<Button title={t('tasks.buttons.edit')} onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
existingTask: record,
|
||||
},
|
||||
});
|
||||
}}>
|
||||
<EditFilled/>
|
||||
<Space direction="horizontal">
|
||||
<Button
|
||||
title={t("tasks.buttons.edit")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
existingTask: record
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
<Button title={t('tasks.buttons.complete')}
|
||||
onClick={() => toggleCompletedStatus(record.id, record.completed)}>
|
||||
{record.completed ? <CheckCircleOutlined/> :
|
||||
<CheckCircleFilled/>}
|
||||
<Button
|
||||
title={t("tasks.buttons.complete")}
|
||||
onClick={() => toggleCompletedStatus(record.id, record.completed)}
|
||||
>
|
||||
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
|
||||
</Button>
|
||||
<Button title={t('tasks.buttons.delete')}
|
||||
onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
|
||||
{record.deleted ? <DeleteFilled/> : <DeleteOutlined/>}
|
||||
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
|
||||
{record.deleted ? <DeleteFilled /> : <DeleteOutlined />}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {text: ""},
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
const handleCreateTask = useCallback(() => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobid: parentJobId,
|
||||
[relationshipType]: relationshipId,
|
||||
},
|
||||
});
|
||||
}, [ parentJobId, relationshipId, relationshipType, setTaskUpsertContext]);
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobid: parentJobId,
|
||||
[relationshipType]: relationshipId
|
||||
}
|
||||
});
|
||||
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext]);
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.columnKey;
|
||||
search.sortorder = sorter.order;
|
||||
history({search: queryString.stringify(search)});
|
||||
history({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
const handleSwitchChange = useCallback((param, value) => {
|
||||
if (value) {
|
||||
search[param] = "true";
|
||||
} else {
|
||||
delete search[param];
|
||||
}
|
||||
history({search: queryString.stringify(search)});
|
||||
}, [history, search]);
|
||||
const handleSwitchChange = useCallback(
|
||||
(param, value) => {
|
||||
if (value) {
|
||||
search[param] = "true";
|
||||
} else {
|
||||
delete search[param];
|
||||
}
|
||||
history({ search: queryString.stringify(search) });
|
||||
},
|
||||
[history, search]
|
||||
);
|
||||
|
||||
const expandableRow = (record) => {
|
||||
return <Card title={t('tasks.fields.description')} size='small'>
|
||||
{record.description}
|
||||
</Card>
|
||||
return (
|
||||
<Card title={t("tasks.fields.description")} size="small">
|
||||
{record.description}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -288,46 +296,43 @@ function TaskListComponent({
|
||||
*/
|
||||
const tasksExtra = useCallback(() => {
|
||||
return (
|
||||
<Space direction='horizontal'>
|
||||
<Space direction="horizontal">
|
||||
{!onlyMine && (
|
||||
<Switch
|
||||
checkedChildren={t('tasks.buttons.myTasks')}
|
||||
unCheckedChildren={t('tasks.buttons.allTasks')}
|
||||
title={t('tasks.titles.mine')}
|
||||
checkedChildren={t("tasks.buttons.myTasks")}
|
||||
unCheckedChildren={t("tasks.buttons.allTasks")}
|
||||
title={t("tasks.titles.mine")}
|
||||
checked={mine === "true"}
|
||||
onChange={(value) => handleSwitchChange('mine', value)}
|
||||
onChange={(value) => handleSwitchChange("mine", value)}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
checkedChildren={<CheckCircleFilled/>}
|
||||
unCheckedChildren={<CheckCircleOutlined/>}
|
||||
title={t('tasks.titles.completed')}
|
||||
checkedChildren={<CheckCircleFilled />}
|
||||
unCheckedChildren={<CheckCircleOutlined />}
|
||||
title={t("tasks.titles.completed")}
|
||||
checked={completed === "true"}
|
||||
onChange={(value) => handleSwitchChange('completed', value)}
|
||||
onChange={(value) => handleSwitchChange("completed", value)}
|
||||
/>
|
||||
<Switch
|
||||
checkedChildren={<DeleteOutlined/>}
|
||||
unCheckedChildren={<DeleteFilled/>}
|
||||
title={t('tasks.titles.deleted')}
|
||||
checkedChildren={<DeleteOutlined />}
|
||||
unCheckedChildren={<DeleteFilled />}
|
||||
title={t("tasks.titles.deleted")}
|
||||
checked={deleted === "true"}
|
||||
onChange={(value) => handleSwitchChange('deleted', value)}
|
||||
onChange={(value) => handleSwitchChange("deleted", value)}
|
||||
/>
|
||||
<Button title={t('tasks.buttons.create')} onClick={handleCreateTask}>
|
||||
<PlusCircleFilled/>{t('tasks.buttons.create')}
|
||||
<Button title={t("tasks.buttons.create")} onClick={handleCreateTask}>
|
||||
<PlusCircleFilled />
|
||||
{t("tasks.buttons.create")}
|
||||
</Button>
|
||||
<Button title={t('tasks.buttons.refresh')}
|
||||
onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
<Button title={t("tasks.buttons.refresh")} onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}, [refetch, deleted, completed, mine, onlyMine, t, handleSwitchChange, handleCreateTask]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={titleTranslation}
|
||||
extra={tasksExtra()}
|
||||
>
|
||||
<Card title={titleTranslation} extra={tasksExtra()}>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{
|
||||
@@ -335,16 +340,16 @@ function TaskListComponent({
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
responsive: true,
|
||||
showQuickJumper: true,
|
||||
showQuickJumper: true
|
||||
}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
scroll={{x: true}}
|
||||
scroll={{ x: true }}
|
||||
dataSource={tasks}
|
||||
onChange={handleTableChange}
|
||||
expandable={{
|
||||
expandedRowRender: expandableRow,
|
||||
rowExpandable: record => record.description,
|
||||
rowExpandable: (record) => record.description
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -1,73 +1,63 @@
|
||||
import queryString from "query-string";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import {
|
||||
MUTATION_TOGGLE_TASK_COMPLETED,
|
||||
MUTATION_TOGGLE_TASK_DELETED,
|
||||
} from "../../graphql/tasks.queries.js";
|
||||
import {pageLimit} from "../../utils/config.js";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED } from "../../graphql/tasks.queries.js";
|
||||
import { pageLimit } from "../../utils/config.js";
|
||||
import AlertComponent from "../alert/alert.component.jsx";
|
||||
import React, {useEffect} from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import TaskListComponent from "./task-list.component.jsx";
|
||||
import {notification} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions.js";
|
||||
import { notification } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
|
||||
export default function TaskListContainer({
|
||||
bodyshop,
|
||||
titleTranslation,
|
||||
query,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
currentUser,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
showRo = true
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
bodyshop,
|
||||
titleTranslation,
|
||||
query,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
currentUser,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
showRo = true
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams;
|
||||
const { page, sortcolumn, sortorder, deleted, completed, mine } = searchParams;
|
||||
const dispatch = useDispatch();
|
||||
const {loading, error, data, refetch} = useQuery(
|
||||
query,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
bodyshop: bodyshop.id,
|
||||
[relationshipType]: relationshipId,
|
||||
deleted: deleted === 'true',
|
||||
completed: completed === "true",
|
||||
assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]: sortorder
|
||||
? sortorder === "descend"
|
||||
? "desc"
|
||||
: "asc"
|
||||
: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
const { loading, error, data, refetch } = useQuery(query, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
bodyshop: bodyshop.id,
|
||||
[relationshipType]: relationshipId,
|
||||
deleted: deleted === "true",
|
||||
completed: completed === "true",
|
||||
assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Refetch tasks when a task is updated
|
||||
*/
|
||||
useEffect(() => {
|
||||
const handleTaskUpdated = async (event) => {
|
||||
await refetch().catch(e => `Something went wrong fetching tasks: ${e.message || ''}`);
|
||||
await refetch().catch((e) => `Something went wrong fetching tasks: ${e.message || ""}`);
|
||||
};
|
||||
window.addEventListener('taskUpdated', handleTaskUpdated);
|
||||
window.addEventListener("taskUpdated", handleTaskUpdated);
|
||||
|
||||
// Clean up the event listener when the component is unmounted.
|
||||
return () => {
|
||||
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
||||
window.removeEventListener("taskUpdated", handleTaskUpdated);
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
@@ -97,27 +87,25 @@ export default function TaskListContainer({
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: toggledTask.data.update_tasks_by_pk.jobid,
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.completed ? AuditTrailMapping.tasksCompleted(
|
||||
toggledTask.data.update_tasks_by_pk.title,
|
||||
currentUser.email
|
||||
) : AuditTrailMapping.tasksUncompleted(
|
||||
toggledTask.data.update_tasks_by_pk.title,
|
||||
currentUser.email
|
||||
),
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.completed
|
||||
? AuditTrailMapping.tasksCompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
|
||||
: AuditTrailMapping.tasksUncompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
|
||||
type: toggledTask?.data?.update_tasks_by_pk?.completed ? "tasksCompleted" : "tasksUncompleted"
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||
detail: {message: 'A task has been completed.'},
|
||||
}));
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("taskUpdated", {
|
||||
detail: { message: "A task has been completed." }
|
||||
})
|
||||
);
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.completed"),
|
||||
message: t("tasks.successes.completed")
|
||||
});
|
||||
} catch (err) {
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.completed"),
|
||||
message: t("tasks.failures.completed")
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -148,35 +136,31 @@ export default function TaskListContainer({
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: toggledTask.data.update_tasks_by_pk.jobid,
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.deleted ? AuditTrailMapping.tasksDeleted(
|
||||
toggledTask.data.update_tasks_by_pk.title,
|
||||
currentUser.email
|
||||
) : AuditTrailMapping.tasksUndeleted(
|
||||
toggledTask.data.update_tasks_by_pk.title,
|
||||
currentUser.email
|
||||
),
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.deleted
|
||||
? AuditTrailMapping.tasksDeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
|
||||
: AuditTrailMapping.tasksUndeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
|
||||
type: toggledTask?.data?.update_tasks_by_pk?.deleted ? "tasksDeleted" : "tasksUndeleted"
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||
detail: {message: 'A task has been deleted.'},
|
||||
}));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("taskUpdated", {
|
||||
detail: { message: "A task has been deleted." }
|
||||
})
|
||||
);
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.deleted"),
|
||||
message: t("tasks.successes.deleted")
|
||||
});
|
||||
} catch (err) {
|
||||
console.dir(err);
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.deleted"),
|
||||
message: t("tasks.failures.deleted")
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<TaskListComponent
|
||||
|
||||
@@ -1,58 +1,53 @@
|
||||
import {Col, Form, Input, Row, Select, Switch} from "antd";
|
||||
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js";
|
||||
import dayjs from '../../utils/day';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import { connect } from "react-redux";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TaskUpsertModalComponent);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalComponent);
|
||||
|
||||
export function TaskUpsertModalComponent({
|
||||
form,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
selectedJobId,
|
||||
setSelectedJobId,
|
||||
selectedJobDetails,
|
||||
loading,
|
||||
error,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
form,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
selectedJobId,
|
||||
setSelectedJobId,
|
||||
selectedJobDetails,
|
||||
loading,
|
||||
error
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const datePickerPresets = [
|
||||
{label: t('tasks.date_presets.today'), value: dayjs()},
|
||||
{label: t('tasks.date_presets.tomorrow'), value: dayjs().add(1, 'day')},
|
||||
{label: t('tasks.date_presets.next_week'), value: dayjs().add(1, 'week')},
|
||||
{label: t('tasks.date_presets.two_weeks'), value: dayjs().add(2, 'weeks')},
|
||||
{label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')},
|
||||
{label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')},
|
||||
{label: t('tasks.date_presets.three_months'), value: dayjs().add(3, 'month')},
|
||||
|
||||
{ label: t("tasks.date_presets.today"), value: dayjs() },
|
||||
{ label: t("tasks.date_presets.tomorrow"), value: dayjs().add(1, "day") },
|
||||
{ label: t("tasks.date_presets.next_week"), value: dayjs().add(1, "week") },
|
||||
{ label: t("tasks.date_presets.two_weeks"), value: dayjs().add(2, "weeks") },
|
||||
{ label: t("tasks.date_presets.three_weeks"), value: dayjs().add(3, "weeks") },
|
||||
{ label: t("tasks.date_presets.one_month"), value: dayjs().add(1, "month") },
|
||||
{ label: t("tasks.date_presets.three_months"), value: dayjs().add(3, "month") }
|
||||
];
|
||||
|
||||
|
||||
const clearRelations = () => {
|
||||
form.setFieldsValue({
|
||||
billid: null,
|
||||
partsorderid: null,
|
||||
joblineid: null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the selected job id
|
||||
@@ -64,8 +59,7 @@ export function TaskUpsertModalComponent({
|
||||
clearRelations();
|
||||
};
|
||||
|
||||
|
||||
if (loading || error) return <LoadingSkeleton active/>;
|
||||
if (loading || error) return <LoadingSkeleton active />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -76,24 +70,20 @@ export function TaskUpsertModalComponent({
|
||||
name="title"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t("tasks.fields.title")}/>
|
||||
<Input placeholder={t("tasks.fields.title")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.priority")}
|
||||
name="priority"
|
||||
initialValue={3}
|
||||
>
|
||||
<Form.Item label={t("tasks.fields.priority")} name="priority" initialValue={3}>
|
||||
<Select
|
||||
options={[
|
||||
{value: 3, label: t("tasks.fields.priorities.low")},
|
||||
{value: 2, label: t("tasks.fields.priorities.medium")},
|
||||
{value: 1, label: t("tasks.fields.priorities.high")},
|
||||
{ value: 3, label: t("tasks.fields.priorities.low") },
|
||||
{ value: 2, label: t("tasks.fields.priorities.medium") },
|
||||
{ value: 1, label: t("tasks.fields.priorities.high") }
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -106,11 +96,11 @@ export function TaskUpsertModalComponent({
|
||||
initialValue={false}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Switch/>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -122,23 +112,27 @@ export function TaskUpsertModalComponent({
|
||||
label={t("tasks.fields.jobid")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<JobSearchSelectComponent placeholder={t('tasks.placeholders.jobid')}
|
||||
onSelect={changeJobId} onClear={changeJobId} autoFocus={false}/>
|
||||
<JobSearchSelectComponent
|
||||
placeholder={t("tasks.placeholders.jobid")}
|
||||
onSelect={changeJobId}
|
||||
onClear={changeJobId}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.joblineid")}
|
||||
name="joblineid"
|
||||
>
|
||||
<Select allowClear placeholder={t("tasks.placeholders.joblineid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}>
|
||||
<Form.Item label={t("tasks.fields.joblineid")} name="joblineid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.joblineid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
>
|
||||
{selectedJobDetails?.joblines?.map((jobline) => (
|
||||
<Select.Option key={jobline.id} value={jobline.id}>
|
||||
{jobline.line_desc}
|
||||
@@ -148,12 +142,12 @@ export function TaskUpsertModalComponent({
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.partsorderid")}
|
||||
name="partsorderid"
|
||||
>
|
||||
<Select allowClear placeholder={t("tasks.placeholders.partsorderid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}>
|
||||
<Form.Item label={t("tasks.fields.partsorderid")} name="partsorderid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.partsorderid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
>
|
||||
{selectedJobDetails?.parts_orders?.map((partsOrder) => (
|
||||
<Select.Option key={partsOrder.id} value={partsOrder.id}>
|
||||
{partsOrder.order_number} - {partsOrder.vendor.name}
|
||||
@@ -163,12 +157,12 @@ export function TaskUpsertModalComponent({
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.billid")}
|
||||
name="billid"
|
||||
>
|
||||
<Select allowClear placeholder={t("tasks.placeholders.billid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}>
|
||||
<Form.Item label={t("tasks.fields.billid")} name="billid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.billid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
>
|
||||
{selectedJobDetails?.bills?.map((bill) => (
|
||||
<Select.Option key={bill.id} value={bill.id}>
|
||||
{bill.invoice_number} - {bill.vendor.name}
|
||||
@@ -186,46 +180,36 @@ export function TaskUpsertModalComponent({
|
||||
initialValue={currentUser.email}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select placeholder={t("tasks.placeholders.assigned_to")}>
|
||||
{bodyshop.employees.filter(x => x.active).map((employee) => (
|
||||
<Select.Option key={employee.id} value={employee.user_email}>
|
||||
{employee.first_name} {employee.last_name}
|
||||
</Select.Option>
|
||||
))}
|
||||
{bodyshop.employees
|
||||
.filter((x) => x.active)
|
||||
.map((employee) => (
|
||||
<Select.Option key={employee.id} value={employee.user_email}>
|
||||
{employee.first_name} {employee.last_name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.due_date")}
|
||||
name="due_date"
|
||||
>
|
||||
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets}/>
|
||||
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
||||
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.remind_at")}
|
||||
name="remind_at"
|
||||
>
|
||||
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets}/>
|
||||
<Form.Item label={t("tasks.fields.remind_at")} name="remind_at">
|
||||
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.description")}
|
||||
name="description"
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={8}
|
||||
placeholder={t("tasks.fields.description")}
|
||||
/>
|
||||
<Form.Item label={t("tasks.fields.description")} name="description">
|
||||
<Input.TextArea rows={8} placeholder={t("tasks.fields.description")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import {Form, Modal, notification} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {
|
||||
MUTATION_INSERT_NEW_TASK,
|
||||
MUTATION_UPDATE_TASK,
|
||||
QUERY_GET_TASK_BY_ID
|
||||
} from "../../graphql/tasks.queries";
|
||||
import {QUERY_GET_TASKS_JOB_DETAILS_BY_ID} from "../../graphql/jobs.queries.js";
|
||||
import {toggleModalVisible} from "../../redux/modals/modals.actions";
|
||||
import {selectTaskUpsert} from "../../redux/modals/modals.selectors";
|
||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Form, Modal, notification } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
|
||||
import { QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import TaskUpsertModalComponent from "./task-upsert-modal.component";
|
||||
import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
import dayjs from '../../utils/day';
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
taskUpsert: selectTaskUpsert,
|
||||
taskUpsert: selectTaskUpsert
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
|
||||
@@ -32,32 +28,22 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(insertAuditTrail({ jobid, billid, operation, type }))
|
||||
});
|
||||
|
||||
export function TaskUpsertModalContainer({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
taskUpsert,
|
||||
toggleModalVisible,
|
||||
insertAuditTrail
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
|
||||
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
|
||||
const {open, context, actions} = taskUpsert;
|
||||
const {jobid, joblineid, billid, partsorderid, taskId, existingTask} = context;
|
||||
const {refetch} = actions;
|
||||
const { open, context, actions } = taskUpsert;
|
||||
const { jobid, joblineid, billid, partsorderid, taskId, existingTask } = context;
|
||||
const { refetch } = actions;
|
||||
const [form] = Form.useForm();
|
||||
const [selectedJobId, setSelectedJobId] = useState(null);
|
||||
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
|
||||
const [jobIdState, setJobIdState] = useState(null);
|
||||
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
data
|
||||
} = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
|
||||
variables: {id: jobIdState},
|
||||
skip: !jobIdState,
|
||||
const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
|
||||
variables: { id: jobIdState },
|
||||
skip: !jobIdState
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -65,8 +51,8 @@ export function TaskUpsertModalContainer({
|
||||
error: taskError,
|
||||
data: taskData
|
||||
} = useQuery(QUERY_GET_TASK_BY_ID, {
|
||||
variables: {id: taskId},
|
||||
skip: !taskId,
|
||||
variables: { id: taskId },
|
||||
skip: !taskId
|
||||
});
|
||||
|
||||
// Use Effect to hydrate existing task if only a taskid is provided
|
||||
@@ -100,25 +86,24 @@ export function TaskUpsertModalContainer({
|
||||
form.setFieldsValue(existingTask);
|
||||
} else if (!existingTask && open) {
|
||||
form.resetFields();
|
||||
if (joblineid) form.setFieldsValue({joblineid});
|
||||
if (billid) form.setFieldsValue({billid});
|
||||
if (partsorderid) form.setFieldsValue({partsorderid});
|
||||
if (joblineid) form.setFieldsValue({ joblineid });
|
||||
if (billid) form.setFieldsValue({ billid });
|
||||
if (partsorderid) form.setFieldsValue({ partsorderid });
|
||||
}
|
||||
return () => {
|
||||
setSelectedJobId(null);
|
||||
};
|
||||
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
|
||||
|
||||
|
||||
/**
|
||||
* Remove the taskid from the URL
|
||||
*/
|
||||
const removeTaskIdFromUrl = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has('taskid')) return;
|
||||
urlParams.delete('taskid');
|
||||
if (!urlParams.has("taskid")) return;
|
||||
urlParams.delete("taskid");
|
||||
history(`${window.location.pathname}?${urlParams}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle existing task
|
||||
@@ -132,48 +117,51 @@ export function TaskUpsertModalContainer({
|
||||
variables: {
|
||||
taskId: existingTask.id,
|
||||
task: replaceUndefinedWithNull(values)
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (!taskData.errors) {
|
||||
const oldTask = taskData?.data?.update_tasks?.returning[0];
|
||||
const oldTask = taskData?.data?.update_tasks?.returning[0];
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: oldTask.jobid,
|
||||
operation: AuditTrailMapping.tasksUpdated(
|
||||
oldTask.title,
|
||||
currentUser.email
|
||||
),
|
||||
operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
|
||||
type: "tasksUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (isAssignedToDirty) {
|
||||
// TODO This is being moved serverside
|
||||
axios.post("/sendemail", {
|
||||
from: {
|
||||
name: bodyshop.shopname,
|
||||
address: bodyshop.email,
|
||||
},
|
||||
ReplyTo: {
|
||||
Email: 'noreply@imex.online'
|
||||
},
|
||||
to: values.assigned_to,
|
||||
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
||||
templateStrings: {
|
||||
header: values.title,
|
||||
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
|
||||
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${existingTask.id}">Please sign in to your account to view the task details.</a>`
|
||||
}
|
||||
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ''}`));
|
||||
axios
|
||||
.post("/sendemail", {
|
||||
from: {
|
||||
name: bodyshop.shopname,
|
||||
address: bodyshop.email
|
||||
},
|
||||
ReplyTo: {
|
||||
Email: "noreply@imex.online"
|
||||
},
|
||||
to: values.assigned_to,
|
||||
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
||||
templateStrings: {
|
||||
header: values.title,
|
||||
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format("MM/DD/YYYY")}` : ""}`,
|
||||
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${existingTask.id}">Please sign in to your account to view the task details.</a>`
|
||||
}
|
||||
})
|
||||
.catch((e) =>
|
||||
console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ""}`)
|
||||
);
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||
detail: {message: 'A task has been created or edited.'},
|
||||
}));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("taskUpdated", {
|
||||
detail: { message: "A task has been created or edited." }
|
||||
})
|
||||
);
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.updated"),
|
||||
message: t("tasks.successes.updated")
|
||||
});
|
||||
|
||||
if (refetch) await refetch();
|
||||
@@ -189,56 +177,59 @@ export function TaskUpsertModalContainer({
|
||||
...values,
|
||||
created_by: currentUser.email,
|
||||
bodyshopid: bodyshop.id
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
|
||||
|
||||
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
|
||||
const newTaskID = newTask?.id;
|
||||
|
||||
|
||||
if (!newTaskData.errors) {
|
||||
insertAuditTrail({
|
||||
jobid: newTask.jobid,
|
||||
operation: AuditTrailMapping.tasksCreated(
|
||||
newTask.title,
|
||||
currentUser.email
|
||||
),
|
||||
operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
|
||||
type: "tasksCreated"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (refetch) await refetch();
|
||||
|
||||
form.resetFields();
|
||||
toggleModalVisible();
|
||||
|
||||
|
||||
// send notification to the assigned user
|
||||
// TODO: This is being moved serverside
|
||||
axios.post("/sendemail", {
|
||||
from: {
|
||||
name: bodyshop.shopname,
|
||||
address: bodyshop.email,
|
||||
},
|
||||
replyTo: {
|
||||
Email: 'noreply@imex.online'
|
||||
},
|
||||
to: values.assigned_to,
|
||||
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
||||
templateName: 'taskAssigned',
|
||||
templateStrings: {
|
||||
header: values.title,
|
||||
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
|
||||
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${newTaskID}">Please sign to your account to view the task details.</a>`
|
||||
}
|
||||
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ''}`));
|
||||
axios
|
||||
.post("/sendemail", {
|
||||
from: {
|
||||
name: bodyshop.shopname,
|
||||
address: bodyshop.email
|
||||
},
|
||||
replyTo: {
|
||||
Email: "noreply@imex.online"
|
||||
},
|
||||
to: values.assigned_to,
|
||||
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
|
||||
templateName: "taskAssigned",
|
||||
templateStrings: {
|
||||
header: values.title,
|
||||
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format("MM/DD/YYYY")}` : ""}`,
|
||||
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${newTaskID}">Please sign to your account to view the task details.</a>`
|
||||
}
|
||||
})
|
||||
.catch((e) =>
|
||||
console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ""}`)
|
||||
);
|
||||
|
||||
window.dispatchEvent(new CustomEvent('taskUpdated', {
|
||||
detail: {message: 'A task has been created or edited.'},
|
||||
}));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("taskUpdated", {
|
||||
detail: { message: "A task has been created or edited." }
|
||||
})
|
||||
);
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.created"),
|
||||
message: t("tasks.successes.created")
|
||||
});
|
||||
};
|
||||
|
||||
@@ -248,7 +239,7 @@ export function TaskUpsertModalContainer({
|
||||
* @returns {Promise<[{jobid, bodyshopid, created_by},...*]>}
|
||||
*/
|
||||
const handleFinish = async (formValues) => {
|
||||
const {...values} = formValues;
|
||||
const { ...values } = formValues;
|
||||
if (existingTask) {
|
||||
await handleExistingTask(values);
|
||||
} else {
|
||||
@@ -273,18 +264,18 @@ export function TaskUpsertModalContainer({
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)}
|
||||
error={error} data={data}
|
||||
selectedJobId={selectedJobId}
|
||||
setSelectedJobId={setSelectedJobId}
|
||||
selectedJobDetails={selectedJobDetails}/>
|
||||
|
||||
<TaskUpsertModalComponent
|
||||
form={form}
|
||||
loading={loading || (taskId && taskLoading)}
|
||||
error={error}
|
||||
data={data}
|
||||
selectedJobId={selectedJobId}
|
||||
setSelectedJobId={setSelectedJobId}
|
||||
selectedJobDetails={selectedJobDetails}
|
||||
/>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TaskUpsertModalContainer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {gql} from "@apollo/client";
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const INSERT_NEW_BILL = gql`
|
||||
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`
|
||||
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
||||
@@ -1979,19 +1979,19 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
kmout
|
||||
qb_multiple_payers
|
||||
lbr_adjustments
|
||||
payments {
|
||||
amount
|
||||
created_at
|
||||
date
|
||||
exportedat
|
||||
id
|
||||
jobid
|
||||
memo
|
||||
payer
|
||||
paymentnum
|
||||
transactionid
|
||||
type
|
||||
}
|
||||
payments {
|
||||
amount
|
||||
created_at
|
||||
date
|
||||
exportedat
|
||||
id
|
||||
jobid
|
||||
memo
|
||||
payer
|
||||
paymentnum
|
||||
transactionid
|
||||
type
|
||||
}
|
||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||
id
|
||||
removed
|
||||
@@ -2004,7 +2004,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
notes
|
||||
notes
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
@@ -2016,9 +2016,9 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
prt_dsmk_p
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
act_price_before_ppc
|
||||
sublet_ignored
|
||||
sublet_completed
|
||||
act_price_before_ppc
|
||||
sublet_ignored
|
||||
sublet_completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {gql} from "@apollo/client";
|
||||
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
const PARTIAL_TASK_FIELDS = gql`
|
||||
fragment TaskFields on tasks {
|
||||
@@ -70,7 +69,8 @@ export const QUERY_GET_TASK_BY_ID = gql`
|
||||
tasks_by_pk(id: $id) {
|
||||
...TaskFields
|
||||
}
|
||||
}`;
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ALL_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
@@ -88,20 +88,20 @@ export const QUERY_ALL_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -129,22 +129,22 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
joblineid: {_eq: $joblineid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
joblineid: { _eq: $joblineid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
joblineid: {_eq: $joblineid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
joblineid: { _eq: $joblineid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -172,22 +172,22 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
partsorderid: {_eq: $partsorderid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
partsorderid: { _eq: $partsorderid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
partsorderid: {_eq: $partsorderid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
partsorderid: { _eq: $partsorderid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -215,22 +215,22 @@ export const QUERY_BILL_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
billid: {_eq: $billid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
billid: { _eq: $billid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
billid: {_eq: $billid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
billid: { _eq: $billid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -258,22 +258,22 @@ export const QUERY_JOB_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
jobid: {_eq: $jobid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
jobid: { _eq: $jobid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
jobid: {_eq: $jobid},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
assigned_to: {_eq: $assigned_to},
|
||||
completed: {_eq: $completed}
|
||||
jobid: { _eq: $jobid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -299,20 +299,20 @@ export const QUERY_MY_TASKS_PAGINATED = gql`
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
assigned_to: {_eq: $user},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
completed: {_eq: $completed}
|
||||
assigned_to: { _eq: $user }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
assigned_to: {_eq: $user},
|
||||
bodyshopid: {_eq: $bodyshop},
|
||||
deleted: {_eq: $deleted},
|
||||
completed: {_eq: $completed}
|
||||
assigned_to: { _eq: $user }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
@@ -328,7 +328,7 @@ export const QUERY_MY_TASKS_PAGINATED = gql`
|
||||
*/
|
||||
export const MUTATION_TOGGLE_TASK_COMPLETED = gql`
|
||||
mutation MUTATION_TOGGLE_TASK_COMPLETED($id: uuid!, $completed: Boolean!, $completed_at: timestamptz) {
|
||||
update_tasks_by_pk(pk_columns: {id: $id}, _set: {completed: $completed, completed_at: $completed_at}) {
|
||||
update_tasks_by_pk(pk_columns: { id: $id }, _set: { completed: $completed, completed_at: $completed_at }) {
|
||||
id
|
||||
title
|
||||
completed
|
||||
@@ -344,7 +344,7 @@ export const MUTATION_TOGGLE_TASK_COMPLETED = gql`
|
||||
*/
|
||||
export const MUTATION_TOGGLE_TASK_DELETED = gql`
|
||||
mutation MUTATION_TOGGLE_TASK_DELETED($id: uuid!, $deleted: Boolean!, $deleted_at: timestamptz) {
|
||||
update_tasks_by_pk(pk_columns: {id: $id}, _set: {deleted: $deleted, deleted_at: $deleted_at}) {
|
||||
update_tasks_by_pk(pk_columns: { id: $id }, _set: { deleted: $deleted, deleted_at: $deleted_at }) {
|
||||
id
|
||||
title
|
||||
deleted
|
||||
@@ -359,7 +359,7 @@ export const MUTATION_TOGGLE_TASK_DELETED = gql`
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
export const MUTATION_INSERT_NEW_TASK = gql`
|
||||
mutation MUTATION_INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) {
|
||||
mutation MUTATION_INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) {
|
||||
insert_tasks(objects: $taskInput) {
|
||||
returning {
|
||||
id
|
||||
@@ -414,4 +414,4 @@ export const MUTATION_UPDATE_TASK = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
@@ -8,64 +8,53 @@ import Icon, {
|
||||
SyncOutlined,
|
||||
ToolFilled
|
||||
} from "@ant-design/icons";
|
||||
import {Badge, Button, Divider, Form, notification, Space, Tabs} from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
import { Badge, Button, Divider, Form, notification, Space, Tabs } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
import Axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks} from "react-icons/fa";
|
||||
import {connect} from "react-redux";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import FormFieldsChanged
|
||||
from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks } from "react-icons/fa";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
|
||||
import JobLineUpsertModalContainer
|
||||
from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
||||
import JobReconciliationModal
|
||||
from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
||||
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
||||
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
||||
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
|
||||
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component";
|
||||
import JobsConvertButton from "../../components/jobs-convert-button/jobs-convert-button.component";
|
||||
import JobsDetailDatesComponent
|
||||
from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
||||
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
||||
import JobsDetailGeneral from "../../components/jobs-detail-general/jobs-detail-general.component";
|
||||
import JobsDetailHeaderActions
|
||||
from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component";
|
||||
import JobsDetailHeaderActions from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component";
|
||||
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
||||
import JobsDetailLaborContainer
|
||||
from "../../components/jobs-detail-labor/jobs-detail-labor.container";
|
||||
import JobsDetailLaborContainer from "../../components/jobs-detail-labor/jobs-detail-labor.container";
|
||||
import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container";
|
||||
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
|
||||
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
|
||||
import JobsDocumentsGalleryContainer
|
||||
from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery
|
||||
from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
import NoteUpsertModalComponent
|
||||
from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ScheduleJobModalContainer
|
||||
from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
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 NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
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 UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import _ from "lodash";
|
||||
import JobProfileDataWarning
|
||||
from "../../components/job-profile-data-warning/job-profile-data-warning.component";
|
||||
import {DateTimeFormat} from "../../utils/DateFormatter";
|
||||
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import {HasFeatureAccess} from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
|
||||
import {QUERY_JOB_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
|
||||
import { QUERY_JOB_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -73,29 +62,35 @@ const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) => dispatch(setModalContext({
|
||||
context: context,
|
||||
modal: "printCenter"
|
||||
})),
|
||||
insertAuditTrail: ({jobid, operation, type}) => dispatch(insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
}))
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "printCenter"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export function JobsDetailPage({
|
||||
bodyshop,
|
||||
setPrintCenterContext,
|
||||
jobRO,
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
currentUser,
|
||||
insertAuditTrail,
|
||||
refetch
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
bodyshop,
|
||||
setPrintCenterContext,
|
||||
jobRO,
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
currentUser,
|
||||
insertAuditTrail,
|
||||
refetch
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const history = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -111,13 +106,13 @@ export function JobsDetailPage({
|
||||
|
||||
useEffect(() => {
|
||||
const handleTaskUpdated = async (event) => {
|
||||
await refetch().catch(e => `Something went wrong fetching tasks: ${e.message || ''}`);
|
||||
await refetch().catch((e) => `Something went wrong fetching tasks: ${e.message || ""}`);
|
||||
};
|
||||
window.addEventListener('taskUpdated', handleTaskUpdated);
|
||||
window.addEventListener("taskUpdated", handleTaskUpdated);
|
||||
|
||||
// Clean up the event listener when the component is unmounted.
|
||||
return () => {
|
||||
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
||||
window.removeEventListener("taskUpdated", handleTaskUpdated);
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
@@ -156,7 +151,7 @@ export function JobsDetailPage({
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
cieca_pfo: {...job.cieca_pfo, ...values.cieca_pfo}
|
||||
cieca_pfo: { ...job.cieca_pfo, ...values.cieca_pfo }
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -251,15 +246,15 @@ export function JobsDetailPage({
|
||||
}}
|
||||
key="refresh"
|
||||
>
|
||||
<SyncOutlined/>
|
||||
<SyncOutlined />
|
||||
{t("general.labels.refresh")}
|
||||
</Button>
|
||||
<JobsChangeStatus job={job}/>
|
||||
<JobSyncButton job={job}/>
|
||||
<JobsChangeStatus job={job} />
|
||||
<JobSyncButton job={job} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: {refetch: refetch},
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
id: job.id,
|
||||
job: job,
|
||||
@@ -269,12 +264,11 @@ export function JobsDetailPage({
|
||||
}}
|
||||
key="printing"
|
||||
>
|
||||
<PrinterFilled/>
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<JobsConvertButton job={job} refetch={refetch}
|
||||
parentFormIsFieldsTouched={form.isFieldsTouched}/>
|
||||
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch}/>
|
||||
<JobsConvertButton job={job} refetch={refetch} parentFormIsFieldsTouched={form.isFieldsTouched} />
|
||||
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
|
||||
<Button type="primary" loading={loading} disabled={jobRO} onClick={() => form.submit()}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
@@ -283,10 +277,10 @@ export function JobsDetailPage({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ScheduleJobModalContainer/>
|
||||
<JobReconciliationModal/>
|
||||
<JobLineUpsertModalContainer/>
|
||||
<NoteUpsertModalComponent/>
|
||||
<ScheduleJobModalContainer />
|
||||
<JobReconciliationModal />
|
||||
<JobLineUpsertModalContainer />
|
||||
<NoteUpsertModalComponent />
|
||||
<Form
|
||||
form={form}
|
||||
name="JobDetailForm"
|
||||
@@ -300,120 +294,128 @@ export function JobsDetailPage({
|
||||
title={job.ro_number || t("general.labels.na")}
|
||||
extra={menuExtra}
|
||||
/>
|
||||
<JobsDetailHeader job={job}/>
|
||||
<Divider type="horizontal"/>
|
||||
<JobProfileDataWarning job={job}/>
|
||||
<FormFieldsChanged form={form}/>
|
||||
<JobsDetailHeader job={job} />
|
||||
<Divider type="horizontal" />
|
||||
<JobProfileDataWarning job={job} />
|
||||
<FormFieldsChanged form={form} />
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history({search: `?tab=${key}`})}
|
||||
tabBarStyle={{fontWeight: "bold", borderBottom: "10px"}}
|
||||
onChange={(key) => history({ search: `?tab=${key}` })}
|
||||
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
|
||||
items={[
|
||||
{
|
||||
key: "general",
|
||||
icon: <Icon component={FaShieldAlt}/>,
|
||||
icon: <Icon component={FaShieldAlt} />,
|
||||
label: t("menus.jobsdetail.general"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailGeneral job={job} form={form}/>
|
||||
children: <JobsDetailGeneral job={job} form={form} />
|
||||
},
|
||||
{
|
||||
key: "repairdata",
|
||||
icon: <BarsOutlined/>,
|
||||
icon: <BarsOutlined />,
|
||||
label: t("menus.jobsdetail.repairdata"),
|
||||
forceRender: true,
|
||||
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch}
|
||||
form={form}/>
|
||||
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
|
||||
},
|
||||
{
|
||||
key: "rates",
|
||||
icon: <DollarCircleOutlined/>,
|
||||
icon: <DollarCircleOutlined />,
|
||||
label: t("menus.jobsdetail.rates"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailRates job={job} form={form}/>
|
||||
children: <JobsDetailRates job={job} form={form} />
|
||||
},
|
||||
{
|
||||
key: "totals",
|
||||
icon: <DollarCircleOutlined/>,
|
||||
icon: <DollarCircleOutlined />,
|
||||
label: t("menus.jobsdetail.totals"),
|
||||
children: <JobsDetailTotals job={job} refetch={refetch}/>
|
||||
children: <JobsDetailTotals job={job} refetch={refetch} />
|
||||
},
|
||||
{
|
||||
key: "partssublet",
|
||||
icon: <ToolFilled/>,
|
||||
label: HasFeatureAccess({featureName: "bills", bodyshop})
|
||||
icon: <ToolFilled />,
|
||||
label: HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||
? t("menus.jobsdetail.partssublet")
|
||||
: t("menus.jobsdetail.parts"),
|
||||
children: <JobsDetailPliContainer job={job}/>
|
||||
children: <JobsDetailPliContainer job={job} />
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "labor",
|
||||
icon: <Icon component={FaHardHat}/>,
|
||||
label: t("menus.jobsdetail.labor"),
|
||||
children: <JobsDetailLaborContainer job={job} jobId={job.id}/>
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "labor",
|
||||
icon: <Icon component={FaHardHat} />,
|
||||
label: t("menus.jobsdetail.labor"),
|
||||
children: <JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "lifecycle",
|
||||
icon: <BarsOutlined/>,
|
||||
icon: <BarsOutlined />,
|
||||
label: t("menus.jobsdetail.lifecycle"),
|
||||
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/>
|
||||
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
|
||||
},
|
||||
{
|
||||
key: "dates",
|
||||
icon: <CalendarFilled/>,
|
||||
icon: <CalendarFilled />,
|
||||
label: t("menus.jobsdetail.dates"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailDatesComponent job={job}/>
|
||||
children: <JobsDetailDatesComponent job={job} />
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: true,
|
||||
promanager: HasFeatureAccess({featureName: "media", bodyshop})
|
||||
promanager: HasFeatureAccess({ featureName: "media", bodyshop })
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "documents",
|
||||
icon: <FileImageFilled/>,
|
||||
label: t("jobs.labels.documents"),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job}/>
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id}/>
|
||||
)
|
||||
}
|
||||
]
|
||||
{
|
||||
key: "documents",
|
||||
icon: <FileImageFilled />,
|
||||
label: t("jobs.labels.documents"),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job} />
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id} />
|
||||
)
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "notes",
|
||||
icon: <Icon component={FaRegStickyNote}/>,
|
||||
icon: <Icon component={FaRegStickyNote} />,
|
||||
label: t("jobs.labels.notes"),
|
||||
children: <JobNotesContainer jobId={job.id}/>
|
||||
children: <JobNotesContainer jobId={job.id} />
|
||||
},
|
||||
{
|
||||
key: "audit",
|
||||
icon: <HistoryOutlined/>,
|
||||
icon: <HistoryOutlined />,
|
||||
label: t("jobs.labels.audit"),
|
||||
children: <JobAuditTrail jobId={job.id}/>
|
||||
children: <JobAuditTrail jobId={job.id} />
|
||||
},
|
||||
{
|
||||
key: 'tasks',
|
||||
icon: <FaTasks/>,
|
||||
label: <Space direction='horizontal'>
|
||||
{t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 &&
|
||||
<Badge count={job.tasks_aggregate.aggregate.count}/>}
|
||||
</Space>,
|
||||
children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop}
|
||||
relationshipType={'jobid'} relationshipId={job.id}
|
||||
query={QUERY_JOB_TASKS_PAGINATED}
|
||||
titleTranslation='tasks.titles.job_tasks' showRo={false}/>
|
||||
},
|
||||
key: "tasks",
|
||||
icon: <FaTasks />,
|
||||
label: (
|
||||
<Space direction="horizontal">
|
||||
{t("jobs.labels.tasks")}
|
||||
{job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />}
|
||||
</Space>
|
||||
),
|
||||
children: (
|
||||
<TaskListContainer
|
||||
currentUser={currentUser}
|
||||
bodyshop={bodyshop}
|
||||
relationshipType={"jobid"}
|
||||
relationshipId={job.id}
|
||||
query={QUERY_JOB_TASKS_PAGINATED}
|
||||
titleTranslation="tasks.titles.job_tasks"
|
||||
showRo={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Form>
|
||||
@@ -424,7 +426,7 @@ export function JobsDetailPage({
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
||||
|
||||
const transformJobToForm = (job) => {
|
||||
const transformedJob = {...job};
|
||||
const transformedJob = { ...job };
|
||||
|
||||
transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => {
|
||||
acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Button, Collapse, FloatButton, Layout, Space, Spin, Tag} from "antd";
|
||||
import { Button, Collapse, FloatButton, Layout, Space, Spin, Tag } from "antd";
|
||||
// import preval from "preval.macro";
|
||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, Route, Routes} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, Route, Routes } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
|
||||
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
|
||||
import ConflictComponent from "../../components/conflict/conflict.component";
|
||||
@@ -17,26 +17,21 @@ import TestComponent from "../../components/_test/test.page";
|
||||
import HeaderContainer from "../../components/header/header.container";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
||||
import PrintCenterModalContainer
|
||||
from "../../components/print-center-modal/print-center-modal.container";
|
||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
||||
import {requestForToken} from "../../firebase/firebase.utils";
|
||||
import {selectBodyshop, selectInstanceConflict} from "../../redux/user/user.selectors";
|
||||
import { requestForToken } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
||||
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import {setJoyRideFinished} from "../../redux/application/application.actions.js";
|
||||
import {
|
||||
selectEnableJoyRide,
|
||||
selectJoyRideSteps
|
||||
} from "../../redux/application/application.selectors.js";
|
||||
import { setJoyRideFinished } from "../../redux/application/application.actions.js";
|
||||
import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import "./manage.page.styles.scss";
|
||||
|
||||
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
const CardPaymentModalContainer = lazy(() =>
|
||||
import("../../components/card-payment-modal/card-payment-modal.container.")
|
||||
const CardPaymentModalContainer = lazy(
|
||||
() => import("../../components/card-payment-modal/card-payment-modal.container.")
|
||||
);
|
||||
|
||||
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
||||
@@ -64,8 +59,8 @@ const JobCostingModal = lazy(() => import("../../components/job-costing-modal/jo
|
||||
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
|
||||
const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container"));
|
||||
const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container"));
|
||||
const TimeTicketModalTask = lazy(() =>
|
||||
import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
|
||||
const TimeTicketModalTask = lazy(
|
||||
() => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
|
||||
);
|
||||
const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container"));
|
||||
const ProductionListPage = lazy(() => import("../production-list/production-list.container"));
|
||||
@@ -106,7 +101,7 @@ const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
|
||||
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
|
||||
|
||||
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
|
||||
const {Content, Footer} = Layout;
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
conflict: selectInstanceConflict,
|
||||
@@ -119,8 +114,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
|
||||
});
|
||||
|
||||
export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished}) {
|
||||
const {t} = useTranslation();
|
||||
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const [tours, setTours] = useState([]);
|
||||
|
||||
@@ -156,264 +151,266 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PaymentModalContainer/>
|
||||
<PaymentModalContainer />
|
||||
|
||||
<CardPaymentModalContainer/>
|
||||
<TaskUpsertModalContainer/>
|
||||
<BreadCrumbs/>
|
||||
<BillEnterModalContainer/>
|
||||
<JobCostingModal/>
|
||||
<ReportCenterModal/>
|
||||
<EmailOverlayContainer/>
|
||||
<TimeTicketModalContainer/>
|
||||
<TimeTicketModalTask/>
|
||||
<PrintCenterModalContainer/>
|
||||
<CardPaymentModalContainer />
|
||||
<TaskUpsertModalContainer />
|
||||
<BreadCrumbs />
|
||||
<BillEnterModalContainer />
|
||||
<JobCostingModal />
|
||||
<ReportCenterModal />
|
||||
<EmailOverlayContainer />
|
||||
<TimeTicketModalContainer />
|
||||
<TimeTicketModalTask />
|
||||
<PrintCenterModalContainer />
|
||||
<Routes>
|
||||
<Route path="/_test" element={<TestComponent/>}/>
|
||||
<Route path="/" element={<ManageRootPage/>}/>
|
||||
<Route path="/_test" element={<TestComponent />} />
|
||||
<Route path="/" element={<ManageRootPage />} />
|
||||
<Route
|
||||
path="/jobs"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId/intake"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobIntake/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobIntake />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId/deliver"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobDeliver/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobDeliver />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId/checklist"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobChecklistView/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobChecklistView />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId/close"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsClose/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsClose />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId/admin"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsAdmin/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsAdmin />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/all"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AllJobs/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AllJobs />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/ready"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ReadyJobs/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ReadyJobs />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/new"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsCreateContainerPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsCreateContainerPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jobs/:jobId"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsDetailPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsDetailPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/temporarydocs/"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<TempDocs/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<TempDocs />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/tasks/mytasks'
|
||||
path="/tasks/mytasks"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<MyTasksPage/>
|
||||
</Suspense>}
|
||||
<Suspense fallback={<Spin />}>
|
||||
<MyTasksPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/tasks/alltasks'
|
||||
path="/tasks/alltasks"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AllTasksPage/>
|
||||
</Suspense>}
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AllTasksPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/inventory/"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<InventoryListPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<InventoryListPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<CourtesyCarsPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<CourtesyCarsPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/new"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<CourtesyCarCreateContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<CourtesyCarCreateContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/contracts"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ContractsList/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ContractsList />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/contracts/new"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ContractCreatePage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ContractCreatePage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/contracts/:contractId"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ContractDetailPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ContractDetailPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/courtesycars/:ccId"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<CourtesyCarDetailContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<CourtesyCarDetailContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ProfilePage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ProfilePage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/vehicles"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<VehiclesContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<VehiclesContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/production/list"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ProductionListPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ProductionListPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/production/board"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ProductionBoardPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ProductionBoardPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/vehicles/:vehId"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<VehiclesDetailContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<VehiclesDetailContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/bills"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<BillsListPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<BillsListPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/owners"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<OwnersContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<OwnersContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/owners/:ownerId"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<OwnersDetailContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<OwnersDetailContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/schedule"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ScheduleContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ScheduleContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/available"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<JobsAvailablePage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<JobsAvailablePage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/shop"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ShopPage/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ShopPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
@@ -426,16 +423,16 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
<Route
|
||||
path="/shop/vendors"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ShopVendorPageContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ShopVendorPageContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/shop/csi"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ShopCsiPageContainer/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ShopCsiPageContainer />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
@@ -443,8 +440,8 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
<Route
|
||||
path="/accounting/qbo"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AccountingQboCallback/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AccountingQboCallback />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
@@ -452,56 +449,56 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
<Route
|
||||
path="/accounting/receivables"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AccountingReceivables/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AccountingReceivables />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/accounting/payables"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AccountingPayables/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AccountingPayables />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/accounting/payments"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AccountingPayments/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AccountingPayments />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/accounting/exportlogs"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ExportLogs/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ExportLogs />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/ttapprovals"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<TtApprovals/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<TtApprovals />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/partsqueue"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<PartsQueue/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<PartsQueue />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/phonebook"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<Phonebook/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Phonebook />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
@@ -509,65 +506,65 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
<Route
|
||||
path="/payments"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<PaymentsAll/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<PaymentsAll />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/shiftclock"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<ShiftClock/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ShiftClock />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/scoreboard"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<Scoreboard/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Scoreboard />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/timetickets"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<TimeTicketsAll/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<TimeTicketsAll />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/help"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<Help/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Help />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route path="/emailtest" element={<EmailTest/>}/>
|
||||
<Route path="/emailtest" element={<EmailTest />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<Dashboard/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Dashboard />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dms"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<Dms/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Dms />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dmsap"
|
||||
element={
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<DmsPayables/>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<DmsPayables />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
@@ -577,16 +574,16 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
|
||||
let PageContent;
|
||||
|
||||
if (conflict) PageContent = <ConflictComponent/>;
|
||||
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent/>;
|
||||
if (conflict) PageContent = <ConflictComponent />;
|
||||
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
|
||||
else PageContent = AppRouteTable;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible}/>
|
||||
<Layout style={{minHeight: "100vh"}} className="layout-container">
|
||||
<UpdateAlert/>
|
||||
<HeaderContainer/>
|
||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
||||
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
||||
<UpdateAlert />
|
||||
<HeaderContainer />
|
||||
<Content className="content-container">
|
||||
<Joyride
|
||||
debug
|
||||
@@ -603,12 +600,12 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<PartnerPingComponent/>
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundary/>} showDialog>
|
||||
<PartnerPingComponent />
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
|
||||
{PageContent}
|
||||
</Sentry.ErrorBoundary>
|
||||
|
||||
<FloatButton.BackTop style={{right: 100, bottom: 25}}/>
|
||||
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
|
||||
</Content>
|
||||
<Footer>
|
||||
<div
|
||||
@@ -620,7 +617,7 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
margin: "1rem 0rem"
|
||||
}}
|
||||
>
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div>
|
||||
{`${InstanceRenderManager({
|
||||
imex: t("titles.imexonline"),
|
||||
@@ -628,9 +625,9 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
promanager: t("titles.promanager")
|
||||
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
||||
</div>
|
||||
<div id="noticeable-widget" style={{marginLeft: "1rem"}}/>
|
||||
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
|
||||
</div>
|
||||
<Link to="/disclaimer" target="_blank" style={{color: "#ccc"}}>
|
||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
||||
Disclaimer & Notices
|
||||
</Link>
|
||||
</div>
|
||||
@@ -647,8 +644,7 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
|
||||
Get Tours
|
||||
</Button>
|
||||
{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}
|
||||
</Tag>
|
||||
))}
|
||||
|
||||
@@ -1,75 +1,70 @@
|
||||
import React, {useEffect} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TasksPageComponent from "./tasks.page.component";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import TaskPageTypes from "./taskPageTypes.jsx";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions.js";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions.js";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function MyTasksPageContainer({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const searchParams = queryString.parse((useLocation().search));
|
||||
bodyshop,
|
||||
currentUser,
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
useEffect(() => {
|
||||
document.title = t("titles.all_tasks", {
|
||||
app: InstanceRenderManager({
|
||||
imex: '$t(titles.imexonline)',
|
||||
rome: '$t(titles.romeonline)',
|
||||
promanager: '$t(titles.promanager)'
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)",
|
||||
promanager: "$t(titles.promanager)"
|
||||
})
|
||||
});
|
||||
setSelectedHeader("all_tasks");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/tasks/alltasks",
|
||||
label: t("titles.bc.all_tasks"),
|
||||
},]);
|
||||
label: t("titles.bc.all_tasks")
|
||||
}
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
// This takes care of the ability to deep link a task from the URL (Dispatches the modal)
|
||||
useEffect(() => {
|
||||
// Check for a query string in the URL
|
||||
const urlParams = new URLSearchParams(searchParams);
|
||||
const taskId = urlParams.get('taskid');
|
||||
const taskId = urlParams.get("taskid");
|
||||
if (taskId) {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
taskId,
|
||||
},
|
||||
taskId
|
||||
}
|
||||
});
|
||||
urlParams.delete('taskid');
|
||||
urlParams.delete("taskid");
|
||||
}
|
||||
}, [setTaskUpsertContext]);
|
||||
|
||||
return (
|
||||
<TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser}
|
||||
bodyshop={bodyshop}/>
|
||||
);
|
||||
return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MyTasksPageContainer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);
|
||||
|
||||
@@ -1,49 +1,44 @@
|
||||
import React, {useEffect} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TasksPageComponent from "./tasks.page.component";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import TaskPageTypes from "./taskPageTypes.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
||||
});
|
||||
|
||||
export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, setSelectedHeader}) {
|
||||
const {t} = useTranslation();
|
||||
export function MyTasksPageContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
document.title = t("titles.my_tasks", {
|
||||
app: InstanceRenderManager({
|
||||
imex: '$t(titles.imexonline)',
|
||||
rome: '$t(titles.romeonline)',
|
||||
promanager: '$t(titles.promanager)'
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)",
|
||||
promanager: "$t(titles.promanager)"
|
||||
})
|
||||
});
|
||||
setSelectedHeader("my_tasks");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/tasks/mytasks",
|
||||
label: t("titles.bc.my_tasks"),
|
||||
},]);
|
||||
label: t("titles.bc.my_tasks")
|
||||
}
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser}
|
||||
bodyshop={bodyshop}/>
|
||||
);
|
||||
return <TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MyTasksPageContainer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const TaskPageTypes = {
|
||||
MY_TASKS: 'myTasks',
|
||||
ALL_TASKS: 'allTasks',
|
||||
MY_TASKS: "myTasks",
|
||||
ALL_TASKS: "allTasks"
|
||||
};
|
||||
|
||||
export default TaskPageTypes;
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import React from "react";
|
||||
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
|
||||
import {QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
|
||||
import { QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
import taskPageTypes from "./taskPageTypes.jsx";
|
||||
|
||||
export default function TasksPageComponent({bodyshop, currentUser, type}) {
|
||||
export default function TasksPageComponent({ bodyshop, currentUser, type }) {
|
||||
switch (type) {
|
||||
case taskPageTypes.MY_TASKS:
|
||||
return <TaskListContainer onlyMine={true} relationshipId={currentUser.email}
|
||||
relationshipType={'user'} query={QUERY_MY_TASKS_PAGINATED}
|
||||
bodyshop={bodyshop} titleTranslation={'tasks.titles.my_tasks'}
|
||||
currentUser={currentUser}/>
|
||||
return (
|
||||
<TaskListContainer
|
||||
onlyMine={true}
|
||||
relationshipId={currentUser.email}
|
||||
relationshipType={"user"}
|
||||
query={QUERY_MY_TASKS_PAGINATED}
|
||||
bodyshop={bodyshop}
|
||||
titleTranslation={"tasks.titles.my_tasks"}
|
||||
currentUser={currentUser}
|
||||
/>
|
||||
);
|
||||
case taskPageTypes.ALL_TASKS:
|
||||
return <TaskListContainer query={QUERY_ALL_TASKS_PAGINATED} bodyshop={bodyshop}
|
||||
titleTranslation={'tasks.titles.all_tasks'}
|
||||
currentUser={currentUser}/>
|
||||
return (
|
||||
<TaskListContainer
|
||||
query={QUERY_ALL_TASKS_PAGINATED}
|
||||
bodyshop={bodyshop}
|
||||
titleTranslation={"tasks.titles.all_tasks"}
|
||||
currentUser={currentUser}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <></>
|
||||
return <></>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const INITIAL_STATE = {
|
||||
billEnter: { ...baseModal },
|
||||
courtesyCarReturn: { ...baseModal },
|
||||
noteUpsert: { ...baseModal },
|
||||
taskUpsert: {...baseModal, },
|
||||
taskUpsert: { ...baseModal },
|
||||
schedule: { ...baseModal },
|
||||
partsOrder: { ...baseModal },
|
||||
timeTicket: { ...baseModal },
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
"markforreexport": "Mark for Re-export",
|
||||
"new": "New Bill",
|
||||
"nobilllines": "This part has not yet been recieved.",
|
||||
"noneselected": "No bill selected.",
|
||||
"noneselected": "No bill selected.",
|
||||
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
|
||||
"printlabels": "Print Labels",
|
||||
"retailtotal": "Bills Retail Total",
|
||||
@@ -375,18 +375,18 @@
|
||||
"md_payment_types": "Payment Types",
|
||||
"md_referral_sources": "Referral Sources",
|
||||
"md_ro_guard": {
|
||||
"enabled": "RO Guard Enabled?",
|
||||
"enforce_ar": "Enforce AR Balance",
|
||||
"enforce_bills": "Enforce Bill Discrepancy",
|
||||
"enforce_cm": "Enforce Credit Memo Entry",
|
||||
"enforce_labor": "Enforce Labor Allocation",
|
||||
"enforce_ppd": "Enforce PPD Sync",
|
||||
"enforce_profit": "Enforce Profit Requirement",
|
||||
"enforce_sublet": "Enforce Sublet Completion",
|
||||
"masterbypass": "Master Bypass Password (not encrypted)",
|
||||
"totalgppercent_minimum": "Minimum Total Gross Profit %"
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enabled": "RO Guard Enabled?",
|
||||
"enforce_ar": "Enforce AR Balance",
|
||||
"enforce_bills": "Enforce Bill Discrepancy",
|
||||
"enforce_cm": "Enforce Credit Memo Entry",
|
||||
"enforce_labor": "Enforce Labor Allocation",
|
||||
"enforce_ppd": "Enforce PPD Sync",
|
||||
"enforce_profit": "Enforce Profit Requirement",
|
||||
"enforce_sublet": "Enforce Sublet Completion",
|
||||
"masterbypass": "Master Bypass Password (not encrypted)",
|
||||
"totalgppercent_minimum": "Minimum Total Gross Profit %"
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enable_tasks": "Enable Hour Flagging",
|
||||
"hourstype": "Hour Types",
|
||||
"memo": "Time Ticket Memo",
|
||||
@@ -659,7 +659,7 @@
|
||||
"licensing": "Licensing",
|
||||
"md_parts_scan": "Parts Scan Rules",
|
||||
"md_ro_guard": "RO Guard",
|
||||
"md_tasks_presets": "Tasks Presets",
|
||||
"md_tasks_presets": "Tasks Presets",
|
||||
"md_to_emails": "Preset To Emails",
|
||||
"md_to_emails_emails": "Emails",
|
||||
"messagingpresets": "Messaging Presets",
|
||||
@@ -681,9 +681,9 @@
|
||||
"title": "Responsibility Centers"
|
||||
},
|
||||
"roguard": {
|
||||
"title": "RO Guard"
|
||||
},
|
||||
"scheduling": "SMART Scheduling",
|
||||
"title": "RO Guard"
|
||||
},
|
||||
"scheduling": "SMART Scheduling",
|
||||
"scoreboardsetup": "Scoreboard Setup",
|
||||
"shopinfo": "Shop Information",
|
||||
"speedprint": "Speed Print Configuration",
|
||||
@@ -1380,7 +1380,7 @@
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Retail Price",
|
||||
"act_price_before_ppc": "Original Part Price",
|
||||
"act_price_before_ppc": "Original Part Price",
|
||||
"ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
|
||||
"assigned_team": "Team",
|
||||
"assigned_team_name": "Team {{name}}",
|
||||
@@ -1852,7 +1852,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"accountsreceivable": "Accounts Receivable",
|
||||
"act_price_ppc": "New Part Price",
|
||||
"act_price_ppc": "New Part Price",
|
||||
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",
|
||||
"actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).",
|
||||
"actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).",
|
||||
@@ -1973,7 +1973,7 @@
|
||||
"markforreexport": "Mark for Re-export",
|
||||
"mash": "Shop Materials",
|
||||
"masterbypass": "Master Bypass Password",
|
||||
"materials": {
|
||||
"materials": {
|
||||
"mapa": ""
|
||||
},
|
||||
"missingprofileinfo": "This job has missing tax profile info. To ensure correct totals calculations, re-import the job.",
|
||||
@@ -1982,12 +1982,12 @@
|
||||
"notes": "Notes",
|
||||
"othertotal": "Other Totals",
|
||||
"outstanding_ar": "A balance is outstanding on this RO. Payments can still be entered when the job is closed. ",
|
||||
"outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.",
|
||||
"outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.",
|
||||
"outstanding_reconciliation_discrep": "At least one discrepancy is not 0. This may indicate that this job is not properly reconciled and should not be closed.",
|
||||
"outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ",
|
||||
"outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.",
|
||||
"override_header": "Override estimate header on import?",
|
||||
"outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.",
|
||||
"outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.",
|
||||
"outstanding_reconciliation_discrep": "At least one discrepancy is not 0. This may indicate that this job is not properly reconciled and should not be closed.",
|
||||
"outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ",
|
||||
"outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.",
|
||||
"override_header": "Override estimate header on import?",
|
||||
"ownerassociation": "Owner Association",
|
||||
"parts": "Parts",
|
||||
"parts_lines": "Parts Lines",
|
||||
@@ -1996,7 +1996,7 @@
|
||||
"partsfilter": "Parts Only",
|
||||
"partssubletstotal": "Parts & Sublets Total",
|
||||
"partstotal": "Parts Total (ex. Taxes)",
|
||||
"performance": "Performance",
|
||||
"performance": "Performance",
|
||||
"pimraryamountpayable": "Total Primary Payable",
|
||||
"plitooltips": {
|
||||
"billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).",
|
||||
@@ -2012,9 +2012,9 @@
|
||||
},
|
||||
"ppc": "This line contains a part price change.",
|
||||
"ppdnotexported": "PPDs not Exported",
|
||||
"profileadjustments": "Profile Disc./Mkup",
|
||||
"profileadjustments": "Profile Disc./Mkup",
|
||||
"profits": "Job Profits",
|
||||
"prt_dsmk_total": "Line Item Adjustment",
|
||||
"prt_dsmk_total": "Line Item Adjustment",
|
||||
"rates": "Rates",
|
||||
"rates_subtotal": "All Rates Subtotal",
|
||||
"reconciliation": {
|
||||
@@ -2034,19 +2034,19 @@
|
||||
"remove_from_ar": "Remove from AR",
|
||||
"returntotals": "Return Totals",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "Profit margins enforced.",
|
||||
"enforce_ar": "AR collection enforced.",
|
||||
"enforce_bills": "Bill discrepancy enforced.",
|
||||
"enforce_cm": "Credit memo entry enforced.",
|
||||
"enforce_labor": "Labor allocations enforced.",
|
||||
"enforce_ppd": "PPD sync enforced.",
|
||||
"enforce_sublet": "Sublet completion enforced.",
|
||||
"enforce_validation": "Master Bypass Required: {{message}}",
|
||||
"enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job."
|
||||
},
|
||||
"roguard": "RO Guard",
|
||||
"roguardwarnings": "RO Guard Warnings",
|
||||
"rosaletotal": "RO Parts Total",
|
||||
"eforce_profit": "Profit margins enforced.",
|
||||
"enforce_ar": "AR collection enforced.",
|
||||
"enforce_bills": "Bill discrepancy enforced.",
|
||||
"enforce_cm": "Credit memo entry enforced.",
|
||||
"enforce_labor": "Labor allocations enforced.",
|
||||
"enforce_ppd": "PPD sync enforced.",
|
||||
"enforce_sublet": "Sublet completion enforced.",
|
||||
"enforce_validation": "Master Bypass Required: {{message}}",
|
||||
"enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job."
|
||||
},
|
||||
"roguard": "RO Guard",
|
||||
"roguardwarnings": "RO Guard Warnings",
|
||||
"rosaletotal": "RO Parts Total",
|
||||
"sale_additional": "Sales - Additional",
|
||||
"sale_labor": "Sales - Labor",
|
||||
"sale_parts": "Sales - Parts",
|
||||
@@ -2056,7 +2056,7 @@
|
||||
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ",
|
||||
"specialcoveragepolicy": "Special Coverage Policy Applies",
|
||||
"state_tax_amt": "Provincial/State Taxes",
|
||||
"subletsnotcompleted": "Outstanding Sublets",
|
||||
"subletsnotcompleted": "Outstanding Sublets",
|
||||
"subletstotal": "Sublets Total",
|
||||
"subtotal": "Subtotal",
|
||||
"supplementnote": "The Job had a supplement imported.",
|
||||
@@ -2596,7 +2596,7 @@
|
||||
"markreexported": "Payment marked for re-export successfully",
|
||||
"payment": "Payment created successfully. ",
|
||||
"paymentupdate": "Payment updated successfully. ",
|
||||
"stripe": "Credit card transaction charged successfully."
|
||||
"stripe": "Credit card transaction charged successfully."
|
||||
}
|
||||
},
|
||||
"phonebook": {
|
||||
|
||||
@@ -374,18 +374,18 @@
|
||||
"md_payment_types": "",
|
||||
"md_referral_sources": "",
|
||||
"md_ro_guard": {
|
||||
"enabled": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"masterbypass": "",
|
||||
"totalgppercent_minimum": ""
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enabled": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"masterbypass": "",
|
||||
"totalgppercent_minimum": ""
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enable_tasks": "",
|
||||
"hourstype": "",
|
||||
"memo": "",
|
||||
@@ -658,7 +658,7 @@
|
||||
"licensing": "",
|
||||
"md_parts_scan": "",
|
||||
"md_ro_guard": "",
|
||||
"md_tasks_presets": "",
|
||||
"md_tasks_presets": "",
|
||||
"md_to_emails": "",
|
||||
"md_to_emails_emails": "",
|
||||
"messagingpresets": "",
|
||||
@@ -678,9 +678,9 @@
|
||||
"sales_tax_codes": "",
|
||||
"tax_accounts": "",
|
||||
"title": ""
|
||||
},
|
||||
"roguard": {
|
||||
"title": ""
|
||||
},
|
||||
"roguard": {
|
||||
"title": ""
|
||||
},
|
||||
"scheduling": "",
|
||||
"scoreboardsetup": "",
|
||||
@@ -1380,7 +1380,7 @@
|
||||
"fields": {
|
||||
"act_price": "Precio actual",
|
||||
"act_price_before_ppc": "",
|
||||
"ah_detail_line": "",
|
||||
"ah_detail_line": "",
|
||||
"assigned_team": "",
|
||||
"assigned_team_name": "",
|
||||
"create_ppc": "",
|
||||
@@ -1851,7 +1851,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"accountsreceivable": "",
|
||||
"act_price_ppc": "",
|
||||
"act_price_ppc": "",
|
||||
"actual_completion_inferred": "",
|
||||
"actual_delivery_inferred": "",
|
||||
"actual_in_inferred": "",
|
||||
@@ -1972,7 +1972,7 @@
|
||||
"markforreexport": "",
|
||||
"mash": "",
|
||||
"masterbypass": "",
|
||||
"materials": {
|
||||
"materials": {
|
||||
"mapa": ""
|
||||
},
|
||||
"missingprofileinfo": "",
|
||||
@@ -1980,12 +1980,12 @@
|
||||
"net_repairs": "",
|
||||
"notes": "Notas",
|
||||
"othertotal": "",
|
||||
"outstanding_ar": "",
|
||||
"outstanding_credit_memos": "",
|
||||
"outstanding_ppd": "",
|
||||
"outstanding_reconciliation_discrep": "",
|
||||
"outstanding_sublets": "",
|
||||
"outstandinghours": "",
|
||||
"outstanding_ar": "",
|
||||
"outstanding_credit_memos": "",
|
||||
"outstanding_ppd": "",
|
||||
"outstanding_reconciliation_discrep": "",
|
||||
"outstanding_sublets": "",
|
||||
"outstandinghours": "",
|
||||
"override_header": "¿Anular encabezado estimado al importar?",
|
||||
"ownerassociation": "",
|
||||
"parts": "Partes",
|
||||
@@ -1996,7 +1996,7 @@
|
||||
"partssubletstotal": "",
|
||||
"partstotal": "",
|
||||
"performance": "",
|
||||
"pimraryamountpayable": "",
|
||||
"pimraryamountpayable": "",
|
||||
"plitooltips": {
|
||||
"billtotal": "",
|
||||
"calculatedcreditsnotreceived": "",
|
||||
@@ -2011,9 +2011,9 @@
|
||||
},
|
||||
"ppc": "",
|
||||
"ppdnotexported": "",
|
||||
"profileadjustments": "",
|
||||
"profileadjustments": "",
|
||||
"profits": "",
|
||||
"prt_dsmk_total": "",
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Tarifas",
|
||||
"rates_subtotal": "",
|
||||
"reconciliation": {
|
||||
@@ -2033,19 +2033,19 @@
|
||||
"remove_from_ar": "",
|
||||
"returntotals": "",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
},
|
||||
"roguard": "",
|
||||
"roguardwarnings": "",
|
||||
"rosaletotal": "",
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
},
|
||||
"roguard": "",
|
||||
"roguardwarnings": "",
|
||||
"rosaletotal": "",
|
||||
"sale_additional": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
@@ -2055,7 +2055,7 @@
|
||||
"scheduledinchange": "",
|
||||
"specialcoveragepolicy": "",
|
||||
"state_tax_amt": "",
|
||||
"subletsnotcompleted": "",
|
||||
"subletsnotcompleted": "",
|
||||
"subletstotal": "",
|
||||
"subtotal": "",
|
||||
"supplementnote": "",
|
||||
@@ -2595,7 +2595,7 @@
|
||||
"markreexported": "",
|
||||
"payment": "",
|
||||
"paymentupdate": "",
|
||||
"stripe": ""
|
||||
"stripe": ""
|
||||
}
|
||||
},
|
||||
"phonebook": {
|
||||
|
||||
@@ -374,18 +374,18 @@
|
||||
"md_payment_types": "",
|
||||
"md_referral_sources": "",
|
||||
"md_ro_guard": {
|
||||
"enabled": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"masterbypass": "",
|
||||
"totalgppercent_minimum": ""
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enabled": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"masterbypass": "",
|
||||
"totalgppercent_minimum": ""
|
||||
},
|
||||
"md_tasks_presets": {
|
||||
"enable_tasks": "",
|
||||
"hourstype": "",
|
||||
"memo": "",
|
||||
@@ -658,7 +658,7 @@
|
||||
"licensing": "",
|
||||
"md_parts_scan": "",
|
||||
"md_ro_guard": "",
|
||||
"md_tasks_presets": "",
|
||||
"md_tasks_presets": "",
|
||||
"md_to_emails": "",
|
||||
"md_to_emails_emails": "",
|
||||
"messagingpresets": "",
|
||||
@@ -678,9 +678,9 @@
|
||||
"sales_tax_codes": "",
|
||||
"tax_accounts": "",
|
||||
"title": ""
|
||||
},
|
||||
"roguard": {
|
||||
"title": ""
|
||||
},
|
||||
"roguard": {
|
||||
"title": ""
|
||||
},
|
||||
"scheduling": "",
|
||||
"scoreboardsetup": "",
|
||||
@@ -1380,7 +1380,7 @@
|
||||
"fields": {
|
||||
"act_price": "Prix actuel",
|
||||
"act_price_before_ppc": "",
|
||||
"ah_detail_line": "",
|
||||
"ah_detail_line": "",
|
||||
"assigned_team": "",
|
||||
"assigned_team_name": "",
|
||||
"create_ppc": "",
|
||||
@@ -1851,7 +1851,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"accountsreceivable": "",
|
||||
"act_price_ppc": "",
|
||||
"act_price_ppc": "",
|
||||
"actual_completion_inferred": "",
|
||||
"actual_delivery_inferred": "",
|
||||
"actual_in_inferred": "",
|
||||
@@ -1972,7 +1972,7 @@
|
||||
"markforreexport": "",
|
||||
"mash": "",
|
||||
"masterbypass": "",
|
||||
"materials": {
|
||||
"materials": {
|
||||
"mapa": ""
|
||||
},
|
||||
"missingprofileinfo": "",
|
||||
@@ -1980,12 +1980,12 @@
|
||||
"net_repairs": "",
|
||||
"notes": "Remarques",
|
||||
"othertotal": "",
|
||||
"outstanding_ar": "",
|
||||
"outstanding_credit_memos": "",
|
||||
"outstanding_ppd": "",
|
||||
"outstanding_reconciliation_discrep": "",
|
||||
"outstanding_sublets": "",
|
||||
"outstandinghours": "",
|
||||
"outstanding_ar": "",
|
||||
"outstanding_credit_memos": "",
|
||||
"outstanding_ppd": "",
|
||||
"outstanding_reconciliation_discrep": "",
|
||||
"outstanding_sublets": "",
|
||||
"outstandinghours": "",
|
||||
"override_header": "Remplacer l'en-tête d'estimation à l'importation?",
|
||||
"ownerassociation": "",
|
||||
"parts": "les pièces",
|
||||
@@ -1996,7 +1996,7 @@
|
||||
"partssubletstotal": "",
|
||||
"partstotal": "",
|
||||
"performance": "",
|
||||
"pimraryamountpayable": "",
|
||||
"pimraryamountpayable": "",
|
||||
"plitooltips": {
|
||||
"billtotal": "",
|
||||
"calculatedcreditsnotreceived": "",
|
||||
@@ -2011,9 +2011,9 @@
|
||||
},
|
||||
"ppc": "",
|
||||
"ppdnotexported": "",
|
||||
"profileadjustments": "",
|
||||
"profileadjustments": "",
|
||||
"profits": "",
|
||||
"prt_dsmk_total": "",
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Les taux",
|
||||
"rates_subtotal": "",
|
||||
"reconciliation": {
|
||||
@@ -2033,19 +2033,19 @@
|
||||
"remove_from_ar": "",
|
||||
"returntotals": "",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
},
|
||||
"roguard": "",
|
||||
"roguardwarnings": "",
|
||||
"rosaletotal": "",
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
},
|
||||
"roguard": "",
|
||||
"roguardwarnings": "",
|
||||
"rosaletotal": "",
|
||||
"sale_additional": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
@@ -2055,7 +2055,7 @@
|
||||
"scheduledinchange": "",
|
||||
"specialcoveragepolicy": "",
|
||||
"state_tax_amt": "",
|
||||
"subletsnotcompleted": "",
|
||||
"subletsnotcompleted": "",
|
||||
"subletstotal": "",
|
||||
"subtotal": "",
|
||||
"supplementnote": "",
|
||||
@@ -2595,7 +2595,7 @@
|
||||
"markreexported": "",
|
||||
"payment": "",
|
||||
"paymentupdate": "",
|
||||
"stripe": ""
|
||||
"stripe": ""
|
||||
}
|
||||
},
|
||||
"phonebook": {
|
||||
|
||||
@@ -40,33 +40,37 @@ const AuditTrailMapping = {
|
||||
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
|
||||
jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }),
|
||||
jobvoid: () => i18n.t("audit_trail.messages.jobvoid"),
|
||||
// Tasks Entries
|
||||
tasksCreated: (title, createdBy) => i18n.t("audit_trail.messages.tasks_created", {
|
||||
title,
|
||||
createdBy
|
||||
}),
|
||||
tasksUpdated: (title, updatedBy) => i18n.t("audit_trail.messages.tasks_updated", {
|
||||
title,
|
||||
updatedBy
|
||||
}),
|
||||
tasksDeleted: (title, deletedBy) => i18n.t("audit_trail.messages.tasks_deleted", {
|
||||
title,
|
||||
deletedBy
|
||||
}),
|
||||
tasksUndeleted: (title, undeletedBy) => i18n.t("audit_trail.messages.tasks_undeleted", {
|
||||
title,
|
||||
undeletedBy
|
||||
}),
|
||||
tasksCompleted: (title, completedBy) => i18n.t("audit_trail.messages.tasks_completed", {
|
||||
title,
|
||||
completedBy
|
||||
}),
|
||||
tasksUncompleted: (title, uncompletedBy) => i18n.t("audit_trail.messages.tasks_uncompleted", {
|
||||
title,
|
||||
uncompletedBy
|
||||
}),
|
||||
|
||||
|
||||
// Tasks Entries
|
||||
tasksCreated: (title, createdBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_created", {
|
||||
title,
|
||||
createdBy
|
||||
}),
|
||||
tasksUpdated: (title, updatedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_updated", {
|
||||
title,
|
||||
updatedBy
|
||||
}),
|
||||
tasksDeleted: (title, deletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_deleted", {
|
||||
title,
|
||||
deletedBy
|
||||
}),
|
||||
tasksUndeleted: (title, undeletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_undeleted", {
|
||||
title,
|
||||
undeletedBy
|
||||
}),
|
||||
tasksCompleted: (title, completedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_completed", {
|
||||
title,
|
||||
completedBy
|
||||
}),
|
||||
tasksUncompleted: (title, uncompletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_uncompleted", {
|
||||
title,
|
||||
uncompletedBy
|
||||
})
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
@@ -29,7 +29,7 @@ export function replaceUndefinedWithNull(obj, keys) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
if (keys) {
|
||||
return [key, (keys.includes(key) && value === undefined) ? null : value];
|
||||
return [key, keys.includes(key) && value === undefined ? null : value];
|
||||
} else {
|
||||
return [key, value === undefined ? null : value];
|
||||
}
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -56,6 +56,7 @@
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5900,6 +5901,21 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const moment = require("moment");
|
||||
const {default: RenderInstanceManager} = require("../utils/instanceMgr");
|
||||
const {header, end, start} = require("./html");
|
||||
const { default: RenderInstanceManager } = require("../utils/instanceMgr");
|
||||
const { header, end, start } = require("./html");
|
||||
|
||||
// Required Strings
|
||||
// - header - The header of the email
|
||||
@@ -13,21 +13,21 @@ const {header, end, start} = require("./html");
|
||||
|
||||
const defaultFooter = () => {
|
||||
return RenderInstanceManager({
|
||||
imex: 'ImEX Online Collision Repair Management System',
|
||||
rome: 'Rome Technologies',
|
||||
promanager: 'ProManager',
|
||||
imex: "ImEX Online Collision Repair Management System",
|
||||
rome: "Rome Technologies",
|
||||
promanager: "ProManager"
|
||||
});
|
||||
};
|
||||
|
||||
const now = () => moment().format('MM/DD/YYYY @ hh:mm a');
|
||||
const now = () => moment().format("MM/DD/YYYY @ hh:mm a");
|
||||
|
||||
const generateEmailTemplate = (strings) => {
|
||||
|
||||
return `
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">`
|
||||
+ header
|
||||
+ start
|
||||
+ `
|
||||
return (
|
||||
`
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">` +
|
||||
header +
|
||||
start +
|
||||
`
|
||||
<table class="row">
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -101,8 +101,9 @@ const generateEmailTemplate = (strings) => {
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>`
|
||||
+ end
|
||||
</table>` +
|
||||
end
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = generateEmailTemplate;
|
||||
|
||||
@@ -5,13 +5,13 @@ require("dotenv").config({
|
||||
const axios = require("axios");
|
||||
let nodemailer = require("nodemailer");
|
||||
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 logger = require("../utils/logger");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const {isObject} = require("lodash");
|
||||
const generateEmailTemplate = require('./generateTemplate');
|
||||
const { isObject } = require("lodash");
|
||||
const generateEmailTemplate = require("./generateTemplate");
|
||||
|
||||
const ses = new aws.SES({
|
||||
// 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({
|
||||
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;
|
||||
try {
|
||||
transporter.sendMail(
|
||||
@@ -60,7 +60,7 @@ exports.sendServerEmail = async function ({subject, text}) {
|
||||
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 {
|
||||
transporter.sendMail(
|
||||
{
|
||||
@@ -125,12 +125,12 @@ exports.sendEmail = async (req, res) => {
|
||||
attachments:
|
||||
[
|
||||
...((req.body.attachments &&
|
||||
req.body.attachments.map((a) => {
|
||||
return {
|
||||
filename: a.filename,
|
||||
path: a.path
|
||||
};
|
||||
})) ||
|
||||
req.body.attachments.map((a) => {
|
||||
return {
|
||||
filename: a.filename,
|
||||
path: a.path
|
||||
};
|
||||
})) ||
|
||||
[]),
|
||||
...downloadedMedia.map((a) => {
|
||||
return {
|
||||
@@ -186,14 +186,14 @@ exports.sendEmail = async (req, res) => {
|
||||
subject: req.body.subject,
|
||||
bodyshopid: req.body.bodyshopid
|
||||
});
|
||||
res.status(500).json({success: false, error: err});
|
||||
res.status(500).json({ success: false, error: err });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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");
|
||||
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.
|
||||
|
||||
${body.bounce?.bouncedRecipients.map(
|
||||
(r) =>
|
||||
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||
(r) =>
|
||||
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||
`
|
||||
)}
|
||||
)}
|
||||
`
|
||||
},
|
||||
(err, info) => {
|
||||
|
||||
Reference in New Issue
Block a user