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

@@ -147,8 +147,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
/> />
} }
> >
{
import.meta.env.PRODUCT_FRUITS_DISABLED !== 'true' && (
<ProductFruits <ProductFruits
workspaceCode={InstanceRenderMgr({ workspaceCode={InstanceRenderMgr({
imex: null, imex: null,
@@ -162,8 +160,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
username: currentUser.email username: currentUser.email
}} }}
/> />
)
}
<Routes> <Routes>
<Route <Route

View File

@@ -18,19 +18,25 @@ import {FaTasks} from "react-icons/fa";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop, bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "billEnter" modal: "billEnter"
})), })
setReconciliationContext: (context) => dispatch(setModalContext({ ),
setReconciliationContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "reconciliation" modal: "reconciliation"
})), })
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), ),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
}); });
export function BillsListTableComponent({ export function BillsListTableComponent({
@@ -41,7 +47,7 @@ export function BillsListTableComponent({
handleOnRowClick, handleOnRowClick,
setBillEnterContext, setBillEnterContext,
setReconciliationContext, setReconciliationContext,
setTaskUpsertContext, setTaskUpsertContext
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -56,7 +62,6 @@ export function BillsListTableComponent({
const bills = billsQuery.data ? billsQuery.data.bills : []; const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery; const { refetch } = billsQuery;
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space wrap> <Space wrap>
{showView && ( {showView && (
@@ -64,14 +69,17 @@ export function BillsListTableComponent({
<EditFilled /> <EditFilled />
</Button> </Button>
)} )}
<Button title={t('tasks.buttons.create')} onClick={() => { <Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({ setTaskUpsertContext({
context: { context: {
jobid: job.id, jobid: job.id,
billid: record.id billid: record.id
} }
}); });
}}> }}
>
<FaTasks /> <FaTasks />
</Button> </Button>
<BillDeleteButton bill={record} jobid={job.id} /> <BillDeleteButton bill={record} jobid={job.id} />

View File

@@ -31,23 +31,14 @@ import {Layout, Menu, Switch, Tooltip} from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs"; import { BsKanban } from "react-icons/bs";
import { import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
FaCalendarAlt,
FaCarCrash,
FaCreditCard,
FaFileInvoiceDollar,
FaTasks
} from "react-icons/fa";
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
import { IoBusinessOutline } from "react-icons/io5"; import { IoBusinessOutline } from "react-icons/io5";
import { RiSurveyLine } from "react-icons/ri"; import { RiSurveyLine } from "react-icons/ri";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
selectRecentItems,
selectSelectedHeader
} from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
@@ -64,28 +55,43 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "billEnter" modal: "billEnter"
})), })
setTimeTicketContext: (context) => dispatch(setModalContext({ ),
setTimeTicketContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "timeTicket" modal: "timeTicket"
})), })
),
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
setReportCenterContext: (context) => dispatch(setModalContext({ setReportCenterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "reportCenter" modal: "reportCenter"
})), })
),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) => dispatch(setModalContext({ setCardPaymentContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "cardPayment" modal: "cardPayment"
})), })
setTaskUpsertContext: (context) => dispatch(setModalContext({ ),
setTaskUpsertContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: 'taskUpsert' modal: "taskUpsert"
})), })
)
}); });
function Header({ function Header({
@@ -100,7 +106,7 @@ function Header({
setReportCenterContext, setReportCenterContext,
recentItems, recentItems,
setCardPaymentContext, setCardPaymentContext,
setTaskUpsertContext, setTaskUpsertContext
}) { }) {
const { const {
treatments: { ImEXPay, DmsAp, Simple_Inventory } treatments: { ImEXPay, DmsAp, Simple_Inventory }
@@ -258,8 +264,7 @@ function Header({
const accountingExportChildren = [ const accountingExportChildren = [
{ {
key: "receivables", key: "receivables",
label: <Link label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
} }
]; ];
@@ -429,14 +434,12 @@ function Header({
{ {
key: "contracts", key: "contracts",
icon: <FileFilled />, icon: <FileFilled />,
label: <Link label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
}, },
{ {
key: "newcontract", key: "newcontract",
icon: <FileAddFilled />, icon: <FileAddFilled />,
label: <Link label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
} }
] ]
} }
@@ -472,36 +475,36 @@ function Header({
] ]
: []), : []),
{ {
key: 'tasks', key: "tasks",
id: 'tasks', id: "tasks",
icon: <FaTasks />, icon: <FaTasks />,
label: t('menus.header.tasks'), label: t("menus.header.tasks"),
children: [ children: [
{ {
key: 'createTask', key: "createTask",
icon: <PlusCircleOutlined />, icon: <PlusCircleOutlined />,
label: t('menus.header.create_task'), label: t("menus.header.create_task"),
onClick: () => { onClick: () => {
setTaskUpsertContext({ setTaskUpsertContext({
actions: {}, actions: {},
context: {}, context: {}
}); });
}, }
}, },
{ {
key: 'mytasks', key: "mytasks",
icon: <FaTasks />, icon: <FaTasks />,
label: <Link to="/manage/tasks/mytasks">{t('menus.header.my_tasks')}</Link>, label: <Link to="/manage/tasks/mytasks">{t("menus.header.my_tasks")}</Link>
}, },
{ {
key: 'all_tasks', key: "all_tasks",
icon: <FaTasks />, icon: <FaTasks />,
label: <Link to="/manage/tasks/alltasks">{t('menus.header.all_tasks')}</Link>, label: <Link to="/manage/tasks/alltasks">{t("menus.header.all_tasks")}</Link>
} }
] ]
}, },
{ {
key: 'shopsubmenu', key: "shopsubmenu",
icon: <SettingOutlined />, icon: <SettingOutlined />,
label: t("menus.header.shop"), label: t("menus.header.shop"),
children: [ children: [

View File

@@ -115,8 +115,7 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
key: line.id, key: line.id,
children: ( children: (
<Space split={<Divider type="vertical" />} wrap> <Space split={<Divider type="vertical" />} wrap>
<Link <Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name} {bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
<Space> <Space>
{t("parts_dispatch_lines.fields.accepted_at")} {t("parts_dispatch_lines.fields.accepted_at")}
@@ -133,10 +132,15 @@ export function JobLinesExpander({jobline, jobid, bodyshop, currentUser}) {
/> />
</Col> </Col>
<Col md={24} lg={24}> <Col md={24} lg={24}>
<TaskListContainer currentUser={currentUser} bodyshop={bodyshop} parentJobId={jobid} <TaskListContainer
relationshipType={'joblineid'} relationshipId={jobline.id} currentUser={currentUser}
bodyshop={bodyshop}
parentJobId={jobid}
relationshipType={"joblineid"}
relationshipId={jobline.id}
query={QUERY_JOBLINE_TASKS_PAGINATED} query={QUERY_JOBLINE_TASKS_PAGINATED}
titleTranslation='tasks.titles.job_tasks'/> titleTranslation="tasks.titles.job_tasks"
/>
</Col> </Col>
</Row> </Row>
); );

View File

@@ -54,7 +54,7 @@ const mapDispatchToProps = (dispatch) => ({
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })), setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), 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({ export function JobLinesComponent({
@@ -334,14 +334,17 @@ export function JobLinesComponent({
> >
<EditFilled /> <EditFilled />
</Button> </Button>
<Button title={t('tasks.buttons.create')} onClick={() => { <Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({ setTaskUpsertContext({
context: { context: {
jobid: job.id, jobid: job.id,
joblineid: record.id joblineid: record.id
} }
}); });
}}> }}
>
<FaTasks /> <FaTasks />
</Button> </Button>
<Button <Button

View File

@@ -4,38 +4,23 @@ import { Select, Space, Spin, Tag } from "antd";
import _ from "lodash"; import _ from "lodash";
import React, { forwardRef, useEffect, useState } from "react"; import React, { forwardRef, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
SEARCH_JOBS_FOR_AUTOCOMPLETE
} from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const { Option } = Select; const { Option } = Select;
const JobSearchSelect = ( 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 ref
) => { ) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [theOptions, setTheOptions] = useState([]); 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 [ const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = useLazyQuery(
callIdSearch, SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE
{ );
loading: idLoading,
error: idError,
data: idData
}
] = useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => { const executeSearch = (v) => {
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v); if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v);

View File

@@ -21,7 +21,6 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
const { t } = useTranslation(); const { t } = useTranslation();
const data = useMemo(() => { const data = useMemo(() => {
return [ return [
{ {
key: t("jobs.labels.subtotal"), key: t("jobs.labels.subtotal"),

View File

@@ -1,28 +1,13 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
Button,
Card,
Dropdown,
Form,
Input,
Modal,
notification,
Popconfirm,
Popover,
Select,
Space
} from "antd";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
CANCEL_APPOINTMENTS_BY_JOB_ID,
INSERT_MANUAL_APPT
} from "../../graphql/appointments.queries";
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
@@ -44,8 +29,7 @@ import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import JobsDetailHeaderActionsToggleProduction import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
from "./jobs-detail-header-actions.toggle-production";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -55,32 +39,50 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
setBillEnterContext: (context) => dispatch(setModalContext({ setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "billEnter" modal: "billEnter"
})), })
),
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
setJobCostingContext: (context) => dispatch(setModalContext({ setJobCostingContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "jobCosting" modal: "jobCosting"
})), })
setTimeTicketContext: (context) => dispatch(setModalContext({ ),
setTimeTicketContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "timeTicket" modal: "timeTicket"
})), })
setCardPaymentContext: (context) => dispatch(setModalContext({ ),
setCardPaymentContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "cardPayment" modal: "cardPayment"
})), })
insertAuditTrail: ({jobid, operation, type}) => dispatch(insertAuditTrail({ ),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid, jobid,
operation, operation,
type type
})), })
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ ),
setTimeTicketTaskContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "timeTicketTask" modal: "timeTicketTask"
})), })
),
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)) setMessage: (text) => dispatch(setMessage(text))
@@ -680,8 +682,7 @@ export function JobsDetailHeaderActions({
{ {
key: "checklist", key: "checklist",
disabled: !job.converted, disabled: !job.converted,
label: <Link label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
} }
], ],
rome: "USE_IMEX", rome: "USE_IMEX",

View File

@@ -1,9 +1,4 @@
import { import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons";
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
SyncOutlined
} from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
import queryString from "query-string"; import queryString from "query-string";

View File

@@ -19,12 +19,10 @@ import {DateFormatter} from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.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 PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component"; import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
@@ -32,19 +30,25 @@ import {FaTasks} from "react-icons/fa";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop, bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "billEnter" modal: "billEnter"
})), })
setPartsReceiveContext: (context) => dispatch(setModalContext({ ),
setPartsReceiveContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "partsReceive" modal: "partsReceive"
})), })
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), ),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
}); });
export function PartsOrderListTableComponent({ export function PartsOrderListTableComponent({
@@ -55,7 +59,7 @@ export function PartsOrderListTableComponent({
billsQuery, billsQuery,
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
setTaskUpsertContext, setTaskUpsertContext
}) { }) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
@@ -87,7 +91,7 @@ export function PartsOrderListTableComponent({
const { refetch } = billsQuery; const { refetch } = billsQuery;
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space direction='horizontal' wrap> <Space direction="horizontal" wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button onClick={() => handleOnRowClick(record)}>
<EyeFilled /> <EyeFilled />
@@ -119,14 +123,17 @@ export function PartsOrderListTableComponent({
> >
{t("parts_orders.actions.receive")} {t("parts_orders.actions.receive")}
</Button> </Button>
<Button title={t('tasks.buttons.create')} onClick={() => { <Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({ setTaskUpsertContext({
context: { context: {
jobid: job.id, jobid: job.id,
partsorderid: record.id partsorderid: record.id
} }
}); });
}}> }}
>
<FaTasks /> <FaTasks />
</Button> </Button>
<Popconfirm <Popconfirm
@@ -388,8 +395,7 @@ export function PartsOrderListTableComponent({
return ( return (
<div> <div>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} <PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
extra={recordActions(record)}/>
<Table <Table
scroll={{ scroll={{
x: true //y: "50rem" x: true //y: "50rem"

View File

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

View File

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

View File

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

View File

@@ -4,11 +4,7 @@ import React, {useEffect, useState} from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
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 { QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTaskUpsert } from "../../redux/modals/modals.selectors"; import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
@@ -17,14 +13,14 @@ import TaskUpsertModalComponent from "./task-upsert-modal.component";
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js"; import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import dayjs from '../../utils/day'; import dayjs from "../../utils/day";
import { insertAuditTrail } from "../../redux/application/application.actions.js"; import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
taskUpsert: selectTaskUpsert, taskUpsert: selectTaskUpsert
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")), toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
@@ -32,13 +28,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(insertAuditTrail({ jobid, billid, operation, type })) dispatch(insertAuditTrail({ jobid, billid, operation, type }))
}); });
export function TaskUpsertModalContainer({ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
bodyshop,
currentUser,
taskUpsert,
toggleModalVisible,
insertAuditTrail
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useNavigate(); const history = useNavigate();
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK); const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
@@ -51,13 +41,9 @@ export function TaskUpsertModalContainer({
const [selectedJobDetails, setSelectedJobDetails] = useState(null); const [selectedJobDetails, setSelectedJobDetails] = useState(null);
const [jobIdState, setJobIdState] = useState(null); const [jobIdState, setJobIdState] = useState(null);
const { const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
loading,
error,
data
} = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
variables: { id: jobIdState }, variables: { id: jobIdState },
skip: !jobIdState, skip: !jobIdState
}); });
const { const {
@@ -66,7 +52,7 @@ export function TaskUpsertModalContainer({
data: taskData data: taskData
} = useQuery(QUERY_GET_TASK_BY_ID, { } = useQuery(QUERY_GET_TASK_BY_ID, {
variables: { id: taskId }, variables: { id: taskId },
skip: !taskId, skip: !taskId
}); });
// Use Effect to hydrate existing task if only a taskid is provided // Use Effect to hydrate existing task if only a taskid is provided
@@ -109,16 +95,15 @@ export function TaskUpsertModalContainer({
}; };
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]); }, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
/** /**
* Remove the taskid from the URL * Remove the taskid from the URL
*/ */
const removeTaskIdFromUrl = () => { const removeTaskIdFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has('taskid')) return; if (!urlParams.has("taskid")) return;
urlParams.delete('taskid'); urlParams.delete("taskid");
history(`${window.location.pathname}?${urlParams}`); history(`${window.location.pathname}?${urlParams}`);
} };
/** /**
* Handle existing task * Handle existing task
@@ -132,7 +117,7 @@ export function TaskUpsertModalContainer({
variables: { variables: {
taskId: existingTask.id, taskId: existingTask.id,
task: replaceUndefinedWithNull(values) task: replaceUndefinedWithNull(values)
}, }
}); });
if (!taskData.errors) { if (!taskData.errors) {
@@ -140,40 +125,43 @@ export function TaskUpsertModalContainer({
insertAuditTrail({ insertAuditTrail({
jobid: oldTask.jobid, jobid: oldTask.jobid,
operation: AuditTrailMapping.tasksUpdated( operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
oldTask.title,
currentUser.email
),
type: "tasksUpdated" type: "tasksUpdated"
}); });
} }
if (isAssignedToDirty) { if (isAssignedToDirty) {
// TODO This is being moved serverside // TODO This is being moved serverside
axios.post("/sendemail", { axios
.post("/sendemail", {
from: { from: {
name: bodyshop.shopname, name: bodyshop.shopname,
address: bodyshop.email, address: bodyshop.email
}, },
ReplyTo: { ReplyTo: {
Email: 'noreply@imex.online' Email: "noreply@imex.online"
}, },
to: values.assigned_to, to: values.assigned_to,
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`, subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateStrings: { templateStrings: {
header: values.title, header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`, 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>` 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 || ''}`)); })
.catch((e) =>
console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ""}`)
);
} }
window.dispatchEvent(new CustomEvent('taskUpdated', { window.dispatchEvent(
detail: {message: 'A task has been created or edited.'}, new CustomEvent("taskUpdated", {
})); detail: { message: "A task has been created or edited." }
})
);
notification["success"]({ notification["success"]({
message: t("tasks.successes.updated"), message: t("tasks.successes.updated")
}); });
if (refetch) await refetch(); if (refetch) await refetch();
@@ -189,9 +177,9 @@ export function TaskUpsertModalContainer({
...values, ...values,
created_by: currentUser.email, created_by: currentUser.email,
bodyshopid: bodyshop.id bodyshopid: bodyshop.id
}, }
], ]
}, }
}); });
const newTask = newTaskData?.data?.insert_tasks?.returning[0]; const newTask = newTaskData?.data?.insert_tasks?.returning[0];
@@ -200,10 +188,7 @@ export function TaskUpsertModalContainer({
if (!newTaskData.errors) { if (!newTaskData.errors) {
insertAuditTrail({ insertAuditTrail({
jobid: newTask.jobid, jobid: newTask.jobid,
operation: AuditTrailMapping.tasksCreated( operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
newTask.title,
currentUser.email
),
type: "tasksCreated" type: "tasksCreated"
}); });
} }
@@ -215,30 +200,36 @@ export function TaskUpsertModalContainer({
// send notification to the assigned user // send notification to the assigned user
// TODO: This is being moved serverside // TODO: This is being moved serverside
axios.post("/sendemail", { axios
.post("/sendemail", {
from: { from: {
name: bodyshop.shopname, name: bodyshop.shopname,
address: bodyshop.email, address: bodyshop.email
}, },
replyTo: { replyTo: {
Email: 'noreply@imex.online' Email: "noreply@imex.online"
}, },
to: values.assigned_to, to: values.assigned_to,
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`, subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateName: 'taskAssigned', templateName: "taskAssigned",
templateStrings: { templateStrings: {
header: values.title, header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`, 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>` 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 || ''}`)); })
.catch((e) =>
console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ""}`)
);
window.dispatchEvent(new CustomEvent('taskUpdated', { window.dispatchEvent(
detail: {message: 'A task has been created or edited.'}, new CustomEvent("taskUpdated", {
})); detail: { message: "A task has been created or edited." }
})
);
notification["success"]({ notification["success"]({
message: t("tasks.successes.created"), message: t("tasks.successes.created")
}); });
}; };
@@ -273,18 +264,18 @@ export function TaskUpsertModalContainer({
destroyOnClose destroyOnClose
> >
<Form form={form} onFinish={handleFinish} layout="vertical"> <Form form={form} onFinish={handleFinish} layout="vertical">
<TaskUpsertModalComponent form={form} loading={loading || (taskId && taskLoading)} <TaskUpsertModalComponent
error={error} data={data} form={form}
loading={loading || (taskId && taskLoading)}
error={error}
data={data}
selectedJobId={selectedJobId} selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId} setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}/> selectedJobDetails={selectedJobDetails}
/>
</Form> </Form>
</Modal> </Modal>
); );
} }
export default connect( export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);
mapStateToProps,
mapDispatchToProps
)(TaskUpsertModalContainer);

View File

@@ -1,6 +1,5 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
const PARTIAL_TASK_FIELDS = gql` const PARTIAL_TASK_FIELDS = gql`
fragment TaskFields on tasks { fragment TaskFields on tasks {
id id
@@ -70,7 +69,8 @@ export const QUERY_GET_TASK_BY_ID = gql`
tasks_by_pk(id: $id) { tasks_by_pk(id: $id) {
...TaskFields ...TaskFields
} }
}`; }
`;
export const QUERY_ALL_TASKS_PAGINATED = gql` export const QUERY_ALL_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS} ${PARTIAL_TASK_FIELDS}
@@ -88,9 +88,9 @@ export const QUERY_ALL_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -98,9 +98,9 @@ export const QUERY_ALL_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -129,10 +129,10 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
joblineid: {_eq: $joblineid}, joblineid: { _eq: $joblineid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -140,10 +140,10 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
joblineid: {_eq: $joblineid}, joblineid: { _eq: $joblineid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -172,10 +172,10 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
partsorderid: {_eq: $partsorderid}, partsorderid: { _eq: $partsorderid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -183,10 +183,10 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
partsorderid: {_eq: $partsorderid}, partsorderid: { _eq: $partsorderid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -215,10 +215,10 @@ export const QUERY_BILL_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
billid: {_eq: $billid}, billid: { _eq: $billid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -226,10 +226,10 @@ export const QUERY_BILL_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
billid: {_eq: $billid}, billid: { _eq: $billid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -258,10 +258,10 @@ export const QUERY_JOB_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
jobid: {_eq: $jobid}, jobid: { _eq: $jobid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -269,10 +269,10 @@ export const QUERY_JOB_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
jobid: {_eq: $jobid}, jobid: { _eq: $jobid }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
assigned_to: {_eq: $assigned_to}, assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -299,9 +299,9 @@ export const QUERY_MY_TASKS_PAGINATED = gql`
limit: $limit limit: $limit
order_by: $order order_by: $order
where: { where: {
assigned_to: {_eq: $user}, assigned_to: { _eq: $user }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -309,9 +309,9 @@ export const QUERY_MY_TASKS_PAGINATED = gql`
} }
tasks_aggregate( tasks_aggregate(
where: { where: {
assigned_to: {_eq: $user}, assigned_to: { _eq: $user }
bodyshopid: {_eq: $bodyshop}, bodyshopid: { _eq: $bodyshop }
deleted: {_eq: $deleted}, deleted: { _eq: $deleted }
completed: { _eq: $completed } completed: { _eq: $completed }
} }
) { ) {
@@ -414,4 +414,4 @@ export const MUTATION_UPDATE_TASK = gql`
} }
} }
} }
` `;

View File

@@ -20,38 +20,28 @@ import {FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks} from "react-icons/fa";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import FormFieldsChanged import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component"; import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
import JobLineUpsertModalContainer import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"; import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
import JobReconciliationModal
from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component"; import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component"; import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component";
import JobsConvertButton from "../../components/jobs-convert-button/jobs-convert-button.component"; import JobsConvertButton from "../../components/jobs-convert-button/jobs-convert-button.component";
import JobsDetailDatesComponent import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
from "../../components/jobs-detail-dates/jobs-detail-dates.component";
import JobsDetailGeneral from "../../components/jobs-detail-general/jobs-detail-general.component"; import JobsDetailGeneral from "../../components/jobs-detail-general/jobs-detail-general.component";
import JobsDetailHeaderActions import JobsDetailHeaderActions from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component";
from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component";
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component"; import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
import JobsDetailLaborContainer import JobsDetailLaborContainer from "../../components/jobs-detail-labor/jobs-detail-labor.container";
from "../../components/jobs-detail-labor/jobs-detail-labor.container";
import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container"; import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container";
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component"; import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component"; import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
import JobsDocumentsGalleryContainer import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import JobsDocumentsLocalGallery
from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import NoteUpsertModalComponent import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
from "../../components/note-upsert-modal/note-upsert-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import ScheduleJobModalContainer
from "../../components/schedule-job-modal/schedule-job-modal.container";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
@@ -59,8 +49,7 @@ import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import UndefinedToNull from "../../utils/undefinedtonull"; import UndefinedToNull from "../../utils/undefinedtonull";
import _ from "lodash"; import _ from "lodash";
import JobProfileDataWarning import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
from "../../components/job-profile-data-warning/job-profile-data-warning.component";
import { DateTimeFormat } from "../../utils/DateFormatter"; import { DateTimeFormat } from "../../utils/DateFormatter";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
@@ -73,15 +62,21 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => dispatch(setModalContext({ setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context, context: context,
modal: "printCenter" modal: "printCenter"
})), })
insertAuditTrail: ({jobid, operation, type}) => dispatch(insertAuditTrail({ ),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid, jobid,
operation, operation,
type type
})) })
)
}); });
export function JobsDetailPage({ export function JobsDetailPage({
@@ -111,13 +106,13 @@ export function JobsDetailPage({
useEffect(() => { useEffect(() => {
const handleTaskUpdated = async (event) => { 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. // Clean up the event listener when the component is unmounted.
return () => { return () => {
window.removeEventListener('taskUpdated', handleTaskUpdated); window.removeEventListener("taskUpdated", handleTaskUpdated);
}; };
}, [refetch]); }, [refetch]);
@@ -272,8 +267,7 @@ export function JobsDetailPage({
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
<JobsConvertButton job={job} refetch={refetch} <JobsConvertButton job={job} refetch={refetch} parentFormIsFieldsTouched={form.isFieldsTouched} />
parentFormIsFieldsTouched={form.isFieldsTouched}/>
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} /> <JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
<Button type="primary" loading={loading} disabled={jobRO} onClick={() => form.submit()}> <Button type="primary" loading={loading} disabled={jobRO} onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}
@@ -321,8 +315,7 @@ export function JobsDetailPage({
icon: <BarsOutlined />, icon: <BarsOutlined />,
label: t("menus.jobsdetail.repairdata"), label: t("menus.jobsdetail.repairdata"),
forceRender: true, forceRender: true,
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
form={form}/>
}, },
{ {
key: "rates", key: "rates",
@@ -403,17 +396,26 @@ export function JobsDetailPage({
children: <JobAuditTrail jobId={job.id} /> children: <JobAuditTrail jobId={job.id} />
}, },
{ {
key: 'tasks', key: "tasks",
icon: <FaTasks />, icon: <FaTasks />,
label: <Space direction='horizontal'> label: (
{t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 && <Space direction="horizontal">
<Badge count={job.tasks_aggregate.aggregate.count}/>} {t("jobs.labels.tasks")}
</Space>, {job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />}
children: <TaskListContainer currentUser={currentUser} bodyshop={bodyshop} </Space>
relationshipType={'jobid'} relationshipId={job.id} ),
children: (
<TaskListContainer
currentUser={currentUser}
bodyshop={bodyshop}
relationshipType={"jobid"}
relationshipId={job.id}
query={QUERY_JOB_TASKS_PAGINATED} query={QUERY_JOB_TASKS_PAGINATED}
titleTranslation='tasks.titles.job_tasks' showRo={false}/> titleTranslation="tasks.titles.job_tasks"
}, showRo={false}
/>
)
}
]} ]}
/> />
</Form> </Form>

View File

@@ -17,26 +17,21 @@ import TestComponent from "../../components/_test/test.page";
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { requestForToken } from "../../firebase/firebase.utils"; import { requestForToken } from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import { setJoyRideFinished } from "../../redux/application/application.actions.js"; import { setJoyRideFinished } from "../../redux/application/application.actions.js";
import { import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js";
selectEnableJoyRide,
selectJoyRideSteps
} from "../../redux/application/application.selectors.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsPage = lazy(() => import("../jobs/jobs.page"));
const CardPaymentModalContainer = lazy(() => const CardPaymentModalContainer = lazy(
import("../../components/card-payment-modal/card-payment-modal.container.") () => import("../../components/card-payment-modal/card-payment-modal.container.")
); );
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container")); const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
@@ -64,8 +59,8 @@ const JobCostingModal = lazy(() => import("../../components/job-costing-modal/jo
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container")); const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container")); const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container"));
const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container")); const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container"));
const TimeTicketModalTask = lazy(() => const TimeTicketModalTask = lazy(
import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") () => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
); );
const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container")); const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container"));
const ProductionListPage = lazy(() => import("../production-list/production-list.container")); const ProductionListPage = lazy(() => import("../production-list/production-list.container"));
@@ -260,18 +255,20 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
} }
/> />
<Route <Route
path='/tasks/mytasks' path="/tasks/mytasks"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<MyTasksPage /> <MyTasksPage />
</Suspense>} </Suspense>
}
/> />
<Route <Route
path='/tasks/alltasks' path="/tasks/alltasks"
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<AllTasksPage /> <AllTasksPage />
</Suspense>} </Suspense>
}
/> />
<Route <Route
path="/inventory/" path="/inventory/"
@@ -647,8 +644,7 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR
Get Tours Get Tours
</Button> </Button>
{tours.map((tour) => ( {tours.map((tour) => (
<Tag key={tour.id} <Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
{tour.name} {tour.name}
</Tag> </Tag>
))} ))}

View File

@@ -14,13 +14,13 @@ import {useLocation} from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
}); });
export function MyTasksPageContainer({ export function MyTasksPageContainer({
@@ -31,45 +31,40 @@ export function MyTasksPageContainer({
setTaskUpsertContext setTaskUpsertContext
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const searchParams = queryString.parse((useLocation().search)); const searchParams = queryString.parse(useLocation().search);
useEffect(() => { useEffect(() => {
document.title = t("titles.all_tasks", { document.title = t("titles.all_tasks", {
app: InstanceRenderManager({ app: InstanceRenderManager({
imex: '$t(titles.imexonline)', imex: "$t(titles.imexonline)",
rome: '$t(titles.romeonline)', rome: "$t(titles.romeonline)",
promanager: '$t(titles.promanager)' promanager: "$t(titles.promanager)"
}) })
}); });
setSelectedHeader("all_tasks"); setSelectedHeader("all_tasks");
setBreadcrumbs([ setBreadcrumbs([
{ {
link: "/manage/tasks/alltasks", link: "/manage/tasks/alltasks",
label: t("titles.bc.all_tasks"), label: t("titles.bc.all_tasks")
},]); }
]);
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
// This takes care of the ability to deep link a task from the URL (Dispatches the modal) // This takes care of the ability to deep link a task from the URL (Dispatches the modal)
useEffect(() => { useEffect(() => {
// Check for a query string in the URL // Check for a query string in the URL
const urlParams = new URLSearchParams(searchParams); const urlParams = new URLSearchParams(searchParams);
const taskId = urlParams.get('taskid'); const taskId = urlParams.get("taskid");
if (taskId) { if (taskId) {
setTaskUpsertContext({ setTaskUpsertContext({
context: { context: {
taskId, taskId
}, }
}); });
urlParams.delete('taskid'); urlParams.delete("taskid");
} }
}, [setTaskUpsertContext]); }, [setTaskUpsertContext]);
return ( return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
<TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser}
bodyshop={bodyshop}/>
);
} }
export default connect( export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);
mapStateToProps,
mapDispatchToProps
)(MyTasksPageContainer);

View File

@@ -11,12 +11,12 @@ import TaskPageTypes from "./taskPageTypes.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
}); });
export function MyTasksPageContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader }) { export function MyTasksPageContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader }) {
@@ -24,26 +24,21 @@ export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, set
useEffect(() => { useEffect(() => {
document.title = t("titles.my_tasks", { document.title = t("titles.my_tasks", {
app: InstanceRenderManager({ app: InstanceRenderManager({
imex: '$t(titles.imexonline)', imex: "$t(titles.imexonline)",
rome: '$t(titles.romeonline)', rome: "$t(titles.romeonline)",
promanager: '$t(titles.promanager)' promanager: "$t(titles.promanager)"
}) })
}); });
setSelectedHeader("my_tasks"); setSelectedHeader("my_tasks");
setBreadcrumbs([ setBreadcrumbs([
{ {
link: "/manage/tasks/mytasks", link: "/manage/tasks/mytasks",
label: t("titles.bc.my_tasks"), label: t("titles.bc.my_tasks")
},]); }
]);
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
return ( return <TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
<TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser}
bodyshop={bodyshop}/>
);
} }
export default connect( export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);
mapStateToProps,
mapDispatchToProps
)(MyTasksPageContainer);

View File

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

View File

@@ -6,16 +6,27 @@ import taskPageTypes from "./taskPageTypes.jsx";
export default function TasksPageComponent({ bodyshop, currentUser, type }) { export default function TasksPageComponent({ bodyshop, currentUser, type }) {
switch (type) { switch (type) {
case taskPageTypes.MY_TASKS: case taskPageTypes.MY_TASKS:
return <TaskListContainer onlyMine={true} relationshipId={currentUser.email} return (
relationshipType={'user'} query={QUERY_MY_TASKS_PAGINATED} <TaskListContainer
bodyshop={bodyshop} titleTranslation={'tasks.titles.my_tasks'} onlyMine={true}
currentUser={currentUser}/> relationshipId={currentUser.email}
relationshipType={"user"}
query={QUERY_MY_TASKS_PAGINATED}
bodyshop={bodyshop}
titleTranslation={"tasks.titles.my_tasks"}
currentUser={currentUser}
/>
);
case taskPageTypes.ALL_TASKS: case taskPageTypes.ALL_TASKS:
return <TaskListContainer query={QUERY_ALL_TASKS_PAGINATED} bodyshop={bodyshop} return (
titleTranslation={'tasks.titles.all_tasks'} <TaskListContainer
currentUser={currentUser}/> query={QUERY_ALL_TASKS_PAGINATED}
bodyshop={bodyshop}
titleTranslation={"tasks.titles.all_tasks"}
currentUser={currentUser}
/>
);
default: default:
return <></> return <></>;
} }
} }

View File

@@ -13,7 +13,7 @@ const INITIAL_STATE = {
billEnter: { ...baseModal }, billEnter: { ...baseModal },
courtesyCarReturn: { ...baseModal }, courtesyCarReturn: { ...baseModal },
noteUpsert: { ...baseModal }, noteUpsert: { ...baseModal },
taskUpsert: {...baseModal, }, taskUpsert: { ...baseModal },
schedule: { ...baseModal }, schedule: { ...baseModal },
partsOrder: { ...baseModal }, partsOrder: { ...baseModal },
timeTicket: { ...baseModal }, timeTicket: { ...baseModal },

View File

@@ -41,32 +41,36 @@ const AuditTrailMapping = {
jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }), jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }),
jobvoid: () => i18n.t("audit_trail.messages.jobvoid"), jobvoid: () => i18n.t("audit_trail.messages.jobvoid"),
// Tasks Entries // Tasks Entries
tasksCreated: (title, createdBy) => i18n.t("audit_trail.messages.tasks_created", { tasksCreated: (title, createdBy) =>
i18n.t("audit_trail.messages.tasks_created", {
title, title,
createdBy createdBy
}), }),
tasksUpdated: (title, updatedBy) => i18n.t("audit_trail.messages.tasks_updated", { tasksUpdated: (title, updatedBy) =>
i18n.t("audit_trail.messages.tasks_updated", {
title, title,
updatedBy updatedBy
}), }),
tasksDeleted: (title, deletedBy) => i18n.t("audit_trail.messages.tasks_deleted", { tasksDeleted: (title, deletedBy) =>
i18n.t("audit_trail.messages.tasks_deleted", {
title, title,
deletedBy deletedBy
}), }),
tasksUndeleted: (title, undeletedBy) => i18n.t("audit_trail.messages.tasks_undeleted", { tasksUndeleted: (title, undeletedBy) =>
i18n.t("audit_trail.messages.tasks_undeleted", {
title, title,
undeletedBy undeletedBy
}), }),
tasksCompleted: (title, completedBy) => i18n.t("audit_trail.messages.tasks_completed", { tasksCompleted: (title, completedBy) =>
i18n.t("audit_trail.messages.tasks_completed", {
title, title,
completedBy completedBy
}), }),
tasksUncompleted: (title, uncompletedBy) => i18n.t("audit_trail.messages.tasks_uncompleted", { tasksUncompleted: (title, uncompletedBy) =>
i18n.t("audit_trail.messages.tasks_uncompleted", {
title, title,
uncompletedBy uncompletedBy
}), })
}; };
export default AuditTrailMapping; export default AuditTrailMapping;

View File

@@ -29,7 +29,7 @@ export function replaceUndefinedWithNull(obj, keys) {
return Object.fromEntries( return Object.fromEntries(
Object.entries(obj).map(([key, value]) => { Object.entries(obj).map(([key, value]) => {
if (keys) { if (keys) {
return [key, (keys.includes(key) && value === undefined) ? null : value]; return [key, keys.includes(key) && value === undefined ? null : value];
} else { } else {
return [key, value === undefined ? null : value]; return [key, value === undefined ? null : value];
} }

16
package-lock.json generated
View File

@@ -56,6 +56,7 @@
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"prettier": "^3.2.5",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
}, },
"engines": { "engines": {
@@ -5900,6 +5901,21 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",

View File

@@ -66,6 +66,7 @@
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"prettier": "^3.2.5",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
} }
} }

View File

@@ -13,21 +13,21 @@ const {header, end, start} = require("./html");
const defaultFooter = () => { const defaultFooter = () => {
return RenderInstanceManager({ return RenderInstanceManager({
imex: 'ImEX Online Collision Repair Management System', imex: "ImEX Online Collision Repair Management System",
rome: 'Rome Technologies', rome: "Rome Technologies",
promanager: 'ProManager', promanager: "ProManager"
}); });
}; };
const now = () => moment().format('MM/DD/YYYY @ hh:mm a'); const now = () => moment().format("MM/DD/YYYY @ hh:mm a");
const generateEmailTemplate = (strings) => { const generateEmailTemplate = (strings) => {
return (
return ` `
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">` <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">` +
+ header header +
+ start start +
+ ` `
<table class="row"> <table class="row">
<tbody> <tbody>
<tr> <tr>
@@ -101,8 +101,9 @@ const generateEmailTemplate = (strings) => {
</th> </th>
</tr> </tr>
</tbody> </tbody>
</table>` </table>` +
+ end end
);
}; };
module.exports = generateEmailTemplate; module.exports = generateEmailTemplate;

View File

@@ -11,7 +11,7 @@ const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const { isObject } = require("lodash"); const { isObject } = require("lodash");
const generateEmailTemplate = require('./generateTemplate'); const generateEmailTemplate = require("./generateTemplate");
const ses = new aws.SES({ const ses = new aws.SES({
// The key apiVersion is no longer supported in v3, and can be removed. // The key apiVersion is no longer supported in v3, and can be removed.