- 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,7 +148,7 @@ 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
@@ -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,13 +64,28 @@ 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({
@@ -77,16 +101,16 @@ function Header({
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,18 +358,18 @@ 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>
} }
] ]
@@ -353,7 +378,7 @@ function Header({
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "scoreboard", bodyshop }) promanager: HasFeatureAccess({featureName: "scoreboard", bodyshop})
}) })
? [ ? [
{ {
@@ -361,7 +386,7 @@ function Header({
}, },
{ {
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>
} }
] ]
@@ -370,17 +395,17 @@ function Header({
}, },
{ {
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>
} }
] ]
@@ -393,23 +418,25 @@ function Header({
? [ ? [
{ {
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", key: "newcontract",
icon: <FileAddFilled />, icon: <FileAddFilled/>,
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link> label: <Link
to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
} }
] ]
} }
@@ -420,7 +447,7 @@ function Header({
? [ ? [
{ {
key: "accounting", key: "accounting",
icon: <DollarCircleFilled />, icon: <DollarCircleFilled/>,
label: t("menus.header.accounting"), label: t("menus.header.accounting"),
children: accountingChildren children: accountingChildren
} }
@@ -428,18 +455,18 @@ function Header({
: []), : []),
{ {
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>
} }
] ]
@@ -447,7 +474,7 @@ function Header({
{ {
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,18 +528,18 @@ 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>
@@ -49,7 +46,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
? 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>
@@ -117,8 +114,9 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
? 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
to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name} {bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
<Space> <Space>
{t("parts_dispatch_lines.fields.accepted_at")} {t("parts_dispatch_lines.fields.accepted_at")}
@@ -135,7 +133,10 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
/> />
</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,14 +54,33 @@ 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))
@@ -68,8 +103,8 @@ export function JobsDetailHeaderActions({
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,14 +689,14 @@ 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})
}) })
? [ ? [
{ {
@@ -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>
) )
@@ -810,7 +846,7 @@ export function JobsDetailHeaderActions({
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
rome: true, rome: true,
promanager: HasFeatureAccess({ featureName: "bills", bodyshop }) promanager: HasFeatureAccess({featureName: "bills", bodyshop})
}) })
? [ ? [
{ {
@@ -821,7 +857,7 @@ export function JobsDetailHeaderActions({
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",
@@ -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}

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,14 +47,14 @@ 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 === ""
@@ -71,7 +76,7 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
: []; : [];
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: {
@@ -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);
@@ -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>
); );
@@ -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();
@@ -120,7 +120,7 @@ function TaskListComponent({
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");
} }
} }
); );
@@ -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"),
@@ -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: [
@@ -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

@@ -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(

View File

@@ -406,9 +406,13 @@ 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'/>
}, },
]} ]}
/> />

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,106 +156,106 @@ 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>
} }
/> />
@@ -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

@@ -2138,7 +2138,7 @@
"complete": "", "complete": "",
"delete": "", "delete": "",
"edit": "", "edit": "",
"refresh":"", "refresh": "",
"myTasks": "", "myTasks": "",
"allTasks": "" "allTasks": ""
}, },
@@ -2156,11 +2156,11 @@
"bill": "", "bill": "",
"priorities": { "priorities": {
"low": "", "low": "",
"medium":"", "medium": "",
"high": "" "high": ""
}, },
"title": "", "title": "",
"joblineid":"", "joblineid": "",
"jobid": "", "jobid": "",
"partsorderid": "", "partsorderid": "",
"billid": "", "billid": "",

View File

@@ -2138,7 +2138,7 @@
"complete": "", "complete": "",
"delete": "", "delete": "",
"edit": "", "edit": "",
"refresh":"", "refresh": "",
"myTasks": "", "myTasks": "",
"allTasks": "" "allTasks": ""
}, },
@@ -2156,11 +2156,11 @@
"bill": "", "bill": "",
"priorities": { "priorities": {
"low": "", "low": "",
"medium":"", "medium": "",
"high": "" "high": ""
}, },
"title": "", "title": "",
"joblineid":"", "joblineid": "",
"jobid": "", "jobid": "",
"partsorderid": "", "partsorderid": "",
"billid": "", "billid": "",

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,7 +5,7 @@ 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;
@@ -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(
{ {
@@ -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;
} }
@@ -284,7 +284,7 @@ ${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) => {