- reapply proper prettier formatting.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-04-03 14:09:09 -04:00
parent e51f72ff98
commit ab031c01de
26 changed files with 10734 additions and 10641 deletions

View File

@@ -6,7 +6,7 @@ import {connect} from "react-redux";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors"; import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions"; import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {DateFormatter} from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort, dateSort} from "../../utils/sorters"; import {alphaSort, dateSort} from "../../utils/sorters";
@@ -22,7 +22,10 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), setBillEnterContext: (context) => dispatch(setModalContext({
context: context,
modal: "billEnter"
})),
setReconciliationContext: (context) => dispatch(setModalContext({ setReconciliationContext: (context) => dispatch(setModalContext({
context: context, context: context,
modal: "reconciliation" modal: "reconciliation"
@@ -40,7 +43,7 @@ export function BillsListTableComponent({
setReconciliationContext, setReconciliationContext,
setTaskUpsertContext, setTaskUpsertContext,
}) { }) {
const { t } = useTranslation(); const {t} = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {} sortedInfo: {}
@@ -51,14 +54,14 @@ export function BillsListTableComponent({
const Templates = TemplateList("bill"); const Templates = TemplateList("bill");
const bills = billsQuery.data ? billsQuery.data.bills : []; const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery; const {refetch} = billsQuery;
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space wrap> <Space wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button onClick={() => handleOnRowClick(record)}>
<EditFilled /> <EditFilled/>
</Button> </Button>
)} )}
<Button title={t('tasks.buttons.create')} onClick={() => { <Button title={t('tasks.buttons.create')} onClick={() => {
@@ -71,9 +74,9 @@ export function BillsListTableComponent({
}}> }}>
<FaTasks/> <FaTasks/>
</Button> </Button>
<BillDeleteButton bill={record} jobid={job.id} /> <BillDeleteButton bill={record} jobid={job.id}/>
<BillDetailEditReturnComponent <BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }} data={{bills_by_pk: {...record, jobid: job.id}}}
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO} disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
/> />
@@ -81,9 +84,9 @@ export function BillsListTableComponent({
<PrintWrapperComponent <PrintWrapperComponent
templateObject={{ templateObject={{
name: Templates.inhouse_invoice.key, name: Templates.inhouse_invoice.key,
variables: { id: record.id } variables: {id: record.id}
}} }}
messageObject={{ subject: Templates.inhouse_invoice.subject }} messageObject={{subject: Templates.inhouse_invoice.subject}}
/> />
)} )}
</Space> </Space>
@@ -126,7 +129,7 @@ export function BillsListTableComponent({
key: "is_credit_memo", key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.is_credit_memo} /> render: (text, record) => <Checkbox checked={record.is_credit_memo}/>
}, },
{ {
title: t("bills.fields.exported"), title: t("bills.fields.exported"),
@@ -134,7 +137,7 @@ export function BillsListTableComponent({
key: "exported", key: "exported",
sorter: (a, b) => a.exported - b.exported, sorter: (a, b) => a.exported - b.exported,
sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.exported} /> render: (text, record) => <Checkbox checked={record.exported}/>
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
@@ -145,18 +148,18 @@ export function BillsListTableComponent({
]; ];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({...state, filteredInfo: filters, sortedInfo: sorter});
}; };
const filteredBills = bills const filteredBills = bills
? searchText === "" ? searchText === ""
? bills ? bills
: bills.filter( : bills.filter(
(b) => (b) =>
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
) )
: []; : [];
return ( return (
@@ -165,14 +168,14 @@ export function BillsListTableComponent({
extra={ extra={
<Space wrap> <Space wrap>
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined/>
</Button> </Button>
{job && job.converted ? ( {job && job.converted ? (
<> <>
<Button <Button
onClick={() => { onClick={() => {
setBillEnterContext({ setBillEnterContext({
actions: { refetch: billsQuery.refetch }, actions: {refetch: billsQuery.refetch},
context: { context: {
job job
} }
@@ -184,7 +187,7 @@ export function BillsListTableComponent({
<Button <Button
onClick={() => { onClick={() => {
setReconciliationContext({ setReconciliationContext({
actions: { refetch: billsQuery.refetch }, actions: {refetch: billsQuery.refetch},
context: { context: {
job, job,
bills: (billsQuery.data && billsQuery.data.bills) || [] bills: (billsQuery.data && billsQuery.data.bills) || []

View File

@@ -26,26 +26,35 @@ import Icon, {
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { Layout, Menu, Switch, Tooltip } from "antd"; import {Layout, Menu, Switch, Tooltip} from "antd";
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { BsKanban } from "react-icons/bs"; import {BsKanban} from "react-icons/bs";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa"; import {
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; FaCalendarAlt,
import { IoBusinessOutline } from "react-icons/io5"; FaCarCrash,
import { RiSurveyLine } from "react-icons/ri"; FaCreditCard,
import { connect } from "react-redux"; FaFileInvoiceDollar,
import { Link } from "react-router-dom"; FaTasks
import { createStructuredSelector } from "reselect"; } from "react-icons/fa";
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors"; import {GiPayMoney, GiPlayerTime, GiSettingsKnobs} from "react-icons/gi";
import { setModalContext } from "../../redux/modals/modals.actions"; import {IoBusinessOutline} from "react-icons/io5";
import { signOutStart } from "../../redux/user/user.actions"; import {RiSurveyLine} from "react-icons/ri";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import {connect} from "react-redux";
import { FiLogOut } from "react-icons/fi"; import {Link} from "react-router-dom";
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler"; import {createStructuredSelector} from "reselect";
import {
selectRecentItems,
selectSelectedHeader
} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {signOutStart} from "../../redux/user/user.actions";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import {FiLogOut} from "react-icons/fi";
import {checkBeta, handleBeta, setBeta} from "../../utils/betaHandler";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -55,38 +64,53 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), setBillEnterContext: (context) => dispatch(setModalContext({
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })), context: context,
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), modal: "billEnter"
setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })), })),
setTimeTicketContext: (context) => dispatch(setModalContext({
context: context,
modal: "timeTicket"
})),
setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
setReportCenterContext: (context) => dispatch(setModalContext({
context: context,
modal: "reportCenter"
})),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), setCardPaymentContext: (context) => dispatch(setModalContext({
setTaskUpsertContext: (context) => dispatch(setModalContext({context: context, modal: 'taskUpsert'})), context: context,
modal: "cardPayment"
})),
setTaskUpsertContext: (context) => dispatch(setModalContext({
context: context,
modal: 'taskUpsert'
})),
}); });
function Header({ function Header({
handleMenuClick, handleMenuClick,
currentUser, currentUser,
bodyshop, bodyshop,
selectedHeader, selectedHeader,
signOutStart, signOutStart,
setBillEnterContext, setBillEnterContext,
setTimeTicketContext, setTimeTicketContext,
setPaymentContext, setPaymentContext,
setReportCenterContext, setReportCenterContext,
recentItems, recentItems,
setCardPaymentContext, setCardPaymentContext,
setTaskUpsertContext, setTaskUpsertContext,
}) { }) {
const { const {
treatments: { ImEXPay, DmsAp, Simple_Inventory } treatments: {ImEXPay, DmsAp, Simple_Inventory}
} = useSplitTreatments({ } = useSplitTreatments({
attributes: {}, attributes: {},
names: ["ImEXPay", "DmsAp", "Simple_Inventory"], names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
splitKey: bodyshop && bodyshop.imexshopid splitKey: bodyshop && bodyshop.imexshopid
}); });
const [betaSwitch, setBetaSwitch] = useState(false); const [betaSwitch, setBetaSwitch] = useState(false);
const { t } = useTranslation(); const {t} = useTranslation();
useEffect(() => { useEffect(() => {
const isBeta = checkBeta(); const isBeta = checkBeta();
@@ -105,18 +129,18 @@ function Header({
InstanceRenderManager({ InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "bills", bodyshop }) promanager: HasFeatureAccess({featureName: "bills", bodyshop})
}) })
) { ) {
accountingChildren.push( accountingChildren.push(
{ {
key: "bills", key: "bills",
icon: <Icon component={FaFileInvoiceDollar} />, icon: <Icon component={FaFileInvoiceDollar}/>,
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link> label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
}, },
{ {
key: "enterbills", key: "enterbills",
icon: <Icon component={GiPayMoney} />, icon: <Icon component={GiPayMoney}/>,
label: t("menus.header.enterbills"), label: t("menus.header.enterbills"),
onClick: () => { onClick: () => {
setBillEnterContext({ setBillEnterContext({
@@ -135,7 +159,7 @@ function Header({
}, },
{ {
key: "inventory", key: "inventory",
icon: <Icon component={FaFileInvoiceDollar} />, icon: <Icon component={FaFileInvoiceDollar}/>,
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link> label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
} }
); );
@@ -144,7 +168,7 @@ function Header({
InstanceRenderManager({ InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "payments", bodyshop }) promanager: HasFeatureAccess({featureName: "payments", bodyshop})
}) })
) { ) {
accountingChildren.push( accountingChildren.push(
@@ -153,12 +177,12 @@ function Header({
}, },
{ {
key: "allpayments", key: "allpayments",
icon: <BankFilled />, icon: <BankFilled/>,
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link> label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
}, },
{ {
key: "enterpayments", key: "enterpayments",
icon: <Icon component={FaCreditCard} />, icon: <Icon component={FaCreditCard}/>,
label: t("menus.header.enterpayment"), label: t("menus.header.enterpayment"),
onClick: () => { onClick: () => {
setPaymentContext({ setPaymentContext({
@@ -173,7 +197,7 @@ function Header({
if (ImEXPay.treatment === "on") { if (ImEXPay.treatment === "on") {
accountingChildren.push({ accountingChildren.push({
key: "entercardpayments", key: "entercardpayments",
icon: <Icon component={FaCreditCard} />, icon: <Icon component={FaCreditCard}/>,
label: t("menus.header.entercardpayment"), label: t("menus.header.entercardpayment"),
onClick: () => { onClick: () => {
setCardPaymentContext({ setCardPaymentContext({
@@ -188,7 +212,7 @@ function Header({
InstanceRenderManager({ InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
}) })
) { ) {
accountingChildren.push( accountingChildren.push(
@@ -197,7 +221,7 @@ function Header({
}, },
{ {
key: "timetickets", key: "timetickets",
icon: <FieldTimeOutlined />, icon: <FieldTimeOutlined/>,
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link> label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
} }
); );
@@ -205,14 +229,14 @@ function Header({
if (bodyshop?.md_tasks_presets?.use_approvals) { if (bodyshop?.md_tasks_presets?.use_approvals) {
accountingChildren.push({ accountingChildren.push({
key: "ttapprovals", key: "ttapprovals",
icon: <FieldTimeOutlined />, icon: <FieldTimeOutlined/>,
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link> label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
}); });
} }
accountingChildren.push( accountingChildren.push(
{ {
key: "entertimetickets", key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />, icon: <Icon component={GiPlayerTime}/>,
label: t("menus.header.entertimeticket"), label: t("menus.header.entertimeticket"),
onClick: () => { onClick: () => {
setTimeTicketContext({ setTimeTicketContext({
@@ -234,7 +258,8 @@ function Header({
const accountingExportChildren = [ const accountingExportChildren = [
{ {
key: "receivables", key: "receivables",
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link> label: <Link
to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
} }
]; ];
@@ -266,12 +291,12 @@ function Header({
InstanceRenderManager({ InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "export", bodyshop }) promanager: HasFeatureAccess({featureName: "export", bodyshop})
}) })
) { ) {
accountingChildren.push({ accountingChildren.push({
key: "accountingexport", key: "accountingexport",
icon: <ExportOutlined />, icon: <ExportOutlined/>,
label: t("menus.header.export"), label: t("menus.header.export"),
children: accountingExportChildren children: accountingExportChildren
}); });
@@ -280,44 +305,44 @@ function Header({
const menuItems = [ const menuItems = [
{ {
key: "home", key: "home",
icon: <HomeFilled />, icon: <HomeFilled/>,
label: <Link to="/manage/">{t("menus.header.home")}</Link> label: <Link to="/manage/">{t("menus.header.home")}</Link>
}, },
{ {
key: "schedule", key: "schedule",
icon: <Icon component={FaCalendarAlt} />, icon: <Icon component={FaCalendarAlt}/>,
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link> label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
}, },
{ {
key: "jobssubmenu", key: "jobssubmenu",
id: "header-jobs", id: "header-jobs",
icon: <Icon component={FaCarCrash} />, icon: <Icon component={FaCarCrash}/>,
label: t("menus.header.jobs"), label: t("menus.header.jobs"),
children: [ children: [
{ {
key: "activejobs", key: "activejobs",
icon: <FileFilled />, icon: <FileFilled/>,
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link> label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
}, },
{ {
key: "readyjobs", key: "readyjobs",
icon: <CheckCircleOutlined />, icon: <CheckCircleOutlined/>,
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link> label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
}, },
{ {
key: "parts-queue", key: "parts-queue",
icon: <ToolFilled />, icon: <ToolFilled/>,
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link> label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
}, },
{ {
key: "availablejobs", key: "availablejobs",
id: "header-jobs-available", id: "header-jobs-available",
icon: <ImportOutlined />, icon: <ImportOutlined/>,
label: <Link to="/manage/available">{t("menus.header.availablejobs")}</Link> label: <Link to="/manage/available">{t("menus.header.availablejobs")}</Link>
}, },
{ {
key: "newjob", key: "newjob",
icon: <FileAddOutlined />, icon: <FileAddOutlined/>,
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link> label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
}, },
{ {
@@ -325,7 +350,7 @@ function Header({
}, },
{ {
key: "alljobs", key: "alljobs",
icon: <UnorderedListOutlined />, icon: <UnorderedListOutlined/>,
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link> label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
}, },
{ {
@@ -333,54 +358,54 @@ function Header({
}, },
{ {
key: "productionlist", key: "productionlist",
icon: <ScheduleOutlined />, icon: <ScheduleOutlined/>,
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link> label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
}, },
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "visualboard", bodyshop }) promanager: HasFeatureAccess({featureName: "visualboard", bodyshop})
}) })
? [ ? [
{ {
key: "productionboard", key: "productionboard",
icon: <Icon component={BsKanban} />, icon: <Icon component={BsKanban}/>,
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link> label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
} }
] ]
: []), : []),
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "scoreboard", bodyshop }) promanager: HasFeatureAccess({featureName: "scoreboard", bodyshop})
}) })
? [ ? [
{ {
type: "divider" type: "divider"
}, },
{ {
key: "scoreboard", key: "scoreboard",
icon: <LineChartOutlined />, icon: <LineChartOutlined/>,
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link> label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
} }
] ]
: []) : [])
] ]
}, },
{ {
key: "customers", key: "customers",
icon: <UserOutlined />, icon: <UserOutlined/>,
label: t("menus.header.customers"), label: t("menus.header.customers"),
children: [ children: [
{ {
key: "owners", key: "owners",
icon: <TeamOutlined />, icon: <TeamOutlined/>,
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link> label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
}, },
{ {
key: "vehicles", key: "vehicles",
icon: <CarFilled />, icon: <CarFilled/>,
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link> label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
} }
] ]
@@ -391,63 +416,65 @@ function Header({
promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }), promanager: false // HasFeatureAccess({ featureName: 'courtesycars', bodyshop }),
}) })
? [ ? [
{ {
key: "ccs", key: "ccs",
icon: <CarFilled />, icon: <CarFilled/>,
label: t("menus.header.courtesycars"), label: t("menus.header.courtesycars"),
children: [ children: [
{ {
key: "courtesycarsall", key: "courtesycarsall",
icon: <CarFilled />, icon: <CarFilled/>,
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link> label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
}, },
{ {
key: "contracts", key: "contracts",
icon: <FileFilled />, icon: <FileFilled/>,
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link> label: <Link
}, to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
{ },
key: "newcontract", {
icon: <FileAddFilled />, key: "newcontract",
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link> icon: <FileAddFilled/>,
} label: <Link
] to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
} }
] ]
}
]
: []), : []),
...(accountingChildren.length > 0 ...(accountingChildren.length > 0
? [ ? [
{ {
key: "accounting", key: "accounting",
icon: <DollarCircleFilled />, icon: <DollarCircleFilled/>,
label: t("menus.header.accounting"), label: t("menus.header.accounting"),
children: accountingChildren children: accountingChildren
} }
] ]
: []), : []),
{ {
key: "phonebook", key: "phonebook",
icon: <PhoneOutlined />, icon: <PhoneOutlined/>,
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link> label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
}, },
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "media", bodyshop }) promanager: HasFeatureAccess({featureName: "media", bodyshop})
}) })
? [ ? [
{ {
key: "temporarydocs", key: "temporarydocs",
icon: <PaperClipOutlined />, icon: <PaperClipOutlined/>,
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link> label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
} }
] ]
: []), : []),
{ {
key: 'tasks', key: 'tasks',
id: 'tasks', id: 'tasks',
icon: <FaTasks />, icon: <FaTasks/>,
label: t('menus.header.tasks'), label: t('menus.header.tasks'),
children: [ children: [
{ {
@@ -475,22 +502,22 @@ function Header({
}, },
{ {
key: 'shopsubmenu', key: 'shopsubmenu',
icon: <SettingOutlined />, icon: <SettingOutlined/>,
label: t("menus.header.shop"), label: t("menus.header.shop"),
children: [ children: [
{ {
key: "shop", key: "shop",
icon: <Icon component={GiSettingsKnobs} />, icon: <Icon component={GiSettingsKnobs}/>,
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link> label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
}, },
{ {
key: "dashboard", key: "dashboard",
icon: <DashboardFilled />, icon: <DashboardFilled/>,
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link> label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
}, },
{ {
key: "reportcenter", key: "reportcenter",
icon: <BarChartOutlined />, icon: <BarChartOutlined/>,
label: t("menus.header.reportcenter"), label: t("menus.header.reportcenter"),
onClick: () => { onClick: () => {
setReportCenterContext({ setReportCenterContext({
@@ -501,21 +528,21 @@ function Header({
}, },
{ {
key: "shop-vendors", key: "shop-vendors",
icon: <Icon component={IoBusinessOutline} />, icon: <Icon component={IoBusinessOutline}/>,
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link> label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
}, },
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "csi", bodyshop }) promanager: HasFeatureAccess({featureName: "csi", bodyshop})
}) })
? [ ? [
{ {
key: "shop-csi", key: "shop-csi",
icon: <Icon component={RiSurveyLine} />, icon: <Icon component={RiSurveyLine}/>,
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link> label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
} }
] ]
: []) : [])
] ]
}, },
@@ -525,14 +552,14 @@ function Header({
children: [ children: [
{ {
key: "signout", key: "signout",
icon: <Icon component={FiLogOut} />, icon: <Icon component={FiLogOut}/>,
danger: true, danger: true,
label: t("user.actions.signout"), label: t("user.actions.signout"),
onClick: () => signOutStart() onClick: () => signOutStart()
}, },
{ {
key: "help", key: "help",
icon: <Icon component={QuestionCircleFilled} />, icon: <Icon component={QuestionCircleFilled}/>,
label: t("menus.header.help"), label: t("menus.header.help"),
onClick: () => { onClick: () => {
window.open( window.open(
@@ -558,19 +585,19 @@ function Header({
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
}) })
? [ ? [
{ {
key: "shiftclock", key: "shiftclock",
icon: <Icon component={GiPlayerTime} />, icon: <Icon component={GiPlayerTime}/>,
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link> label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
} }
] ]
: []), : []),
{ {
key: "profile", key: "profile",
icon: <UserOutlined />, icon: <UserOutlined/>,
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link> label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
} }
// { // {
@@ -604,7 +631,7 @@ function Header({
}, },
{ {
key: "recent", key: "recent",
icon: <ClockCircleFilled />, icon: <ClockCircleFilled/>,
children: recentItems.map((i, idx) => ({ children: recentItems.map((i, idx) => ({
key: idx, key: idx,
label: <Link to={i.url}>{i.label}</Link> label: <Link to={i.url}>{i.label}</Link>
@@ -618,7 +645,7 @@ function Header({
imex: () => { imex: () => {
menuItems.push({ menuItems.push({
key: "beta-switch", key: "beta-switch",
style: { marginLeft: "auto" }, style: {marginLeft: "auto"},
label: ( label: (
<Tooltip <Tooltip
title={`A more modern ${InstanceRenderManager({ title={`A more modern ${InstanceRenderManager({
@@ -627,9 +654,9 @@ function Header({
promanager: t("titles.promanager") promanager: t("titles.promanager")
})} is ready for you to try! You can switch back at any time.`} })} is ready for you to try! You can switch back at any time.`}
> >
<InfoCircleOutlined /> <InfoCircleOutlined/>
<span style={{ marginRight: 8 }}>Try the new app</span> <span style={{marginRight: 8}}>Try the new app</span>
<Switch checked={betaSwitch} onChange={betaSwitchChange} /> <Switch checked={betaSwitch} onChange={betaSwitchChange}/>
</Tooltip> </Tooltip>
) )
}); });

View File

@@ -1,20 +1,17 @@
import { useQuery } from "@apollo/client"; import {useQuery} from "@apollo/client";
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd"; import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { Link } from "react-router-dom"; import {Link} from "react-router-dom";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries"; import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import { import {QUERY_JOBLINE_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
QUERY_JOB_TASKS_PAGINATED,
QUERY_JOBLINE_TASKS_PAGINATED
} from "../../graphql/tasks.queries.js";
import TaskListContainer from "../task-list/task-list.container.jsx"; import TaskListContainer from "../task-list/task-list.container.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -26,9 +23,9 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander); export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) { export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
const { t } = useTranslation(); const {t} = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
@@ -36,8 +33,8 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
} }
}); });
if (loading) return <Skeleton />; if (loading) return <Skeleton/>;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error"/>;
return ( return (
<Row> <Row>
@@ -47,23 +44,23 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
items={ items={
data.parts_order_lines.length > 0 data.parts_order_lines.length > 0
? data.parts_order_lines.map((line) => ({ ? data.parts_order_lines.map((line) => ({
key: line.id, key: line.id,
children: ( children: (
<Space split={<Divider type="vertical" />} wrap> <Space split={<Divider type="vertical"/>} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}> <Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
{line.parts_order.order_number} {line.parts_order.order_number}
</Link> </Link>
<DateFormatter>{line.parts_order.order_date}</DateFormatter> <DateFormatter>{line.parts_order.order_date}</DateFormatter>
{line.parts_order.vendor.name} {line.parts_order.vendor.name}
</Space> </Space>
) )
})) }))
: [ : [
{ {
key: "no-orders", key: "no-orders",
children: t("parts_orders.labels.notyetordered") children: t("parts_orders.labels.notyetordered")
} }
] ]
} }
/>{" "} />{" "}
</Col> </Col>
@@ -73,39 +70,39 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
items={ items={
data.billlines.length > 0 data.billlines.length > 0
? data.billlines.map((line) => ({ ? data.billlines.map((line) => ({
key: line.id, key: line.id,
children: ( children: (
<Row wrap> <Row wrap>
<Col span={4}> <Col span={4}>
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}> <Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
{line.bill.invoice_number} {line.bill.invoice_number}
</Link> </Link>
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
{`${t("billlines.fields.actual_price")}: `} {`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter> <CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span> </span>
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
{`${t("billlines.fields.actual_cost")}: `} {`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter> <CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span> </span>
</Col> </Col>
<Col span={4}> <Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter> <DateFormatter>{line.bill.date}</DateFormatter>
</Col> </Col>
<Col span={4}> {line.bill.vendor.name}</Col> <Col span={4}> {line.bill.vendor.name}</Col>
</Row> </Row>
) )
})) }))
: [ : [
{ {
key: "no-orders", key: "no-orders",
children: t("parts_orders.labels.notyetordered") children: t("parts_orders.labels.notyetordered")
} }
] ]
} }
/> />
</Col> </Col>
@@ -115,27 +112,31 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
items={ items={
data.parts_dispatch_lines.length > 0 data.parts_dispatch_lines.length > 0
? data.parts_dispatch_lines.map((line) => ({ ? data.parts_dispatch_lines.map((line) => ({
key: line.id, key: line.id,
children: ( children: (
<Space split={<Divider type="vertical" />} wrap> <Space split={<Divider type="vertical"/>} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link> <Link
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name} to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
<Space> {bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
{t("parts_dispatch_lines.fields.accepted_at")} <Space>
<DateFormatter>{line.accepted_at}</DateFormatter> {t("parts_dispatch_lines.fields.accepted_at")}
</Space> <DateFormatter>{line.accepted_at}</DateFormatter>
</Space> </Space>
) </Space>
})) )
}))
: { : {
key: "dispatch-lines", key: "dispatch-lines",
children: t("parts_orders.labels.notyetordered") children: t("parts_orders.labels.notyetordered")
} }
} }
/> />
</Col> </Col>
<Col md={24} lg={24}> <Col md={24} lg={24}>
<TaskListContainer currentUser={currentUser} bodyshop={bodyshop} parentJobId={jobid} relationshipType={'joblineid'} relationshipId={jobline.id} query={QUERY_JOBLINE_TASKS_PAGINATED} titleTranslation='tasks.titles.job_tasks'/> <TaskListContainer currentUser={currentUser} bodyshop={bodyshop} parentJobId={jobid}
relationshipType={'joblineid'} relationshipId={jobline.id}
query={QUERY_JOBLINE_TASKS_PAGINATED}
titleTranslation='tasks.titles.job_tasks'/>
</Col> </Col>
</Row> </Row>
); );

View File

@@ -457,7 +457,7 @@ export function JobLinesComponent({
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician} disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
onClick={() => { onClick={() => {
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: {refetch: refetch },
context: { context: {
disableInvNumber: true, disableInvNumber: true,
job: { id: job.id }, job: { id: job.id },
@@ -465,7 +465,7 @@ export function JobLinesComponent({
vendorid: bodyshop.inhousevendorid, vendorid: bodyshop.inhousevendorid,
invoice_number: "ih", invoice_number: "ih",
isinhouse: true, isinhouse: true,
date: new dayjs(), date: dayjs(),
total: 0, total: 0,
billlines: selectedLines.map((p) => { billlines: selectedLines.map((p) => {
return { return {

View File

@@ -154,7 +154,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
setLoading(true); setLoading(true);
form.setFieldsValue({ form.setFieldsValue({
// date: new dayjs(), // date: dayjs(),
// bodyhrs: Math.round(v.bodyhrs * 10) / 10, // bodyhrs: Math.round(v.bodyhrs * 10) / 10,
// painthrs: Math.round(v.painthrs * 10) / 10, // painthrs: Math.round(v.painthrs * 10) / 10,
}); });

View File

@@ -1,35 +1,51 @@
import { DownCircleFilled } from "@ant-design/icons"; import {DownCircleFilled} from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client"; import {useApolloClient, useMutation} from "@apollo/client";
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd"; import {
import React, { useMemo, useState } from "react"; Button,
import { useTranslation } from "react-i18next"; Card,
import { connect } from "react-redux"; Dropdown,
import { Link, useNavigate } from "react-router-dom"; Form,
import { createStructuredSelector } from "reselect"; Input,
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; Modal,
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; notification,
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; Popconfirm,
import { insertAuditTrail } from "../../redux/application/application.actions"; Popover,
import { selectJobReadOnly } from "../../redux/application/application.selectors"; Select,
import { setModalContext } from "../../redux/modals/modals.actions"; Space
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; } from "antd";
import React, {useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {auth, logImEXEvent} from "../../firebase/firebase.utils";
import {
CANCEL_APPOINTMENTS_BY_JOB_ID,
INSERT_MANUAL_APPT
} from "../../graphql/appointments.queries";
import {DELETE_JOB, UPDATE_JOB, VOID_JOB} from "../../graphql/jobs.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import axios from "axios"; import axios from "axios";
import { setEmailOptions } from "../../redux/email/email.actions"; import {setEmailOptions} from "../../redux/email/email.actions";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import {openChatByPhone, setMessage} from "../../redux/messaging/messaging.actions";
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries"; import {GET_CURRENT_QUESTIONSET_ID, INSERT_CSI} from "../../graphql/csi.queries";
import { TemplateList } from "../../utils/TemplateConstants"; import {TemplateList} from "../../utils/TemplateConstants";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import {HasFeatureAccess} from "../feature-wrapper/feature-wrapper.component";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import {DateTimeFormatter} from "../../utils/DateFormatter";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; import JobsDetailHeaderActionsToggleProduction
from "./jobs-detail-header-actions.toggle-production";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -38,38 +54,57 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), setScheduleContext: (context) => dispatch(setModalContext({context: context, modal: "schedule"})),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), setBillEnterContext: (context) => dispatch(setModalContext({
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), context: context,
setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })), modal: "billEnter"
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })), })),
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), setPaymentContext: (context) => dispatch(setModalContext({context: context, modal: "payment"})),
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })), setJobCostingContext: (context) => dispatch(setModalContext({
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })), context: context,
modal: "jobCosting"
})),
setTimeTicketContext: (context) => dispatch(setModalContext({
context: context,
modal: "timeTicket"
})),
setCardPaymentContext: (context) => dispatch(setModalContext({
context: context,
modal: "cardPayment"
})),
insertAuditTrail: ({jobid, operation, type}) => dispatch(insertAuditTrail({
jobid,
operation,
type
})),
setTimeTicketTaskContext: (context) => dispatch(setModalContext({
context: context,
modal: "timeTicketTask"
})),
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)) setMessage: (text) => dispatch(setMessage(text))
}); });
export function JobsDetailHeaderActions({ export function JobsDetailHeaderActions({
job, job,
bodyshop, bodyshop,
currentUser, currentUser,
refetch, refetch,
setScheduleContext, setScheduleContext,
setBillEnterContext, setBillEnterContext,
setPaymentContext, setPaymentContext,
setJobCostingContext, setJobCostingContext,
jobRO, jobRO,
setTimeTicketContext, setTimeTicketContext,
setCardPaymentContext, setCardPaymentContext,
insertAuditTrail, insertAuditTrail,
setEmailOptions, setEmailOptions,
openChatByPhone, openChatByPhone,
setMessage, setMessage,
setTimeTicketTaskContext setTimeTicketTaskContext
}) { }) {
const { t } = useTranslation(); const {t} = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
const history = useNavigate(); const history = useNavigate();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -83,7 +118,7 @@ export function JobsDetailHeaderActions({
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID); const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
const { const {
treatments: { ImEXPay } treatments: {ImEXPay}
} = useSplitTreatments({ } = useSplitTreatments({
attributes: {}, attributes: {},
names: ["ImEXPay"], names: ["ImEXPay"],
@@ -117,7 +152,7 @@ export function JobsDetailHeaderActions({
DuplicateJob( DuplicateJob(
client, client,
job.id, job.id,
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, {defaultOpenStatus: bodyshop.md_ro_statuses.default_imported},
(newJobId) => { (newJobId) => {
history(`/manage/jobs/${newJobId}`); history(`/manage/jobs/${newJobId}`);
notification["success"]({ notification["success"]({
@@ -128,7 +163,7 @@ export function JobsDetailHeaderActions({
); );
const handleDuplicateConfirm = () => const handleDuplicateConfirm = () =>
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => { DuplicateJob(client, job.id, {defaultOpenStatus: bodyshop.md_ro_statuses.default_imported}, (newJobId) => {
history(`/manage/jobs/${newJobId}`); history(`/manage/jobs/${newJobId}`);
notification["success"]({ notification["success"]({
message: t("jobs.successes.duplicated") message: t("jobs.successes.duplicated")
@@ -142,7 +177,7 @@ export function JobsDetailHeaderActions({
try { try {
insertAppointment({ insertAppointment({
variables: { variables: {
apt: { ...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id } apt: {...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id}
}, },
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"] refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"]
}); });
@@ -159,7 +194,7 @@ export function JobsDetailHeaderActions({
const handleDeleteJob = async () => { const handleDeleteJob = async () => {
//delete the job. //delete the job.
const result = await deleteJob({ variables: { id: job.id } }); const result = await deleteJob({variables: {id: job.id}});
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ notification["success"]({
@@ -220,7 +255,7 @@ export function JobsDetailHeaderActions({
}); });
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("csi.successes.created") }); notification["success"]({message: t("csi.successes.created")});
} else { } else {
notification["error"]({ notification["error"]({
message: t("csi.errors.creating", { message: t("csi.errors.creating", {
@@ -375,7 +410,7 @@ export function JobsDetailHeaderActions({
try { try {
QbXmlResponse = await axios.post( QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables", "/accounting/qbxml/receivables",
{ jobIds: [job.id], custDataOnly: true }, {jobIds: [job.id], custDataOnly: true},
{ {
headers: { headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}` Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
@@ -484,7 +519,7 @@ export function JobsDetailHeaderActions({
setIsCancelScheduleModalVisible(false); setIsCancelScheduleModalVisible(false);
}; };
const handleLostSaleFinish = async ({ lost_sale_reason }) => { const handleLostSaleFinish = async ({lost_sale_reason}) => {
const jobUpdate = await cancelAllAppointments({ const jobUpdate = await cancelAllAppointments({
variables: { variables: {
jobid: job.id, jobid: job.id,
@@ -524,10 +559,10 @@ export function JobsDetailHeaderActions({
} }
]} ]}
> >
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item label={t("appointments.fields.note")} name="note"> <Form.Item label={t("appointments.fields.note")} name="note">
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("appointments.fields.start")} label={t("appointments.fields.start")}
@@ -542,7 +577,7 @@ export function JobsDetailHeaderActions({
<FormDateTimePickerComponent <FormDateTimePickerComponent
onBlur={() => { onBlur={() => {
const start = form.getFieldValue("start"); const start = form.getFieldValue("start");
form.setFieldsValue({ end: start.add(30, "minutes") }); form.setFieldsValue({end: start.add(30, "minutes")});
}} }}
/> />
</Form.Item> </Form.Item>
@@ -554,10 +589,10 @@ export function JobsDetailHeaderActions({
required: true required: true
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
({ getFieldValue }) => ({ ({getFieldValue}) => ({
async validator(rule, value) { async validator(rule, value) {
if (value) { if (value) {
const { start } = form.getFieldsValue(); const {start} = form.getFieldsValue();
if (dayjs(start).isAfter(dayjs(value))) { if (dayjs(start).isAfter(dayjs(value))) {
return Promise.reject(t("employees.labels.endmustbeafterstart")); return Promise.reject(t("employees.labels.endmustbeafterstart"));
} else { } else {
@@ -570,7 +605,7 @@ export function JobsDetailHeaderActions({
}) })
]} ]}
> >
<FormDateTimePickerComponent /> <FormDateTimePickerComponent/>
</Form.Item> </Form.Item>
<Form.Item label={t("appointments.fields.color")} name="color"> <Form.Item label={t("appointments.fields.color")} name="color">
<Select> <Select>
@@ -601,7 +636,7 @@ export function JobsDetailHeaderActions({
onClick: () => { onClick: () => {
logImEXEvent("job_header_schedule"); logImEXEvent("job_header_schedule");
setScheduleContext({ setScheduleContext({
actions: { refetch: refetch }, actions: {refetch: refetch},
context: { context: {
jobId: job.id, jobId: job.id,
job: job, job: job,
@@ -645,7 +680,8 @@ export function JobsDetailHeaderActions({
{ {
key: "checklist", key: "checklist",
disabled: !job.converted, disabled: !job.converted,
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link> label: <Link
to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
} }
], ],
rome: "USE_IMEX", rome: "USE_IMEX",
@@ -653,35 +689,35 @@ export function JobsDetailHeaderActions({
{ {
key: "toggleproduction", key: "toggleproduction",
disabled: !job.converted || jobRO, disabled: !job.converted || jobRO,
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} /> label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch}/>
} }
] ]
}), }),
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: "USE_IMEX", rome: "USE_IMEX",
promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) promanager: HasFeatureAccess({featureName: "timetickets", bodyshop})
}) })
? [ ? [
{ {
key: "entertimetickets", key: "entertimetickets",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
label: t("timetickets.actions.enter"), label: t("timetickets.actions.enter"),
onClick: () => { onClick: () => {
logImEXEvent("job_header_enter_time_ticekts"); logImEXEvent("job_header_enter_time_ticekts");
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { context: {
jobId: job.id, jobId: job.id,
created_by: currentUser.displayName created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName) ? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email : currentUser.email
} }
}); });
}
} }
] }
]
: []) : [])
]; ];
@@ -692,7 +728,7 @@ export function JobsDetailHeaderActions({
onClick: () => { onClick: () => {
setTimeTicketTaskContext({ setTimeTicketTaskContext({
actions: {}, actions: {},
context: { jobid: job.id } context: {jobid: job.id}
}); });
}, },
label: t("timetickets.actions.claimtasks") label: t("timetickets.actions.claimtasks")
@@ -708,7 +744,7 @@ export function JobsDetailHeaderActions({
setPaymentContext({ setPaymentContext({
actions: {}, actions: {},
context: { jobid: job.id } context: {jobid: job.id}
}); });
} }
}); });
@@ -723,18 +759,18 @@ export function JobsDetailHeaderActions({
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: {},
context: { jobid: job.id } context: {jobid: job.id}
}); });
} }
}); });
} }
if (HasFeatureAccess({ featureName: "courtesycars" })) { if (HasFeatureAccess({featureName: "courtesycars"})) {
menuItems.push({ menuItems.push({
key: "cccontract", key: "cccontract",
disabled: jobRO || !job.converted, disabled: jobRO || !job.converted,
label: ( label: (
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new"> <Link state={{jobId: job.id}} to="/manage/courtesycars/contracts/new">
{t("menus.jobsactions.newcccontract")} {t("menus.jobsactions.newcccontract")}
</Link> </Link>
) )
@@ -744,17 +780,17 @@ export function JobsDetailHeaderActions({
menuItems.push( menuItems.push(
job.inproduction job.inproduction
? { ? {
key: "removefromproduction", key: "removefromproduction",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.removefromproduction"), label: t("jobs.actions.removefromproduction"),
onClick: () => AddToProduction(client, job.id, refetch, true) onClick: () => AddToProduction(client, job.id, refetch, true)
} }
: { : {
key: "addtoproduction", key: "addtoproduction",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.addtoproduction"), label: t("jobs.actions.addtoproduction"),
onClick: () => AddToProduction(client, job.id, refetch) onClick: () => AddToProduction(client, job.id, refetch)
} }
); );
menuItems.push( menuItems.push(
@@ -810,25 +846,25 @@ export function JobsDetailHeaderActions({
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "bills", bodyshop }) promanager: HasFeatureAccess({featureName: "bills", bodyshop})
}) })
? [ ? [
{ {
key: "postbills", key: "postbills",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.postbills"), label: t("jobs.actions.postbills"),
onClick: () => { onClick: () => {
logImEXEvent("job_header_enter_bills"); logImEXEvent("job_header_enter_bills");
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: {refetch: refetch},
context: { context: {
job: job job: job
} }
}); });
}
} }
] }
]
: []), : []),
{ {
@@ -839,7 +875,7 @@ export function JobsDetailHeaderActions({
const result = await updateJob({ const result = await updateJob({
variables: { variables: {
jobId: job.id, jobId: job.id,
job: { queued_for_parts: true } job: {queued_for_parts: true}
} }
}); });
@@ -889,7 +925,7 @@ export function JobsDetailHeaderActions({
InstanceRenderManager({ InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "export", bodyshop }) promanager: HasFeatureAccess({featureName: "export", bodyshop})
}) })
) { ) {
menuItems.push({ menuItems.push({
@@ -900,7 +936,7 @@ export function JobsDetailHeaderActions({
}); });
} }
if (HasFeatureAccess({ featureName: "csi", bodyshop })) { if (HasFeatureAccess({featureName: "csi", bodyshop})) {
const children = [ const children = [
{ {
key: "email", key: "email",
@@ -930,20 +966,20 @@ export function JobsDetailHeaderActions({
...job.csiinvites.map((item, idx) => { ...job.csiinvites.map((item, idx) => {
return item.completedon return item.completedon
? { ? {
key: idx, key: idx,
label: ( label: (
<Link to={`/manage/shop/csi?responseid=${item.id}`}> <Link to={`/manage/shop/csi?responseid=${item.id}`}>
<DateTimeFormatter>{item.completedon}</DateTimeFormatter> <DateTimeFormatter>{item.completedon}</DateTimeFormatter>
</Link> </Link>
) )
} }
: { : {
key: idx, key: idx,
onClick: () => { onClick: () => {
navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`); navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`);
}, },
label: t("general.actions.copylink") label: t("general.actions.copylink")
}; };
}) })
); );
} }
@@ -963,7 +999,7 @@ export function JobsDetailHeaderActions({
logImEXEvent("job_header_job_costing"); logImEXEvent("job_header_job_costing");
setJobCostingContext({ setJobCostingContext({
actions: { refetch: refetch }, actions: {refetch: refetch},
context: { context: {
jobId: job.id jobId: job.id
} }
@@ -1073,10 +1109,10 @@ export function JobsDetailHeaderActions({
<Dropdown menu={menu} trigger={["click"]} key="changestatus"> <Dropdown menu={menu} trigger={["click"]} key="changestatus">
<Button> <Button>
<span>{t("general.labels.actions")}</span> <span>{t("general.labels.actions")}</span>
<DownCircleFilled /> <DownCircleFilled/>
</Button> </Button>
</Dropdown> </Dropdown>
<Popover content={popOverContent} open={visibility} /> <Popover content={popOverContent} open={visibility}/>
</> </>
); );
} }

View File

@@ -1,16 +1,16 @@
import { SyncOutlined } from "@ant-design/icons"; import {SyncOutlined} from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd"; import {Button, Card, Input, Space, Table, Typography} from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash"; import _ from "lodash";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import {Link, useLocation, useNavigate} from "react-router-dom";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config"; import {pageLimit} from "../../utils/config";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
@@ -23,15 +23,15 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export function JobsList({ bodyshop, refetch, loading, jobs, total }) { export function JobsList({bodyshop, refetch, loading, jobs, total}) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]); const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false); const [searchLoading, setSearchLoading] = useState(false);
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
const { page, sortcolumn, sortorder } = search; const {page, sortcolumn, sortorder} = search;
const history = useNavigate(); const history = useNavigate();
const { t } = useTranslation(); const {t} = useTranslation();
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -54,11 +54,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}> <Link to={"/manage/owners/" + record.ownerid}>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record}/>
</Link> </Link>
) : ( ) : (
<span> <span>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record}/>
</span> </span>
); );
} }
@@ -69,7 +69,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} /> render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id}/>
}, },
{ {
title: t("jobs.fields.ownr_ph2"), title: t("jobs.fields.ownr_ph2"),
@@ -77,7 +77,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} /> render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id}/>
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
@@ -92,7 +92,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
}, },
filteredValue: filter?.status || null, filteredValue: filter?.status || null,
filters: bodyshop.md_ro_statuses.statuses.map((s) => { filters: bodyshop.md_ro_statuses.statuses.map((s) => {
return { text: s, value: [s] }; return {text: s, value: [s]};
}), }),
onFilter: (value, record) => value.includes(record.status) onFilter: (value, record) => value.includes(record.status)
}, },
@@ -178,7 +178,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
delete search.statusFilters; delete search.statusFilters;
} }
setFilter(filters); setFilter(filters);
history({ search: queryString.stringify(search) }); history({search: queryString.stringify(search)});
}; };
useEffect(() => { useEffect(() => {
@@ -210,13 +210,13 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
{search.search && ( {search.search && (
<> <>
<Typography.Title level={4}> <Typography.Title level={4}>
{t("general.labels.searchresults", { search: search.search })} {t("general.labels.searchresults", {search: search.search})}
</Typography.Title> </Typography.Title>
<Button <Button
onClick={() => { onClick={() => {
delete search.search; delete search.search;
delete search.page; delete search.page;
history({ search: queryString.stringify(search) }); history({search: queryString.stringify(search)});
}} }}
> >
{t("general.actions.clear")} {t("general.actions.clear")}
@@ -224,13 +224,13 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
</> </>
)} )}
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined/>
</Button> </Button>
<Input.Search <Input.Search
placeholder={search.search || t("general.labels.search")} placeholder={search.search || t("general.labels.search")}
onSearch={(value) => { onSearch={(value) => {
search.search = value; search.search = value;
history({ search: queryString.stringify(search) }); history({search: queryString.stringify(search)});
searchJobs(value); searchJobs(value);
}} }}
loading={loading || searchLoading} loading={loading || searchLoading}
@@ -244,15 +244,15 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
pagination={ pagination={
search?.search search?.search
? { ? {
pageSize: pageLimit, pageSize: pageLimit,
showSizeChanger: false showSizeChanger: false
} }
: { : {
pageSize: pageLimit, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
showSizeChanger: false showSizeChanger: false
} }
} }
columns={columns} columns={columns}
rowKey="id" rowKey="id"

View File

@@ -1,23 +1,28 @@
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons"; import {
import { useQuery } from "@apollo/client"; BranchesOutlined,
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; ExclamationCircleFilled,
PauseCircleOutlined,
SyncOutlined
} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, {useState} from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import {Link, useLocation, useNavigate} from "react-router-dom";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; import {QUERY_ALL_ACTIVE_JOBS} from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import {onlyUnique} from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import {alphaSort, statusSort} from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { setJoyRideSteps } from "../../redux/application/application.actions"; import {setJoyRideSteps} from "../../redux/application/application.actions";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component"; import {OwnerNameDisplayFunction} from "./../owner-name-display/owner-name-display.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -28,13 +33,13 @@ const mapDispatchToProps = (dispatch) => ({
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps)) setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
}); });
export function JobsList({ bodyshop, setJoyRideSteps }) { export function JobsList({bodyshop, setJoyRideSteps}) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams; const {selected} = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
.slice(-1)[0]; .slice(-1)[0];
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"] statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"]
}, },
@@ -42,36 +47,36 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
nextFetchPolicy: "network-only" nextFetchPolicy: "network-only"
}); });
const [state, setState] = useState({ sortedInfo: {} }); const [state, setState] = useState({sortedInfo: {}});
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null); const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
const { t } = useTranslation(); const {t} = useTranslation();
const history = useNavigate(); const history = useNavigate();
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error"/>;
const jobs = data const jobs = data
? searchText === "" ? searchText === ""
? data.jobs ? data.jobs
: data.jobs.filter( : data.jobs.filter(
(j) => (j) =>
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.comments || "").toLowerCase().includes(searchText.toLowerCase()) || (j.comments || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) || (j.plate_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) || (j.est_ct_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) || (j.est_ct_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
) )
: []; : [];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, sortedInfo: sorter }); setState({...state, sortedInfo: sorter});
setFilter(filters); setFilter(filters);
}; };
@@ -102,12 +107,12 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
<Space> <Space>
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? ( {record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" /> <ExclamationCircleFilled className="production-alert"/>
) : null} ) : null}
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />} {record.suspended && <PauseCircleOutlined style={{color: "orangered"}}/>}
{record.iouparent && ( {record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}> <Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{color: "orangered"}}/>
</Tooltip> </Tooltip>
)} )}
</Space> </Space>
@@ -126,11 +131,11 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}> <Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record}/>
</Link> </Link>
) : ( ) : (
<span> <span>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record}/>
</span> </span>
); );
} }
@@ -141,7 +146,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
responsive: ["md"], responsive: ["md"],
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} /> render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id}/>
}, },
{ {
title: t("jobs.fields.ownr_ph2"), title: t("jobs.fields.ownr_ph2"),
@@ -149,7 +154,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
responsive: ["md"], responsive: ["md"],
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} /> render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id}/>
}, },
{ {
@@ -344,7 +349,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
})} })}
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined/>
</Button> </Button>
<Input.Search <Input.Search
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
@@ -359,7 +364,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
> >
<Table <Table
loading={loading} loading={loading}
pagination={{ defaultPageSize: 50 }} pagination={{defaultPageSize: 50}}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={jobs} dataSource={jobs}

View File

@@ -13,7 +13,7 @@ import {logImEXEvent} from "../../firebase/firebase.utils";
import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries"; import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries";
import {selectJobReadOnly} from "../../redux/application/application.selectors"; import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions"; import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {DateFormatter} from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort} from "../../utils/sorters"; import {alphaSort} from "../../utils/sorters";
@@ -33,11 +33,13 @@ import {FaTasks} from "react-icons/fa";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), setBillEnterContext: (context) => dispatch(setModalContext({
context: context,
modal: "billEnter"
})),
setPartsReceiveContext: (context) => dispatch(setModalContext({ setPartsReceiveContext: (context) => dispatch(setModalContext({
context: context, context: context,
modal: "partsReceive" modal: "partsReceive"
@@ -54,8 +56,7 @@ export function PartsOrderListTableComponent({
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
setTaskUpsertContext, setTaskUpsertContext,
currentUser }) {
}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
.slice(-1)[0]; .slice(-1)[0];
@@ -70,9 +71,9 @@ export function PartsOrderListTableComponent({
}; };
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%"; const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers; const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job }); const Templates = TemplateList("partsorder", {job});
const { t } = useTranslation(); const {t} = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {} sortedInfo: {}
}); });
@@ -83,13 +84,13 @@ export function PartsOrderListTableComponent({
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery; const {refetch} = billsQuery;
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space direction='horizontal' wrap> <Space direction='horizontal' wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button onClick={() => handleOnRowClick(record)}>
<EyeFilled /> <EyeFilled/>
</Button> </Button>
)} )}
@@ -98,7 +99,7 @@ export function PartsOrderListTableComponent({
onClick={() => { onClick={() => {
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({ setPartsReceiveContext({
actions: { refetch: refetch }, actions: {refetch: refetch},
context: { context: {
jobId: job.id, jobId: job.id,
job: job, job: job,
@@ -135,11 +136,11 @@ export function PartsOrderListTableComponent({
//Delete the parts return.! //Delete the parts return.!
await deletePartsOrder({ await deletePartsOrder({
variables: { partsOrderId: record.id }, variables: {partsOrderId: record.id},
update(cache) { update(cache) {
cache.modify({ cache.modify({
fields: { fields: {
parts_orders(existingPartsOrders, { readField }) { parts_orders(existingPartsOrders, {readField}) {
return existingPartsOrders.filter((billref) => record.id !== readField("id", billref)); return existingPartsOrders.filter((billref) => record.id !== readField("id", billref));
} }
} }
@@ -149,7 +150,7 @@ export function PartsOrderListTableComponent({
}} }}
> >
<Button disabled={jobRO}> <Button disabled={jobRO}>
<DeleteFilled /> <DeleteFilled/>
</Button> </Button>
</Popconfirm> </Popconfirm>
<FeatureWrapperComponent featureName="bills" noauth={() => null}> <FeatureWrapperComponent featureName="bills" noauth={() => null}>
@@ -159,7 +160,7 @@ export function PartsOrderListTableComponent({
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: {refetch: refetch},
context: { context: {
job: job, job: job,
bill: { bill: {
@@ -179,7 +180,7 @@ export function PartsOrderListTableComponent({
? pol.jobline.part_type ? pol.jobline.part_type
: null : null
: responsibilityCenters.defaults && : responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null) (responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null : null
}; };
}) })
@@ -194,7 +195,7 @@ export function PartsOrderListTableComponent({
<PrintWrapper <PrintWrapper
templateObject={{ templateObject={{
name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key, name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key,
variables: { id: record.id } variables: {id: record.id}
}} }}
messageObject={{ messageObject={{
subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject, subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject,
@@ -235,7 +236,7 @@ export function PartsOrderListTableComponent({
key: "return", key: "return",
sorter: (a, b) => a.return - b.return, sorter: (a, b) => a.return - b.return,
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.return} /> render: (text, record) => <Checkbox checked={record.return}/>
}, },
{ {
title: t("parts_orders.fields.deliver_by"), title: t("parts_orders.fields.deliver_by"),
@@ -259,7 +260,7 @@ export function PartsOrderListTableComponent({
]; ];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({...state, filteredInfo: filters, sortedInfo: sorter});
}; };
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder); const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
@@ -290,15 +291,15 @@ export function PartsOrderListTableComponent({
}, },
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [ ? [
{ {
title: t("parts_orders.fields.cost"), title: t("parts_orders.fields.cost"),
dataIndex: "cost", dataIndex: "cost",
key: "cost", key: "cost",
sorter: (a, b) => a.cost - b.cost, sorter: (a, b) => a.cost - b.cost,
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter> render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
} }
] ]
: []), : []),
{ {
title: t("parts_orders.fields.part_type"), title: t("parts_orders.fields.part_type"),
@@ -326,19 +327,19 @@ export function PartsOrderListTableComponent({
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return ...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [ ? [
{ {
title: t("parts_orders.fields.cm_received"), title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received", dataIndex: "cm_received",
key: "cm_received", key: "cm_received",
render: (text, record) => ( render: (text, record) => (
<PartsOrderCmReceived <PartsOrderCmReceived
orderLineId={record.id} orderLineId={record.id}
checked={record.cm_received} checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id} partsorderid={selectedPartsOrderRecord.id}
/> />
) )
} }
] ]
: []), : []),
{ {
title: t("parts_orders.fields.backordered_on"), title: t("parts_orders.fields.backordered_on"),
@@ -387,7 +388,8 @@ export function PartsOrderListTableComponent({
return ( return (
<div> <div>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} /> <PageHeader title={record && `${record.vendor.name} - ${record.order_number}`}
extra={recordActions(record)}/>
<Table <Table
scroll={{ scroll={{
x: true //y: "50rem" x: true //y: "50rem"
@@ -397,7 +399,7 @@ export function PartsOrderListTableComponent({
dataSource={record.parts_order_lines} dataSource={record.parts_order_lines}
/> />
<DataLabel label={t("parts_orders.fields.comments")}> <DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div> <div style={{whiteSpace: "pre"}}>{record.comments}</div>
</DataLabel> </DataLabel>
</div> </div>
); );
@@ -407,10 +409,10 @@ export function PartsOrderListTableComponent({
? searchText === "" ? searchText === ""
? parts_orders ? parts_orders
: parts_orders.filter( : parts_orders.filter(
(b) => (b) =>
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || (b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
) )
: []; : [];
return ( return (
@@ -419,7 +421,7 @@ export function PartsOrderListTableComponent({
extra={ extra={
<Space wrap> <Space wrap>
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined/>
</Button> </Button>
<Input.Search <Input.Search
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
@@ -432,7 +434,7 @@ export function PartsOrderListTableComponent({
</Space> </Space>
} }
> >
<PartsReceiveModalContainer /> <PartsReceiveModalContainer/>
<Drawer <Drawer
placement="right" placement="right"
onClose={() => handleOnRowClick(null)} onClose={() => handleOnRowClick(null)}

View File

@@ -158,7 +158,7 @@ export function PartsOrderModalContainer({
vendorid: bodyshop.inhousevendorid, vendorid: bodyshop.inhousevendorid,
invoice_number: "ih", invoice_number: "ih",
isinhouse: true, isinhouse: true,
date: new dayjs(), date: dayjs(),
total: 0, total: 0,
billlines: values.parts_order_lines.data.map((p) => { billlines: values.parts_order_lines.data.map((p) => {
return { return {

View File

@@ -46,7 +46,7 @@ const DueDateRecord = ({dueDate}) => {
* @constructor * @constructor
*/ */
const PriorityLabel = ({priority}) => { const PriorityLabel = ({priority}) => {
switch(priority) { switch (priority) {
case 1: case 1:
return <div> return <div>
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/> High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
@@ -89,7 +89,7 @@ function TaskListComponent({
toggleDeletedStatus, toggleDeletedStatus,
relationshipType, relationshipType,
relationshipId, relationshipId,
onlyMine , onlyMine,
parentJobId parentJobId
}) { }) {
const {t} = useTranslation(); const {t} = useTranslation();
@@ -108,7 +108,7 @@ function TaskListComponent({
const history = useNavigate(); const history = useNavigate();
const columns = []; const columns = [];
if (!onlyMine) { if (!onlyMine) {
columns.push( columns.push(
{ {
@@ -118,14 +118,14 @@ function TaskListComponent({
width: '8%', width: '8%',
sorter: true, sorter: true,
sortOrder: sortcolumn === "assigned_to" && sortorder, sortOrder: sortcolumn === "assigned_to" && sortorder,
render: (text, record) => { render: (text, record) => {
const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to); const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to);
return employee ? `${ employee.first_name} ${ employee.last_name}` : t("general.labels.na"); return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
} }
} }
); );
} }
columns.push( columns.push(
{ {
title: t("tasks.fields.job.ro_number"), title: t("tasks.fields.job.ro_number"),
@@ -134,7 +134,8 @@ function TaskListComponent({
width: '8%', width: '8%',
render: (text, record) => render: (text, record) =>
record.job record.job
? <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link> ? <Link
to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
: t("general.labels.na") : t("general.labels.na")
}, },
{ {
@@ -151,7 +152,8 @@ function TaskListComponent({
width: '8%', width: '8%',
render: (text, record) => render: (text, record) =>
record.parts_order record.parts_order
? <Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}> ? <Link
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name {record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}` ? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
: t("general.labels.na")} : t("general.labels.na")}
@@ -186,7 +188,7 @@ function TaskListComponent({
sorter: true, sorter: true,
sortOrder: sortcolumn === "due_date" && sortorder, sortOrder: sortcolumn === "due_date" && sortorder,
width: '8%', width: '8%',
render: (text, record) => <DueDateRecord dueDate={record.due_date} />, render: (text, record) => <DueDateRecord dueDate={record.due_date}/>,
}, },
{ {
title: t("tasks.fields.remind_at"), title: t("tasks.fields.remind_at"),
@@ -195,7 +197,7 @@ function TaskListComponent({
sorter: true, sorter: true,
sortOrder: sortcolumn === "remind_at" && sortorder, sortOrder: sortcolumn === "remind_at" && sortorder,
width: '8%', width: '8%',
render: (text, record) => <DueDateRecord dueDate={record.remind_at} />, render: (text, record) => <DueDateRecord dueDate={record.remind_at}/>,
}, },
{ {
title: t("tasks.fields.priority"), title: t("tasks.fields.priority"),
@@ -267,9 +269,9 @@ function TaskListComponent({
} }
history({search: queryString.stringify(search)}); history({search: queryString.stringify(search)});
}; };
const expandableRow = (record) => { const expandableRow = (record) => {
return <Card title={t('tasks.fields.description')} size='small'> return <Card title={t('tasks.fields.description')} size='small'>
{record.description} {record.description}
</Card> </Card>
}; };
@@ -289,7 +291,7 @@ function TaskListComponent({
checked={mine === "true"} checked={mine === "true"}
onChange={(value) => handleSwitchChange('mine', value)} onChange={(value) => handleSwitchChange('mine', value)}
/> />
)} )}
<Switch <Switch
checkedChildren={<CheckCircleFilled/>} checkedChildren={<CheckCircleFilled/>}
unCheckedChildren={<CheckCircleOutlined/>} unCheckedChildren={<CheckCircleOutlined/>}

View File

@@ -12,7 +12,16 @@ import TaskListComponent from "./task-list.component.jsx";
import {notification} from "antd"; import {notification} from "antd";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
export default function TaskListContainer({bodyshop, titleTranslation ,query, relationshipType, relationshipId, currentUser, onlyMine, parentJobId}) { export default function TaskListContainer({
bodyshop,
titleTranslation,
query,
relationshipType,
relationshipId,
currentUser,
onlyMine,
parentJobId
}) {
const {t} = useTranslation(); const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams; const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams;
@@ -26,7 +35,7 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
[relationshipType]: relationshipId, [relationshipType]: relationshipId,
deleted: deleted === 'true', deleted: deleted === 'true',
completed: completed === "true", completed: completed === "true",
assigned_to: mine === "true" ? currentUser.email: undefined, // replace currentUserID with the actual ID of the current user assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
offset: page ? (page - 1) * pageLimit : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit, limit: pageLimit,
order: [ order: [
@@ -56,7 +65,7 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
window.removeEventListener('taskUpdated', handleTaskUpdated); window.removeEventListener('taskUpdated', handleTaskUpdated);
}; };
}, [refetch]); }, [refetch]);
/** /**
* Toggle task completed mutation * Toggle task completed mutation
*/ */
@@ -81,8 +90,8 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
// refetch().catch((e) => { // refetch().catch((e) => {
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`); // console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
// }); // });
window.dispatchEvent( new CustomEvent('taskUpdated', { window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: { message: 'A task has been completed.' }, detail: {message: 'A task has been completed.'},
})); }));
notification["success"]({ notification["success"]({
message: t("tasks.successes.completed"), message: t("tasks.successes.completed"),
@@ -115,8 +124,8 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r
deleted_at: deleted_at deleted_at: deleted_at
} }
}); });
window.dispatchEvent( new CustomEvent('taskUpdated', { window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: { message: 'A task has been deleted.' }, detail: {message: 'A task has been deleted.'},
})); }));
// refetch().catch((e) => { // refetch().catch((e) => {
// console.error(`Something went wrong fetching tasks: ${e.message || ''}`); // console.error(`Something went wrong fetching tasks: ${e.message || ''}`);

View File

@@ -56,8 +56,8 @@ export function TaskUpsertModalContainer({
}); });
const { const {
loading: taskLoading, loading: taskLoading,
error: taskError, error: taskError,
data: taskData data: taskData
} = useQuery(QUERY_GET_TASK_BY_ID, { } = useQuery(QUERY_GET_TASK_BY_ID, {
variables: {id: taskId}, variables: {id: taskId},
@@ -139,7 +139,7 @@ export function TaskUpsertModalContainer({
ReplyTo: { ReplyTo: {
Email: 'noreply@imex.online' Email: 'noreply@imex.online'
}, },
to: values.assigned_to, to: values.assigned_to,
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`, subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateStrings: { templateStrings: {
header: values.title, header: values.title,
@@ -189,7 +189,7 @@ export function TaskUpsertModalContainer({
// }); // });
// }, // },
})).data.insert_tasks.returning[0].id; })).data.insert_tasks.returning[0].id;
if (refetch) await refetch(); if (refetch) await refetch();
form.resetFields(); form.resetFields();
@@ -255,7 +255,8 @@ export function TaskUpsertModalContainer({
destroyOnClose destroyOnClose
> >
<Form form={form} onFinish={handleFinish} layout="vertical"> <Form form={form} onFinish={handleFinish} layout="vertical">
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)} error={error} data={data} <TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)}
error={error} data={data}
selectedJobId={selectedJobId} selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId} setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}/> selectedJobDetails={selectedJobDetails}/>

View File

@@ -1,4 +1,4 @@
import { gql } from "@apollo/client"; import {gql} from "@apollo/client";
export const INSERT_NEW_BILL = gql` export const INSERT_NEW_BILL = gql`
mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) { mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) {

View File

@@ -1,4 +1,4 @@
import { gql } from "@apollo/client"; import {gql} from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
@@ -2049,12 +2049,12 @@ export const generate_UPDATE_JOB_KANBAN = (
}`; }`;
return gql` return gql`
mutation UPDATE_JOB_KANBAN { mutation UPDATE_JOB_KANBAN {
${oldChildId ? oldChildQuery : ""} ${oldChildId ? oldChildQuery : ""}
${movedId ? movedQuery : ""} ${movedId ? movedQuery : ""}
${newChildId ? newChildQuery : ""} ${newChildId ? newChildQuery : ""}
} }
`; `;
}; };
export const QUERY_JOB_LBR_ADJUSTMENTS = gql` export const QUERY_JOB_LBR_ADJUSTMENTS = gql`

View File

@@ -386,28 +386,28 @@ export const MUTATION_INSERT_NEW_TASK = gql`
* @type {DocumentNode} * @type {DocumentNode}
*/ */
export const MUTATION_UPDATE_TASK = gql` export const MUTATION_UPDATE_TASK = gql`
mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) { mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) { update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
returning { returning {
id id
created_at created_at
updated_at updated_at
title title
description description
deleted deleted
deleted_at deleted_at
due_date due_date
created_by created_by
assigned_to assigned_to
completed completed
completed_at completed_at
remind_at remind_at
priority priority
jobid jobid
joblineid joblineid
partsorderid partsorderid
billid billid
} }
}
} }
}
` `

View File

@@ -91,7 +91,7 @@ export function JobsDetailPage({
job, job,
mutationUpdateJob, mutationUpdateJob,
handleSubmit, handleSubmit,
currentUser, currentUser,
insertAuditTrail, insertAuditTrail,
refetch refetch
}) { }) {
@@ -120,7 +120,7 @@ export function JobsDetailPage({
window.removeEventListener('taskUpdated', handleTaskUpdated); window.removeEventListener('taskUpdated', handleTaskUpdated);
}; };
}, [refetch]); }, [refetch]);
//useKeyboardSaveShortcut(form.submit); //useKeyboardSaveShortcut(form.submit);
const handleFinish = async (values) => { const handleFinish = async (values) => {
@@ -406,15 +406,19 @@ export function JobsDetailPage({
key: 'tasks', key: 'tasks',
icon: <FaTasks/>, icon: <FaTasks/>,
label: <Space direction='horizontal'> label: <Space direction='horizontal'>
{t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />} {t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 &&
<Badge count={job.tasks_aggregate.aggregate.count}/>}
</Space>, </Space>,
children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop} relationshipType={'jobid'} relationshipId={job.id} query={QUERY_JOB_TASKS_PAGINATED} titleTranslation='tasks.titles.job_tasks'/> children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop}
relationshipType={'jobid'} relationshipId={job.id}
query={QUERY_JOB_TASKS_PAGINATED}
titleTranslation='tasks.titles.job_tasks'/>
}, },
]} ]}
/> />
</Form> </Form>
</div> </div>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage); export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);

View File

@@ -1,4 +1,4 @@
import { FloatButton, Layout, Spin, Collapse, Button, Space, Tag } from "antd"; import {Button, Collapse, FloatButton, Layout, Space, Spin, Tag} from "antd";
// import preval from "preval.macro"; // import preval from "preval.macro";
import React, {lazy, Suspense, useEffect, useState} from "react"; import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
@@ -20,8 +20,8 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
import PrintCenterModalContainer import PrintCenterModalContainer
from "../../components/print-center-modal/print-center-modal.container"; from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { requestForToken } from "../../firebase/firebase.utils"; import {requestForToken} from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import {selectBodyshop, selectInstanceConflict} from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import {setJoyRideFinished} from "../../redux/application/application.actions.js"; import {setJoyRideFinished} from "../../redux/application/application.actions.js";
@@ -106,7 +106,7 @@ const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx")); const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container")); const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
const { Content, Footer } = Layout; const {Content, Footer} = Layout;
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict, conflict: selectInstanceConflict,
@@ -119,8 +119,8 @@ const mapDispatchToProps = (dispatch) => ({
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps)) setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
}); });
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) { export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished}) {
const { t } = useTranslation(); const {t} = useTranslation();
const [chatVisible] = useState(false); const [chatVisible] = useState(false);
const [tours, setTours] = useState([]); const [tours, setTours] = useState([]);
@@ -156,115 +156,115 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
/> />
} }
> >
<PaymentModalContainer /> <PaymentModalContainer/>
<CardPaymentModalContainer /> <CardPaymentModalContainer/>
<TaskUpsertModalContainer/> <TaskUpsertModalContainer/>
<BreadCrumbs /> <BreadCrumbs/>
<BillEnterModalContainer /> <BillEnterModalContainer/>
<JobCostingModal /> <JobCostingModal/>
<ReportCenterModal /> <ReportCenterModal/>
<EmailOverlayContainer /> <EmailOverlayContainer/>
<TimeTicketModalContainer /> <TimeTicketModalContainer/>
<TimeTicketModalTask /> <TimeTicketModalTask/>
<PrintCenterModalContainer /> <PrintCenterModalContainer/>
<Routes> <Routes>
<Route path="/_test" element={<TestComponent />} /> <Route path="/_test" element={<TestComponent/>}/>
<Route path="/" element={<ManageRootPage />} /> <Route path="/" element={<ManageRootPage/>}/>
<Route <Route
path="/jobs" path="/jobs"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsPage /> <JobsPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId/intake" path="/jobs/:jobId/intake"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobIntake /> <JobIntake/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId/deliver" path="/jobs/:jobId/deliver"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobDeliver /> <JobDeliver/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId/checklist" path="/jobs/:jobId/checklist"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobChecklistView /> <JobChecklistView/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId/close" path="/jobs/:jobId/close"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsClose /> <JobsClose/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId/admin" path="/jobs/:jobId/admin"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsAdmin /> <JobsAdmin/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/all" path="/jobs/all"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<AllJobs /> <AllJobs/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/ready" path="/jobs/ready"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ReadyJobs /> <ReadyJobs/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/new" path="/jobs/new"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsCreateContainerPage /> <JobsCreateContainerPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/jobs/:jobId" path="/jobs/:jobId"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsDetailPage /> <JobsDetailPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/temporarydocs/" path="/temporarydocs/"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<TempDocs /> <TempDocs/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path='/tasks/mytasks' path='/tasks/mytasks'
element={ element={
<Suspense fallback={<Spin/>}> <Suspense fallback={<Spin/>}>
<MyTasksPage/> <MyTasksPage/>
</Suspense>} </Suspense>}
/> />
<Route <Route
path='/tasks/alltasks' path='/tasks/alltasks'
@@ -276,144 +276,144 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
<Route <Route
path="/inventory/" path="/inventory/"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<InventoryListPage /> <InventoryListPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/" path="/courtesycars/"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<CourtesyCarsPage /> <CourtesyCarsPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/new" path="/courtesycars/new"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<CourtesyCarCreateContainer /> <CourtesyCarCreateContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/contracts" path="/courtesycars/contracts"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ContractsList /> <ContractsList/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/contracts/new" path="/courtesycars/contracts/new"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ContractCreatePage /> <ContractCreatePage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/contracts/:contractId" path="/courtesycars/contracts/:contractId"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ContractDetailPage /> <ContractDetailPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/courtesycars/:ccId" path="/courtesycars/:ccId"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<CourtesyCarDetailContainer /> <CourtesyCarDetailContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/profile" path="/profile"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ProfilePage /> <ProfilePage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/vehicles" path="/vehicles"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<VehiclesContainer /> <VehiclesContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/production/list" path="/production/list"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ProductionListPage /> <ProductionListPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/production/board" path="/production/board"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ProductionBoardPage /> <ProductionBoardPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/vehicles/:vehId" path="/vehicles/:vehId"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<VehiclesDetailContainer /> <VehiclesDetailContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/bills" path="/bills"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<BillsListPage /> <BillsListPage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/owners" path="/owners"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<OwnersContainer /> <OwnersContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/owners/:ownerId" path="/owners/:ownerId"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<OwnersDetailContainer /> <OwnersDetailContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/schedule" path="/schedule"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ScheduleContainer /> <ScheduleContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/available" path="/available"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<JobsAvailablePage /> <JobsAvailablePage/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/shop" path="/shop"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ShopPage /> <ShopPage/>
</Suspense> </Suspense>
} }
/> />
@@ -426,16 +426,16 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
<Route <Route
path="/shop/vendors" path="/shop/vendors"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ShopVendorPageContainer /> <ShopVendorPageContainer/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/shop/csi" path="/shop/csi"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ShopCsiPageContainer /> <ShopCsiPageContainer/>
</Suspense> </Suspense>
} }
/> />
@@ -443,8 +443,8 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
<Route <Route
path="/accounting/qbo" path="/accounting/qbo"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<AccountingQboCallback /> <AccountingQboCallback/>
</Suspense> </Suspense>
} }
/> />
@@ -452,56 +452,56 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
<Route <Route
path="/accounting/receivables" path="/accounting/receivables"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<AccountingReceivables /> <AccountingReceivables/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/accounting/payables" path="/accounting/payables"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<AccountingPayables /> <AccountingPayables/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/accounting/payments" path="/accounting/payments"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<AccountingPayments /> <AccountingPayments/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/accounting/exportlogs" path="/accounting/exportlogs"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ExportLogs /> <ExportLogs/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/ttapprovals" path="/ttapprovals"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<TtApprovals /> <TtApprovals/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/partsqueue" path="/partsqueue"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<PartsQueue /> <PartsQueue/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/phonebook" path="/phonebook"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<Phonebook /> <Phonebook/>
</Suspense> </Suspense>
} }
/> />
@@ -509,65 +509,65 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
<Route <Route
path="/payments" path="/payments"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<PaymentsAll /> <PaymentsAll/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/shiftclock" path="/shiftclock"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<ShiftClock /> <ShiftClock/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/scoreboard" path="/scoreboard"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<Scoreboard /> <Scoreboard/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/timetickets" path="/timetickets"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<TimeTicketsAll /> <TimeTicketsAll/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/help" path="/help"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<Help /> <Help/>
</Suspense> </Suspense>
} }
/> />
<Route path="/emailtest" element={<EmailTest />} /> <Route path="/emailtest" element={<EmailTest/>}/>
<Route <Route
path="/dashboard" path="/dashboard"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<Dashboard /> <Dashboard/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/dms" path="/dms"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<Dms /> <Dms/>
</Suspense> </Suspense>
} }
/> />
<Route <Route
path="/dmsap" path="/dmsap"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin/>}>
<DmsPayables /> <DmsPayables/>
</Suspense> </Suspense>
} }
/> />
@@ -577,16 +577,16 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
let PageContent; let PageContent;
if (conflict) PageContent = <ConflictComponent />; if (conflict) PageContent = <ConflictComponent/>;
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />; else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent/>;
else PageContent = AppRouteTable; else PageContent = AppRouteTable;
return ( return (
<> <>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} /> <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible}/>
<Layout style={{ minHeight: "100vh" }} className="layout-container"> <Layout style={{minHeight: "100vh"}} className="layout-container">
<UpdateAlert /> <UpdateAlert/>
<HeaderContainer /> <HeaderContainer/>
<Content className="content-container"> <Content className="content-container">
<Joyride <Joyride
debug debug
@@ -603,12 +603,12 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
} }
}} }}
/> />
<PartnerPingComponent /> <PartnerPingComponent/>
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog> <Sentry.ErrorBoundary fallback={<ErrorBoundary/>} showDialog>
{PageContent} {PageContent}
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} /> <FloatButton.BackTop style={{right: 100, bottom: 25}}/>
</Content> </Content>
<Footer> <Footer>
<div <div
@@ -620,7 +620,7 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
margin: "1rem 0rem" margin: "1rem 0rem"
}} }}
> >
<div style={{ display: "flex" }}> <div style={{display: "flex"}}>
<div> <div>
{`${InstanceRenderManager({ {`${InstanceRenderManager({
imex: t("titles.imexonline"), imex: t("titles.imexonline"),
@@ -628,9 +628,9 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
promanager: t("titles.promanager") promanager: t("titles.promanager")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`} })} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div> </div>
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} /> <div id="noticeable-widget" style={{marginLeft: "1rem"}}/>
</div> </div>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}> <Link to="/disclaimer" target="_blank" style={{color: "#ccc"}}>
Disclaimer & Notices Disclaimer & Notices
</Link> </Link>
</div> </div>
@@ -647,7 +647,8 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
Get Tours Get Tours
</Button> </Button>
{tours.map((tour) => ( {tours.map((tour) => (
<Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}> <Tag key={tour.id}
onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
{tour.name} {tour.name}
</Tag> </Tag>
))} ))}

View File

@@ -64,7 +64,8 @@ export function MyTasksPageContainer({
}, [setTaskUpsertContext]); }, [setTaskUpsertContext]);
return ( return (
<TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser} bodyshop={bodyshop}/> <TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser}
bodyshop={bodyshop}/>
); );
} }

View File

@@ -38,7 +38,8 @@ export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, set
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
return ( return (
<TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser} bodyshop={bodyshop}/> <TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser}
bodyshop={bodyshop}/>
); );
} }

View File

@@ -1,6 +1,6 @@
export const TaskPageTypes = { export const TaskPageTypes = {
MY_TASKS: 'myTasks', MY_TASKS: 'myTasks',
ALL_TASKS: 'allTasks', ALL_TASKS: 'allTasks',
}; };
export default TaskPageTypes; export default TaskPageTypes;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,9 @@ const moment = require("moment");
// TODO use InstanceDynamicManager on the footer default to prevent the word IMEX from being hardcoded // TODO use InstanceDynamicManager on the footer default to prevent the word IMEX from being hardcoded
const generateEmailTemplate = (strings) => { const generateEmailTemplate = (strings) => {
let now = () =>moment().format('MM/DD/YYYY @ hh:mm a'); let now = () => moment().format('MM/DD/YYYY @ hh:mm a');
return ` return `
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">

View File

@@ -5,13 +5,13 @@ require("dotenv").config({
const axios = require("axios"); const axios = require("axios");
let nodemailer = require("nodemailer"); let nodemailer = require("nodemailer");
let aws = require("@aws-sdk/client-ses"); let aws = require("@aws-sdk/client-ses");
let { defaultProvider } = require("@aws-sdk/credential-provider-node"); let {defaultProvider} = require("@aws-sdk/credential-provider-node");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const {isObject} = require("lodash"); const {isObject} = require("lodash");
const generateEmailTemplate = require('./generateTemplate'); const generateEmailTemplate = require('./generateTemplate');
const ses = new aws.SES({ const ses = new aws.SES({
// The key apiVersion is no longer supported in v3, and can be removed. // The key apiVersion is no longer supported in v3, and can be removed.
@@ -25,10 +25,10 @@ const ses = new aws.SES({
}); });
let transporter = nodemailer.createTransport({ let transporter = nodemailer.createTransport({
SES: { ses, aws } SES: {ses, aws}
}); });
exports.sendServerEmail = async function ({ subject, text }) { exports.sendServerEmail = async function ({subject, text}) {
if (process.env.NODE_ENV === undefined) return; if (process.env.NODE_ENV === undefined) return;
try { try {
transporter.sendMail( transporter.sendMail(
@@ -60,7 +60,7 @@ exports.sendServerEmail = async function ({ subject, text }) {
logger.log("server-email-failure", "error", null, null, error); logger.log("server-email-failure", "error", null, null, error);
} }
}; };
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) { exports.sendTaskEmail = async function ({to, subject, text, attachments}) {
try { try {
transporter.sendMail( transporter.sendMail(
{ {
@@ -125,12 +125,12 @@ exports.sendEmail = async (req, res) => {
attachments: attachments:
[ [
...((req.body.attachments && ...((req.body.attachments &&
req.body.attachments.map((a) => { req.body.attachments.map((a) => {
return { return {
filename: a.filename, filename: a.filename,
path: a.path path: a.path
}; };
})) || })) ||
[]), []),
...downloadedMedia.map((a) => { ...downloadedMedia.map((a) => {
return { return {
@@ -157,7 +157,7 @@ exports.sendEmail = async (req, res) => {
replyTo: req.body.ReplyTo.Email, replyTo: req.body.ReplyTo.Email,
to: req.body.to, to: req.body.to,
cc: req.body.cc, cc: req.body.cc,
subject: req.body.subject, subject: req.body.subject,
templateStrings: req.body.templateStrings templateStrings: req.body.templateStrings
// info, // info,
}); });
@@ -186,14 +186,14 @@ exports.sendEmail = async (req, res) => {
subject: req.body.subject, subject: req.body.subject,
bodyshopid: req.body.bodyshopid bodyshopid: req.body.bodyshopid
}); });
res.status(500).json({ success: false, error: err }); res.status(500).json({success: false, error: err});
} }
} }
); );
}; };
async function getImage(imageUrl) { async function getImage(imageUrl) {
let image = await axios.get(imageUrl, { responseType: "arraybuffer" }); let image = await axios.get(imageUrl, {responseType: "arraybuffer"});
let raw = Buffer.from(image.data).toString("base64"); let raw = Buffer.from(image.data).toString("base64");
return "data:" + image.headers["content-type"] + ";base64," + raw; return "data:" + image.headers["content-type"] + ";base64," + raw;
} }
@@ -281,10 +281,10 @@ exports.emailBounce = async function (req, res) {
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. })} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
${body.bounce?.bouncedRecipients.map( ${body.bounce?.bouncedRecipients.map(
(r) => (r) =>
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode} `Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
` `
)} )}
` `
}, },
(err, info) => { (err, info) => {