Compare commits

..

14 Commits

Author SHA1 Message Date
Patrick Fic
f1afc81e7d Resolve UI issue. 2023-01-20 07:59:18 -08:00
Patrick Fic
6d5b8baadf Automatic code mods. 2023-01-17 20:22:03 -08:00
Patrick Fic
07e36415e4 Automatic code mods. 2023-01-17 20:21:49 -08:00
Patrick Fic
8dc480826b Antd package update. 2023-01-17 20:10:59 -08:00
Patrick Fic
533a5b87ec Changing of deprecated antd props. 2023-01-17 20:00:29 -08:00
Patrick Fic
7bd83557c1 Remainder of menu refactoring updates. 2023-01-17 19:48:37 -08:00
Patrick Fic
ef290e79b1 Antd 4.24.x compatibility updates => Menu fixes. 2023-01-17 19:14:31 -08:00
Patrick Fic
713b3f4f6d Documents Gallery Package Fixes 2023-01-17 13:59:44 -08:00
Patrick Fic
8fd9614f69 Package cleanup and upgrades. 2023-01-17 11:41:45 -08:00
Patrick Fic
6682a98770 Merge branch 'master' into feature/major-package-upgrades 2023-01-17 09:43:54 -08:00
Patrick Fic
be61850d18 Merge in latest release. 2023-01-04 11:33:53 -08:00
Patrick Fic
f4208a2212 Client side package updates. 2022-12-22 15:06:10 -08:00
Patrick Fic
0b7278a2a8 Server side upgrades 2022-12-22 12:31:16 -08:00
Patrick Fic
bee078fe18 Add user definted rates to labor misc for autohouse. 2022-12-20 12:38:41 -08:00
424 changed files with 49297 additions and 28527 deletions

View File

@@ -1,3 +1,14 @@
Yarn Dependency Management:
To force upgrades for some packages:
yarn upgrade-interactive --latest
To Start Hasura CLI:
npx hasura console
Migrating to Staging:
npx hasura migrate apply --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
npx hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret 'Test-ImEXOnlineBySnaptSoftware!'
NGROK TEsting: NGROK TEsting:
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000" ./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
@@ -10,4 +21,4 @@ hasura migrate apply --version "1620771761757" --skip-execution --endpoint https
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#' hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
Generate the license file: Generate the license file:
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite $ generate-license-file --input package.json --output third-party-licenses.txt --overwrite

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,6 @@ REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4' REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000 REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,4 +1,3 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507 REACT_APP_GA_CODE=231103507

View File

@@ -1,6 +1,5 @@
// craco.config.js // craco.config.js
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
const SentryWebpackPlugin = require("@sentry/webpack-plugin"); const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = { module.exports = {
@@ -20,37 +19,6 @@ module.exports = {
ignore: ["node_modules", "webpack.config.js"], ignore: ["node_modules", "webpack.config.js"],
}, },
}, },
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {
...(process.env.NODE_ENV === "development"
? { "@primary-color": "#a51d1d" }
: {
//"@primary-color": "#1DA57A"
}),
// "@primary-color": " #1890ff", // primary color for all components
// "@link-color": "#1890ff", // link color
// "@success-color": "#52c41a", // success state color
// "@warning-color": "#faad14", // warning state color
// "@error-color": "#f5222d", // error state color
// "@font-size-base": "14px", // major text font size
// " @heading-color": "rgba(0, 0, 0, 0.85)", // heading text color
// "@text-color": "rgba(0, 0, 0, 0.65)", // major text color
// "@text-color-secondary": "rgba(0, 0, 0, 0.45)", // secondary text color
// "@disabled-color": "rgba(0, 0, 0, 0.25)", // disable state color
// "@border-radius-base": "2px", // major border radius
// "@border-color-base": "#d9d9d9", // major border color
// "@box-shadow-base":
// "0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers }",
},
javascriptEnabled: true,
},
},
},
},
], ],
webpack: { webpack: {
configure: (webpackConfig) => ({ configure: (webpackConfig) => ({

View File

@@ -4,73 +4,67 @@
"private": true, "private": true,
"proxy": "http://localhost:4000", "proxy": "http://localhost:4000",
"dependencies": { "dependencies": {
"@apollo/client": "^3.7.9", "@ant-design/pro-layout": "^7.6.1",
"@apollo/client": "^3.6.9",
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.0.0", "@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.40.0", "@sentry/react": "^7.28.1",
"@sentry/tracing": "^7.40.0", "@sentry/tracing": "^7.28.1",
"@splitsoftware/splitio-react": "^1.8.1", "@splitsoftware/splitio-react": "^1.6.0",
"@tanem/react-nprogress": "^5.0.8", "@tanem/react-nprogress": "^5.0.8",
"antd": "^4.24.8", "antd": "5.1.5",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.0",
"axios": "^1.3.4", "axios": "^1.2.3",
"craco-less": "^2.0.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.0.1",
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^9.17.1", "firebase": "^9.9.1",
"graphql": "^16.6.0", "graphql": "^16.5.0",
"i18next": "^22.4.10", "i18next": "^22.4.6",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"jsoneditor": "^9.9.0",
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.21", "libphonenumber-js": "^1.10.9",
"logrocket": "^3.0.1", "logrocket": "^3.0.1",
"markerjs2": "^2.28.1", "markerjs2": "^2.22.0",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.41", "moment-timezone": "^0.5.34",
"normalize-url": "^8.0.0", "normalize-url": "^8.0.0",
"phone": "^3.1.35", "phone": "^3.1.32",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^7.1.3", "query-string": "^8.1.0",
"rc-queue-anim": "^2.0.0", "rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6", "rc-scroll-anim": "^2.7.6",
"react": "^17.0.2", "react": "^18.2.0",
"react-big-calendar": "^1.6.8", "react-big-calendar": "^1.5.2",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^4.1.1", "react-dom": "^18.2.0",
"react-dom": "^17.0.2", "react-drag-listview": "^2.0.0",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^1.0.0", "react-grid-gallery": "^1.0.0",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^12.2.0", "react-i18next": "^12.1.1",
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.4.3", "react-number-format": "^5.1.2",
"react-number-format": "^5.1.3",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-resizable": "^3.0.4", "react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
"recharts": "^2.4.3", "recharts": "^2.1.12",
"redux": "^4.2.1", "redux": "^4.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.2.2", "redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.4", "redux-state-sync": "^3.1.4",
"reselect": "^4.1.7", "reselect": "^4.1.6",
"sass": "^1.58.3", "sass": "^1.54.0",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.5.1",
"styled-components": "^5.3.6", "styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.4", "web-vitals": "^3.1.0",
"workbox-background-sync": "^6.5.3", "workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3", "workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.5.3", "workbox-cacheable-response": "^6.5.3",
@@ -82,12 +76,12 @@
"workbox-range-requests": "^6.5.3", "workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3", "workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3", "workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3", "workbox-streams": "^6.5.3"
"yauzl": "^2.10.0"
}, },
"scripts": { "scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start", "start": "craco start",
"startrs": "react-scripts start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build", "build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test": "env-cmd -f .env.test yarn run build", "build:test": "env-cmd -f .env.test yarn run build",
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'", "build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
@@ -120,8 +114,8 @@
}, },
"devDependencies": { "devDependencies": {
"@sentry/webpack-plugin": "^1.20.0", "@sentry/webpack-plugin": "^1.20.0",
"@testing-library/cypress": "^8.0.3", "@testing-library/cypress": "^9.0.0",
"cypress": "^10.3.1", "cypress": "^12.3.0",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.11", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",

View File

@@ -143,28 +143,13 @@
} }
} }
//Update row highlighting on production board.
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td { .ant-table-tbody > tr.ant-table-row:hover > td {
background: #e7f3ff !important; background: #eaeaea !important;
} }
.ant-table-tbody > tr.ant-table-row-selected > td { .job-line-manual{
background: #e6f7ff !important;
}
.job-line-manual {
color: tomato; color: tomato;
font-style: italic; font-style: italic;
} }
td.ant-table-column-sort {
background-color: transparent;
}
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4;
}
.rowWithColor > td {
background-color: var(--bgColor) !important;
}

View File

@@ -61,7 +61,7 @@ export function AllocationsAssignmentComponent({
); );
return ( return (
<Popover content={popContent} visible={visibility}> <Popover content={popContent} open={visibility}>
<Button onClick={() => setVisibility(true)}> <Button onClick={() => setVisibility(true)}>
{t("allocations.actions.assign")} {t("allocations.actions.assign")}
</Button> </Button>

View File

@@ -59,7 +59,7 @@ export default connect(
); );
return ( return (
<Popover content={popContent} visible={visibility}> <Popover content={popContent} open={visibility}>
<Button disabled={disabled} onClick={() => setVisibility(true)}> <Button disabled={disabled} onClick={() => setVisibility(true)}>
{t("allocations.actions.assign")} {t("allocations.actions.assign")}
</Button> </Button>

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { DELETE_BILL } from "../../graphql/bills.queries"; import { DELETE_BILL } from "../../graphql/bills.queries";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function BillDeleteButton({ bill, callback }) { export default function BillDeleteButton({ bill }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL); const [deleteBill] = useMutation(DELETE_BILL);
@@ -36,8 +36,6 @@ export default function BillDeleteButton({ bill, callback }) {
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") }); notification["success"]({ message: t("bills.successes.deleted") });
if (callback && typeof callback === "function") callback(bill.id);
} else { } else {
//Check if it's an fkey violation. //Check if it's an fkey violation.
const error = JSON.stringify(result.errors); const error = JSON.stringify(result.errors);

View File

@@ -1,5 +1,6 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd"; import { PageHeader } from '@ant-design/pro-layout';
import { Button, Form, Popconfirm, Space } from "antd";
import moment from "moment"; import moment from "moment";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -178,7 +179,7 @@ export function BillDetailEditcontainer({
<BillDetailEditReturn data={data} /> <BillDetailEditReturn data={data} />
<Popconfirm <Popconfirm
visible={visible} open={visible}
onConfirm={() => form.submit()} onConfirm={() => form.submit()}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }} okButtonProps={{ loading: updateLoading }}

View File

@@ -77,7 +77,7 @@ export function BillDetailEditReturn({
return ( return (
<> <>
<Modal <Modal
visible={visible} open={visible}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
destroyOnClose destroyOnClose
title={t("bills.actions.return")} title={t("bills.actions.return")}

View File

@@ -32,7 +32,7 @@ export default function BillDetailEditcontainer() {
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
destroyOnClose destroyOnClose
visible={search.billid} open={search.billid}
> >
<BillDetailEditComponent /> <BillDetailEditComponent />
</Drawer> </Drawer>

View File

@@ -1,18 +1,18 @@
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Checkbox, Form, Modal, Space, notification } from "antd"; import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useState, useMemo } 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 { INSERT_NEW_BILL } from "../../graphql/bills.queries"; import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { import {
QUERY_JOB_LBR_ADJUSTMENTS, QUERY_JOB_LBR_ADJUSTMENTS,
UPDATE_JOB, UPDATE_JOB,
} from "../../graphql/jobs.queries"; } from "../../graphql/jobs.queries";
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries"; import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
@@ -20,15 +20,15 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import confirmDialog from "../../utils/asyncConfirm"; import confirmDialog from "../../utils/asyncConfirm";
import useLocalStorage from "../../utils/useLocalStorage"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillFormContainer from "../bill-form/bill-form.container"; import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility"; import { handleUpload } from "../documents-upload/documents-upload.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import useLocalStorage from "../../utils/useLocalStorage";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal, billEnterModal: selectBillEnterModal,
@@ -126,17 +126,6 @@ function BillEnterModalContainer({
deductedfromlbr: deductedfromlbr, deductedfromlbr: deductedfromlbr,
lbr_adjustment, lbr_adjustment,
joblineid: i.joblineid === "noline" ? null : i.joblineid, joblineid: i.joblineid === "noline" ? null : i.joblineid,
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) ||
false,
state:
(i.applicable_taxes && i.applicable_taxes.state) ||
false,
local:
(i.applicable_taxes && i.applicable_taxes.local) ||
false,
},
}; };
}), }),
}, },
@@ -355,7 +344,7 @@ function BillEnterModalContainer({
<Modal <Modal
title={t("bills.labels.new")} title={t("bills.labels.new")}
width={"98%"} width={"98%"}
visible={billEnterModal.visible} open={billEnterModal.visible}
okText={t("general.actions.save")} okText={t("general.actions.save")}
keyboard="false" keyboard="false"
onOk={() => form.submit()} onOk={() => form.submit()}

View File

@@ -7,9 +7,7 @@ import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors"; import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss"; import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs, breadcrumbs: selectBreadcrumbs,
@@ -17,12 +15,6 @@ const mapStateToProps = createStructuredSelector({
}); });
export function BreadCrumbs({ breadcrumbs, bodyshop }) { export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
return ( return (
<Row className="breadcrumb-container"> <Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}> <Col xs={24} sm={24} md={16}>
@@ -46,7 +38,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
</Breadcrumb> </Breadcrumb>
</Col> </Col>
<Col xs={24} sm={24} md={8}> <Col xs={24} sm={24} md={8}>
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />} <GlobalSearch />
</Col> </Col>
</Row> </Row>
); );

View File

@@ -70,7 +70,7 @@ export function ContractsFindModalContainer({
return ( return (
<Modal <Modal
visible={visible} open={visible}
width="70%" width="70%"
title={t("payments.labels.findermodal")} title={t("payments.labels.findermodal")}
onCancel={() => toggleModalVisible()} onCancel={() => toggleModalVisible()}

View File

@@ -10,10 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
const handleFinish = async (values) => { const handleFinish = async (values) => {
logImEXEvent("job_ca_bc_pvrt_calculate"); logImEXEvent("job_ca_bc_pvrt_calculate");
form.setFieldsValue({ form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
});
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
setVisibility(false); setVisibility(false);
}; };
@@ -38,7 +35,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
<Popover <Popover
destroyTooltipOnHide destroyTooltipOnHide
content={popContent} content={popContent}
visible={visibility} open={visibility}
disabled={disabled} disabled={disabled}
> >
<Button disabled={disabled} onClick={() => setVisibility(true)}> <Button disabled={disabled} onClick={() => setVisibility(true)}>

View File

@@ -1,19 +1,12 @@
import { Badge, List, Tag } from "antd"; import { Badge, List, Tag } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List as VirtualizedList,
} from "react-virtualized";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { TimeAgoFormatter } from "../../utils/DateFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import "./chat-conversation-list.styles.scss"; import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -25,95 +18,59 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)), dispatch(setSelectedConversation(conversationId)),
}); });
function ChatConversationListComponent({ export function ChatConversationListComponent({
conversationList, conversationList,
selectedConversation, selectedConversation,
setSelectedConversation, setSelectedConversation,
loadMoreConversations,
}) { }) {
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 60,
});
const rowRenderer = ({ index, key, style, parent }) => {
const item = conversationList[index];
return (
<CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<List.Item
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
style={style}
>
<div
style={{
display: "inline-block",
}}
>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div style={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
</CellMeasurer>
);
};
return ( return (
<div className="chat-list-container"> <div className="chat-list-container">
<AutoSizer> <List
{({ height, width }) => ( bordered
<VirtualizedList dataSource={conversationList}
height={height} renderItem={(item) => (
width={width} <List.Item
rowCount={conversationList.length} key={item.id}
rowHeight={cache.rowHeight} onClick={() => setSelectedConversation(item.id)}
rowRenderer={rowRenderer} className={`chat-list-item ${
onScroll={({ scrollTop, scrollHeight, clientHeight }) => { item.id === selectedConversation
if (scrollTop + clientHeight === scrollHeight) { ? "chat-list-selected-conversation"
loadMoreConversations(); : null
} }`}
}} >
/> <div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div sryle={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
)} )}
</AutoSizer> />
</div> </div>
); );
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps

View File

@@ -3,9 +3,8 @@
} }
.chat-list-container { .chat-list-container {
flex: 1; flex: 1;
overflow: hidden; overflow: auto;
height: 100%; height: 100%;
border: 1px solid gainsboro;
} }
.chat-list-item { .chat-list-item {
@@ -22,6 +21,4 @@
.ro-number-tag { .ro-number-tag {
align-self: baseline; align-self: baseline;
} }
padding: 12px 24px;
border-bottom: 1px solid gainsboro;
} }

View File

@@ -42,7 +42,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
MARK_MESSAGES_AS_READ_BY_CONVERSATION, MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{ {
variables: { conversationId: selectedConversation }, variables: { conversationId: selectedConversation },
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
update(cache) { update(cache) {
cache.modify({ cache.modify({
id: cache.identify({ id: cache.identify({

View File

@@ -88,8 +88,8 @@ export function ChatMediaSelector({
} }
title={t("messaging.labels.selectmedia")} title={t("messaging.labels.selectmedia")}
trigger="click" trigger="click"
visible={visible} open={visible}
onVisibleChange={handleVisibleChange} onOpenChange={handleVisibleChange}
> >
<Badge count={selectedMedia.filter((s) => s.isSelected).length}> <Badge count={selectedMedia.filter((s) => s.isSelected).length}>
<PictureFilled style={{ margin: "0 .5rem" }} /> <PictureFilled style={{ margin: "0 .5rem" }} />

View File

@@ -4,16 +4,13 @@ import {
ShrinkOutlined, ShrinkOutlined,
SyncOutlined, SyncOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useLazyQuery, useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries";
CONVERSATION_LIST_QUERY,
UNREAD_CONVERSATION_COUNT,
} from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { import {
selectChatVisible, selectChatVisible,
@@ -40,21 +37,12 @@ export function ChatPopupComponent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [pollInterval, setpollInterval] = useState(0); const [pollInterval, setpollInterval] = useState(0);
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
...(pollInterval > 0 ? { pollInterval } : {}), ...(pollInterval > 0 ? { pollInterval } : {}),
}); });
const [getConversations, { loading, data, refetch, fetchMore }] =
useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken"); const fcmToken = sessionStorage.getItem("fcmtoken");
useEffect(() => { useEffect(() => {
@@ -66,24 +54,15 @@ export function ChatPopupComponent({
}, [fcmToken]); }, [fcmToken]);
useEffect(() => { useEffect(() => {
if (chatVisible) if (called && chatVisible) refetch();
getConversations({ }, [chatVisible, called, refetch]);
variables: {
offset: 0,
},
});
}, [chatVisible, getConversations]);
const loadMoreConversations = useCallback(() => { const unreadCount = data
if (data) ? data.conversations.reduce(
fetchMore({ (acc, val) => val.messages_aggregate.aggregate.count + acc,
variables: { 0
offset: data.conversations.length, )
}, : 0;
});
}, [data, fetchMore]);
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
return ( return (
<Badge count={unreadCount}> <Badge count={unreadCount}>
@@ -118,7 +97,6 @@ export function ChatPopupComponent({
) : ( ) : (
<ChatConversationListComponent <ChatConversationListComponent
conversationList={data ? data.conversations : []} conversationList={data ? data.conversations : []}
loadMoreConversations={loadMoreConversations}
/> />
)} )}
</Col> </Col>

View File

@@ -1,5 +1,5 @@
import { PlusCircleOutlined } from "@ant-design/icons"; import { PlusCircleOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -16,19 +16,15 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function ChatPresetsComponent({ bodyshop, setMessage, className }) { export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
const menu = ( const menu = bodyshop.md_messaging_presets.map((i, idx) => ({
<Menu> label: i.label,
{bodyshop.md_messaging_presets.map((i, idx) => ( key: idx,
<Menu.Item onClick={() => setMessage(i.text)} key={idx}> onClick: () => setMessage(i.text),
{i.label} }));
</Menu.Item>
))}
</Menu>
);
return ( return (
<div className={className}> <div className={className}>
<Dropdown trigger={["click"]} overlay={menu}> <Dropdown trigger={["click"]} menu={{ items: menu }} placement="top">
<PlusCircleOutlined /> <PlusCircleOutlined />
</Dropdown> </Dropdown>
</div> </div>

View File

@@ -390,7 +390,7 @@ export function ContractConvertToRo({
return ( return (
<div> <div>
<Popover content={popContent} visible={visible}> <Popover content={popContent} open={visible}>
<Button <Button
onClick={() => setVisible(true)} onClick={() => setVisible(true)}
loading={loading} loading={loading}

View File

@@ -4,7 +4,7 @@ import moment from "moment";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component"; import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component"; import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component"; import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
@@ -165,9 +165,7 @@ export default function ContractFormComponent({
/> />
</div> </div>
)} )}
{ <ContractLicenseDecodeButton form={form} />
//<ContractLicenseDecodeButton form={form} />
}
</Space> </Space>
</div> </div>
<LayoutFormRow header={t("contracts.labels.driverinformation")}> <LayoutFormRow header={t("contracts.labels.driverinformation")}>

View File

@@ -55,7 +55,7 @@ export default function ContractLicenseDecodeButton({ form }) {
return ( return (
<div> <div>
<Modal <Modal
visible={modalVisible} open={modalVisible}
okText={t("contracts.actions.senddltoform")} okText={t("contracts.actions.senddltoform")}
onOk={handleInsertForm} onOk={handleInsertForm}
okButtonProps={{ disabled: !!!decodedBarcode }} okButtonProps={{ disabled: !!!decodedBarcode }}

View File

@@ -59,7 +59,7 @@ export function ContractsFindModalContainer({
return ( return (
<Modal <Modal
visible={visible} open={visible}
width="70%" width="70%"
title={t("contracts.labels.findermodal")} title={t("contracts.labels.findermodal")}
onCancel={() => toggleModalVisible()} onCancel={() => toggleModalVisible()}

View File

@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons"; import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -13,25 +13,14 @@ const mapStateToProps = createStructuredSelector({
export function ContractsRatesChangeButton({ disabled, form, bodyshop }) { export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => { const menu = bodyshop.md_ccc_rates.map((rate, idx) => ({
const { label, ...rate } = item.props.value; onClick: () => form.setFieldsValue(rate),
form.setFieldsValue(rate); key: idx,
}; label: rate.label,
}));
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_ccc_rates.map((rate, idx) => (
<Menu.Item value={rate} key={idx}>
{rate.label}
</Menu.Item>
))}
</Menu>
</div>
);
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={{ items: menu }} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -1,6 +1,7 @@
import { WarningFilled } from "@ant-design/icons"; import { WarningFilled } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { Button, Form, Input, InputNumber, PageHeader, Space } from "antd"; import { PageHeader } from '@ant-design/pro-layout';
import { Button, Form, Input, InputNumber, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";

View File

@@ -64,7 +64,7 @@ export function CCReturnModalContainer({
return ( return (
<Modal <Modal
title={t("courtesycars.labels.return")} title={t("courtesycars.labels.return")}
visible={visible} open={visible}
onCancel={() => toggleModalVisible()} onCancel={() => toggleModalVisible()}
width={"90%"} width={"90%"}
okText={t("general.actions.save")} okText={t("general.actions.save")}

View File

@@ -3,21 +3,19 @@ import {
Button, Button,
Card, Card,
Dropdown, Dropdown,
Input, Input, Space,
Menu,
Space,
Table, Table,
Tooltip, Tooltip
} from "antd"; } from "antd";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import moment from "moment";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) { export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
@@ -179,10 +177,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
</Button> </Button>
<Dropdown <Dropdown
trigger="click" trigger="click"
overlay={ menu={{
<Menu> items: [
<Menu.Item {
onClick={() => label: t(
"printcenter.courtesycarcontract.courtesy_car_inventory"
),
key: "cc_inv",
onClick: () =>
GenerateDocument( GenerateDocument(
{ {
name: TemplateList("courtesycar").courtesy_car_inventory name: TemplateList("courtesycar").courtesy_car_inventory
@@ -193,13 +195,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
}, },
{}, {},
"p" "p"
) ),
} },
> ],
{t("printcenter.courtesycarcontract.courtesy_car_inventory")} }}
</Menu.Item>
</Menu>
}
> >
<Button>{t("general.labels.print")}</Button> <Button>{t("general.labels.print")}</Button>
</Dropdown> </Dropdown>

View File

@@ -1,235 +0,0 @@
import {
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { Card, Space, Table, Tooltip } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
export default function DashboardScheduledInToday({ data, ...cardProps }) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
if (!data) return null;
if (!data.scheduled_in_today)
return <DashboardRefreshRequired {...cardProps} />;
const appt = []; // Flatten Data
data.scheduled_in_today.forEach((item) => {
var i = {
canceled: item.canceled,
id: item.id,
alt_transport: item.job.alt_transport,
clm_no: item.job.clm_no,
jobid: item.job.jobid,
ins_co_nm: item.job.ins_co_nm,
iouparent: item.job.iouparent,
ownerid: item.job.ownerid,
ownr_co_nm: item.job.ownr_co_nm,
ownr_ea: item.job.ownr_ea,
ownr_fn: item.job.ownr_fn,
ownr_ln: item.job.ownr_ln,
ownr_ph1: item.job.ownr_ph1,
ownr_ph2: item.job.ownr_ph2,
production_vars: item.job.production_vars,
ro_number: item.job.ro_number,
suspended: item.job.suspended,
v_make_desc: item.job.v_make_desc,
v_model_desc: item.job.v_model_desc,
v_model_yr: item.job.v_model_yr,
v_vin: item.job.v_vin,
vehicleid: item.job.vehicleid,
note: item.note,
start: moment(item.start).format("hh:mm a"),
title: item.title,
};
appt.push(i);
});
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.ownr_ea"),
dataIndex: "ownr_ea",
key: "ownr_ea",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
responsive: ["md"],
},
{
title: t("appointments.fields.time"),
dataIndex: "start",
key: "start",
ellipsis: true,
responsive: ["md"],
},
{
title: t("appointments.fields.alt_transport"),
dataIndex: "alt_transport",
key: "alt_transport",
ellipsis: true,
responsive: ["md"],
},
];
const handleTableChange = (sorter) => {
setState({ ...state, sortedInfo: sorter });
};
return (
<Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}>
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"
style={{ height: "85%" }}
dataSource={appt}
/>
</div>
</Card>
);
}
export const DashboardScheduledInTodayGql = `
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
.startOf("day")
.toISOString()}", _lte: "${moment()
.endOf("day")
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
canceled
id
job {
alt_transport
clm_no
jobid: id
ins_co_nm
iouparent
ownerid
ownr_co_nm
ownr_ea
ownr_fn
ownr_ln
ownr_ph1
ownr_ph2
production_vars
ro_number
suspended
v_make_desc
v_model_desc
v_model_yr
v_vin
vehicleid
}
note
start
title
}
`;

View File

@@ -1,210 +0,0 @@
import {
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { Card, Space, Table, Tooltip } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
if (!data) return null;
if (!data.scheduled_out_today)
return <DashboardRefreshRequired {...cardProps} />;
data.scheduled_out_today.forEach((item) => {
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
});
data.scheduled_out_today.sort(function (a, b) {
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
});
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.ownr_ea"),
dataIndex: "ownr_ea",
key: "ownr_ea",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
responsive: ["md"],
},
{
title: t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion",
key: "scheduled_completion",
ellipsis: true,
responsive: ["md"],
},
{
title: t("appointments.fields.alt_transport"),
dataIndex: "alt_transport",
key: "alt_transport",
ellipsis: true,
responsive: ["md"],
},
];
const handleTableChange = (sorter) => {
setState({ ...state, sortedInfo: sorter });
};
return (
<Card
title={t("dashboard.titles.scheduledouttoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
{...cardProps}
>
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"
style={{ height: "85%" }}
dataSource={data.scheduled_out_today}
/>
</div>
</Card>
);
}
export const DashboardScheduledOutTodayGql = `
scheduled_out_today: jobs(where: {
date_invoiced: {_is_null: true},
ro_number: {_is_null: false},
voided: {_eq: false},
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
_lte: "${moment().endOf("day").toISOString()}"}}) {
alt_transport
clm_no
jobid: id
ins_co_nm
iouparent
ownerid
ownr_co_nm
ownr_ea
ownr_fn
ownr_ln
ownr_ph1
ownr_ph2
production_vars
ro_number
scheduled_completion
suspended
v_make_desc
v_model_desc
v_model_yr
v_vin
vehicleid
}
`;

View File

@@ -1,6 +1,7 @@
import Icon, { SyncOutlined } from "@ant-design/icons"; import Icon, { SyncOutlined } from "@ant-design/icons";
import { gql, useMutation, useQuery } from "@apollo/client"; import { gql, useMutation, useQuery } from "@apollo/client";
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd"; import { PageHeader } from '@ant-design/pro-layout';
import { Button, Dropdown, Menu, notification, Space } from "antd";
import i18next from "i18next"; import i18next from "i18next";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
@@ -37,12 +38,6 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
//Combination of the following: //Combination of the following:
// /node_modules/react-grid-layout/css/styles.css // /node_modules/react-grid-layout/css/styles.css
// /node_modules/react-resizable/css/styles.css // /node_modules/react-resizable/css/styles.css
import DashboardScheduledInToday, {
DashboardScheduledInTodayGql,
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
import DashboardScheduledOutToday, {
DashboardScheduledOutTodayGql,
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
import "./dashboard-grid.styles.scss"; import "./dashboard-grid.styles.scss";
import { GenerateDashboardData } from "./dashboard-grid.utils"; import { GenerateDashboardData } from "./dashboard-grid.utils";
@@ -123,17 +118,15 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
); );
const existingLayoutKeys = state.items.map((i) => i.i); const existingLayoutKeys = state.items.map((i) => i.i);
const addComponentOverlay = ( const addComponentOverlay = (
<Menu onClick={handleAddComponent}> <Menu
{Object.keys(componentList).map((key) => ( onClick={handleAddComponent}
<Menu.Item items={Object.keys(componentList).map((key) => ({
key={key} key: key,
value={key} value: key,
disabled={existingLayoutKeys.includes(key)} disabled: existingLayoutKeys.includes(key),
> label: componentList[key].label,
{componentList[key].label} }))}
</Menu.Item> ></Menu>
))}
</Menu>
); );
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
@@ -274,28 +267,6 @@ const componentList = {
w: 2, w: 2,
h: 2, h: 2,
}, },
ScheduleInToday: {
label: i18next.t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
}),
component: DashboardScheduledInToday,
gqlFragment: DashboardScheduledInTodayGql,
minW: 10,
minH: 2,
w: 10,
h: 2,
},
ScheduleOutToday: {
label: i18next.t("dashboard.titles.scheduledouttoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
}),
component: DashboardScheduledOutToday,
gqlFragment: DashboardScheduledOutTodayGql,
minW: 10,
minH: 2,
w: 10,
h: 2,
},
}; };
const createDashboardQuery = (state) => { const createDashboardQuery = (state) => {

View File

@@ -8,8 +8,6 @@ export default function DataLabel({
vertical, vertical,
visible = true, visible = true,
valueStyle = {}, valueStyle = {},
valueClassName,
onValueClick,
...props ...props
}) { }) {
if (!visible || (hideIfNull && !!!children)) return null; if (!visible || (hideIfNull && !!!children)) return null;
@@ -30,10 +28,7 @@ export default function DataLabel({
marginLeft: ".3rem", marginLeft: ".3rem",
fontWeight: "bolder", fontWeight: "bolder",
wordWrap: "break-word", wordWrap: "break-word",
cursor: onValueClick !== undefined ? "pointer" : "",
}} }}
className={valueClassName}
onClick={onValueClick}
> >
{typeof children === "string" ? ( {typeof children === "string" ? (
<Typography.Text style={valueStyle}>{children}</Typography.Text> <Typography.Text style={valueStyle}>{children}</Typography.Text>

View File

@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
key: "status", key: "status",
}, },
{ {
title: t("bills.fields.invoice_number"), title: t("jobs.fields.ro_number"),
dataIndex: ["Posting", "Reference"], dataIndex: ["Posting", "Reference"],
key: "reference", key: "reference",
}, },

View File

@@ -51,7 +51,7 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
<> <>
<Modal <Modal
width={"90%"} width={"90%"}
visible={visible} open={visible}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
onOk={() => { onOk={() => {
form.setFieldsValue({ form.setFieldsValue({

View File

@@ -6,13 +6,10 @@ import {
Dropdown, Dropdown,
Form, Form,
Input, Input,
InputNumber, InputNumber, Select,
Menu,
Select,
Space, Space,
Statistic, Statistic,
Switch, Typography
Typography,
} from "antd"; } from "antd";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import moment from "moment"; import moment from "moment";
@@ -184,13 +181,6 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
<Space> <Space>
<DmsCdkMakes form={form} socket={socket} job={job} /> <DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch /> <DmsCdkMakesRefetch />
<Form.Item
name="dms_unsold"
label={t("jobs.fields.dms.dms_unsold")}
initialValue={false}
>
<Switch />
</Form.Item>
</Space> </Space>
</div> </div>
)} )}
@@ -285,36 +275,32 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
<div> <div>
{t("jobs.fields.dms.payer.controlnumber")}{" "} {t("jobs.fields.dms.payer.controlnumber")}{" "}
<Dropdown <Dropdown
overlay={ menu={{
<Menu> items:
{bodyshop.cdk_configuration.controllist && bodyshop.cdk_configuration.controllist &&
bodyshop.cdk_configuration.controllist.map( bodyshop.cdk_configuration.controllist.map(
(key, idx) => ( (key, idx) => ({
<Menu.Item key: idx,
key={idx} label: key.name,
onClick={() => { onClick: () => {
form.setFieldsValue({ form.setFieldsValue({
payers: form payers: form
.getFieldValue("payers") .getFieldValue("payers")
.map((row, mapIndex) => { .map((row, mapIndex) => {
if (index !== mapIndex) if (index !== mapIndex)
return row; return row;
return { return {
...row, ...row,
controlnumber: controlnumber:
key.controlnumber, key.controlnumber,
}; };
}), }),
}); });
}} },
> })
{key.name} ),
</Menu.Item> }}
)
)}
</Menu>
}
> >
<a href=" #" onClick={(e) => e.preventDefault()}> <a href=" #" onClick={(e) => e.preventDefault()}>
<DownOutlined /> <DownOutlined />

View File

@@ -1,28 +1,28 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons"; import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import { import {
Button,
Divider, Divider,
Dropdown,
Form, Form,
Input, Input,
Menu,
Select, Select,
Space,
Tabs, Tabs,
Upload, Upload,
Space,
Menu,
Dropdown,
Button,
} from "antd"; } from "antd";
import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia"; import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import EmailDocumentsComponent from "../email-documents/email-documents.component"; import { selectEmailConfig } from "../../redux/email/email.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -54,51 +54,26 @@ export function EmailOverlayComponent({
]), ]),
}); });
}; };
const handle_CC_Click = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
const menu = ( const menu = (
<div> <div>
<Menu onClick={handleClick}> <Menu
{bodyshop.employees onClick={handleClick}
.filter((e) => e.user_email) items={[
.map((e, idx) => ( ...bodyshop.employees
<Menu.Item value={e.user_email} key={idx}> .filter((e) => e.user_email)
{`${e.first_name} ${e.last_name}`} .map((e, idx) => ({
</Menu.Item> value: e.user_email,
))} key: idx,
{bodyshop.md_to_emails.map((e, idx) => ( label: `${e.first_name} ${e.last_name}`,
<Menu.Item value={e.emails} key={idx + "group"}> })),
{e.label} ...bodyshop.md_to_emails.map((e, idx) => ({
</Menu.Item> value: e.emails,
))} key: idx + "group",
</Menu> label: e.label,
</div> })),
); ]}
/>
const menuCC = (
<div>
<Menu onClick={handle_CC_Click}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div> </div>
); );
@@ -150,23 +125,7 @@ export function EmailOverlayComponent({
> >
<Select mode="tags" tokenSeparators={[",", ";"]} /> <Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t("emails.fields.cc")} name="cc">
label={
<Space>
{t("emails.fields.cc")}
<Dropdown overlay={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]} /> <Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item

View File

@@ -174,7 +174,7 @@ export function EmailOverlayContainer({
return ( return (
<Modal <Modal
destroyOnClose={true} destroyOnClose={true}
visible={modalVisible} open={modalVisible}
maskClosable={false} maskClosable={false}
width={"80%"} width={"80%"}
onOk={() => form.submit()} onOk={() => form.submit()}

View File

@@ -94,7 +94,7 @@ const FormInputNUmberCalculator = (
return ( return (
<div> <div>
<Popover content={popContent} visible={history.length > 0}> <Popover content={popContent} open={history.length > 0}>
<InputNumber <InputNumber
ref={ref} ref={ref}
value={value} value={value}

View File

@@ -1,216 +0,0 @@
import { AutoComplete, Divider, Input, Space } from "antd";
import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
const executeSearch = async (v) => {
if (v && v && v !== "" && v.length >= 3) {
try {
setLoading(true);
const searchData = await axios.post("/search", {
search: v,
});
const resultsByType = {
payments: [],
jobs: [],
bills: [],
owners: [],
vehicles: [],
};
searchData.data.hits.hits.forEach((hit) => {
resultsByType[hit._index].push(hit._source);
});
setData([
{
label: renderTitle(t("menus.header.search.jobs")),
options: resultsByType.jobs.map((job) => {
return {
key: job.id,
value: job.ro_number || "N/A",
label: (
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span>
<span>
<OwnerNameDisplay ownerObject={job} />
</span>
<span>{`${job.v_model_yr || ""} ${
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.owners")),
options: resultsByType.owners.map((owner) => {
return {
key: owner.id,
value: OwnerNameDisplayFunction(owner),
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space
size="small"
split={<Divider type="vertical" />}
wrap
>
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => {
return {
key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: (
<Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>
{`${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span>
<span>{vehicle.plate_no || ""}</span>
<span>
<VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.payments")),
options: resultsByType.payments.map((payment) => {
return {
key: payment.id,
value: `${payment.job?.ro_number} ${payment.amount}`,
label: (
<Link to={`/manage/jobs/${payment.job?.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job?.ro_number}</span>
<span>{payment.memo || ""}</span>
<span>{payment.amount || ""}</span>
<span>{payment.transactionid || ""}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.bills")),
options: resultsByType.bills.map((bill) => {
return {
key: bill.id,
value: `${bill.invoice_number} - ${bill.vendor.name}`,
label: (
<Link to={`/manage/bills?billid=${bill.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{bill.invoice_number}</span>
<span>{bill.vendor.name}</span>
<span>{bill.date}</span>
</Space>
</Link>
),
};
}),
},
// {
// label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => {
// return {
// key: pb.id,
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`,
// label: (
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
// <Space size="small" split={<Divider type="vertical" />}>
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`}</span>
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
// <span>{pb.email}</span>
// </Space>
// </Link>
// ),
// };
// }),
// },
]);
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setLoading(false);
}
}
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
debouncedExecuteSearch(value);
};
const renderTitle = (title) => {
return <span>{title}</span>;
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -1,5 +1,6 @@
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { AutoComplete, Divider, Input, Space } from "antd"; import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -18,18 +19,11 @@ export default function GlobalSearch() {
useLazyQuery(GLOBAL_SEARCH_QUERY); useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => { const executeSearch = (v) => {
if ( if (v && v.variables.search && v.variables.search !== "") callSearch(v);
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 750); const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => { const handleSearch = (value) => {
console.log("Handle Search");
debouncedExecuteSearch({ variables: { search: value } }); debouncedExecuteSearch({ variables: { search: value } });
}; };
@@ -44,7 +38,7 @@ export default function GlobalSearch() {
options: data.search_jobs.map((job) => { options: data.search_jobs.map((job) => {
return { return {
key: job.id, key: job.id,
value: job.ro_number || "N/A", value: job.ro_number,
label: ( label: (
<Link to={`/manage/jobs/${job.id}`}> <Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
@@ -184,18 +178,13 @@ export default function GlobalSearch() {
<AutoComplete <AutoComplete
options={options} options={options}
onSearch={handleSearch} onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => { onSelect={(val, opt) => {
history.push(opt.label.props.to); history.push(opt.label.props.to);
}} }}
> ></AutoComplete>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
); );
} }

View File

@@ -106,306 +106,428 @@ function Header({
selectedKeys={[selectedHeader]} selectedKeys={[selectedHeader]}
onClick={handleMenuClick} onClick={handleMenuClick}
subMenuCloseDelay={0.3} subMenuCloseDelay={0.3}
> items={[
<Menu.Item key="home" icon={<HomeFilled />}>
<Link to="/manage">{t("menus.header.home")}</Link>
</Menu.Item>
<Menu.Item key="schedule" icon={<Icon component={FaCalendarAlt} />}>
<Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
</Menu.Item>
<Menu.SubMenu
key="jobssubmenu"
icon={<Icon component={FaCarCrash} />}
title={t("menus.header.jobs")}
>
<Menu.Item key="activejobs" icon={<FileFilled />}>
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key="readyjobs" icon={<CheckCircleOutlined />}>
<Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
</Menu.Item>
<Menu.Item key="parts-queue" icon={<ToolFilled />}>
<Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
</Menu.Item>
<Menu.Item key="availablejobs" icon={<ImportOutlined />}>
<Link to="/manage/available">
{t("menus.header.availablejobs")}
</Link>
</Menu.Item>
<Menu.Item key="newjob" icon={<FileAddOutlined />}>
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
</Menu.Item>
<Menu.Divider key="div1" />
<Menu.Item key="alljobs" icon={<UnorderedListOutlined />}>
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Divider key="div2" />
<Menu.Item key="productionlist" icon={<ScheduleOutlined />}>
<Link to="/manage/production/list">
{t("menus.header.productionlist")}
</Link>
</Menu.Item>
<Menu.Item key="productionboard" icon={<Icon component={BsKanban} />}>
<Link to="/manage/production/board">
{t("menus.header.productionboard")}
</Link>
</Menu.Item>
<Menu.Divider key="div3" />
<Menu.Item key="scoreboard" icon={<LineChartOutlined />}>
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
key="customers"
icon={<UserOutlined />}
title={t("menus.header.customers")}
>
<Menu.Item key="owners" icon={<TeamOutlined />}>
<Link to="/manage/owners">{t("menus.header.owners")}</Link>
</Menu.Item>
<Menu.Item key="vehicles" icon={<CarFilled />}>
<Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
key="ccs"
icon={<CarFilled />}
title={t("menus.header.courtesycars")}
>
<Menu.Item key="courtesycarsall" icon={<CarFilled />}>
<Link to="/manage/courtesycars">
{t("menus.header.courtesycars-all")}
</Link>
</Menu.Item>
<Menu.Item key="contracts" icon={<FileFilled />}>
<Link to="/manage/courtesycars/contracts">
{t("menus.header.courtesycars-contracts")}
</Link>
</Menu.Item>
<Menu.Item key="newcontract" icon={<FileAddFilled />}>
<Link to="/manage/courtesycars/contracts/new">
{t("menus.header.courtesycars-newcontract")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
key="accounting"
icon={<DollarCircleFilled />}
title={t("menus.header.accounting")}
>
<Menu.Item
key="bills"
icon={<Icon component={FaFileInvoiceDollar} />}
>
<Link to="/manage/bills">{t("menus.header.bills")}</Link>
</Menu.Item>
<Menu.Item
key="enterbills"
icon={<Icon component={GiPayMoney} />}
onClick={() => {
setBillEnterContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.enterbills")}
</Menu.Item>
{Simple_Inventory.treatment === "on" && (
<>
<Menu.Divider key="div4" />
<Menu.Item
key="inventory"
icon={<Icon component={FaFileInvoiceDollar} />}
>
<Link to="/manage/inventory">
{t("menus.header.inventory")}
</Link>
</Menu.Item>
</>
)}
<Menu.Divider key="div7" />
<Menu.Item key="allpayments" icon={<BankFilled />}>
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
</Menu.Item>
<Menu.Item
key="enterpayments"
onClick={() => {
setPaymentContext({
actions: {},
context: null,
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.enterpayment")}
</Menu.Item>
<Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets">
{t("menus.header.timetickets")}
</Link>
</Menu.Item>
<Menu.Item
key="entertimetickets"
icon={<Icon component={GiPlayerTime} />}
onClick={() => {
setTimeTicketContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.entertimeticket")}
</Menu.Item>
<Menu.Divider key="div6" />
<Menu.SubMenu
key="accountingexport"
title={t("menus.header.export")}
icon={<ExportOutlined />}
>
<Menu.Item key="receivables">
<Link to="/manage/accounting/receivables">
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
{(!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) ||
DmsAp.treatment === "on") && (
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item>
)}
{!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) && (
<Menu.Item key="payments">
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item>
)}
<Menu.Item key="export-logs">
<Link to="/manage/accounting/exportlogs">
{t("menus.header.export-logs")}
</Link>
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
<Menu.Item key="phonebook" icon={<PhoneOutlined />}>
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
</Menu.Item>
<Menu.Item key="temporarydocs" icon={<PaperClipOutlined />}>
<Link to="/manage/temporarydocs">
{t("menus.header.temporarydocs")}
</Link>
</Menu.Item>
<Menu.SubMenu
key="shopsubmenu"
title={t("menus.header.shop")}
icon={<SettingOutlined />}
>
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
<Link to="/manage/shop?tab=info">
{t("menus.header.shop_config")}
</Link>
</Menu.Item>
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
</Menu.Item>
<Menu.Item
key="reportcenter"
icon={<BarChartOutlined />}
onClick={() => {
setReportCenterContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.reportcenter")}
</Menu.Item>
<Menu.Item
key="shop-vendors"
icon={<Icon component={IoBusinessOutline} />}
>
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
<Menu.Item key="shop-csi" icon={<Icon component={RiSurveyLine} />}>
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
key="user"
title={
currentUser.displayName ||
currentUser.email ||
t("general.labels.unknown")
}
>
<Menu.Item key="signout" danger onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item
key="help"
onClick={() => {
window.open("https://help.imex.online/", "_blank");
}}
icon={<Icon component={QuestionCircleFilled} />}
>
{t("menus.header.help")}
</Menu.Item>
<Menu.Item
key="rescue"
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item>
<Menu.Item key="profile">
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
</Menu.Item>
{ {
// <Menu.SubMenu key: "home",
// key="langselecter" icon: <HomeFilled />,
// title={ label: <Link to="/manage">{t("menus.header.home")}</Link>,
// <span> },
// <GlobalOutlined /> {
// <span>{t("menus.currentuser.languageselector")}</span> key: "schedule",
// </span> icon: <Icon component={FaCalendarAlt} />,
// } label: (
// > <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
// <Menu.Item actiontype="lang-select" key="en-US"> ),
// {t("general.languages.english")} },
// </Menu.Item> {
// <Menu.Item actiontype="lang-select" key="fr-CA"> key: "jobssubmenu",
// {t("general.languages.french")} icon: <Icon component={FaCarCrash} />,
// </Menu.Item> label: t("menus.header.jobs"),
// <Menu.Item actiontype="lang-select" key="es-MX"> children: [
// {t("general.languages.spanish")} {
// </Menu.Item> key: "activejobs",
// </Menu.SubMenu> icon: <FileFilled />,
} label: (
</Menu.SubMenu> <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
<Menu.SubMenu key="recent" title={<ClockCircleFilled />}> ),
{recentItems.map((i, idx) => ( },
<Menu.Item key={idx}> {
<Link to={i.url}>{i.label}</Link> key: "readyjobs",
</Menu.Item> icon: <CheckCircleOutlined />,
))} label: (
</Menu.SubMenu> <Link to="/manage/jobs/ready">
</Menu> {t("menus.header.readyjobs")}
</Link>
),
},
{
key: "parts-queue",
icon: <ToolFilled />,
label: (
<Link to="/manage/partsqueue">
{t("menus.header.parts-queue")}
</Link>
),
},
{
key: "availablejobs",
icon: <ImportOutlined />,
label: (
<Link to="/manage/available">
{t("menus.header.availablejobs")}
</Link>
),
},
{
key: "newjob",
icon: <FileAddOutlined />,
label: (
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
),
},
{ type: "divider" },
{
key: "alljobs",
icon: <UnorderedListOutlined />,
label: (
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
),
},
{ type: "divider" },
{
key: "productionlist",
icon: <ScheduleOutlined />,
label: (
<Link to="/manage/production/list">
{t("menus.header.productionlist")}
</Link>
),
},
{
key: "productionboard",
icon: <Icon component={BsKanban} />,
label: (
<Link to="/manage/production/board">
{t("menus.header.productionboard")}
</Link>
),
},
{
type: "divider",
},
{
key: "scoreboard",
icon: <LineChartOutlined />,
label: (
<Link to="/manage/scoreboard">
{t("menus.header.scoreboard")}
</Link>
),
},
],
},
{
key: "customers",
icon: <UserOutlined />,
label: t("menus.header.customers"),
children: [
{
key: "owners",
icon: <TeamOutlined />,
label: (
<Link to="/manage/owners">{t("menus.header.owners")}</Link>
),
},
{
key: "vehicles",
icon: <CarFilled />,
label: (
<Link to="/manage/vehicles">
{t("menus.header.vehicles")}
</Link>
),
},
],
},
{
key: "ccs",
icon: <CarFilled />,
label: t("menus.header.courtesycars"),
children: [
{
key: "courtesycarsall",
icon: <CarFilled />,
label: (
<Link to="/manage/courtesycars">
{t("menus.header.courtesycars-all")}
</Link>
),
},
{
key: "contracts",
icon: <FileFilled />,
label: (
<Link to="/manage/courtesycars/contracts">
{t("menus.header.courtesycars-contracts")}
</Link>
),
},
{
key: "newcontract",
icon: <FileAddFilled />,
label: (
<Link to="/manage/courtesycars/contracts/new">
{t("menus.header.courtesycars-newcontract")}
</Link>
),
},
],
},
{
key: "accounting",
icon: <DollarCircleFilled />,
label: t("menus.header.accounting"),
children: [
{
key: "bills",
icon: <Icon component={FaFileInvoiceDollar} />,
label: (
<Link to="/manage/bills">{t("menus.header.bills")}</Link>
),
},
{
key: "enterbills",
icon: <Icon component={GiPayMoney} />,
onClick: () => {
setBillEnterContext({
actions: {},
context: {},
});
},
label: t("menus.header.enterbills"),
},
...(Simple_Inventory.treatment === "on"
? [
{
type: "divider",
},
{
key: "inventory",
icon: <Icon component={FaFileInvoiceDollar} />,
label: (
<Link to="/manage/inventory">
{t("menus.header.inventory")}
</Link>
),
},
]
: []),
{ type: "divider" },
{
key: "allpayments",
icon: <BankFilled />,
label: (
<Link to="/manage/payments">
{t("menus.header.allpayments")}
</Link>
),
},
{
key: "enterpayments",
onClick: () => {
setPaymentContext({
actions: {},
context: null,
});
},
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.enterpayment"),
},
{ type: "divider" },
{
key: "timetickets",
icon: <FieldTimeOutlined />,
label: (
<Link to="/manage/timetickets">
{t("menus.header.timetickets")}
</Link>
),
},
{
key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />,
onClick: () => {
setTimeTicketContext({
actions: {},
context: {},
});
},
label: t("menus.header.entertimeticket"),
},
{ type: "divider" },
{
key: "accountingexport",
icon: <ExportOutlined />,
label: t("menus.header.export"),
children: [
{
key: "receivables",
label: (
<Link to="/manage/accounting/receivables">
{t("menus.header.accounting-receivables")}
</Link>
),
},
...(!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) || DmsAp.treatment === "on"
? [
{
key: "payables",
label: (
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
),
},
]
: []),
...(!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
)
? [
{
key: "payments",
label: (
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
),
},
]
: []),
{
key: "export-logs",
label: (
<Link to="/manage/accounting/exportlogs">
{t("menus.header.export-logs")}
</Link>
),
},
],
},
],
},
{
key: "phonebook",
icon: <PhoneOutlined />,
label: (
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
),
},
{
key: "temporarydocs",
icon: <PaperClipOutlined />,
label: (
<Link to="/manage/temporarydocs">
{t("menus.header.temporarydocs")}
</Link>
),
},
{
key: "shopsubmenu",
icon: <SettingOutlined />,
label: t("menus.header.shop"),
children: [
{
key: "shop",
icon: <Icon component={GiSettingsKnobs} />,
label: (
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
),
},
{
key: "dashboard",
icon: <DashboardFilled />,
label: (
<Link to="/manage/dashboard">
{t("menus.header.dashboard")}
</Link>
),
},
{
key: "reportcenter",
icon: <BarChartOutlined />,
onClick: () => {
setReportCenterContext({
actions: {},
context: {},
});
},
label: t("menus.header.reportcenter"),
},
{
key: "shop-vendors",
icon: <Icon component={IoBusinessOutline} />,
label: (
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
),
},
{
key: "shop-csi",
icon: <Icon component={RiSurveyLine} />,
label: (
<Link to="/manage/shop/csi">
{t("menus.header.shop_csi")}
</Link>
),
},
],
},
{
key: "user",
label:
currentUser.displayName ||
currentUser.email ||
t("general.labels.unknown"),
children: [
{
key: "signout",
danger: true,
onClick: () => signOutStart(),
label: t("user.actions.signout"),
},
{
key: "help",
icon: <Icon component={QuestionCircleFilled} />,
onClick: () => {
window.open("https://help.imex.online/", "_blank");
},
label: t("menus.header.help"),
},
{
key: "rescue",
onClick: () => {
window.open("https://imexrescue.com/", "_blank");
},
label: t("menus.header.rescueme"),
},
{
key: "shiftclock",
label: (
<Link to="/manage/shiftclock">
{t("menus.header.shiftclock")}
</Link>
),
},
{
key: "profile",
label: (
<Link to="/manage/profile">
{t("menus.currentuser.profile")}
</Link>
),
},
],
},
{
key: "recent",
label: <ClockCircleFilled />,
children: recentItems.map((i, idx) => ({
key: idx,
label: <Link to={i.url}>{i.label}</Link>,
})),
},
]}
></Menu>
</Layout.Header> </Layout.Header>
); );
} }

View File

@@ -103,7 +103,7 @@ export function InventoryUpsertModalContainer({
? t("inventory.actions.edit") ? t("inventory.actions.edit")
: t("inventory.actions.new") : t("inventory.actions.new")
} }
visible={visible} open={visible}
okText={t("general.actions.save")} okText={t("general.actions.save")}
onOk={() => { onOk={() => {
form.submit(); form.submit();

View File

@@ -96,7 +96,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
return ( return (
<> <>
<Button onClick={showModal}>{t("printcenter.jobs.3rdpartypayer")}</Button> <Button onClick={showModal}>{t("printcenter.jobs.3rdpartypayer")}</Button>
<Modal visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}> <Modal open={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}
autoComplete={"off"} autoComplete={"off"}

View File

@@ -1,11 +1,11 @@
import React from "react"; import { DownOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { Dropdown, notification } from "antd";
import React 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 { Dropdown, Menu, notification } from "antd"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { DownOutlined } from "@ant-design/icons";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -37,18 +37,19 @@ export function JobAltTransportChange({ bodyshop, job }) {
}); });
} }
}; };
const menu = ( const menu = [
<Menu selectedKeys={[job && job.alt_transport]} onClick={onClick}> ...(bodyshop.appt_alt_transport &&
{bodyshop.appt_alt_transport && bodyshop.appt_alt_transport.map((alt) => ({
bodyshop.appt_alt_transport.map((alt) => ( key: alt,
<Menu.Item key={alt}>{alt}</Menu.Item> label: alt,
))} value: alt,
<Menu.Divider /> onClick,
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item> }))),
</Menu> { type: "divider" },
); { key: "null", label: t("general.actions.clear"), onClick },
];
return ( return (
<Dropdown overlay={menu}> <Dropdown menu={{ items: menu }}>
<a href=" #" onClick={(e) => e.preventDefault()}> <a href=" #" onClick={(e) => e.preventDefault()}>
<DownOutlined /> <DownOutlined />
</a> </a>

View File

@@ -1,11 +1,11 @@
import React from "react"; import { DownOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; import { Dropdown, notification } from "antd";
import React 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 { Dropdown, Menu, notification } from "antd"; import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import { DownOutlined } from "@ant-design/icons";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -44,21 +44,23 @@ export function ScheduleEventColor({ bodyshop, event }) {
bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0] bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]
?.label; ?.label;
const menu = ( const menu = {
<Menu selectedKeys={[event.color]} onClick={onClick}> items: [
{bodyshop.appt_colors && ...(bodyshop.appt_colors &&
bodyshop.appt_colors.map((color) => ( bodyshop.appt_colors.map((color) => ({
<Menu.Item style={{ color: color.color.hex }} key={color.color.hex}> style: { color: color.color.hex },
{color.label} key: color.color.hex,
</Menu.Item> label: color.label,
))} }))),
<Menu.Divider /> { type: "divider" },
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item> { key: "null", value: t("general.actions.clear") },
</Menu> ],
); selectedKeys: [event.color],
onClick: onClick,
};
return ( return (
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<a href=" #" onClick={(e) => e.preventDefault()}> <a href=" #" onClick={(e) => e.preventDefault()}>
{selectedColor} {selectedColor}
<DownOutlined /> <DownOutlined />

View File

@@ -2,13 +2,9 @@ import { AlertFilled } from "@ant-design/icons";
import { import {
Button, Button,
Divider, Divider,
Dropdown, Dropdown, notification,
Form,
Menu,
notification,
Popover, Popover,
Select, Space
Space,
} from "antd"; } from "antd";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import moment from "moment"; import moment from "moment";
@@ -20,7 +16,7 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import {
openChatByPhone, openChatByPhone,
setMessage, setMessage
} from "../../redux/messaging/messaging.actions"; } from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -29,11 +25,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -61,10 +57,7 @@ export function ScheduleEventComponent({
const blockContent = ( const blockContent = (
<div> <div>
<Button <Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived}
>
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
</div> </div>
@@ -154,10 +147,12 @@ export function ScheduleEventComponent({
) : null} ) : null}
{event.job ? ( {event.job ? (
<Dropdown <Dropdown
overlay={ menu={{
<Menu> items: [
<Menu.Item {
onClick={() => { disabled: event.arrived,
label: t("general.labels.email"),
onClick: () => {
const Template = TemplateList("job").appointment_reminder; const Template = TemplateList("job").appointment_reminder;
GenerateDocument( GenerateDocument(
{ {
@@ -171,13 +166,12 @@ export function ScheduleEventComponent({
"e", "e",
event.job && event.job.id event.job && event.job.id
); );
}} },
disabled={event.arrived} },
> {
{t("general.labels.email")} label: t("general.labels.sms"),
</Menu.Item> disabled: event.arrived || !bodyshop.messagingservicesid,
<Menu.Item onClick: () => {
onClick={() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA"); const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) { if (p && p.isValid()) {
openChatByPhone({ openChatByPhone({
@@ -197,57 +191,18 @@ export function ScheduleEventComponent({
message: t("messaging.error.invalidphone"), message: t("messaging.error.invalidphone"),
}); });
} }
}} },
disabled={event.arrived || !bodyshop.messagingservicesid} },
> ],
{t("general.labels.sms")} }}
</Menu.Item>
</Menu>
}
> >
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown> </Dropdown>
) : null} ) : null}
<Popover
trigger="click" <Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
disabled={event.arrived} {t("appointments.actions.cancel")}
content={ </Button>
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}
@@ -328,8 +283,8 @@ export function ScheduleEventComponent({
return ( return (
<Popover <Popover
visible={visible} open={visible}
onVisibleChange={(vis) => !event.vacation && setVisible(vis)} onOpenChange={(vis) => !event.vacation && setVisible(vis)}
trigger="click" trigger="click"
content={event.block ? blockContent : popoverContent} content={event.block ? blockContent : popoverContent}
style={{ style={{

View File

@@ -11,7 +11,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const handleCancel = async ({ id, lost_sale_reason }) => { const handleCancel = async (id) => {
logImEXEvent("schedule_cancel_appt"); logImEXEvent("schedule_cancel_appt");
const cancelAppt = await cancelAppointment({ const cancelAppt = await cancelAppointment({
@@ -38,8 +38,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
job: { job: {
date_scheduled: null, date_scheduled: null,
scheduled_in: null, scheduled_in: null,
scheduled_completion: null, scheduled_completion:null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported, status: bodyshop.md_ro_statuses.default_imported,
}, },
}, },

View File

@@ -39,7 +39,7 @@ export function JobCostingModalContainer({
return ( return (
<Modal <Modal
visible={visible} open={visible}
title={t("jobs.labels.jobcosting")} title={t("jobs.labels.jobcosting")}
onOk={() => { onOk={() => {
toggleModalVisible(); toggleModalVisible();

View File

@@ -76,7 +76,7 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
return ( return (
<Drawer <Drawer
visible={!!selected} open={!!selected}
destroyOnClose destroyOnClose
width={drawerPercentage} width={drawerPercentage}
placement="right" placement="right"

View File

@@ -1,24 +1,16 @@
import { import {
DeleteFilled, DeleteFilled,
EditFilled,
FilterFilled, FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined, SyncOutlined,
WarningFilled, WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import { PageHeader } from '@ant-design/pro-layout';
Button, import { Button, Dropdown, Input, Space, Table, Tag } from "antd";
Dropdown,
Input,
Menu,
PageHeader,
Space,
Table,
Tag,
} from "antd";
import axios from "axios"; import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -38,13 +30,13 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; // import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container"; // import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container"; // import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash"; import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment"; import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component"; import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -105,9 +97,6 @@ export function JobLinesComponent({
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({ onCell: (record) => ({
className: record.manual_line && "job-line-manual", className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
},
}), }),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -348,7 +337,7 @@ export function JobLinesComponent({
onClick={() => { onClick={() => {
setJobLineEditContext({ setJobLineEditContext({
actions: { refetch: refetch, submit: form && form.submit }, actions: { refetch: refetch, submit: form && form.submit },
context: { ...record, jobid: job.id }, context: record,
}); });
}} }}
> >
@@ -410,16 +399,17 @@ export function JobLinesComponent({
} }
}; };
const markMenu = ( const markMenu = {
<Menu onClick={handleMark}> onClick: handleMark,
<Menu.Item key="PAA">{t("joblines.fields.part_types.PAA")}</Menu.Item> items: [
<Menu.Item key="PAN">{t("joblines.fields.part_types.PAN")}</Menu.Item> { key: "PAA", label: t("joblines.fields.part_types.PAA") },
<Menu.Item key="PAL">{t("joblines.fields.part_types.PAL")}</Menu.Item> { key: "PAN", label: t("joblines.fields.part_types.PAN") },
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item> { key: "PAL", label: t("joblines.fields.part_types.PAL") },
<Menu.Divider /> { key: "PAS", label: t("joblines.fields.part_types.PAS") },
<Menu.Item key="clear">{t("general.labels.clear")}</Menu.Item> { type: "divider" },
</Menu> { key: "clear", label: t("general.labels.clear") },
); ],
};
return ( return (
<div> <div>
@@ -549,7 +539,7 @@ export function JobLinesComponent({
> >
<FilterFilled /> {t("jobs.actions.filterpartsonly")} <FilterFilled /> {t("jobs.actions.filterpartsonly")}
</Button> </Button>
<Dropdown overlay={markMenu} trigger={["click"]}> <Dropdown menu={markMenu} trigger={["click"]}>
<Button>{t("jobs.actions.mark")}</Button> <Button>{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
<Button <Button

View File

@@ -87,7 +87,7 @@ export function JobEmployeeAssignments({
); );
return ( return (
<Popover destroyTooltipOnHide content={popContent} visible={visibility}> <Popover destroyTooltipOnHide content={popContent} open={visibility}>
<Spin spinning={loading}> <Spin spinning={loading}>
<DataLabel label={t("jobs.fields.employee_body")}> <DataLabel label={t("jobs.fields.employee_body")}>
{body ? ( {body ? (

View File

@@ -225,7 +225,7 @@ export function JobLineConvertToLabor({
<Popover <Popover
disabled={jobline.convertedtolbr} disabled={jobline.convertedtolbr}
content={overlay} content={overlay}
visible={visibility} open={visibility}
placement="bottom" placement="bottom"
> >
<Tooltip title={t("joblines.actions.converttolabor")}> <Tooltip title={t("joblines.actions.converttolabor")}>

View File

@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons"; import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -21,30 +21,17 @@ export function JoblinePresetButton({ bodyshop, form }) {
form.setFieldsValue(item); form.setFieldsValue(item);
}; };
const menu = ( const menu = {
<Menu items: bodyshop.md_jobline_presets.map((i, idx) => ({
style={{ onClick: () => handleSelect(i),
columnCount: Math.max( key: idx,
Math.floor(bodyshop.md_jobline_presets.length / 15), label: i.label,
1 })),
), };
}}
>
{bodyshop.md_jobline_presets.map((i, idx) => (
<Menu.Item
onClick={() => handleSelect(i)}
key={idx}
style={{ breakInside: "avoid" }}
>
{i.label}
</Menu.Item>
))}
</Menu>
);
return ( return (
<div> <div>
<Dropdown trigger={["click"]} overlay={menu}> <Dropdown trigger={["click"]} menu={menu}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href="# " href="# "

View File

@@ -40,11 +40,6 @@ export function JobLinesUpsertModalComponent({
{}, {},
bodyshop.imexshopid bodyshop.imexshopid
); );
const { Autohouse_Detail_line } = useTreatments(
["Autohouse_Detail_line"],
{},
bodyshop.imexshopid
);
return ( return (
<Modal <Modal
@@ -54,7 +49,7 @@ export function JobLinesUpsertModalComponent({
: t("joblines.labels.new") : t("joblines.labels.new")
} }
forceRender forceRender
visible={visible} open={visible}
width="60%" width="60%"
okText={t("general.actions.save")} okText={t("general.actions.save")}
onOk={() => form.submit()} onOk={() => form.submit()}
@@ -160,40 +155,6 @@ export function JobLinesUpsertModalComponent({
> >
<InputNumber precision={1} /> <InputNumber precision={1} />
</Form.Item> </Form.Item>
{Autohouse_Detail_line.treatment === "on" && (
<Form.Item
label={t("joblines.fields.ah_detail_line")}
name="ah_detail_line"
valuePropName="checked"
dependencies={["mod_lbr_ty"]}
initialValue={false}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (
value === false ||
value === undefined ||
value === null
)
return Promise.resolve();
if (
value === true &&
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
getFieldValue("mod_lbr_ty")
)
) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.ahdetailonlyonuserdefinedtypes")
);
},
}),
]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("joblines.fields.part_type")} name="part_type"> <Form.Item label={t("joblines.fields.part_type")} name="part_type">
@@ -257,6 +218,7 @@ export function JobLinesUpsertModalComponent({
rules={[ rules={[
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") { if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve(); return Promise.resolve();
} }
@@ -267,6 +229,7 @@ export function JobLinesUpsertModalComponent({
}), }),
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
console.log(value, !!value);
if ( if (
!!getFieldValue("part_type") === (!!value || value === 0) !!getFieldValue("part_type") === (!!value || value === 0)
) { ) {
@@ -289,7 +252,7 @@ export function JobLinesUpsertModalComponent({
name="prt_dsmk_p" name="prt_dsmk_p"
initialValue={0} initialValue={0}
> >
<InputNumber precision={0} min={-100} max={100} /> <InputNumber precision={0} min={0} max={100} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.tax_part")} label={t("joblines.fields.tax_part")}

View File

@@ -13,13 +13,8 @@ import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
import UndefinedToNull from "../../utils/undefinedtonull"; import UndefinedToNull from "../../utils/undefinedtonull";
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component"; import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobLineEditModal: selectJobLineEditModal, jobLineEditModal: selectJobLineEditModal,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")), toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
@@ -28,13 +23,7 @@ const mapDispatchToProps = (dispatch) => ({
function JobLinesUpsertModalContainer({ function JobLinesUpsertModalContainer({
jobLineEditModal, jobLineEditModal,
toggleModalVisible, toggleModalVisible,
bodyshop,
}) { }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
const [updateJobLine] = useMutation(UPDATE_JOB_LINE); const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
@@ -51,15 +40,7 @@ function JobLinesUpsertModalContainer({
manual_line: !( manual_line: !(
jobLineEditModal.context && jobLineEditModal.context.id jobLineEditModal.context && jobLineEditModal.context.id
), ),
...UndefinedToNull({ ...UndefinedToNull(values),
...values,
prt_dsmk_m: Dinero({
amount: Math.round((values.act_price || 0) * 100),
})
.percentage(Math.abs(values.prt_dsmk_p || 0))
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
.toFormat(0.0),
}),
}, },
], ],
}, },
@@ -87,15 +68,7 @@ function JobLinesUpsertModalContainer({
const r = await updateJobLine({ const r = await updateJobLine({
variables: { variables: {
lineId: jobLineEditModal.context.id, lineId: jobLineEditModal.context.id,
line: { line: values,
...values,
prt_dsmk_m: Dinero({
amount: Math.round(values.act_price * 100),
})
.percentage(Math.abs(values.prt_dsmk_p || 0))
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
.toFormat(0.0),
},
}, },
refetchQueries: ["GET_LINE_TICKET_BY_PK"], refetchQueries: ["GET_LINE_TICKET_BY_PK"],
}); });
@@ -119,9 +92,6 @@ function JobLinesUpsertModalContainer({
} }
toggleModalVisible(); toggleModalVisible();
} }
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid);
}
setLoading(false); setLoading(false);
}; };

View File

@@ -99,7 +99,7 @@ export function JobPayments({
render: (text, record) => ( render: (text, record) => (
<Space wrap> <Space wrap>
<Button <Button
// disabled={record.exportedat} disabled={record.exportedat}
onClick={() => { onClick={() => {
setPaymentContext({ setPaymentContext({
actions: { refetch: refetch }, actions: { refetch: refetch },

View File

@@ -42,7 +42,7 @@ function JobReconciliationModalContainer({
<Modal <Modal
title={t("jobs.labels.reconciliationheader")} title={t("jobs.labels.reconciliationheader")}
width={"95%"} width={"95%"}
visible={visible} open={visible}
okText={t("general.actions.close")} okText={t("general.actions.close")}
onOk={handleCancel} onOk={handleCancel}
onCancel={handleCancel} onCancel={handleCancel}

View File

@@ -186,7 +186,7 @@ export default function ScoreboardAddButton({
}; };
return ( return (
<Popover content={overlay} visible={visibility} placement="bottom"> <Popover content={overlay} open={visibility} placement="bottom">
<Button <Button
loading={loading} loading={loading}
disabled={disabled} disabled={disabled}

View File

@@ -1,6 +1,6 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification } from "antd"; import { Button, Dropdown, notification } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -40,20 +40,18 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
}); });
}; };
const statusmenu = ( const statusmenu = {
<Menu onClick: (e) => {
onClick={(e) => { updateJobStatus(e.key);
updateJobStatus(e.key); },
}} items: bodyshop.md_ro_statuses.statuses.map((item) => ({
> key: item,
{bodyshop.md_ro_statuses.statuses.map((item) => ( label: item,
<Menu.Item key={item}>{item}</Menu.Item> })),
))} };
</Menu>
);
return ( return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus"> <Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round"> <Button shape="round">
<span>{job.status}</span> <span>{job.status}</span>

View File

@@ -3,9 +3,8 @@ import {
useApolloClient, useApolloClient,
useLazyQuery, useLazyQuery,
useMutation, useMutation,
useQuery useQuery,
} from "@apollo/client"; } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd"; import { Col, notification, Row } from "antd";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
@@ -20,7 +19,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
DELETE_AVAILABLE_JOB, DELETE_AVAILABLE_JOB,
QUERY_AVAILABLE_JOBS, QUERY_AVAILABLE_JOBS,
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
} from "../../graphql/available-jobs.queries"; } from "../../graphql/available-jobs.queries";
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
@@ -28,11 +27,10 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm"; import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component"; import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -55,11 +53,6 @@ export function JobsAvailableContainer({
currentUser, currentUser,
insertAuditTrail, insertAuditTrail,
}) { }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
@@ -162,9 +155,6 @@ export function JobsAvailableContainer({
}, },
}) })
.then((r) => { .then((r) => {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({ notification["success"]({
message: t("jobs.successes.created"), message: t("jobs.successes.created"),
onClick: () => { onClick: () => {
@@ -251,9 +241,7 @@ export function JobsAvailableContainer({
}, },
}, },
}); });
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) { if (updateResult.errors) {
//error while inserting //error while inserting
notification["error"]({ notification["error"]({

View File

@@ -1,6 +1,6 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification } from "antd"; import { Button, Dropdown, notification } 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 { connect } from "react-redux"; import { connect } from "react-redux";
@@ -81,29 +81,32 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
} }
}, [job, setAvailableStatuses, bodyshop]); }, [job, setAvailableStatuses, bodyshop]);
const statusmenu = ( const statusmenu = {
<Menu onClick: (e) => {
onClick={(e) => { updateJobStatus(e.key);
updateJobStatus(e.key); },
}} items: [
> ...availableStatuses.map((item) => ({
{availableStatuses.map((item) => ( key: item,
<Menu.Item key={item}>{item}</Menu.Item> label: item,
))} })),
{job.converted && ( ...(job.converted
<> ? [
<Menu.Divider /> {
{otherStages.map((item, idx) => ( type: "divider",
<Menu.Item key={item}>{item}</Menu.Item> },
))} ...otherStages.map((item, idx) => ({
</> key: item,
)} label: item,
</Menu> })),
); ]
: []),
],
};
return ( return (
<Dropdown <Dropdown
overlay={statusmenu} menu={statusmenu}
trigger={["click"]} trigger={["click"]}
key="changestatus" key="changestatus"
disabled={jobRO || !job.converted} disabled={jobRO || !job.converted}

View File

@@ -1,4 +1,4 @@
import { Button, Dropdown, Menu } from "antd"; import { Button, Dropdown } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -62,18 +62,17 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
); );
}; };
const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( const overlay = {
<Menu onClick={handleMenuClick}> onClick: handleMenuClick,
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ( items: bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ({
<Menu.Item disabled={disabled} key={mapping.name}> label: mapping.name,
{mapping.name} key: mapping.name,
</Menu.Item> disabled: disabled,
))} })),
</Menu> };
);
return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? ( return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
<Dropdown overlay={overlay}> <Dropdown menu={overlay}>
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button> <Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
</Dropdown> </Dropdown>
) : ( ) : (

View File

@@ -43,22 +43,14 @@ export function JobsConvertButton({
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleConvert = async ({ employee_csr, category, ...values }) => { const handleConvert = async (values) => {
if (parentFormIsFieldsTouched()) { if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion")); alert(t("jobs.labels.savebeforeconversion"));
return; return;
} }
setLoading(true); setLoading(true);
const res = await mutationConvertJob({ const res = await mutationConvertJob({
variables: { variables: { jobId: job.id, ...values },
jobId: job.id,
job: {
converted: true,
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
...(bodyshop.enforce_conversion_category ? { category } : {}),
...values,
},
},
}); });
if (values.ca_gst_registrant) { if (values.ca_gst_registrant) {
@@ -95,7 +87,6 @@ export function JobsConvertButton({
driveable: true, driveable: true,
towin: false, towin: false,
employee_csr: job.employee_csr, employee_csr: job.employee_csr,
category: job.category,
}} }}
> >
<Form.Item <Form.Item
@@ -109,8 +100,8 @@ export function JobsConvertButton({
]} ]}
> >
<Select> <Select>
{bodyshop.md_ins_cos.map((s, i) => ( {bodyshop.md_ins_cos.map((s) => (
<Select.Option key={i} value={s.name}> <Select.Option key={s.name} value={s.name}>
{s.name} {s.name}
</Select.Option> </Select.Option>
))} ))}
@@ -199,35 +190,13 @@ export function JobsConvertButton({
</Select> </Select>
</Form.Item> </Form.Item>
)} )}
{bodyshop.enforce_conversion_category && ( <Form.Item
<Form.Item label={t("jobs.fields.ca_gst_registrant")}
name={"category"} name="ca_gst_registrant"
label={t("jobs.fields.category")} valuePropName="checked"
rules={[ >
{ <Switch />
required: bodyshop.enforce_conversion_category, </Form.Item>
//message: t("general.validation.required"),
},
]}
>
<Select allowClear>
{bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
)}
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Form.Item <Form.Item
label={t("jobs.fields.driveable")} label={t("jobs.fields.driveable")}
name="driveable" name="driveable"
@@ -257,7 +226,7 @@ export function JobsConvertButton({
if (job.converted) return <></>; if (job.converted) return <></>;
return ( return (
<Popover visible={visible} content={popMenu}> <Popover open={visible} content={popMenu}>
<Button <Button
key="convert" key="convert"
type="danger" type="danger"

View File

@@ -224,15 +224,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
> >
<CurrencyInput /> <CurrencyInput />
</Form.Item> </Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && ( <Form.Item
<Form.Item label={t("jobs.fields.ca_gst_registrant")}
label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant"
name="ca_gst_registrant" valuePropName="checked"
valuePropName="checked" >
> <Switch />
<Switch /> </Form.Item>
</Form.Item>
)}
<Form.Item <Form.Item
label={t("jobs.fields.other_amount_payable")} label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable" name="other_amount_payable"

View File

@@ -9,11 +9,7 @@ const colSpan = {
lg: { span: 12 }, lg: { span: 12 },
}; };
export default function JobsCreateVehicleInfoComponent({ export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) {
loading,
vehicles,
form,
}) {
const [state, setState] = useContext(JobCreateContext); const [state, setState] = useContext(JobCreateContext);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -62,7 +58,7 @@ export default function JobsCreateVehicleInfoComponent({
/> />
</Col> </Col>
<Col {...colSpan}> <Col {...colSpan}>
<JobsCreateVehicleInfoNewComponent form={form}/> <JobsCreateVehicleInfoNewComponent />
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@@ -20,7 +20,6 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
<JobsCreateVehicleInfoComponent <JobsCreateVehicleInfoComponent
loading={loading} loading={loading}
vehicles={data ? data.search_vehicles : null} vehicles={data ? data.search_vehicles : null}
form={form}
/> />
); );
} }

View File

@@ -4,9 +4,8 @@ import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
export default function JobsCreateVehicleInfoNewComponent({ form }) { export default function JobsCreateVehicleInfoNewComponent() {
const [state] = useContext(JobCreateContext); const [state] = useContext(JobCreateContext);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -26,7 +25,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow noDivider> <LayoutFormRow grow>
<Form.Item <Form.Item
label={t("vehicles.fields.v_color")} label={t("vehicles.fields.v_color")}
name={["vehicle", "data", "v_color"]} name={["vehicle", "data", "v_color"]}
@@ -53,9 +52,8 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow noDivider> <LayoutFormRow grow>
<Form.Item <Form.Item
span={10}
label={t("vehicles.fields.v_make_desc")} label={t("vehicles.fields.v_make_desc")}
name={["vehicle", "data", "v_make_desc"]} name={["vehicle", "data", "v_make_desc"]}
rules={[ rules={[
@@ -68,7 +66,6 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
span={11}
label={t("vehicles.fields.v_model_desc")} label={t("vehicles.fields.v_model_desc")}
name={["vehicle", "data", "v_model_desc"]} name={["vehicle", "data", "v_model_desc"]}
rules={[ rules={[
@@ -80,11 +77,6 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
> >
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
<JobsCreateVehicleInfoPredefined
disabled={!state.vehicle.new}
form={form}
span={1}
/>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("vehicles.forms.registration")} grow> <LayoutFormRow header={t("vehicles.forms.registration")} grow>

View File

@@ -1,81 +0,0 @@
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
import { Button, Input, Popover, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import PredefinedVehicles from "./predefined-vehicles.js";
export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const { t } = useTranslation();
const handleOpenChange = (newOpen) => {
setOpen(newOpen);
setSearch("");
};
const filteredPredefinedVehicles =
search === ""
? PredefinedVehicles
: PredefinedVehicles.filter(
(v) =>
v.make.toLowerCase().includes(search.toLowerCase()) ||
v.model.toLowerCase().includes(search.toLowerCase())
);
const popContent = () => (
<div>
<Table
size="small"
title={() => <Input.Search onSearch={(value) => setSearch(value)} />}
dataSource={filteredPredefinedVehicles}
columns={[
{
dataIndex: "make",
key: "make",
title: t("vehicles.fields.v_make_desc"),
},
{
dataIndex: "model",
key: "model",
title: t("vehicles.fields.v_model_desc"),
},
{
dataIndex: "select",
key: "select",
title: t("general.labels.actions"),
render: (value, record) => (
<Button
disabled={disabled}
onClick={() => {
form.setFieldsValue({
vehicle: {
data: {
v_make_desc: record.make,
v_model_desc: record.model,
},
},
});
setOpen(false);
setSearch("");
}}
>
<PlusOutlined />
</Button>
),
},
]}
/>
</div>
);
return (
<Popover
content={popContent}
trigger="click"
open={open}
placement="left"
onOpenChange={handleOpenChange}
destroyTooltipOnHide
>
<SearchOutlined style={{ cursor: "pointer" }} />
</Popover>
);
}

View File

@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons"; import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -15,20 +15,17 @@ export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
form.setFieldsValue(est); form.setFieldsValue(est);
}; };
const menu = ( const menu = {
<div> onClick: handleClick,
<Menu onClick={handleClick}> items: bodyshop.md_estimators.map((est, idx) => ({
{bodyshop.md_estimators.map((est, idx) => ( value: est,
<Menu.Item value={est} key={idx}> key: idx,
{`${est.est_ct_fn} ${est.est_ct_ln}`} label: `${est.est_ct_fn || ""} ${est.est_ct_ln || ""}`.trim(),
</Menu.Item> })),
))} };
</Menu>
</div>
);
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons"; import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -15,23 +15,21 @@ export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
form.setFieldsValue(est); form.setFieldsValue(est);
}; };
const menu = ( const menu = {
<Menu onClick: handleClick,
onClick={handleClick} style: {
style={{ columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1,
columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1, },
}} items: bodyshop.md_filehandlers.map((est, idx) => ({
> value: est,
{bodyshop.md_filehandlers.map((est, idx) => ( key: idx,
<Menu.Item value={est} key={idx} style={{ breakInside: "avoid" }}> style: { breakInside: "avoid" },
{`${est.ins_ct_fn} ${est.ins_ct_ln}`} label: `${est.ins_ct_fn || ""} ${est.ins_ct_ln || ""}`.trim(),
</Menu.Item> })),
))} };
</Menu>
);
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -256,7 +256,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
</FormRow> </FormRow>
<FormRow header={t("jobs.forms.other")}> <FormRow header={t("jobs.forms.other")}>
<Form.Item label={t("jobs.fields.category")} name="category"> <Form.Item label={t("jobs.fields.category")} name="category">
<Select disabled={jobRO} allowClear> <Select disabled={jobRO}>
{bodyshop.md_categories.map((s) => ( {bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}> <Select.Option key={s} value={s}>
{s} {s}
@@ -289,12 +289,6 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
> >
<Input disabled={jobRO} /> <Input disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item
label={t("jobs.fields.lost_sale_reason")}
name="lost_sale_reason"
>
<Input disabled={jobRO} allowClear />
</Form.Item>
</FormRow> </FormRow>
</div> </div>
); );

View File

@@ -4,7 +4,6 @@ import {
Card, Card,
Form, Form,
Input, Input,
Menu,
notification, notification,
Popover, Popover,
Select, Select,
@@ -13,25 +12,11 @@ import {
import moment from "moment"; import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; import { INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
const mapStateToProps = createStructuredSelector({ export default function JobsDetailHeaderAddEvent({ bodyshop, jobid }) {
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDetailHeaderAddEvent);
export function JobsDetailHeaderAddEvent({ bodyshop, jobid, ...props }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
@@ -153,11 +138,12 @@ export function JobsDetailHeaderAddEvent({ bodyshop, jobid, ...props }) {
setVisibility(true); setVisibility(true);
}; };
return ( return {
<Popover content={overlay} visible={visibility}> key: "addmanualevent",
<Menu.Item {...props} onClick={handleClick}> label: (
<Popover content={overlay} open={visibility} onClick={handleClick}>
{t("appointments.labels.manualevent")} {t("appointments.labels.manualevent")}
</Menu.Item> </Popover>
</Popover> ),
); };
} }

View File

@@ -1,15 +1,6 @@
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, Dropdown, notification, Popconfirm } from "antd";
Button,
Dropdown,
Form,
Menu,
notification,
Popconfirm,
Popover,
Select,
} from "antd";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -22,14 +13,13 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent"; import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component"; import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component"; import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -115,13 +105,14 @@ export function JobsDetailHeaderActions({
}); });
}; };
const statusmenu = ( const statusmenu = {
<Menu key="popovermenu"> items: [
<Menu.Item {
disabled={!jobInPreProduction || !job.converted || jobRO} key: "schedule",
onClick={() => { disabled: !jobInPreProduction || !job.converted || jobRO,
label: t("jobs.actions.schedule"),
onClick: () => {
logImEXEvent("job_header_schedule"); logImEXEvent("job_header_schedule");
setScheduleContext({ setScheduleContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
@@ -130,228 +121,228 @@ export function JobsDetailHeaderActions({
alt_transport: job.alt_transport, alt_transport: job.alt_transport,
}, },
}); });
}} },
> },
{t("jobs.actions.schedule")} {
</Menu.Item> key: "cancelappts",
<Menu.Item disabled: job.status !== bodyshop.md_ro_statuses.default_scheduled,
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} label: (
> <Popconfirm
<Popover title={t("general.labels.areyousure")}
trigger="click" okText="Yes"
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} cancelText="No"
content={ onClick={(e) => e.stopPropagation()}
<Form disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
layout="vertical" onConfirm={async () => {
onFinish={async ({ lost_sale_reason }) => { const jobUpdate = await cancelAllAppointments({
const jobUpdate = await cancelAllAppointments({ variables: {
variables: { jobid: job.id,
jobid: job.id, job: {
job: { date_scheduled: null,
date_scheduled: null, scheduled_in: null,
scheduled_in: null, scheduled_completion: null,
scheduled_completion: null, status: bodyshop.md_ro_statuses.default_imported,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported,
},
}, },
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
}); });
if (!jobUpdate.errors) { return;
notification["success"]({ }
message: t("appointments.successes.canceled"), }}
}); getPopupContainer={(trigger) => trigger.parentNode}
return; >
} {t("menus.jobsactions.cancelallappointments")}
}} </Popconfirm>
> ),
<Form.Item },
name="lost_sale_reason" {
label={t("jobs.fields.lost_sale_reason")} key: "intake",
rules={[ disabled:
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button
htmlType="submit"
disabled={
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
{t("menus.jobsactions.cancelallappointments")}
</Popover>
</Menu.Item>
<Menu.Item
disabled={
!!job.intakechecklist || !!job.intakechecklist ||
!jobInPreProduction || !jobInPreProduction ||
!job.converted || !job.converted ||
jobRO jobRO,
} label:
> !!job.intakechecklist ||
{!!job.intakechecklist || !jobInPreProduction ||
!jobInPreProduction || !job.converted ||
!job.converted || jobRO ? (
jobRO ? ( t("jobs.actions.intake")
t("jobs.actions.intake") ) : (
) : ( <Link to={`/manage/jobs/${job.id}/intake`}>
<Link to={`/manage/jobs/${job.id}/intake`}> {t("jobs.actions.intake")}
{t("jobs.actions.intake")} </Link>
</Link> ),
)} },
</Menu.Item> {
<Menu.Item disabled={!jobInProduction || jobRO}> disabled: !jobInProduction || jobRO,
{!jobInProduction ? ( key: "deliver",
label: !jobInProduction ? (
t("jobs.actions.deliver") t("jobs.actions.deliver")
) : ( ) : (
<Link to={`/manage/jobs/${job.id}/deliver`}> <Link to={`/manage/jobs/${job.id}/deliver`}>
{t("jobs.actions.deliver")} {t("jobs.actions.deliver")}
</Link> </Link>
)} ),
</Menu.Item> },
<Menu.Item disabled={!job.converted}> {
<Link to={`/manage/jobs/${job.id}/checklist`}> key: "viewchecklist",
{t("jobs.actions.viewchecklist")} disabled: !job.converted,
</Link> label: (
</Menu.Item> <Link to={`/manage/jobs/${job.id}/checklist`}>
<Menu.Item {t("jobs.actions.viewchecklist")}
key="entertimetickets" </Link>
disabled={ ),
},
{
key: "entertimetickets",
disabled:
!job.converted || !job.converted ||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced) (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
} onClick: () => {
onClick={() => {
logImEXEvent("job_header_enter_time_ticekts"); logImEXEvent("job_header_enter_time_ticekts");
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { jobId: job.id }, context: { jobId: job.id },
}); });
}} },
> label: t("timetickets.actions.enter"),
{t("timetickets.actions.enter")} },
</Menu.Item> {
<Menu.Item key: "enterpayments",
key="enterpayments" disabled: !job.converted,
disabled={!job.converted} label: t("menus.header.enterpayment"),
onClick={() => { onClick: () => {
logImEXEvent("job_header_enter_payment"); logImEXEvent("job_header_enter_payment");
setPaymentContext({ setPaymentContext({
actions: {}, actions: {},
context: { jobid: job.id }, context: { jobid: job.id },
}); });
}} },
> },
{t("menus.header.enterpayment")} {
</Menu.Item> key: "cccontract",
<Menu.Item key="cccontract" disabled={jobRO || !job.converted}> disabled: jobRO || !job.converted,
<Link label: (
to={{ <Link
pathname: "/manage/courtesycars/contracts/new", to={{
state: { jobId: job.id }, pathname: "/manage/courtesycars/contracts/new",
}} state: { jobId: job.id },
> }}
{t("menus.jobsactions.newcccontract")} >
</Link> {t("menus.jobsactions.newcccontract")}
</Menu.Item> </Link>
{job.inproduction ? ( ),
<Menu.Item },
key="addtoproduction" ...(job.inproduction
disabled={!job.converted} ? [
onClick={() => AddToProduction(client, job.id, refetch, true)} {
> key: "removetoproduction",
{t("jobs.actions.removefromproduction")} disabled: !job.converted,
</Menu.Item> onClick: () => AddToProduction(client, job.id, refetch, true),
) : ( label: t("jobs.actions.removefromproduction"),
<Menu.Item },
key="addtoproduction" ]
disabled={!job.converted} : [
onClick={() => AddToProduction(client, job.id, refetch)} {
> key: "addtoproduction",
{t("jobs.actions.addtoproduction")} disabled: !job.converted,
</Menu.Item> onClick: () => AddToProduction(client, job.id, refetch),
)} label: t("jobs.actions.addtoproduction"),
<Menu.Item key="togglesuspend" onClick={handleSuspend}> },
{job.suspended ]),
{
key: "togglesuspend",
onClick: handleSuspend,
label: job.suspended
? t("production.actions.unsuspend") ? t("production.actions.unsuspend")
: t("production.actions.suspend")} : t("production.actions.suspend"),
</Menu.Item> },
<Menu.Item key="toggleAlert" onClick={handleAlertToggle}> {
{job.production_vars && job.production_vars.alert key: "toggleAlert",
? t("production.labels.alertoff") onClick: handleAlertToggle,
: t("production.labels.alerton")} label:
</Menu.Item> job.production_vars && job.production_vars.alert
<Menu.SubMenu key="dupe" title={t("menus.jobsactions.duplicate")}> ? t("production.labels.alertoff")
<Menu.Item> : t("production.labels.alerton"),
<Popconfirm },
title={t("jobs.labels.duplicateconfirm")} {
okText="Yes" key: "dupe",
cancelText="No" label: t("menus.jobsactions.duplicate"),
onClick={(e) => e.stopPropagation()} children: [
onConfirm={() => {
DuplicateJob( key: "dupewithlines",
client, label: (
job.id, <Popconfirm
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, title={t("jobs.labels.duplicateconfirm")}
(newJobId) => { okText="Yes"
history.push(`/manage/jobs/${newJobId}`); cancelText="No"
notification["success"]({ onClick={(e) => e.stopPropagation()}
message: t("jobs.successes.duplicated"), onConfirm={() =>
}); DuplicateJob(
}, client,
true job.id,
) {
} defaultOpenStatus:
getPopupContainer={(trigger) => trigger.parentNode} bodyshop.md_ro_statuses.default_imported,
> },
{t("menus.jobsactions.duplicate")} (newJobId) => {
</Popconfirm> history.push(`/manage/jobs/${newJobId}`);
</Menu.Item> notification["success"]({
<Menu.Item> message: t("jobs.successes.duplicated"),
<Popconfirm });
title={t("jobs.labels.duplicateconfirm")} },
okText="Yes" true
cancelText="No" )
onClick={(e) => e.stopPropagation()}
onConfirm={() =>
DuplicateJob(
client,
job.id,
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
(newJobId) => {
history.push(`/manage/jobs/${newJobId}`);
notification["success"]({
message: t("jobs.successes.duplicated"),
});
} }
) getPopupContainer={(trigger) => trigger.parentNode}
} >
getPopupContainer={(trigger) => trigger.parentNode} {t("menus.jobsactions.duplicate")}
> </Popconfirm>
{t("menus.jobsactions.duplicatenolines")} ),
</Popconfirm> },
</Menu.Item> {
</Menu.SubMenu> key: "dupewithoutlines",
label: (
<Menu.Item <Popconfirm
key="postbills" title={t("jobs.labels.duplicateconfirm")}
disabled={!job.converted} okText="Yes"
onClick={() => { cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={() =>
DuplicateJob(
client,
job.id,
{
defaultOpenStatus:
bodyshop.md_ro_statuses.default_imported,
},
(newJobId) => {
history.push(`/manage/jobs/${newJobId}`);
notification["success"]({
message: t("jobs.successes.duplicated"),
});
}
)
}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.duplicatenolines")}
</Popconfirm>
),
},
],
},
{
key: "postbills",
disabled: !job.converted,
label: t("jobs.actions.postbills"),
onClick: () => {
logImEXEvent("job_header_enter_bills"); logImEXEvent("job_header_enter_bills");
setBillEnterContext({ setBillEnterContext({
@@ -360,14 +351,13 @@ export function JobsDetailHeaderActions({
job: job, job: job,
}, },
}); });
}} },
> },
{t("jobs.actions.postbills")} {
</Menu.Item> key: "addtopartsqueue",
<Menu.Item disabled: !job.converted || !jobInProduction || jobRO,
key="addtopartsqueue" label: t("jobs.actions.addtopartsqueue"),
disabled={!job.converted || !jobInProduction || jobRO} onClick: async () => {
onClick={async () => {
const result = await updateJob({ const result = await updateJob({
variables: { variables: {
jobId: job.id, jobId: job.id,
@@ -386,12 +376,12 @@ export function JobsDetailHeaderActions({
}), }),
}); });
} }
}} },
> },
{t("jobs.actions.addtopartsqueue")} {
</Menu.Item> disabled: !jobInPostProduction,
<Menu.Item disabled={!jobInPostProduction} key="closejob"> key: "closejob",
{!jobInPostProduction ? ( label: !jobInPostProduction ? (
t("menus.jobsactions.closejob") t("menus.jobsactions.closejob")
) : ( ) : (
<Link <Link
@@ -401,23 +391,27 @@ export function JobsDetailHeaderActions({
> >
{t("menus.jobsactions.closejob")} {t("menus.jobsactions.closejob")}
</Link> </Link>
)} ),
</Menu.Item> },
<Menu.Item key="admin"> {
<Link key: "admin",
to={{ label: (
pathname: `/manage/jobs/${job.id}/admin`, <Link
}} to={{
> pathname: `/manage/jobs/${job.id}/admin`,
{t("menus.jobsactions.admin")} }}
</Link> >
</Menu.Item> {t("menus.jobsactions.admin")}
<JobsDetailHeaderActionsExportcustdataComponent job={job} /> </Link>
<JobsDetaiLheaderCsi job={job} /> ),
<Menu.Item },
key="jobcosting" JobsDetailHeaderActionsExportcustdataComponent({ bodyshop, job }),
disabled={!job.converted} JobsDetaiLheaderCsi({ job, bodyshop }),
onClick={() => { {
key: "jobcosting",
label: t("jobs.labels.jobcosting"),
disabled: !job.converted,
onClick: () => {
logImEXEvent("job_header_job_costing"); logImEXEvent("job_header_job_costing");
setJobCostingContext({ setJobCostingContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
@@ -425,98 +419,108 @@ export function JobsDetailHeaderActions({
jobId: job.id, jobId: job.id,
}, },
}); });
}} },
> },
{t("jobs.labels.jobcosting")} ...(job && !job.converted
</Menu.Item> ? [
{job && !job.converted && ( {
<Menu.Item> key: "deletejob",
<Popconfirm label: (
title={t("jobs.labels.deleteconfirm")} <Popconfirm
okText={t("general.labels.yes")} title={t("jobs.labels.deleteconfirm")}
cancelText={t("general.labels.no")} okText={t("general.labels.yes")}
onClick={(e) => e.stopPropagation()} cancelText={t("general.labels.no")}
onConfirm={async () => { onClick={(e) => e.stopPropagation()}
//delete the job. onConfirm={async () => {
const result = await deleteJob({ variables: { id: job.id } }); //delete the job.
const result = await deleteJob({
variables: { id: job.id },
});
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.delete"), message: t("jobs.successes.delete"),
}); });
//go back to jobs list. //go back to jobs list.
history.push(`/manage/`); history.push(`/manage/`);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.deleted", { message: t("jobs.errors.deleted", {
error: JSON.stringify(result.errors), error: JSON.stringify(result.errors),
}), }),
}); });
} }
}} }}
getPopupContainer={(trigger) => trigger.parentNode} getPopupContainer={(trigger) => trigger.parentNode}
> >
{t("menus.jobsactions.deletejob")} {t("menus.jobsactions.deletejob")}
</Popconfirm> </Popconfirm>
</Menu.Item> ),
)} },
<JobsDetailHeaderActionsAddevent jobid={job.id} /> ]
{!jobRO && job.converted && ( : []),
<RbacWrapper action="jobs:void" noauth> ///////HEADER ADD EVENT ITEM
<Menu.Item> JobsDetailHeaderActionsAddevent({ jobid: job.id, bodyshop }),
<Popconfirm ...(!jobRO && job.converted
title={t("jobs.labels.voidjob")} ? [
okText="Yes" {
cancelText="No" key: "voidjob",
onClick={(e) => e.stopPropagation()} label: (
onConfirm={async () => { <Popconfirm
//delete the job. title={t("jobs.labels.voidjob")}
const result = await voidJob({ okText="Yes"
variables: { cancelText="No"
jobId: job.id, onClick={(e) => e.stopPropagation()}
job: { onConfirm={async () => {
status: bodyshop.md_ro_statuses.default_void, //delete the job.
voided: true, const result = await voidJob({
scheduled_in: null, variables: {
scheduled_completion: null, jobId: job.id,
inproduction: false, job: {
}, status: bodyshop.md_ro_statuses.default_void,
note: [ voided: true,
{ scheduled_in: null,
jobid: job.id, scheduled_completion: null,
created_by: currentUser.email, inproduction: false,
audit: true, },
text: t("jobs.labels.voidnote"), note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
},
],
}, },
], });
},
}); if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
),
},
]
: []),
],
};
if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
</Menu.Item>
</RbacWrapper>
)}
</Menu>
);
return ( return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus"> <Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
<Button> <Button>
<span>{t("general.labels.actions")}</span> <span>{t("general.labels.actions")}</span>

View File

@@ -1,11 +1,8 @@
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { Menu, notification } from "antd"; import { notification } from "antd";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
GET_CURRENT_QUESTIONSET_ID, GET_CURRENT_QUESTIONSET_ID,
@@ -16,30 +13,14 @@ import {
openChatByPhone, openChatByPhone,
setMessage, setMessage,
} from "../../redux/messaging/messaging.actions"; } from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { store } from "../../redux/store";
import i18n from "../../translations/i18n";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({ export default function JobsDetailHeaderCsi({ bodyshop, job }) {
//currentUser: selectCurrentUser' const { t } = i18n;
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
});
export function JobsDetailHeaderCsi({
setEmailOptions,
bodyshop,
job,
openChatByPhone,
setMessage,
...props
}) {
const { t } = useTranslation();
const [insertCsi] = useMutation(INSERT_CSI); const [insertCsi] = useMutation(INSERT_CSI);
const client = useApolloClient(); const client = useApolloClient();
@@ -97,29 +78,35 @@ export function JobsDetailHeaderCsi({
return; return;
} }
if (e.key === "email") if (e.key === "email")
setEmailOptions({ store.dispatch(
jobid: job.id, setEmailOptions({
messageOptions: { jobid: job.id,
to: [job.ownr_ea], messageOptions: {
replyTo: bodyshop.email, to: [job.ownr_ea],
}, replyTo: bodyshop.email,
template: {
name: TemplateList("job_special").csi_invitation_action.key,
variables: {
id: result.data.insert_csi.returning[0].id,
}, },
}, template: {
}); name: TemplateList("job_special").csi_invitation_action.key,
variables: {
id: result.data.insert_csi.returning[0].id,
},
},
})
);
if (e.key === "text") { if (e.key === "text") {
const p = parsePhoneNumber(job.ownr_ph1, "CA"); const p = parsePhoneNumber(job.ownr_ph1, "CA");
if (p && p.isValid()) { if (p && p.isValid()) {
openChatByPhone({ store.dispatch(
phone_num: p.formatInternational(), openChatByPhone({
jobid: job.id, phone_num: p.formatInternational(),
}); jobid: job.id,
setMessage( })
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` );
store.dispatch(
setMessage(
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
)
); );
} else { } else {
notification["error"]({ notification["error"]({
@@ -140,29 +127,35 @@ export function JobsDetailHeaderCsi({
} }
} else { } else {
if (e.key === "email") if (e.key === "email")
setEmailOptions({ store.dispatch(
jobid: job.id, setEmailOptions({
messageOptions: { jobid: job.id,
to: [job.ownr_ea], messageOptions: {
replyTo: bodyshop.email, to: [job.ownr_ea],
}, replyTo: bodyshop.email,
template: {
name: TemplateList("job_special").csi_invitation_action.key,
variables: {
id: job.csiinvites[0].id,
}, },
}, template: {
}); name: TemplateList("job_special").csi_invitation_action.key,
variables: {
id: job.csiinvites[0].id,
},
},
})
);
if (e.key === "text") { if (e.key === "text") {
const p = parsePhoneNumber(job.ownr_ph1, "CA"); const p = parsePhoneNumber(job.ownr_ph1, "CA");
if (p && p.isValid()) { if (p && p.isValid()) {
openChatByPhone({ store.dispatch(
phone_num: p.formatInternational(), openChatByPhone({
jobid: job.id, phone_num: p.formatInternational(),
}); jobid: job.id,
setMessage( })
`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}` );
store.dispatch(
setMessage(
`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`
)
); );
} else { } else {
notification["error"]({ notification["error"]({
@@ -180,61 +173,51 @@ export function JobsDetailHeaderCsi({
} }
}; };
if (!HasFeatureAccess({ featureName: "csi", bodyshop })) return <></>; if (!HasFeatureAccess({ featureName: "csi", bodyshop })) return {};
return ( return {
<Menu.SubMenu key: "sendcsi",
key="sendcsi" label: t("jobs.actions.sendcsi"),
title={t("jobs.actions.sendcsi")} children: [
disabled={!job.converted} {
{...props} key: "email",
> onClick: handleCreateCsi,
<Menu.Item disabled: !!!job.ownr_ea,
onClick={handleCreateCsi} label: t("general.labels.email"),
key="email" },
disabled={!!!job.ownr_ea} {
> onClick: handleCreateCsi,
{t("general.labels.email")} key: "text",
</Menu.Item> disabled: !!!job.ownr_ph1,
<Menu.Item label: t("general.labels.text"),
onClick={handleCreateCsi} },
key="text" {
disabled={!!!job.ownr_ph1} key: "generate",
> onClick: handleCreateCsi,
{t("general.labels.text")} disabled: job.csiinvites && job.csiinvites.length > 0,
</Menu.Item> label: t("jobs.actions.generatecsi"),
<Menu.Item },
onClick={handleCreateCsi} { type: "divider" },
key="generate" ...job.csiinvites.map((item, idx) => {
disabled={job.csiinvites && job.csiinvites.length > 0} return item.completedon
> ? {
{t("jobs.actions.generatecsi")} key: idx,
</Menu.Item> label: (
<Menu.Divider /> <Link to={`/manage/shop/csi?responseid=${item.id}`}>
{job.csiinvites.map((item, idx) => { <DateTimeFormatter>{item.completedon}</DateTimeFormatter>
return item.completedon ? ( </Link>
<Menu.Item key={idx}> ),
<Link to={`/manage/shop/csi?responseid=${item.id}`}> }
<DateTimeFormatter>{item.completedon}</DateTimeFormatter> : {
</Link> key: idx,
</Menu.Item> onClick: () => {
) : ( navigator.clipboard.writeText(
<Menu.Item `${window.location.protocol}//${window.location.host}/csi/${item.id}`
key={idx} );
onClick={() => { },
navigator.clipboard.writeText( label: t("general.actions.copylink"),
`${window.location.protocol}//${window.location.host}/csi/${item.id}` };
); }),
}} ],
> };
{t("general.actions.copylink")}
</Menu.Item>
);
})}
</Menu.SubMenu>
);
} }
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDetailHeaderCsi);

View File

@@ -1,23 +1,13 @@
import { Menu, notification } from "antd"; import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import React from "react"; import i18next from "i18next";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ export default function JobsDetailHeaderActionexportCustomerData({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function JobsDetailHeaderActionexportCustomerData({
bodyshop, bodyshop,
job, job,
...props
}) { }) {
const { t } = useTranslation(); const { t } = i18next;
const handleExportCustData = async (e) => { const handleExportCustData = async (e) => {
logImEXEvent("job_export_cust_data"); logImEXEvent("job_export_cust_data");
@@ -100,18 +90,20 @@ export function JobsDetailHeaderActionexportCustomerData({
} }
}; };
return ( return {
<Menu.Item onClick: handleExportCustData,
{...props} key: "exportcustdata",
onClick={handleExportCustData} disabled: !job.converted,
key="exportcustdata" label: t("jobs.actions.exportcustdata"),
disabled={!job.converted} };
> // (
{t("jobs.actions.exportcustdata")} // <Menu.Item
</Menu.Item> // {...props}
); // onClick={handleExportCustData}
// key="exportcustdata"
// disabled={!job.converted}
// >
// {t("jobs.actions.exportcustdata")}
// </Menu.Item>
// );
} }
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDetailHeaderActionexportCustomerData);

View File

@@ -5,7 +5,7 @@ import {
BranchesOutlined, BranchesOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd"; import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React, { useState } from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -56,7 +56,7 @@ const colSpan = {
export function JobsDetailHeader({ job, bodyshop, disabled }) { export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notesClamped, setNotesClamped] = useState(true);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""} const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""} ${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim(); ${job.v_model_desc || ""}`.trim();
@@ -229,8 +229,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel <DataLabel
label={t("vehicles.fields.notes")} label={t("vehicles.fields.notes")}
valueStyle={{ whiteSpace: "pre-wrap" }} valueStyle={{ whiteSpace: "pre-wrap" }}
valueClassName={notesClamped ? "clamp" : ""}
onValueClick={() => setNotesClamped(!notesClamped)}
> >
{job.vehicle.notes} {job.vehicle.notes}
</DataLabel> </DataLabel>

View File

@@ -6,12 +6,3 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.clamp {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
}

View File

@@ -1,5 +1,5 @@
import { DownOutlined } from "@ant-design/icons"; import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd"; import { Dropdown } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -18,20 +18,16 @@ export function JobsDetailRatesChangeButton({ disabled, form, bodyshop }) {
form.setFieldsValue({ ...rate, labor_rate_desc: rate.rate_label }); form.setFieldsValue({ ...rate, labor_rate_desc: rate.rate_label });
}; };
const menu = ( const menu = {
<div> onClick: handleClick,
<Menu onClick={handleClick}> items: bodyshop.md_labor_rates.map((rate, idx) => ({
{bodyshop.md_labor_rates.map((rate, idx) => ( key: idx,
<Menu.Item value={rate} key={idx}> value: rate,
{rate.rate_label} label: rate.rate_label,
</Menu.Item> })),
))} };
</Menu>
</div>
);
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -40,26 +40,24 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
> >
<CurrencyInput disabled={jobRO} min={0} /> <CurrencyInput disabled={jobRO} min={0} />
</Form.Item> </Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && ( <Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}> <Form.Item
<Form.Item label={t("jobs.fields.ca_customer_gst")}
label={t("jobs.fields.ca_customer_gst")} name="ca_customer_gst"
name="ca_customer_gst" >
> <CurrencyInput
<CurrencyInput disabled={jobRO}
disabled={jobRO} min={0}
min={0} max={
max={ Math.round(
Math.round( (job.job_totals &&
(job.job_totals && job.job_totals.totals.federal_tax.amount) ||
job.job_totals.totals.federal_tax.amount) || 0
0 ) / 100
) / 100 }
} />
/> </Form.Item>
</Form.Item> </Tooltip>
</Tooltip>
)}
<Form.Item <Form.Item
label={t("jobs.fields.other_amount_payable")} label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable" name="other_amount_payable"
@@ -84,14 +82,12 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
> >
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} /> <CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item> </Form.Item>
{bodyshop.region_config === "CA_BC" && ( <Space align="end">
<Space align="center"> <Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt"> <CurrencyInput disabled={jobRO} min={0} />
<CurrencyInput disabled={jobRO} min={0} /> </Form.Item>
</Form.Item> <CABCpvrtCalculator form={form} disabled={jobRO} />
<CABCpvrtCalculator form={form} disabled={jobRO} /> </Space>
</Space>
)}
<Form.Item <Form.Item
label={t("jobs.fields.auto_add_ats")} label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats" name="auto_add_ats"
@@ -145,15 +141,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
> >
<InputNumber min={0} max={1} precision={2} disabled={jobRO} /> <InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item> </Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && ( <Form.Item
<Form.Item label={t("jobs.fields.ca_gst_registrant")}
label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant"
name="ca_gst_registrant" valuePropName="checked"
valuePropName="checked" >
> <Switch disabled={jobRO} />
<Switch disabled={jobRO} /> </Form.Item>
</Form.Item>
)}
</FormRow> </FormRow>
<Divider <Divider
orientation="left" orientation="left"

View File

@@ -36,7 +36,7 @@ export function JobsDocumentsDownloadButton({
); );
const imagesToDownload = [ const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected), ...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected), ...galleryImages.other.filter((image) => image.isSelected),
]; ];
function downloadProgress(progressEvent) { function downloadProgress(progressEvent) {
@@ -73,11 +73,9 @@ export function JobsDocumentsDownloadButton({
// id: "media", // id: "media",
// startIn: "downloads", // startIn: "downloads",
// }); // });
// const directory = await parentDir.getDirectoryHandle(identifier, { // const directory = await parentDir.getDirectoryHandle(identifier, {
// create: true, // create: true,
// }); // });
// yauzl.fromBuffer( // yauzl.fromBuffer(
// Buffer.from(theDownloadedZip.data), // Buffer.from(theDownloadedZip.data),
// {}, // {},
@@ -123,7 +121,7 @@ export function JobsDocumentsDownloadButton({
a.click(); a.click();
} }
}; };
return ( return (
<> <>
<Button <Button

View File

@@ -159,7 +159,7 @@ export function JobsDocumentsGalleryReassign({
); );
return ( return (
<Popover content={popContent} visible={visible}> <Popover content={popContent} open={visible}>
<Button <Button
disabled={selectedImages.length < 1} disabled={selectedImages.length < 1}
onClick={() => setVisible(true)} onClick={() => setVisible(true)}

View File

@@ -10,10 +10,8 @@ import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.compon
import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component"; import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component";
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component"; import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component";
import Lightbox from "react-image-lightbox"; import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css"; import "react-image-lightbox/style.css";
function JobsDocumentsComponent({ function JobsDocumentsComponent({
data, data,
jobId, jobId,
@@ -34,16 +32,16 @@ function JobsDocumentsComponent({
const fileType = DetermineFileType(value.type); const fileType = DetermineFileType(value.type);
if (value.type.startsWith("image")) { if (value.type.startsWith("image")) {
acc.images.push({ acc.images.push({
// src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
// src: GenerateSrcUrl(value), // src: GenerateSrcUrl(value),
// thumbnail: GenerateThumbUrl(value), // thumbnail: GenerateThumbUrl(value),
fullsize: GenerateSrcUrl(value), fullsize: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
height: 225, height: 225,
width: 225, width: 225,
isSelected: false, isSelected: false,
key: value.key, key: value.key,
extension: value.extension, extension: value.extension,
id: value.id, id: value.id,
type: value.type, type: value.type,
size: value.size, size: value.size,
@@ -63,8 +61,8 @@ function JobsDocumentsComponent({
const fileName = value.key.split("/").pop(); const fileName = value.key.split("/").pop();
acc.other.push({ acc.other.push({
source: GenerateSrcUrl(value), source: GenerateSrcUrl(value),
//src: "",
src: thumb, src: thumb,
thumbnail: thumb,
tags: [ tags: [
{ {
value: fileName, value: fileName,
@@ -89,6 +87,7 @@ function JobsDocumentsComponent({
height: 225, height: 225,
width: 225, width: 225,
isSelected: false, isSelected: false,
extension: value.extension, extension: value.extension,
key: value.key, key: value.key,
id: value.id, id: value.id,
@@ -197,54 +196,54 @@ function JobsDocumentsComponent({
/> />
</Card> </Card>
</Col> </Col>
{modalState.open && (
<Lightbox
toolbarButtons={[
<EditFilled
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${
window.location.host
}/edit?documentId=${
galleryImages.images[modalState.index].id
}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
/>,
]}
mainSrc={galleryImages.images[modalState.index].fullsize}
nextSrc={
galleryImages.images[
(modalState.index + 1) % galleryImages.images.length
].fullsize
}
prevSrc={
galleryImages.images[
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % galleryImages.images.length,
})
}
/>
)}
</Row> </Row>
{modalState.open && (
<Lightbox
toolbarButtons={[
<EditFilled
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${
window.location.host
}/edit?documentId=${
galleryImages.images[modalState.index].id
}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
/>,
]}
mainSrc={galleryImages.images[modalState.index].fullsize}
nextSrc={
galleryImages.images[
(modalState.index + 1) % galleryImages.images.length
].fullsize
}
prevSrc={
galleryImages.images[
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % galleryImages.images.length,
})
}
/>
)}
</div> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Gallery } from "react-grid-gallery"; import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { GenerateThumbUrl } from "./job-documents.utility"; import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
function JobsDocumentGalleryExternal({ function JobsDocumentGalleryExternal({
data, data,
@@ -15,8 +15,8 @@ function JobsDocumentGalleryExternal({
let documents = data.reduce((acc, value) => { let documents = data.reduce((acc, value) => {
if (value.type.startsWith("image")) { if (value.type.startsWith("image")) {
acc.push({ acc.push({
//src: GenerateSrcUrl(value), src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value), thumbnail: GenerateThumbUrl(value),
thumbnailHeight: 225, thumbnailHeight: 225,
thumbnailWidth: 225, thumbnailWidth: 225,
isSelected: false, isSelected: false,
@@ -39,7 +39,7 @@ function JobsDocumentGalleryExternal({
<Gallery <Gallery
images={galleryImages} images={galleryImages}
backdropClosesModal={true} backdropClosesModal={true}
onSelect={(index, image) => { onSelectImage={(index, image) => {
setgalleryImages( setgalleryImages(
galleryImages.map((g, idx) => galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -18,7 +18,6 @@ import JobsDocumentsLocalDeleteButton from "./jobs-documents-local-gallery.delet
import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download"; import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download";
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component"; import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component"; import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
import Lightbox from "react-image-lightbox"; import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css"; import "react-image-lightbox/style.css";
@@ -62,6 +61,7 @@ export function JobsDocumentsLocalGallery({
} }
} }
}, [job, invoice_number, getJobMedia, getBillMedia]); }, [job, invoice_number, getJobMedia, getBillMedia]);
let optimized; let optimized;
const jobMedia = const jobMedia =
allMedia && allMedia[job.id] allMedia && allMedia[job.id]
@@ -144,7 +144,7 @@ export function JobsDocumentsLocalGallery({
/>, />,
], ],
})} })}
onClick={(index) => { onClick={(index, item) => {
setModalState({ open: true, index: index }); setModalState({ open: true, index: index });
// const media = allMedia[job.id].find( // const media = allMedia[job.id].find(
// (m) => m.optimized === item.src // (m) => m.optimized === item.src

View File

@@ -57,10 +57,6 @@ function JobDocumentsLocalGalleryExternal({
return acc; return acc;
}, []) }, [])
: []; : [];
console.log(
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
documents
);
setgalleryImages(documents); setgalleryImages(documents);
}, [allMedia, jobId, setgalleryImages, t]); }, [allMedia, jobId, setgalleryImages, t]);
@@ -69,7 +65,7 @@ function JobDocumentsLocalGalleryExternal({
<div className="clearfix"> <div className="clearfix">
<Gallery <Gallery
images={galleryImages} images={galleryImages}
onSelect={(index, image) => { onSelectImage={(index, image) => {
setgalleryImages( setgalleryImages(
galleryImages.map((g, idx) => galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -83,7 +83,7 @@ export function JobsDocumentsLocalGalleryReassign({
); );
return ( return (
<Popover content={popContent} visible={visible}> <Popover content={popContent} open={visible}>
<Button <Button
//disabled={selectedImages.length < 1} //disabled={selectedImages.length < 1}
onClick={() => setVisible(true)} onClick={() => setVisible(true)}

View File

@@ -182,7 +182,7 @@ export function JobsExportAllButton({
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}> <Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")} {t("jobs.actions.export")}
</Button> </Button>
); );
} }

View File

@@ -1,9 +1,8 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd"; import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import _ from "lodash"; import _ from "lodash";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useState } from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
@@ -22,8 +21,6 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsList({ bodyshop, refetch, loading, jobs, total }) { export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search; const { page, sortcolumn, sortorder } = search;
const history = useHistory(); const history = useHistory();
@@ -196,28 +193,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}; };
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchJobs();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchJobs(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "jobs",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return ( return (
<Card <Card
extra={ extra={
@@ -230,7 +205,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
<Button <Button
onClick={() => { onClick={() => {
delete search.search; delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
> >
@@ -246,32 +220,24 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
onSearch={(value) => { onSearch={(value) => {
search.search = value; search.search = value;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
searchJobs(value);
}} }}
loading={loading || searchLoading}
enterButton enterButton
/> />
</Space> </Space>
} }
> >
<Table <Table
loading={loading || searchLoading} loading={loading}
pagination={ pagination={{
search?.search position: "top",
? { pageSize: 25,
pageSize: 25, current: parseInt(page || 1),
showSizeChanger: false, total: total,
} showSizeChanger: false,
: { }}
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={search?.search ? openSearchResults : jobs} dataSource={jobs}
onChange={handleTableChange} onChange={handleTableChange}
/> />
</Card> </Card>

View File

@@ -112,9 +112,7 @@ export function JobsList({ bodyshop }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
@@ -262,19 +260,6 @@ export function JobsList({ bodyshop }) {
dataIndex: "ins_co_nm", dataIndex: "ins_co_nm",
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"], responsive: ["md"],
}, },
{ {

View File

@@ -75,27 +75,6 @@ export function JobNotesComponent({
</span> </span>
), ),
}, },
{
title: t("notes.fields.type"),
dataIndex: "type",
key: "type",
width: 120,
filteredValue: filter?.type || null,
filters: [
{ value: "general", text: t("notes.fields.types.general") },
{ value: "customer", text: t("notes.fields.types.customer") },
{ value: "shop", text: t("notes.fields.types.shop") },
{ value: "office", text: t("notes.fields.types.office") },
{ value: "parts", text: t("notes.fields.types.parts") },
{ value: "paint", text: t("notes.fields.types.paint") },
{
value: "supplement",
text: t("notes.fields.types.supplement"),
},
],
onFilter: (value, record) => value.includes(record.type),
render: (text, record) => t(`notes.fields.types.${record.type}`),
},
{ {
title: t("notes.fields.text"), title: t("notes.fields.text"),
dataIndex: "text", dataIndex: "text",
@@ -127,7 +106,7 @@ export function JobNotesComponent({
title: t("notes.actions.actions"), title: t("notes.actions.actions"),
dataIndex: "actions", dataIndex: "actions",
key: "actions", key: "actions",
width: 200, width: 150,
render: (text, record) => ( render: (text, record) => (
<Space wrap> <Space wrap>
<Button <Button

View File

@@ -272,19 +272,6 @@ export function JobsReadyList({ bodyshop }) {
dataIndex: "ins_co_nm", dataIndex: "ins_co_nm",
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"], responsive: ["md"],
}, },
{ {

View File

@@ -171,8 +171,8 @@ export function LaborAllocationsAdjustmentEdit({
return ( return (
<Popover <Popover
visible={visible} open={visible}
onVisibleChange={(vis) => setVisible(vis)} onOpenChange={(vis) => setVisible(vis)}
content={overlay} content={overlay}
trigger="click" trigger="click"
> >

View File

@@ -207,7 +207,7 @@ export function LaborAllocationsTable({
<Card title={t("jobs.labels.laborallocations")}> <Card title={t("jobs.labels.laborallocations")}>
<Table <Table
columns={columns} columns={columns}
rowKey={(record) => `${record.cost_center} ${record.mod_lbr_ty}`} rowKey="cost_center"
pagination={false} pagination={false}
onChange={handleTableChange} onChange={handleTableChange}
dataSource={totals} dataSource={totals}

View File

@@ -6,6 +6,10 @@ export const CalculateAllocationsTotals = (
timetickets, timetickets,
adjustments = [] adjustments = []
) => { ) => {
console.log(
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
adjustments
);
const responsibilitycenters = bodyshop.md_responsibility_centers; const responsibilitycenters = bodyshop.md_responsibility_centers;
const jobCodes = joblines.map((item) => item.mod_lbr_ty); const jobCodes = joblines.map((item) => item.mod_lbr_ty);
//.filter((value, index, self) => self.indexOf(value) === index && !!value); //.filter((value, index, self) => self.indexOf(value) === index && !!value);

View File

@@ -1,14 +1,4 @@
import { import { Checkbox, Col, Form, Input, Row, Space, Switch, Tag } from "antd";
Checkbox,
Col,
Form,
Input,
Row,
Select,
Space,
Switch,
Tag,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -56,28 +46,6 @@ export function NoteUpsertModalComponent({ form, noteUpsertModal }) {
<Switch /> <Switch />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={8}>
<Form.Item
label={t("notes.fields.type")}
name="type"
initialValue="general"
>
<Select
options={[
{ value: "general", label: t("notes.fields.types.general") },
{ value: "customer", label: t("notes.fields.types.customer") },
{ value: "shop", label: t("notes.fields.types.shop") },
{ value: "office", label: t("notes.fields.types.office") },
{ value: "parts", label: t("notes.fields.types.parts") },
{ value: "paint", label: t("notes.fields.types.paint") },
{
value: "supplement",
label: t("notes.fields.types.supplement"),
},
]}
/>
</Form.Item>
</Col>
<Col span={8}> <Col span={8}>
<NotesPresetButton form={form} /> <NotesPresetButton form={form} />
</Col> </Col>

View File

@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
const [updateNote] = useMutation(UPDATE_NOTE); const [updateNote] = useMutation(UPDATE_NOTE);
const { visible, context, actions } = noteUpsertModal; const { visible, context, actions } = noteUpsertModal;
const { jobId, existingNote, text } = context; const { jobId, existingNote } = context;
const { refetch } = actions; const { refetch } = actions;
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -45,12 +45,8 @@ export function NoteUpsertModalContainer({
form.setFieldsValue(existingNote); form.setFieldsValue(existingNote);
} else if (!existingNote && visible) { } else if (!existingNote && visible) {
form.resetFields(); form.resetFields();
if (text) {
form.setFieldValue("text", text);
}
} }
}, [existingNote, form, visible, text]); }, [existingNote, form, visible]);
const handleFinish = async (formValues) => { const handleFinish = async (formValues) => {
const { relatedros, ...values } = formValues; const { relatedros, ...values } = formValues;
@@ -86,7 +82,6 @@ export function NoteUpsertModalContainer({
{ ...values, jobid: jobId, created_by: currentUser.email }, { ...values, jobid: jobId, created_by: currentUser.email },
], ],
}, },
refetchQueries: ["QUERY_NOTES_BY_JOB_PK"],
}); });
if (AdditionalNoteInserts.length > 0) { if (AdditionalNoteInserts.length > 0) {
@@ -122,7 +117,7 @@ export function NoteUpsertModalContainer({
return ( return (
<Modal <Modal
title={existingNote ? t("notes.actions.edit") : t("notes.actions.new")} title={existingNote ? t("notes.actions.edit") : t("notes.actions.new")}
visible={visible} open={visible}
okText={t("general.actions.save")} okText={t("general.actions.save")}
onOk={() => { onOk={() => {
form.submit(); form.submit();

Some files were not shown because too many files have changed in this diff Show More