Fix Formatting issues

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-04-08 22:25:07 -04:00
parent df0f8ef9dc
commit 33c282051b
34 changed files with 1805 additions and 1820 deletions

View File

@@ -1,49 +1,55 @@
import {EditFilled, SyncOutlined} from "@ant-design/icons";
import {Button, Card, Checkbox, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort, dateSort} from "../../utils/sorters";
import {TemplateList} from "../../utils/TemplateConstants";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import {FaTasks} from "react-icons/fa";
import { FaTasks } from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({
context: context,
modal: "billEnter"
})),
setReconciliationContext: (context) => dispatch(setModalContext({
context: context,
modal: "reconciliation"
})),
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setReconciliationContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "reconciliation"
})
),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function BillsListTableComponent({
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setBillEnterContext,
setReconciliationContext,
setTaskUpsertContext,
}) {
const {t} = useTranslation();
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setBillEnterContext,
setReconciliationContext,
setTaskUpsertContext
}) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {}
@@ -54,27 +60,29 @@ export function BillsListTableComponent({
const Templates = TemplateList("bill");
const bills = billsQuery.data ? billsQuery.data.bills : [];
const {refetch} = billsQuery;
const { refetch } = billsQuery;
const recordActions = (record, showView = false) => (
<Space wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<EditFilled/>
<EditFilled />
</Button>
)}
<Button title={t('tasks.buttons.create')} onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
billid: record.id
}
});
}}>
<FaTasks/>
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
billid: record.id
}
});
}}
>
<FaTasks />
</Button>
<BillDeleteButton bill={record} jobid={job.id}/>
<BillDeleteButton bill={record} jobid={job.id} />
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
@@ -84,9 +92,9 @@ export function BillsListTableComponent({
<PrintWrapperComponent
templateObject={{
name: Templates.inhouse_invoice.key,
variables: {id: record.id}
variables: { id: record.id }
}}
messageObject={{subject: Templates.inhouse_invoice.subject}}
messageObject={{ subject: Templates.inhouse_invoice.subject }}
/>
)}
</Space>
@@ -129,7 +137,7 @@ export function BillsListTableComponent({
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.is_credit_memo}/>
render: (text, record) => <Checkbox checked={record.is_credit_memo} />
},
{
title: t("bills.fields.exported"),
@@ -137,7 +145,7 @@ export function BillsListTableComponent({
key: "exported",
sorter: (a, b) => a.exported - b.exported,
sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.exported}/>
render: (text, record) => <Checkbox checked={record.exported} />
},
{
title: t("general.labels.actions"),
@@ -148,18 +156,18 @@ export function BillsListTableComponent({
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const filteredBills = bills
? searchText === ""
? bills
: bills.filter(
(b) =>
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
)
(b) =>
(b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) ||
(b.total || "").toString().toLowerCase().includes(searchText.toLowerCase())
)
: [];
return (
@@ -168,14 +176,14 @@ export function BillsListTableComponent({
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
<SyncOutlined />
</Button>
{job && job.converted ? (
<>
<Button
onClick={() => {
setBillEnterContext({
actions: {refetch: billsQuery.refetch},
actions: { refetch: billsQuery.refetch },
context: {
job
}
@@ -187,7 +195,7 @@ export function BillsListTableComponent({
<Button
onClick={() => {
setReconciliationContext({
actions: {refetch: billsQuery.refetch},
actions: { refetch: billsQuery.refetch },
context: {
job,
bills: (billsQuery.data && billsQuery.data.bills) || []

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import {FaTasks} from "react-icons/fa";
import { FaTasks } from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -54,7 +54,7 @@ const mapDispatchToProps = (dispatch) => ({
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function JobLinesComponent({
@@ -334,15 +334,18 @@ export function JobLinesComponent({
>
<EditFilled />
</Button>
<Button title={t('tasks.buttons.create')} onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
joblineid: record.id
}
});
}}>
<FaTasks/>
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
joblineid: record.id
}
});
}}
>
<FaTasks />
</Button>
<Button
disabled={jobRO}
@@ -457,7 +460,7 @@ export function JobLinesComponent({
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
onClick={() => {
setBillEnterContext({
actions: {refetch: refetch },
actions: { refetch: refetch },
context: {
disableInvNumber: true,
job: { id: job.id },
@@ -465,7 +468,7 @@ export function JobLinesComponent({
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: dayjs(),
date: dayjs(),
total: 0,
billlines: selectedLines.map((p) => {
return {

View File

@@ -2,40 +2,25 @@ import { LoadingOutlined } from "@ant-design/icons";
import { useLazyQuery } from "@apollo/client";
import { Select, Space, Spin, Tag } from "antd";
import _ from "lodash";
import React, {forwardRef, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
SEARCH_JOBS_FOR_AUTOCOMPLETE
} from "../../graphql/jobs.queries";
import React, { forwardRef, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const {Option} = Select;
const { Option } = Select;
const JobSearchSelect = (
{
disabled,
convertedOnly = false,
notInvoiced = false,
notExported = true,
clm_no = false,
...restProps
},
{ disabled, convertedOnly = false, notInvoiced = false, notExported = true, clm_no = false, ...restProps },
ref
) => {
const {t} = useTranslation();
const { t } = useTranslation();
const [theOptions, setTheOptions] = useState([]);
const [callSearch, {loading, error, data}] = useLazyQuery(SEARCH_JOBS_FOR_AUTOCOMPLETE, {});
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_JOBS_FOR_AUTOCOMPLETE, {});
const [
callIdSearch,
{
loading: idLoading,
error: idError,
data: idData
}
] = useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery(
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE
);
const executeSearch = (v) => {
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v);
@@ -48,10 +33,10 @@ const JobSearchSelect = (
search: value,
...(convertedOnly || notExported
? {
...(convertedOnly ? {isConverted: true} : {}),
...(notExported ? {notExported: true} : {}),
...(notInvoiced ? {notInvoiced: true} : {})
}
...(convertedOnly ? { isConverted: true } : {}),
...(notExported ? { notExported: true } : {}),
...(notInvoiced ? { notInvoiced: true } : {})
}
: {})
}
});
@@ -59,7 +44,7 @@ const JobSearchSelect = (
useEffect(() => {
if (restProps.value) {
callIdSearch({variables: {id: restProps.value}}); // Sometimes results in a no-op. Not sure how to fix.
callIdSearch({ variables: { id: restProps.value } }); // Sometimes results in a no-op. Not sure how to fix.
}
}, [restProps.value, callIdSearch]);
@@ -89,14 +74,14 @@ const JobSearchSelect = (
filterOption={false}
onSearch={handleSearch}
//loading={loading || idLoading}
suffixIcon={(loading || idLoading) && <Spin/>}
notFoundContent={loading ? <LoadingOutlined/> : null}
suffixIcon={(loading || idLoading) && <Spin />}
notFoundContent={loading ? <LoadingOutlined /> : null}
{...restProps}
>
{theOptions
? theOptions.map((o) => (
<Option key={o.id} value={o.id} status={o.status}>
<Space align="center">
<Option key={o.id} value={o.id} status={o.status}>
<Space align="center">
<span>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na")
@@ -104,17 +89,17 @@ const JobSearchSelect = (
o.v_model_desc || ""
}`}
</span>
<Tag>
<strong>{o.status}</strong>
</Tag>
</Space>
</Option>
))
<Tag>
<strong>{o.status}</strong>
</Tag>
</Space>
</Option>
))
: null}
</Select>
{error ? <AlertComponent message={error.message} type="error"/> : null}
{idError ? <AlertComponent message={idError.message} type="error"/> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null}
{idError ? <AlertComponent message={idError.message} type="error" /> : null}
</div>
);
};

View File

@@ -21,7 +21,6 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
const { t } = useTranslation();
const data = useMemo(() => {
return [
{
key: t("jobs.labels.subtotal"),
@@ -166,7 +165,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
bold: true
}
];
// TODO: was removed by Patrick during a CI bug fix.
// TODO: was removed by Patrick during a CI bug fix.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);

View File

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

View File

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

View File

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

View File

@@ -1,62 +1,66 @@
import {DeleteFilled, EyeFilled, SyncOutlined} from "@ant-design/icons";
import {useMutation} from "@apollo/client";
import {Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import queryString from "query-string";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {DELETE_PARTS_ORDER} from "../../graphql/parts-orders.queries";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort} from "../../utils/sorters";
import {TemplateList} from "../../utils/TemplateConstants";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta
from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton
from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
import {FaTasks} from "react-icons/fa";
import { FaTasks } from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({
context: context,
modal: "billEnter"
})),
setPartsReceiveContext: (context) => dispatch(setModalContext({
context: context,
modal: "partsReceive"
})),
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setPartsReceiveContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "partsReceive"
})
),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function PartsOrderListTableComponent({
setBillEnterContext,
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setPartsReceiveContext,
setTaskUpsertContext,
}) {
setBillEnterContext,
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setPartsReceiveContext,
setTaskUpsertContext
}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
@@ -71,9 +75,9 @@ export function PartsOrderListTableComponent({
};
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", {job});
const Templates = TemplateList("partsorder", { job });
const {t} = useTranslation();
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {}
});
@@ -84,13 +88,13 @@ export function PartsOrderListTableComponent({
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const {refetch} = billsQuery;
const { refetch } = billsQuery;
const recordActions = (record, showView = false) => (
<Space direction='horizontal' wrap>
<Space direction="horizontal" wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<EyeFilled/>
<EyeFilled />
</Button>
)}
@@ -99,7 +103,7 @@ export function PartsOrderListTableComponent({
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({
actions: {refetch: refetch},
actions: { refetch: refetch },
context: {
jobId: job.id,
job: job,
@@ -119,15 +123,18 @@ export function PartsOrderListTableComponent({
>
{t("parts_orders.actions.receive")}
</Button>
<Button title={t('tasks.buttons.create')} onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
partsorderid: record.id
}
});
}}>
<FaTasks/>
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
partsorderid: record.id
}
});
}}
>
<FaTasks />
</Button>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
@@ -136,11 +143,11 @@ export function PartsOrderListTableComponent({
//Delete the parts return.!
await deletePartsOrder({
variables: {partsOrderId: record.id},
variables: { partsOrderId: record.id },
update(cache) {
cache.modify({
fields: {
parts_orders(existingPartsOrders, {readField}) {
parts_orders(existingPartsOrders, { readField }) {
return existingPartsOrders.filter((billref) => record.id !== readField("id", billref));
}
}
@@ -150,7 +157,7 @@ export function PartsOrderListTableComponent({
}}
>
<Button disabled={jobRO}>
<DeleteFilled/>
<DeleteFilled />
</Button>
</Popconfirm>
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
@@ -160,7 +167,7 @@ export function PartsOrderListTableComponent({
logImEXEvent("parts_order_receive_bill");
setBillEnterContext({
actions: {refetch: refetch},
actions: { refetch: refetch },
context: {
job: job,
bill: {
@@ -180,7 +187,7 @@ export function PartsOrderListTableComponent({
? pol.jobline.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null
};
})
@@ -195,7 +202,7 @@ export function PartsOrderListTableComponent({
<PrintWrapper
templateObject={{
name: record.return ? Templates.parts_return_slip.key : Templates.parts_order.key,
variables: {id: record.id}
variables: { id: record.id }
}}
messageObject={{
subject: record.return ? Templates.parts_return_slip.subject : Templates.parts_order.subject,
@@ -236,7 +243,7 @@ export function PartsOrderListTableComponent({
key: "return",
sorter: (a, b) => a.return - b.return,
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.return}/>
render: (text, record) => <Checkbox checked={record.return} />
},
{
title: t("parts_orders.fields.deliver_by"),
@@ -260,7 +267,7 @@ export function PartsOrderListTableComponent({
];
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
@@ -291,15 +298,15 @@ export function PartsOrderListTableComponent({
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
}
]
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder: state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.cost}</CurrencyFormatter>
}
]
: []),
{
title: t("parts_orders.fields.part_type"),
@@ -327,19 +334,19 @@ export function PartsOrderListTableComponent({
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
)
}
]
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
)
}
]
: []),
{
title: t("parts_orders.fields.backordered_on"),
@@ -388,8 +395,7 @@ export function PartsOrderListTableComponent({
return (
<div>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`}
extra={recordActions(record)}/>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
<Table
scroll={{
x: true //y: "50rem"
@@ -399,7 +405,7 @@ export function PartsOrderListTableComponent({
dataSource={record.parts_order_lines}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{whiteSpace: "pre"}}>{record.comments}</div>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
@@ -409,10 +415,10 @@ export function PartsOrderListTableComponent({
? searchText === ""
? parts_orders
: parts_orders.filter(
(b) =>
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
)
(b) =>
(b.order_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
(b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase())
)
: [];
return (
@@ -421,7 +427,7 @@ export function PartsOrderListTableComponent({
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined/>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
@@ -434,7 +440,7 @@ export function PartsOrderListTableComponent({
</Space>
}
>
<PartsReceiveModalContainer/>
<PartsReceiveModalContainer />
<Drawer
placement="right"
onClose={() => handleOnRowClick(null)}

View File

@@ -1,9 +1,9 @@
import {Button, Card, Space, Switch, Table} from "antd";
import { Button, Card, Space, Switch, Table } from "antd";
import queryString from "query-string";
import React, {useCallback, useState} from "react";
import {useTranslation} from "react-i18next";
import {Link, useLocation, useNavigate} from "react-router-dom";
import {pageLimit} from "../../utils/config";
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { pageLimit } from "../../utils/config";
import dayjs from "../../utils/day";
import {
CheckCircleFilled,
@@ -15,9 +15,9 @@ import {
PlusCircleFilled,
SyncOutlined
} from "@ant-design/icons";
import {DateFormatter} from "../../utils/DateFormatter.jsx";
import {connect} from 'react-redux';
import {setModalContext} from '../../redux/modals/modals.actions';
import { DateFormatter } from "../../utils/DateFormatter.jsx";
import { connect } from "react-redux";
import { setModalContext } from "../../redux/modals/modals.actions";
/**
* Task List Component
@@ -25,7 +25,7 @@ import {setModalContext} from '../../redux/modals/modals.actions';
* @returns {Element}
* @constructor
*/
const DueDateRecord = ({dueDate}) => {
const DueDateRecord = ({ dueDate }) => {
if (!dueDate) return <></>;
const dueDateDayjs = dayjs(dueDate);
@@ -33,11 +33,11 @@ const DueDateRecord = ({dueDate}) => {
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
return (
<div title={relativeDueDate} style={{color: isBeforeToday ? 'red' : 'black'}}>
<div title={relativeDueDate} style={{ color: isBeforeToday ? "red" : "black" }}>
<DateFormatter>{dueDate}</DateFormatter>
</div>
);
}
};
/**
* Priority Label Component
@@ -45,30 +45,38 @@ const DueDateRecord = ({dueDate}) => {
* @returns {Element}
* @constructor
*/
const PriorityLabel = ({priority}) => {
const PriorityLabel = ({ priority }) => {
switch (priority) {
case 1:
return <div>
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
</div>;
return (
<div>
High <ExclamationCircleFilled style={{ marginLeft: "5px", color: "red" }} />
</div>
);
case 2:
return <div>
Medium <ExclamationCircleFilled style={{marginLeft: '5px', color: 'yellow'}}/>
</div>;
return (
<div>
Medium <ExclamationCircleFilled style={{ marginLeft: "5px", color: "yellow" }} />
</div>
);
case 3:
return <div>
Low <ExclamationCircleFilled style={{marginLeft: '5px', color: 'green'}}/>
</div>;
return (
<div>
Low <ExclamationCircleFilled style={{ marginLeft: "5px", color: "green" }} />
</div>
);
default:
return <div>
None <ExclamationCircleFilled style={{marginLeft: '5px', color: 'black'}}/>
</div>;
return (
<div>
None <ExclamationCircleFilled style={{ marginLeft: "5px", color: "black" }} />
</div>
);
}
}
};
const mapDispatchToProps = (dispatch) => ({
// Existing dispatch props...
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
const mapStateToProps = (state) => ({
@@ -78,69 +86,59 @@ const mapStateToProps = (state) => ({
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
function TaskListComponent({
bodyshop,
loading,
tasks,
total,
titleTranslation,
refetch,
toggleCompletedStatus,
setTaskUpsertContext,
toggleDeletedStatus,
relationshipType,
relationshipId,
onlyMine,
parentJobId,
showRo = true,
}) {
const {t} = useTranslation();
bodyshop,
loading,
tasks,
total,
titleTranslation,
refetch,
toggleCompletedStatus,
setTaskUpsertContext,
toggleDeletedStatus,
relationshipType,
relationshipId,
onlyMine,
parentJobId,
showRo = true
}) {
const { t } = useTranslation();
const search = queryString.parse(useLocation().search);
// Extract Query Params
const {
page,
sortcolumn,
sortorder,
deleted,
completed,
mine
} = search;
const { page, sortcolumn, sortorder, deleted, completed, mine } = search;
const history = useNavigate();
const columns = [];
if (!onlyMine) {
columns.push(
{
title: t("tasks.fields.assigned_to"),
dataIndex: "assigned_to",
key: "assigned_to",
width: '8%',
sorter: true,
sortOrder: sortcolumn === "assigned_to" && sortorder,
render: (text, record) => {
const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to);
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
}
columns.push({
title: t("tasks.fields.assigned_to"),
dataIndex: "assigned_to",
key: "assigned_to",
width: "8%",
sorter: true,
sortOrder: sortcolumn === "assigned_to" && sortorder,
render: (text, record) => {
const employee = bodyshop?.employees?.find((e) => e.user_email === record.assigned_to);
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
}
);
});
}
if (showRo) {
columns.push(
{
title: t("tasks.fields.job.ro_number"),
dataIndex: ["job", "ro_number"],
key: "job.ro_number",
width: '8%',
render: (text, record) =>
record.job
? <Link
to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
: t("general.labels.na")
}
);
columns.push({
title: t("tasks.fields.job.ro_number"),
dataIndex: ["job", "ro_number"],
key: "job.ro_number",
width: "8%",
render: (text, record) =>
record.job ? (
<Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
) : (
t("general.labels.na")
)
});
}
columns.push(
@@ -148,44 +146,47 @@ function TaskListComponent({
title: t("tasks.fields.jobline"),
dataIndex: ["jobline", "id"],
key: "jobline.id",
width: '8%',
render: (text, record) => record?.jobline?.line_desc || ''
width: "8%",
render: (text, record) => record?.jobline?.line_desc || ""
},
{
title: t("tasks.fields.parts_order"),
dataIndex: ["parts_order", "id"],
key: "part_order.id",
width: '8%',
width: "8%",
render: (text, record) =>
record.parts_order
? <Link
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
record.parts_order ? (
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
: t("general.labels.na")}
</Link>
: ''
) : (
""
)
},
{
title: t("tasks.fields.bill"),
dataIndex: ["bill", "id"],
key: "bill.id",
width: '8%',
width: "8%",
render: (text, record) =>
record.bill
? <Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
record.bill ? (
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
: t("general.labels.na")}
</Link>
: ''
) : (
""
)
},
{
title: t("tasks.fields.title"),
dataIndex: "title",
key: "title",
sorter: true,
sortOrder: sortcolumn === "title" && sortorder,
sortOrder: sortcolumn === "title" && sortorder
},
{
title: t("tasks.fields.due_date"),
@@ -193,8 +194,8 @@ function TaskListComponent({
key: "due_date",
sorter: true,
sortOrder: sortcolumn === "due_date" && sortorder,
width: '8%',
render: (text, record) => <DueDateRecord dueDate={record.due_date}/>,
width: "8%",
render: (text, record) => <DueDateRecord dueDate={record.due_date} />
},
{
title: t("tasks.fields.remind_at"),
@@ -202,8 +203,8 @@ function TaskListComponent({
key: "remind_at",
sorter: true,
sortOrder: sortcolumn === "remind_at" && sortorder,
width: '8%',
render: (text, record) => <DueDateRecord dueDate={record.remind_at}/>,
width: "8%",
render: (text, record) => <DueDateRecord dueDate={record.remind_at} />
},
{
title: t("tasks.fields.priority"),
@@ -211,75 +212,82 @@ function TaskListComponent({
key: "priority",
sorter: true,
sortOrder: sortcolumn === "priority" && sortorder,
width: '8%',
render: (text, record) => <PriorityLabel priority={record.priority}
/>
width: "8%",
render: (text, record) => <PriorityLabel priority={record.priority} />
},
{
title: t("tasks.fields.actions"),
key: "toggleCompleted",
width: '5%',
width: "5%",
render: (text, record) => (
<Space direction='horizontal'>
<Button title={t('tasks.buttons.edit')} onClick={() => {
setTaskUpsertContext({
context: {
existingTask: record,
},
});
}}>
<EditFilled/>
<Space direction="horizontal">
<Button
title={t("tasks.buttons.edit")}
onClick={() => {
setTaskUpsertContext({
context: {
existingTask: record
}
});
}}
>
<EditFilled />
</Button>
<Button title={t('tasks.buttons.complete')}
onClick={() => toggleCompletedStatus(record.id, record.completed)}>
{record.completed ? <CheckCircleOutlined/> :
<CheckCircleFilled/>}
<Button
title={t("tasks.buttons.complete")}
onClick={() => toggleCompletedStatus(record.id, record.completed)}
>
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
</Button>
<Button title={t('tasks.buttons.delete')}
onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
{record.deleted ? <DeleteFilled/> : <DeleteOutlined/>}
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
{record.deleted ? <DeleteFilled /> : <DeleteOutlined />}
</Button>
</Space>
),
)
}
);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
filteredInfo: { text: "" }
});
const handleCreateTask = useCallback(() => {
setTaskUpsertContext({
actions: {},
context: {
jobid: parentJobId,
[relationshipType]: relationshipId,
},
});
}, [ parentJobId, relationshipId, relationshipType, setTaskUpsertContext]);
setTaskUpsertContext({
actions: {},
context: {
jobid: parentJobId,
[relationshipType]: relationshipId
}
});
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext]);
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
search.page = pagination.current;
search.sortcolumn = sorter.columnKey;
search.sortorder = sorter.order;
history({search: queryString.stringify(search)});
history({ search: queryString.stringify(search) });
};
const handleSwitchChange = useCallback((param, value) => {
if (value) {
search[param] = "true";
} else {
delete search[param];
}
history({search: queryString.stringify(search)});
}, [history, search]);
const handleSwitchChange = useCallback(
(param, value) => {
if (value) {
search[param] = "true";
} else {
delete search[param];
}
history({ search: queryString.stringify(search) });
},
[history, search]
);
const expandableRow = (record) => {
return <Card title={t('tasks.fields.description')} size='small'>
{record.description}
</Card>
return (
<Card title={t("tasks.fields.description")} size="small">
{record.description}
</Card>
);
};
/**
@@ -288,46 +296,43 @@ function TaskListComponent({
*/
const tasksExtra = useCallback(() => {
return (
<Space direction='horizontal'>
<Space direction="horizontal">
{!onlyMine && (
<Switch
checkedChildren={t('tasks.buttons.myTasks')}
unCheckedChildren={t('tasks.buttons.allTasks')}
title={t('tasks.titles.mine')}
checkedChildren={t("tasks.buttons.myTasks")}
unCheckedChildren={t("tasks.buttons.allTasks")}
title={t("tasks.titles.mine")}
checked={mine === "true"}
onChange={(value) => handleSwitchChange('mine', value)}
onChange={(value) => handleSwitchChange("mine", value)}
/>
)}
<Switch
checkedChildren={<CheckCircleFilled/>}
unCheckedChildren={<CheckCircleOutlined/>}
title={t('tasks.titles.completed')}
checkedChildren={<CheckCircleFilled />}
unCheckedChildren={<CheckCircleOutlined />}
title={t("tasks.titles.completed")}
checked={completed === "true"}
onChange={(value) => handleSwitchChange('completed', value)}
onChange={(value) => handleSwitchChange("completed", value)}
/>
<Switch
checkedChildren={<DeleteOutlined/>}
unCheckedChildren={<DeleteFilled/>}
title={t('tasks.titles.deleted')}
checkedChildren={<DeleteOutlined />}
unCheckedChildren={<DeleteFilled />}
title={t("tasks.titles.deleted")}
checked={deleted === "true"}
onChange={(value) => handleSwitchChange('deleted', value)}
onChange={(value) => handleSwitchChange("deleted", value)}
/>
<Button title={t('tasks.buttons.create')} onClick={handleCreateTask}>
<PlusCircleFilled/>{t('tasks.buttons.create')}
<Button title={t("tasks.buttons.create")} onClick={handleCreateTask}>
<PlusCircleFilled />
{t("tasks.buttons.create")}
</Button>
<Button title={t('tasks.buttons.refresh')}
onClick={() => refetch()}>
<SyncOutlined/>
<Button title={t("tasks.buttons.refresh")} onClick={() => refetch()}>
<SyncOutlined />
</Button>
</Space>
);
}, [refetch, deleted, completed, mine, onlyMine, t, handleSwitchChange, handleCreateTask]);
return (
<Card
title={titleTranslation}
extra={tasksExtra()}
>
<Card title={titleTranslation} extra={tasksExtra()}>
<Table
loading={loading}
pagination={{
@@ -335,16 +340,16 @@ function TaskListComponent({
current: parseInt(page || 1),
total: total,
responsive: true,
showQuickJumper: true,
showQuickJumper: true
}}
columns={columns}
rowKey="id"
scroll={{x: true}}
scroll={{ x: true }}
dataSource={tasks}
onChange={handleTableChange}
expandable={{
expandedRowRender: expandableRow,
rowExpandable: record => record.description,
rowExpandable: (record) => record.description
}}
/>
</Card>

View File

@@ -1,73 +1,63 @@
import queryString from "query-string";
import {useLocation} from "react-router-dom";
import {useMutation, useQuery} from "@apollo/client";
import {
MUTATION_TOGGLE_TASK_COMPLETED,
MUTATION_TOGGLE_TASK_DELETED,
} from "../../graphql/tasks.queries.js";
import {pageLimit} from "../../utils/config.js";
import { useLocation } from "react-router-dom";
import { useMutation, useQuery } from "@apollo/client";
import { MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED } from "../../graphql/tasks.queries.js";
import { pageLimit } from "../../utils/config.js";
import AlertComponent from "../alert/alert.component.jsx";
import React, {useEffect} from "react";
import React, { useEffect } from "react";
import TaskListComponent from "./task-list.component.jsx";
import {notification} from "antd";
import {useTranslation} from "react-i18next";
import {useDispatch} from "react-redux";
import {insertAuditTrail} from "../../redux/application/application.actions.js";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
export default function TaskListContainer({
bodyshop,
titleTranslation,
query,
relationshipType,
relationshipId,
currentUser,
onlyMine,
parentJobId,
showRo = true
}) {
const {t} = useTranslation();
bodyshop,
titleTranslation,
query,
relationshipType,
relationshipId,
currentUser,
onlyMine,
parentJobId,
showRo = true
}) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, deleted, completed, mine} = searchParams;
const { page, sortcolumn, sortorder, deleted, completed, mine } = searchParams;
const dispatch = useDispatch();
const {loading, error, data, refetch} = useQuery(
query,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
bodyshop: bodyshop.id,
[relationshipType]: relationshipId,
deleted: deleted === 'true',
completed: completed === "true",
assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
const { loading, error, data, refetch } = useQuery(query, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
bodyshop: bodyshop.id,
[relationshipType]: relationshipId,
deleted: deleted === "true",
completed: completed === "true",
assigned_to: mine === "true" ? currentUser.email : undefined, // replace currentUserID with the actual ID of the current user
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"
}
]
}
);
});
/**
* Refetch tasks when a task is updated
*/
useEffect(() => {
const handleTaskUpdated = async (event) => {
await refetch().catch(e => `Something went wrong fetching tasks: ${e.message || ''}`);
await refetch().catch((e) => `Something went wrong fetching tasks: ${e.message || ""}`);
};
window.addEventListener('taskUpdated', handleTaskUpdated);
window.addEventListener("taskUpdated", handleTaskUpdated);
// Clean up the event listener when the component is unmounted.
return () => {
window.removeEventListener('taskUpdated', handleTaskUpdated);
window.removeEventListener("taskUpdated", handleTaskUpdated);
};
}, [refetch]);
@@ -97,27 +87,25 @@ export default function TaskListContainer({
dispatch(
insertAuditTrail({
jobid: toggledTask.data.update_tasks_by_pk.jobid,
operation: toggledTask?.data?.update_tasks_by_pk?.completed ? AuditTrailMapping.tasksCompleted(
toggledTask.data.update_tasks_by_pk.title,
currentUser.email
) : AuditTrailMapping.tasksUncompleted(
toggledTask.data.update_tasks_by_pk.title,
currentUser.email
),
operation: toggledTask?.data?.update_tasks_by_pk?.completed
? AuditTrailMapping.tasksCompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
: AuditTrailMapping.tasksUncompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
type: toggledTask?.data?.update_tasks_by_pk?.completed ? "tasksCompleted" : "tasksUncompleted"
})
)
);
}
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been completed.'},
}));
window.dispatchEvent(
new CustomEvent("taskUpdated", {
detail: { message: "A task has been completed." }
})
);
notification["success"]({
message: t("tasks.successes.completed"),
message: t("tasks.successes.completed")
});
} catch (err) {
notification["error"]({
message: t("tasks.failures.completed"),
message: t("tasks.failures.completed")
});
}
};
@@ -148,35 +136,31 @@ export default function TaskListContainer({
dispatch(
insertAuditTrail({
jobid: toggledTask.data.update_tasks_by_pk.jobid,
operation: toggledTask?.data?.update_tasks_by_pk?.deleted ? AuditTrailMapping.tasksDeleted(
toggledTask.data.update_tasks_by_pk.title,
currentUser.email
) : AuditTrailMapping.tasksUndeleted(
toggledTask.data.update_tasks_by_pk.title,
currentUser.email
),
operation: toggledTask?.data?.update_tasks_by_pk?.deleted
? AuditTrailMapping.tasksDeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
: AuditTrailMapping.tasksUndeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
type: toggledTask?.data?.update_tasks_by_pk?.deleted ? "tasksDeleted" : "tasksUndeleted"
})
)
);
}
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been deleted.'},
}));
window.dispatchEvent(
new CustomEvent("taskUpdated", {
detail: { message: "A task has been deleted." }
})
);
notification["success"]({
message: t("tasks.successes.deleted"),
message: t("tasks.successes.deleted")
});
} catch (err) {
console.dir(err);
notification["error"]({
message: t("tasks.failures.deleted"),
message: t("tasks.failures.deleted")
});
}
};
if (error) return <AlertComponent message={error.message} type="error"/>;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<TaskListComponent

View File

@@ -1,58 +1,53 @@
import {Col, Form, Input, Row, Select, Switch} from "antd";
import { Col, Form, Input, Row, Select, Switch } from "antd";
import React from "react";
import {useTranslation} from "react-i18next";
import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx";
import {createStructuredSelector} from "reselect";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js";
import dayjs from '../../utils/day';
import { useTranslation } from "react-i18next";
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import dayjs from "../../utils/day";
import {connect} from "react-redux";
import { connect } from "react-redux";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TaskUpsertModalComponent);
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalComponent);
export function TaskUpsertModalComponent({
form,
bodyshop,
currentUser,
selectedJobId,
setSelectedJobId,
selectedJobDetails,
loading,
error,
}) {
const {t} = useTranslation();
form,
bodyshop,
currentUser,
selectedJobId,
setSelectedJobId,
selectedJobDetails,
loading,
error
}) {
const { t } = useTranslation();
const datePickerPresets = [
{label: t('tasks.date_presets.today'), value: dayjs()},
{label: t('tasks.date_presets.tomorrow'), value: dayjs().add(1, 'day')},
{label: t('tasks.date_presets.next_week'), value: dayjs().add(1, 'week')},
{label: t('tasks.date_presets.two_weeks'), value: dayjs().add(2, 'weeks')},
{label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')},
{label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')},
{label: t('tasks.date_presets.three_months'), value: dayjs().add(3, 'month')},
{ label: t("tasks.date_presets.today"), value: dayjs() },
{ label: t("tasks.date_presets.tomorrow"), value: dayjs().add(1, "day") },
{ label: t("tasks.date_presets.next_week"), value: dayjs().add(1, "week") },
{ label: t("tasks.date_presets.two_weeks"), value: dayjs().add(2, "weeks") },
{ label: t("tasks.date_presets.three_weeks"), value: dayjs().add(3, "weeks") },
{ label: t("tasks.date_presets.one_month"), value: dayjs().add(1, "month") },
{ label: t("tasks.date_presets.three_months"), value: dayjs().add(3, "month") }
];
const clearRelations = () => {
form.setFieldsValue({
billid: null,
partsorderid: null,
joblineid: null
});
}
};
/**
* Change the selected job id
@@ -64,8 +59,7 @@ export function TaskUpsertModalComponent({
clearRelations();
};
if (loading || error) return <LoadingSkeleton active/>;
if (loading || error) return <LoadingSkeleton active />;
return (
<>
@@ -76,24 +70,20 @@ export function TaskUpsertModalComponent({
name="title"
rules={[
{
required: true,
},
required: true
}
]}
>
<Input placeholder={t("tasks.fields.title")}/>
<Input placeholder={t("tasks.fields.title")} />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item
label={t("tasks.fields.priority")}
name="priority"
initialValue={3}
>
<Form.Item label={t("tasks.fields.priority")} name="priority" initialValue={3}>
<Select
options={[
{value: 3, label: t("tasks.fields.priorities.low")},
{value: 2, label: t("tasks.fields.priorities.medium")},
{value: 1, label: t("tasks.fields.priorities.high")},
{ value: 3, label: t("tasks.fields.priorities.low") },
{ value: 2, label: t("tasks.fields.priorities.medium") },
{ value: 1, label: t("tasks.fields.priorities.high") }
]}
/>
</Form.Item>
@@ -106,11 +96,11 @@ export function TaskUpsertModalComponent({
initialValue={false}
rules={[
{
required: true,
},
required: true
}
]}
>
<Switch/>
<Switch />
</Form.Item>
</Col>
</Row>
@@ -122,23 +112,27 @@ export function TaskUpsertModalComponent({
label={t("tasks.fields.jobid")}
rules={[
{
required: true,
},
required: true
}
]}
>
<JobSearchSelectComponent placeholder={t('tasks.placeholders.jobid')}
onSelect={changeJobId} onClear={changeJobId} autoFocus={false}/>
<JobSearchSelectComponent
placeholder={t("tasks.placeholders.jobid")}
onSelect={changeJobId}
onClear={changeJobId}
autoFocus={false}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("tasks.fields.joblineid")}
name="joblineid"
>
<Select allowClear placeholder={t("tasks.placeholders.joblineid")}
disabled={!selectedJobDetails || !selectedJobId}>
<Form.Item label={t("tasks.fields.joblineid")} name="joblineid">
<Select
allowClear
placeholder={t("tasks.placeholders.joblineid")}
disabled={!selectedJobDetails || !selectedJobId}
>
{selectedJobDetails?.joblines?.map((jobline) => (
<Select.Option key={jobline.id} value={jobline.id}>
{jobline.line_desc}
@@ -148,12 +142,12 @@ export function TaskUpsertModalComponent({
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.partsorderid")}
name="partsorderid"
>
<Select allowClear placeholder={t("tasks.placeholders.partsorderid")}
disabled={!selectedJobDetails || !selectedJobId}>
<Form.Item label={t("tasks.fields.partsorderid")} name="partsorderid">
<Select
allowClear
placeholder={t("tasks.placeholders.partsorderid")}
disabled={!selectedJobDetails || !selectedJobId}
>
{selectedJobDetails?.parts_orders?.map((partsOrder) => (
<Select.Option key={partsOrder.id} value={partsOrder.id}>
{partsOrder.order_number} - {partsOrder.vendor.name}
@@ -163,12 +157,12 @@ export function TaskUpsertModalComponent({
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.billid")}
name="billid"
>
<Select allowClear placeholder={t("tasks.placeholders.billid")}
disabled={!selectedJobDetails || !selectedJobId}>
<Form.Item label={t("tasks.fields.billid")} name="billid">
<Select
allowClear
placeholder={t("tasks.placeholders.billid")}
disabled={!selectedJobDetails || !selectedJobId}
>
{selectedJobDetails?.bills?.map((bill) => (
<Select.Option key={bill.id} value={bill.id}>
{bill.invoice_number} - {bill.vendor.name}
@@ -186,46 +180,36 @@ export function TaskUpsertModalComponent({
initialValue={currentUser.email}
rules={[
{
required: true,
},
required: true
}
]}
>
<Select placeholder={t("tasks.placeholders.assigned_to")}>
{bodyshop.employees.filter(x => x.active).map((employee) => (
<Select.Option key={employee.id} value={employee.user_email}>
{employee.first_name} {employee.last_name}
</Select.Option>
))}
{bodyshop.employees
.filter((x) => x.active)
.map((employee) => (
<Select.Option key={employee.id} value={employee.user_email}>
{employee.first_name} {employee.last_name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.due_date")}
name="due_date"
>
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets}/>
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.remind_at")}
name="remind_at"
>
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets}/>
<Form.Item label={t("tasks.fields.remind_at")} name="remind_at">
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets} />
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item
label={t("tasks.fields.description")}
name="description"
>
<Input.TextArea
rows={8}
placeholder={t("tasks.fields.description")}
/>
<Form.Item label={t("tasks.fields.description")} name="description">
<Input.TextArea rows={8} placeholder={t("tasks.fields.description")} />
</Form.Item>
</Col>
</Row>

View File

@@ -1,30 +1,26 @@
import {useMutation, useQuery} from "@apollo/client";
import {Form, Modal, notification} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {
MUTATION_INSERT_NEW_TASK,
MUTATION_UPDATE_TASK,
QUERY_GET_TASK_BY_ID
} from "../../graphql/tasks.queries";
import {QUERY_GET_TASKS_JOB_DETAILS_BY_ID} from "../../graphql/jobs.queries.js";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectTaskUpsert} from "../../redux/modals/modals.selectors";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import { useMutation, useQuery } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
import { QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import TaskUpsertModalComponent from "./task-upsert-modal.component";
import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js";
import {useNavigate} from "react-router-dom";
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import dayjs from '../../utils/day';
import {insertAuditTrail} from "../../redux/application/application.actions.js";
import dayjs from "../../utils/day";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
taskUpsert: selectTaskUpsert,
taskUpsert: selectTaskUpsert
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
@@ -32,32 +28,22 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(insertAuditTrail({ jobid, billid, operation, type }))
});
export function TaskUpsertModalContainer({
bodyshop,
currentUser,
taskUpsert,
toggleModalVisible,
insertAuditTrail
}) {
const {t} = useTranslation();
export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
const { t } = useTranslation();
const history = useNavigate();
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
const {open, context, actions} = taskUpsert;
const {jobid, joblineid, billid, partsorderid, taskId, existingTask} = context;
const {refetch} = actions;
const { open, context, actions } = taskUpsert;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask } = context;
const { refetch } = actions;
const [form] = Form.useForm();
const [selectedJobId, setSelectedJobId] = useState(null);
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
const [jobIdState, setJobIdState] = useState(null);
const {
loading,
error,
data
} = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
variables: {id: jobIdState},
skip: !jobIdState,
const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
variables: { id: jobIdState },
skip: !jobIdState
});
const {
@@ -65,8 +51,8 @@ export function TaskUpsertModalContainer({
error: taskError,
data: taskData
} = useQuery(QUERY_GET_TASK_BY_ID, {
variables: {id: taskId},
skip: !taskId,
variables: { id: taskId },
skip: !taskId
});
// Use Effect to hydrate existing task if only a taskid is provided
@@ -100,25 +86,24 @@ export function TaskUpsertModalContainer({
form.setFieldsValue(existingTask);
} else if (!existingTask && open) {
form.resetFields();
if (joblineid) form.setFieldsValue({joblineid});
if (billid) form.setFieldsValue({billid});
if (partsorderid) form.setFieldsValue({partsorderid});
if (joblineid) form.setFieldsValue({ joblineid });
if (billid) form.setFieldsValue({ billid });
if (partsorderid) form.setFieldsValue({ partsorderid });
}
return () => {
setSelectedJobId(null);
};
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
/**
* Remove the taskid from the URL
*/
const removeTaskIdFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has('taskid')) return;
urlParams.delete('taskid');
if (!urlParams.has("taskid")) return;
urlParams.delete("taskid");
history(`${window.location.pathname}?${urlParams}`);
}
};
/**
* Handle existing task
@@ -132,48 +117,51 @@ export function TaskUpsertModalContainer({
variables: {
taskId: existingTask.id,
task: replaceUndefinedWithNull(values)
},
}
});
if (!taskData.errors) {
const oldTask = taskData?.data?.update_tasks?.returning[0];
const oldTask = taskData?.data?.update_tasks?.returning[0];
insertAuditTrail({
jobid: oldTask.jobid,
operation: AuditTrailMapping.tasksUpdated(
oldTask.title,
currentUser.email
),
operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
type: "tasksUpdated"
});
}
if (isAssignedToDirty) {
// TODO This is being moved serverside
axios.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email,
},
ReplyTo: {
Email: 'noreply@imex.online'
},
to: values.assigned_to,
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${existingTask.id}">Please sign in to your account to view the task details.</a>`
}
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ''}`));
axios
.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email
},
ReplyTo: {
Email: "noreply@imex.online"
},
to: values.assigned_to,
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format("MM/DD/YYYY")}` : ""}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${existingTask.id}">Please sign in to your account to view the task details.</a>`
}
})
.catch((e) =>
console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ""}`)
);
}
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
window.dispatchEvent(
new CustomEvent("taskUpdated", {
detail: { message: "A task has been created or edited." }
})
);
notification["success"]({
message: t("tasks.successes.updated"),
message: t("tasks.successes.updated")
});
if (refetch) await refetch();
@@ -189,56 +177,59 @@ export function TaskUpsertModalContainer({
...values,
created_by: currentUser.email,
bodyshopid: bodyshop.id
},
],
},
}
]
}
});
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
const newTaskID = newTask?.id;
if (!newTaskData.errors) {
insertAuditTrail({
jobid: newTask.jobid,
operation: AuditTrailMapping.tasksCreated(
newTask.title,
currentUser.email
),
operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
type: "tasksCreated"
});
}
if (refetch) await refetch();
form.resetFields();
toggleModalVisible();
// send notification to the assigned user
// TODO: This is being moved serverside
axios.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email,
},
replyTo: {
Email: 'noreply@imex.online'
},
to: values.assigned_to,
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateName: 'taskAssigned',
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${newTaskID}">Please sign to your account to view the task details.</a>`
}
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ''}`));
axios
.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email
},
replyTo: {
Email: "noreply@imex.online"
},
to: values.assigned_to,
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateName: "taskAssigned",
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format("MM/DD/YYYY")}` : ""}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${newTaskID}">Please sign to your account to view the task details.</a>`
}
})
.catch((e) =>
console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ""}`)
);
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
window.dispatchEvent(
new CustomEvent("taskUpdated", {
detail: { message: "A task has been created or edited." }
})
);
notification["success"]({
message: t("tasks.successes.created"),
message: t("tasks.successes.created")
});
};
@@ -248,7 +239,7 @@ export function TaskUpsertModalContainer({
* @returns {Promise<[{jobid, bodyshopid, created_by},...*]>}
*/
const handleFinish = async (formValues) => {
const {...values} = formValues;
const { ...values } = formValues;
if (existingTask) {
await handleExistingTask(values);
} else {
@@ -273,18 +264,18 @@ export function TaskUpsertModalContainer({
destroyOnClose
>
<Form form={form} onFinish={handleFinish} layout="vertical">
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)}
error={error} data={data}
selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}/>
<TaskUpsertModalComponent
form={form}
loading={loading || (taskId && taskLoading)}
error={error}
data={data}
selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}
/>
</Form>
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TaskUpsertModalContainer);
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);