Merge branch 'master-AIO' into feature/IO-2520-Kaizen-Data-Pump
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
This commit is contained in:
@@ -31,3 +31,11 @@
|
||||
|
||||
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
||||
|
||||
|
||||
### Statistics
|
||||
|
||||
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
|
||||
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
|
||||
|
||||
### Filters
|
||||
- Allows you to set, and persist filters for estimators and insurance companies
|
||||
|
||||
@@ -1,174 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<link rel="icon" href="/ro-favicon.png" />
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<link rel="icon" href="/pm/pm-favicon.ico" />
|
||||
<% } %>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<link rel="icon" href="/favicon.png"/>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<link rel="icon" href="/ro-favicon.png"/>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<link rel="icon" href="/pm/pm-favicon.ico"/>
|
||||
<% } %>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#1690ff" />
|
||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||
<!-- TODO:AIo Update the individual logos for each.-->
|
||||
<link rel="apple-touch-icon" href="public/logo192.png" />
|
||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="theme-color" content="#1690ff"/>
|
||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||
<!-- TODO:AIo Update the individual logos for each.-->
|
||||
<link rel="apple-touch-icon" href="public/logo192.png"/>
|
||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<meta name="description" content="ImEX Online" />
|
||||
<title>ImEX Online</title>
|
||||
<script type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<meta name="description" content="Rome Online" />
|
||||
<title>Rome Online</title>
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<meta name="description" content="ImEX Online"/>
|
||||
<title>ImEX Online</title>
|
||||
<script type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<meta name="description" content="Rome Online"/>
|
||||
<title>Rome Online</title>
|
||||
|
||||
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||
|
||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
party="LiveChat528346"></call-us-selector>
|
||||
|
||||
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||
|
||||
<!--<call-us
|
||||
|
||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
|
||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||
|
||||
id="wp-live-chat-by-3CX"
|
||||
|
||||
minimized="true"
|
||||
|
||||
animation-style="noanimation"
|
||||
|
||||
party="LiveChat528346"
|
||||
|
||||
minimized-style="bubbleright"
|
||||
|
||||
allow-call="true"
|
||||
|
||||
allow-video="false"
|
||||
|
||||
allow-soundnotifications="true"
|
||||
|
||||
enable-mute="true"
|
||||
|
||||
enable-onmobile="true"
|
||||
|
||||
offline-enabled="true"
|
||||
|
||||
enable="true"
|
||||
|
||||
ignore-queueownership="false"
|
||||
|
||||
authentication="both"
|
||||
|
||||
show-operator-actual-name="true"
|
||||
|
||||
aknowledge-received="true"
|
||||
|
||||
gdpr-enabled="false"
|
||||
|
||||
message-userinfo-format="name"
|
||||
|
||||
message-dateformat="both"
|
||||
|
||||
lang="browser"
|
||||
|
||||
button-icon-type="default"
|
||||
|
||||
greeting-visibility="none"
|
||||
|
||||
greeting-offline-visibility="none"
|
||||
|
||||
chat-delay="2000"
|
||||
|
||||
enable-direct-call="true"
|
||||
|
||||
enable-ga="false"
|
||||
|
||||
></call-us>-->
|
||||
|
||||
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
|
||||
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||
|
||||
<!--<call-us
|
||||
|
||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
|
||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||
|
||||
id="wp-live-chat-by-3CX"
|
||||
|
||||
minimized="true"
|
||||
|
||||
animation-style="noanimation"
|
||||
|
||||
party="LiveChat528346"
|
||||
|
||||
minimized-style="bubbleright"
|
||||
|
||||
allow-call="true"
|
||||
|
||||
allow-video="false"
|
||||
|
||||
allow-soundnotifications="true"
|
||||
|
||||
enable-mute="true"
|
||||
|
||||
enable-onmobile="true"
|
||||
|
||||
offline-enabled="true"
|
||||
|
||||
enable="true"
|
||||
|
||||
ignore-queueownership="false"
|
||||
|
||||
authentication="both"
|
||||
|
||||
show-operator-actual-name="true"
|
||||
|
||||
aknowledge-received="true"
|
||||
|
||||
gdpr-enabled="false"
|
||||
|
||||
message-userinfo-format="name"
|
||||
|
||||
message-dateformat="both"
|
||||
|
||||
lang="browser"
|
||||
|
||||
button-icon-type="default"
|
||||
|
||||
greeting-visibility="none"
|
||||
|
||||
greeting-offline-visibility="none"
|
||||
|
||||
chat-delay="2000"
|
||||
|
||||
enable-direct-call="true"
|
||||
|
||||
enable-ga="false"
|
||||
|
||||
></call-us>-->
|
||||
|
||||
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js
|
||||
id="tcx-callus-js" charset="utf-8"></script>
|
||||
|
||||
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<title>ProManager</title>
|
||||
<meta name="description" content="ProManager"/>
|
||||
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<title>ProManager</title>
|
||||
<meta name="description" content="ProManager" />
|
||||
<% } %>
|
||||
<script>
|
||||
!(function () {
|
||||
'use strict';
|
||||
var e = [
|
||||
'debug',
|
||||
'destroy',
|
||||
'do',
|
||||
'help',
|
||||
'identify',
|
||||
'is',
|
||||
'off',
|
||||
'on',
|
||||
'ready',
|
||||
'render',
|
||||
'reset',
|
||||
'safe',
|
||||
'set',
|
||||
];
|
||||
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||
else {
|
||||
var n = (window.noticeable = window.noticeable || []);
|
||||
|
||||
<% } %>
|
||||
<script>
|
||||
!(function () {
|
||||
'use strict';
|
||||
var e = [
|
||||
'debug',
|
||||
'destroy',
|
||||
'do',
|
||||
'help',
|
||||
'identify',
|
||||
'is',
|
||||
'off',
|
||||
'on',
|
||||
'ready',
|
||||
'render',
|
||||
'reset',
|
||||
'safe',
|
||||
'set',
|
||||
];
|
||||
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||
else {
|
||||
var n = (window.noticeable = window.noticeable || []);
|
||||
function t(e) {
|
||||
return function () {
|
||||
var t = Array.prototype.slice.call(arguments);
|
||||
return t.unshift(e), n.push(t), n;
|
||||
};
|
||||
}
|
||||
!(function () {
|
||||
for (var o = 0; o < e.length; o++) {
|
||||
var r = e[o];
|
||||
n[r] = t(r);
|
||||
}
|
||||
})(),
|
||||
(function () {
|
||||
var e = document.createElement('script');
|
||||
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||
var n = document.head;
|
||||
n.insertBefore(e, n.firstChild);
|
||||
})();
|
||||
function t(e) {
|
||||
return function () {
|
||||
var t = Array.prototype.slice.call(arguments);
|
||||
return t.unshift(e), n.push(t), n;
|
||||
};
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="src/index.jsx"></script>
|
||||
</body>
|
||||
!(function () {
|
||||
for (var o = 0; o < e.length; o++) {
|
||||
var r = e[o];
|
||||
n[r] = t(r);
|
||||
}
|
||||
})(),
|
||||
(function () {
|
||||
var e = document.createElement('script');
|
||||
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||
var n = document.head;
|
||||
n.insertBefore(e, n.firstChild);
|
||||
})();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1280
client/package-lock.json
generated
1280
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,22 +8,22 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.19.11",
|
||||
"@apollo/client": "^3.10.8",
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@apollo/client": "^3.11.4",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.6",
|
||||
"@sentry/cli": "^2.32.2",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@sentry/cli": "^2.33.1",
|
||||
"@sentry/react": "^7.114.0",
|
||||
"@splitsoftware/splitio-react": "^1.12.0",
|
||||
"@splitsoftware/splitio-react": "^1.12.1",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"antd": "^5.19.3",
|
||||
"antd": "^5.20.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.6.8",
|
||||
"axios": "^1.7.4",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.12",
|
||||
@@ -32,45 +32,44 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.12.4",
|
||||
"firebase": "^10.12.5",
|
||||
"graphql": "^16.9.0",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next": "^23.12.3",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.11.4",
|
||||
"logrocket": "^8.1.1",
|
||||
"libphonenumber-js": "^1.11.5",
|
||||
"logrocket": "^8.1.2",
|
||||
"markerjs2": "^2.32.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.0.0",
|
||||
"query-string": "^9.1.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.13.1",
|
||||
"react-big-calendar": "^1.13.2",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^14.1.3",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-joyride": "^2.8.2",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.4.0",
|
||||
"react-popopo": "^2.1.9",
|
||||
"react-product-fruits": "^2.2.6",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.7.12",
|
||||
"react-virtuoso": "^4.10.1",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.0",
|
||||
"redux-actions": "^3.0.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
@@ -80,7 +79,7 @@
|
||||
"styled-components": "^6.1.12",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"userpilot": "^1.3.2",
|
||||
"userpilot": "^1.3.5",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
@@ -131,29 +130,29 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@dotenvx/dotenvx": "^1.6.4",
|
||||
"@dotenvx/dotenvx": "^1.7.0",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@emotion/react": "^11.12.0",
|
||||
"@sentry/webpack-plugin": "^2.21.1",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@sentry/webpack-plugin": "^2.22.2",
|
||||
"@testing-library/cypress": "^10.0.2",
|
||||
"browserslist": "^4.23.2",
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.13.1",
|
||||
"cypress": "^13.13.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.9.3",
|
||||
"memfs": "^4.11.1",
|
||||
"os-browserify": "^0.3.0",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.3.4",
|
||||
"vite": "^5.4.0",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
import handleBeta from "../utils/handleBeta";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
|
||||
@@ -161,3 +161,15 @@
|
||||
.rowWithColor > td {
|
||||
background-color: var(--bgColor) !important;
|
||||
}
|
||||
|
||||
.muted-button {
|
||||
color: lightgray;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
.muted-button:hover {
|
||||
color: darkgrey;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
|
||||
@@ -170,13 +171,22 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
extra={
|
||||
<Space wrap>
|
||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<>
|
||||
<JobMarkSelectedExported
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
||||
<Input.Search
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -13,6 +13,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
@@ -22,7 +23,6 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -153,6 +153,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -188,7 +189,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
}
|
||||
/>
|
||||
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||
<BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse} />
|
||||
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
|
||||
@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
@@ -22,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -41,7 +41,8 @@ export function BillFormComponent({
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
preferredMake
|
||||
preferredMake,
|
||||
disableInHouse
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -177,7 +178,7 @@ export function BillFormComponent({
|
||||
]}
|
||||
>
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
disabled={disabled || disableInHouse}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
@@ -243,7 +244,7 @@ export function BillFormComponent({
|
||||
})
|
||||
]}
|
||||
>
|
||||
<Input disabled={disabled || disableInvNumber} />
|
||||
<Input disabled={disabled || disableInvNumber || disableInHouse} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.date")}
|
||||
@@ -275,7 +276,7 @@ export function BillFormComponent({
|
||||
})
|
||||
]}
|
||||
>
|
||||
<FormDatePicker disabled={disabled} />
|
||||
<DateTimePicker isDateOnly disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.is_credit_memo")}
|
||||
|
||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) {
|
||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
||||
const {
|
||||
treatments: { Simple_Inventory }
|
||||
} = useSplitTreatments({
|
||||
@@ -47,6 +47,7 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
||||
job={lineData ? lineData.jobs_by_pk : null}
|
||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||
disableInvNumber={disableInvNumber}
|
||||
disableInHouse={disableInHouse}
|
||||
loadOutstandingReturns={loadOutstandingReturns}
|
||||
loadInventory={loadInventory}
|
||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.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 FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -196,7 +196,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
{dlExpiresBeforeReturn && (
|
||||
<Space style={{ color: "tomato" }}>
|
||||
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<InputPhone />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<ContractsRatesChangeButton form={form} />
|
||||
|
||||
@@ -10,16 +10,12 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function CourtesyCarCreateFormComponent({
|
||||
form,
|
||||
saveLoading,
|
||||
newCC,
|
||||
}) {
|
||||
export default function CourtesyCarCreateFormComponent({ form, saveLoading, newCC }) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
|
||||
@@ -161,16 +157,16 @@ export default function CourtesyCarCreateFormComponent({
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
</div>
|
||||
<div>
|
||||
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
||||
{() => {
|
||||
@@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
||||
{() => {
|
||||
@@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
||||
{() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function CourtesyCarReturnModalComponent() {
|
||||
const { t } = useTranslation();
|
||||
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.kmend")}
|
||||
|
||||
@@ -36,9 +36,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
};
|
||||
});
|
||||
|
||||
console.log("Scheduled Out Today");
|
||||
console.dir(scheduledOutToday);
|
||||
|
||||
const tvFontSize = 18;
|
||||
const tvFontWeight = "bold";
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import i18n from "../../translations/i18n";
|
||||
import dayjs from "../../utils/day";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -164,7 +164,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
|
||||
@@ -4,7 +4,6 @@ import Markdown from "react-markdown";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { connect } from "react-redux";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
|
||||
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
@@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||
const [formReady, setFormReady] = useState(false);
|
||||
@@ -216,7 +216,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||
<DateTimePicker isDateOnly onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { DatePicker } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
|
||||
|
||||
const dateFormat = "MM/DD/YYYY";
|
||||
|
||||
export function FormDatePicker({
|
||||
bodyshop,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
|
||||
const handleChange = (newDate) => {
|
||||
if (value !== newDate && onChange) {
|
||||
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key.toLowerCase() === "t") {
|
||||
if (onChange) {
|
||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
||||
}
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
if (ref.current && ref.current.blur) ref.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = (e) => {
|
||||
const v = e.target.value;
|
||||
if (!v) return;
|
||||
|
||||
const formats = [
|
||||
"MMDDYY",
|
||||
"MMDDYYYY",
|
||||
"MM/DD/YY",
|
||||
"MM/DD/YYYY",
|
||||
"M/DD/YY",
|
||||
"M/DD/YYYY",
|
||||
"MM/D/YY",
|
||||
"MM/D/YYYY",
|
||||
"M/D/YY",
|
||||
"M/D/YYYY",
|
||||
"D/MM/YY",
|
||||
"D/MM/YYYY",
|
||||
"DD/M/YY",
|
||||
"DD/M/YYYY",
|
||||
"D/M/YY",
|
||||
"D/M/YYYY"
|
||||
];
|
||||
|
||||
let _a;
|
||||
|
||||
// Iterate through formats to find the correct one
|
||||
for (let format of formats) {
|
||||
_a = dayjs(v, format);
|
||||
if (v === _a.format(format)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_a.isValid() && value && value.isValid && value.isValid()) {
|
||||
_a.set({
|
||||
hours: value.hours(),
|
||||
minutes: value.minutes(),
|
||||
seconds: value.seconds(),
|
||||
milliseconds: value.milliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
if (_a.isValid() && onChange) {
|
||||
if (onlyFuture) {
|
||||
if (dayjs().subtract(1, "day").isBefore(_a)) {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
} else {
|
||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
||||
}
|
||||
} else {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown}>
|
||||
<DatePicker
|
||||
ref={ref}
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
format={dateFormat}
|
||||
onBlur={onBlur || handleBlur}
|
||||
showToday={false}
|
||||
disabledTime
|
||||
disabledDate={(d) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(d, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(d);
|
||||
}
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { DatePicker } from "antd";
|
||||
import dayjs from "../../utils/day.js";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
|
||||
|
||||
const dateFormat = "MM/DD/YYYY h:mm a";
|
||||
|
||||
export function FormDateTimePickerEnhanced({
|
||||
bodyshop,
|
||||
value,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
return (
|
||||
<div>
|
||||
<DatePicker
|
||||
ref={ref}
|
||||
value={value ? dayjs(value) : null}
|
||||
format={dateFormat}
|
||||
onBlur={onBlur}
|
||||
showToday={false}
|
||||
disabledDate={(d) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(d, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(d);
|
||||
}
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +1,150 @@
|
||||
import React, { forwardRef } from "react";
|
||||
//import DatePicker from "react-datepicker";
|
||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||
import { Space, TimePicker } from "antd";
|
||||
import { DatePicker } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//To be used as a form element only.
|
||||
import { dateFormats, dateTimeFormats } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
|
||||
// const handleChange = (newDate) => {
|
||||
// if (value !== newDate && onChange) {
|
||||
// onChange(newDate);
|
||||
// }
|
||||
// };
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (newDate === null && onChange) {
|
||||
onChange(null);
|
||||
} else if (newDate && onChange) {
|
||||
onChange(newDate);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const normalizeDateTimeString = (input) => {
|
||||
const upperV = input.toUpperCase().replaceAll(".", "/").replaceAll("-", "/");
|
||||
|
||||
const [datePart, ...timeParts] = upperV.split(" ");
|
||||
|
||||
if (timeParts.length === 0) {
|
||||
return datePart; // If there's no time part, just return the date part.
|
||||
}
|
||||
|
||||
const timePart = timeParts.join(" "); // In case there are multiple spaces, join them back
|
||||
|
||||
// Normalize the time part by ensuring there's a space before AM/PM if not already present
|
||||
const normalizedTime = timePart.replace(/(\d{1,2})(:\d{2})?\s?(AM|PM)/, "$1$2 $3");
|
||||
|
||||
// Combine the date part with the normalized time part
|
||||
return `${datePart} ${normalizedTime}`.trim();
|
||||
};
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e) => {
|
||||
if (!isManualInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsManualInput(false);
|
||||
|
||||
const v = e.target.value;
|
||||
if (!v) return;
|
||||
|
||||
const upperV = normalizeDateTimeString(v);
|
||||
let parsedDate;
|
||||
|
||||
for (const format of isDateOnly ? dateFormats : dateTimeFormats) {
|
||||
parsedDate = dayjs(upperV, format);
|
||||
if (parsedDate.isValid()) break;
|
||||
}
|
||||
|
||||
if (parsedDate && parsedDate.isValid()) {
|
||||
if (isDateOnly) {
|
||||
parsedDate = parsedDate.startOf("day");
|
||||
}
|
||||
|
||||
if (value && value.isValid && value.isValid()) {
|
||||
parsedDate = parsedDate.set({
|
||||
hours: value.hours(),
|
||||
minutes: value.minutes(),
|
||||
seconds: value.seconds(),
|
||||
milliseconds: value.milliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyFuture) {
|
||||
if (dayjs().subtract(1, "day").isBefore(parsedDate)) {
|
||||
onChange(parsedDate);
|
||||
} else {
|
||||
onChange(dayjs().startOf("day"));
|
||||
}
|
||||
} else {
|
||||
onChange(parsedDate);
|
||||
}
|
||||
}
|
||||
},
|
||||
[isManualInput, isDateOnly, onlyFuture, onChange, value]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e) => {
|
||||
setIsManualInput(true);
|
||||
|
||||
if (e.key.toLowerCase() === "t" && onChange) {
|
||||
e.preventDefault();
|
||||
setIsManualInput(false);
|
||||
onChange(dayjs());
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
handleBlur(e);
|
||||
}
|
||||
},
|
||||
[onChange, handleBlur]
|
||||
);
|
||||
|
||||
const handleDisabledDate = useCallback(
|
||||
(current) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(current, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(current);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[onlyToday, onlyFuture]
|
||||
);
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }} id={id}>
|
||||
<FormDatePicker
|
||||
{...restProps}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
|
||||
})}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
onlyFuture={onlyFuture}
|
||||
isDateOnly={false}
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
|
||||
<DatePicker
|
||||
showTime={
|
||||
isDateOnly
|
||||
? false
|
||||
: {
|
||||
format: "hh:mm a",
|
||||
minuteStep: 15,
|
||||
defaultValue: dayjs(dayjs(), "HH:mm:ss")
|
||||
}
|
||||
}
|
||||
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||
value={value ? dayjs(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => dayjs().isAfter(d)
|
||||
})}
|
||||
onChange={onChange}
|
||||
disableSeconds={true}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
onChange={handleChange}
|
||||
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||
onBlur={onBlur || handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
{...restProps}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(DateTimePicker);
|
||||
DateTimePicker.propTypes = {
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
id: PropTypes.string,
|
||||
onlyFuture: PropTypes.bool,
|
||||
onlyToday: PropTypes.bool,
|
||||
isDateOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
|
||||
96
client/src/components/form-date-time-picker/formats.js
Normal file
96
client/src/components/form-date-time-picker/formats.js
Normal file
@@ -0,0 +1,96 @@
|
||||
export const dateTimeFormats = [
|
||||
// Four-digit year with time
|
||||
"M/D/YYYY h:mm A", // Example: 1/5/2023 9:00 AM
|
||||
"M/D/YYYY h:mmA", // Example: 1/5/2023 9:00AM
|
||||
"M/D/YYYY h A", // Example: 1/5/2023 9 AM
|
||||
"M/D/YYYY hA", // Example: 1/5/2023 9AM
|
||||
"M/D/YYYY hh:mm A", // Example: 1/5/2023 02:25 PM
|
||||
"M/D/YYYY hh:mm:ss A", // Example: 1/5/2023 02:25:45 PM
|
||||
|
||||
"MM/D/YYYY h:mm A", // Example: 12/5/2023 9:00 AM
|
||||
"MM/D/YYYY h:mmA", // Example: 12/5/2023 9:00AM
|
||||
"MM/D/YYYY h A", // Example: 12/5/2023 9 AM
|
||||
"MM/D/YYYY hA", // Example: 12/5/2023 9AM
|
||||
"MM/D/YYYY hh:mm A", // Example: 12/5/2023 02:25 PM
|
||||
"MM/D/YYYY hh:mm:ss A", // Example: 12/5/2023 02:25:45 PM
|
||||
|
||||
"M/DD/YYYY h:mm A", // Example: 1/25/2023 9:00 AM
|
||||
"M/DD/YYYY h:mmA", // Example: 1/25/2023 9:00AM
|
||||
"M/DD/YYYY h A", // Example: 1/25/2023 9 AM
|
||||
"M/DD/YYYY hA", // Example: 1/25/2023 9AM
|
||||
"M/DD/YYYY hh:mm A", // Example: 1/25/2023 02:25 PM
|
||||
"M/DD/YYYY hh:mm:ss A", // Example: 1/25/2023 02:25:45 PM
|
||||
|
||||
"MM/DD/YYYY h:mm A", // Example: 12/25/2023 9:00 AM
|
||||
"MM/DD/YYYY h:mmA", // Example: 12/25/2023 9:00AM
|
||||
"MM/DD/YYYY h A", // Example: 12/25/2023 9 AM
|
||||
"MM/DD/YYYY hA", // Example: 12/25/2023 9AM
|
||||
"MM/DD/YYYY hh:mm A", // Example: 12/25/2023 02:25 PM
|
||||
"MM/DD/YYYY hh:mm:ss A", // Example: 12/25/2023 02:25:45 PM
|
||||
|
||||
// Two-digit year with time
|
||||
"M/D/YY h:mm A", // Example: 1/5/23 9:00 AM
|
||||
"M/D/YY h:mmA", // Example: 1/5/23 9:00AM
|
||||
"M/D/YY h A", // Example: 1/5/23 9 AM
|
||||
"M/D/YY hA", // Example: 1/5/23 9AM
|
||||
"M/D/YY hh:mm A", // Example: 1/5/23 02:25 PM
|
||||
"M/D/YY hh:mm:ss A", // Example: 1/5/23 02:25:45 PM
|
||||
|
||||
"MM/D/YY h:mm A", // Example: 12/5/23 9:00 AM
|
||||
"MM/D/YY h:mmA", // Example: 12/5/23 9:00AM
|
||||
"MM/D/YY h A", // Example: 12/5/23 9 AM
|
||||
"MM/D/YY hA", // Example: 12/5/23 9AM
|
||||
"MM/D/YY hh:mm A", // Example: 12/5/23 02:25 PM
|
||||
"MM/D/YY hh:mm:ss A", // Example: 12/5/23 02:25:45 PM
|
||||
|
||||
"M/DD/YY h:mm A", // Example: 1/25/23 9:00 AM
|
||||
"M/DD/YY h:mmA", // Example: 1/25/23 9:00AM
|
||||
"M/DD/YY h A", // Example: 1/25/23 9 AM
|
||||
"M/DD/YY hA", // Example: 1/25/23 9AM
|
||||
"M/DD/YY hh:mm A", // Example: 1/25/23 02:25 PM
|
||||
"M/DD/YY hh:mm:ss A", // Example: 1/25/23 02:25:45 PM
|
||||
|
||||
"MM/DD/YY h:mm A", // Example: 12/25/23 9:00 AM
|
||||
"MM/DD/YY h:mmA", // Example: 12/25/23 9:00AM
|
||||
"MM/DD/YY h A", // Example: 12/25/23 9 AM
|
||||
"MM/DD/YY hA", // Example: 12/25/23 9AM
|
||||
"MM/DD/YY hh:mm A", // Example: 12/25/23 02:25 PM
|
||||
"MM/DD/YY hh:mm:ss A", // Example: 12/25/23 02:25:45 PM
|
||||
|
||||
// Four-digit year without time
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"MM/D/YYYY", // Example: 12/5/2023
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/DD/YYYY", // Example: 12/25/2023
|
||||
|
||||
// Two-digit year without time
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"MM/DD/YY" // Example: 12/25/23
|
||||
];
|
||||
|
||||
// CONFIRMED
|
||||
export const dateFormats = [
|
||||
"MMDDYYYY",
|
||||
"MMDDYY",
|
||||
// Four-digit year
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"MM/D/YYYY", // Example: 12/5/2023
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/DD/YYYY", // Example: 12/25/2023
|
||||
|
||||
// Two-digit year
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"MM/DD/YY", // Example: 12/25/23
|
||||
|
||||
// Explicitly handle single-digit month and day
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"MM/D/YYYY" // Example: 12/5/2023
|
||||
];
|
||||
@@ -43,7 +43,7 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/handleBeta";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
QUERY_SCOREBOARD_ENTRY,
|
||||
UPDATE_SCOREBOARD_ENTRY
|
||||
} from "../../graphql/scoreboard.queries";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps })
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -20,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
||||
@@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
<FormFieldsChanged form={form} />
|
||||
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||
<FormDatePicker format="MM/DD/YYYY" />
|
||||
<DateTimePicker format="MM/DD/YYYY" isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
||||
<DateTimePicker />
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Col, Row, notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -24,6 +23,8 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||
@@ -32,7 +33,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
||||
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -580,12 +580,13 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
promanager: () => {
|
||||
rome: () => {
|
||||
if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") {
|
||||
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
||||
line.mod_lbr_ty = "LAR";
|
||||
}
|
||||
}
|
||||
},
|
||||
promanager: "USE_ROME"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import {
|
||||
Collapse,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
@@ -29,6 +20,7 @@ import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.c
|
||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.regie_number")}
|
||||
name="regie_number"
|
||||
>
|
||||
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||
@@ -116,7 +105,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<FormItemEmail email={getFieldValue("ins_ea")} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||
<Input />
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Form, Input } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||
const [state] = useContext(JobCreateContext);
|
||||
@@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||
<Input disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
||||
<FormDatePicker disabled={!state.vehicle.new} />
|
||||
<DateTimePicker isDateOnly disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
@@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
<div>
|
||||
<FormRow header={t("jobs.forms.estdates")}>
|
||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
||||
<DateTimePicker disabled={jobRO} />
|
||||
@@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
|
||||
<FormRow header={t("jobs.forms.scheddates")}>
|
||||
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||
</Form.Item>
|
||||
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
||||
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
||||
@@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
rules={[
|
||||
{
|
||||
required: jobInPostProduction
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
@@ -13,6 +12,7 @@ import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker isDateOnly disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input disabled={jobRO} />
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
@@ -37,7 +38,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sorter: search?.search
|
||||
? (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, ""))
|
||||
: true,
|
||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.id}>{record.ro_number || t("general.labels.na")}</Link>
|
||||
@@ -49,7 +53,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
key: "ownr_ln",
|
||||
ellipsis: true,
|
||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
|
||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
@@ -67,7 +70,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
},
|
||||
@@ -75,7 +77,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
@@ -85,7 +86,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
key: "status",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: true, // (a, b) => alphaSort(a.status, b.status),
|
||||
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
|
||||
sortOrder: sortcolumn === "status" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.status || t("general.labels.na");
|
||||
@@ -100,7 +101,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
@@ -117,7 +117,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||
sorter: search?.search ? (a, b) => alphaSort(a.plate_no, b.plate_no) : true,
|
||||
sortOrder: sortcolumn === "plate_no" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.plate_no ? record.plate_no : "";
|
||||
@@ -128,7 +128,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sorter: search?.search ? (a, b) => alphaSort(a.clm_no, b.clm_no) : true,
|
||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
||||
},
|
||||
@@ -142,8 +142,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
|
||||
sorter: true, //(a, b) => a.clm_total - b.clm_total,
|
||||
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
|
||||
sortOrder: sortcolumn === "clm_total" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.clm_total ? (
|
||||
@@ -157,7 +156,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.owner_owing"),
|
||||
dataIndex: "owner_owing",
|
||||
key: "owner_owing",
|
||||
|
||||
render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,18 +16,15 @@ import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
export function JobsList({ bodyshop }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification, Popconfirm } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobMarkSelectedExported);
|
||||
|
||||
export function JobMarkSelectedExported({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
jobIds,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
insertAuditTrail
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||
|
||||
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||
const handleUpdate = async () => {
|
||||
setLoading(true);
|
||||
loadingCallback(true);
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
jobIds: jobIds,
|
||||
fields: {
|
||||
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||
date_exported: new Date()
|
||||
}
|
||||
},
|
||||
update(cache) {}
|
||||
});
|
||||
|
||||
await insertExportLog({
|
||||
variables: {
|
||||
logs: jobIds.map((id) => {
|
||||
return {
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid: id,
|
||||
successful: true,
|
||||
message: JSON.stringify([t("general.labels.markedexported")]),
|
||||
useremail: currentUser.email
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
result.data.update_jobs.returning.forEach((job) => {
|
||||
console.log("results job", job.id, "audit: ", AuditTrailMapping.admin_jobmarkexported());
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobmarkexported(),
|
||||
type: "admin_jobmarkexported"
|
||||
});
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
loadingCallback(false);
|
||||
completedCallback && completedCallback([]);
|
||||
setLoading(false);
|
||||
refetch && refetch();
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popconfirm
|
||||
open={open}
|
||||
title={t("general.labels.areyousure")}
|
||||
onCancel={() => setOpen(false)}
|
||||
onConfirm={handleUpdate}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||
{t("jobs.actions.markasexported")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export function PartnerPingComponent({ bodyshop }) {
|
||||
// Execute the created function directly
|
||||
checkPartnerStatus(bodyshop);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bodyshop]);
|
||||
}, [bodyshop?.id]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import { CalendarFilled } from "@ant-design/icons";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -62,7 +62,7 @@ export function PartsOrderBackorderEta({
|
||||
<div>
|
||||
<Form form={form} onFinish={handleFinish}>
|
||||
<Form.Item name="eta">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("general.actions.save")}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -71,7 +71,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
|
||||
<div>
|
||||
<Form form={form} onFinish={handleFinish}>
|
||||
<Form.Item name="eta">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("parts_orders.actions.backordered")}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DeleteFilled, EyeFilled } from "@ant-design/icons";
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
|
||||
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -83,47 +82,34 @@ export function PartsOrderListTableDrawerComponent({
|
||||
sortedInfo: {}
|
||||
});
|
||||
|
||||
const [returnfrombill, setReturnFromBill] = useState();
|
||||
const [billData, setBillData] = useState();
|
||||
const [billData, setBillData] = useState(null);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const selectedpartsorder = search.partsorderid;
|
||||
|
||||
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||
const { refetch } = billsQuery;
|
||||
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
||||
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
||||
|
||||
useEffect(() => {
|
||||
if (returnfrombill === null) {
|
||||
setBillData(null);
|
||||
} else {
|
||||
const fetchData = async () => {
|
||||
const result = await billQuery({
|
||||
variables: { billid: returnfrombill }
|
||||
});
|
||||
setBillData(result.data);
|
||||
};
|
||||
fetchData();
|
||||
}
|
||||
}, [returnfrombill, billQuery]);
|
||||
const fetchData = async () => {
|
||||
if (selectedPartsOrderRecord?.returnfrombill) {
|
||||
try {
|
||||
const { data } = await billQuery({
|
||||
variables: { billid: selectedPartsOrderRecord.returnfrombill }
|
||||
});
|
||||
setBillData(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching bill data:", error);
|
||||
}
|
||||
} else setBillData(null);
|
||||
};
|
||||
fetchData();
|
||||
}, [selectedPartsOrderRecord, billQuery]);
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
const recordActions = (record) => (
|
||||
<Space direction="horizontal" wrap>
|
||||
{showView && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (record.returnfrombill) {
|
||||
setReturnFromBill(record.returnfrombill);
|
||||
} else {
|
||||
setReturnFromBill(null);
|
||||
}
|
||||
handleOnRowClick(record);
|
||||
}}
|
||||
>
|
||||
<EyeFilled />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
||||
onClick={() => {
|
||||
@@ -133,16 +119,14 @@ export function PartsOrderListTableDrawerComponent({
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
partsorderlines: record.parts_order_lines.map((pol) => {
|
||||
return {
|
||||
joblineid: pol.job_line_id,
|
||||
id: pol.id,
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
act_price: pol.act_price,
|
||||
oem_partno: pol.oem_partno
|
||||
};
|
||||
})
|
||||
partsorderlines: record.parts_order_lines.map((pol) => ({
|
||||
joblineid: pol.job_line_id,
|
||||
id: pol.id,
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
act_price: pol.act_price,
|
||||
oem_partno: pol.oem_partno
|
||||
}))
|
||||
}
|
||||
});
|
||||
}}
|
||||
@@ -167,7 +151,6 @@ export function PartsOrderListTableDrawerComponent({
|
||||
disabled={jobRO}
|
||||
onConfirm={async () => {
|
||||
//Delete the parts return.!
|
||||
|
||||
await deletePartsOrder({
|
||||
variables: { partsOrderId: record.id },
|
||||
update(cache) {
|
||||
@@ -191,7 +174,6 @@ export function PartsOrderListTableDrawerComponent({
|
||||
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
|
||||
onClick={() => {
|
||||
logImEXEvent("parts_order_receive_bill");
|
||||
|
||||
setBillEnterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
@@ -199,24 +181,20 @@ export function PartsOrderListTableDrawerComponent({
|
||||
bill: {
|
||||
vendorid: record.vendor.id,
|
||||
is_credit_memo: record.return,
|
||||
billlines: record.parts_order_lines.map((pol) => {
|
||||
return {
|
||||
joblineid: pol.job_line_id || "noline",
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
|
||||
actual_price: pol.act_price,
|
||||
|
||||
cost_center: pol.jobline?.part_type
|
||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||
? pol.jobline.part_type !== "PAE"
|
||||
? pol.jobline.part_type
|
||||
: null
|
||||
: responsibilityCenters.defaults &&
|
||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||
: null
|
||||
};
|
||||
})
|
||||
billlines: record.parts_order_lines.map((pol) => ({
|
||||
joblineid: pol.job_line_id || "noline",
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
actual_price: pol.act_price,
|
||||
cost_center: pol.jobline?.part_type
|
||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||
? pol.jobline.part_type !== "PAE"
|
||||
? pol.jobline.part_type
|
||||
: null
|
||||
: responsibilityCenters.defaults &&
|
||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||
: null
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -243,8 +221,6 @@ export function PartsOrderListTableDrawerComponent({
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
||||
|
||||
const rowExpander = (record) => {
|
||||
const columns = [
|
||||
{
|
||||
|
||||
@@ -6,12 +6,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -74,7 +74,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
||||
]}
|
||||
label={t("parts_orders.fields.deliver_by")}
|
||||
>
|
||||
<FormDatePicker onlyFuture />
|
||||
<DateTimePicker isDateOnly onlyFuture />
|
||||
</Form.Item>
|
||||
{job && job.special_coverage_policy && (
|
||||
<Tag color="tomato">
|
||||
|
||||
@@ -90,7 +90,7 @@ export function BillMarkSelectedExported({
|
||||
onConfirm={handleUpdate}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||
{t("bills.labels.markexported")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DatePickerFormItem from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -77,7 +77,7 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DatePickerFormItem disabled={disabled} />
|
||||
<DateTimePicker isDateOnly disabled={disabled} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export function PaymentMarkSelectedExported({
|
||||
onConfirm={handleUpdate}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||
{t("bills.labels.markexported")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import { Input, Space, Spin } from "antd";
|
||||
import React from "react";
|
||||
import { Button, Input, Space, Spin } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { ExclamationCircleFilled, ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters);
|
||||
|
||||
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
|
||||
const { t } = useTranslation();
|
||||
const [alertFilter, setAlertFilter] = useState(false);
|
||||
|
||||
const toggleAlertFilter = () => {
|
||||
const newAlertFilter = !alertFilter;
|
||||
setAlertFilter(newAlertFilter);
|
||||
setFilter({ ...filter, alert: newAlertFilter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Space wrap>
|
||||
{loading && <Spin />}
|
||||
@@ -35,6 +45,13 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
|
||||
onChange={(emp) => setFilter({ ...filter, employeeId: emp })}
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
type={alertFilter ? "primary" : "default"}
|
||||
onClick={toggleAlertFilter}
|
||||
icon={alertFilter ? <ExclamationCircleFilled /> : <ExclamationCircleOutlined />}
|
||||
>
|
||||
{t("production.labels.alerts")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,6 +290,22 @@ const PartsStatusComponent = ({ metadata, cardSettings }) =>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const TasksToolTip = ({ metadata, cardSettings, t }) =>
|
||||
cardSettings?.tasks && (
|
||||
<Col span={12}>
|
||||
<EllipsesToolTip
|
||||
title={`${t("production.labels.tasks")}: ${metadata.tasks_aggregate?.aggregate?.count || 0}`}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{metadata.tasks_aggregate?.aggregate?.count ? (
|
||||
`T: ${metadata.tasks_aggregate.aggregate.count}`
|
||||
) : (
|
||||
<span>T: 0</span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
|
||||
const { t } = useTranslation();
|
||||
const { metadata } = card;
|
||||
@@ -336,21 +352,15 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
||||
cardSettings?.production_note ||
|
||||
cardSettings?.partsstatus ||
|
||||
cardSettings?.estimator ||
|
||||
cardSettings?.subtotal
|
||||
cardSettings?.subtotal ||
|
||||
cardSettings?.tasks
|
||||
);
|
||||
}, [cardSettings]);
|
||||
|
||||
const headerContent = (
|
||||
<div className="header-content-container">
|
||||
<div className="inner-container">
|
||||
<ProductionAlert
|
||||
record={{
|
||||
id: card.id,
|
||||
production_vars: card?.metadata.production_vars,
|
||||
refetch: card?.refetch
|
||||
}}
|
||||
key="alert"
|
||||
/>
|
||||
<ProductionAlert id={card.id} productionVars={metadata?.production_vars} refetch={card?.refetch} key="alert" />
|
||||
{metadata?.suspended && <PauseCircleOutlined className="circle-outline" key="suspended" />}
|
||||
{metadata?.iouparent && (
|
||||
<EllipsesToolTip
|
||||
@@ -393,6 +403,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
||||
employee_csr={employee_csr}
|
||||
/>
|
||||
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<TasksToolTip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />
|
||||
@@ -15,13 +15,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
||||
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
||||
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
|
||||
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
||||
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
||||
const [filter, setFilter] = useState(defaultFilters);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const [orientation, setOrientation] = useState("vertical");
|
||||
@@ -182,19 +182,14 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
|
||||
);
|
||||
|
||||
const cardSettings = useMemo(
|
||||
() =>
|
||||
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
|
||||
? associationSettings.kanban_settings
|
||||
: defaultKanbanSettings,
|
||||
[associationSettings]
|
||||
);
|
||||
const cardSettings = useMemo(() => {
|
||||
const kanbanSettings = associationSettings?.kanban_settings;
|
||||
return mergeWithDefaults(kanbanSettings);
|
||||
}, [associationSettings]);
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings) => {
|
||||
setLoading(true);
|
||||
setOrientation(newSettings.orientation ? "vertical" : "horizontal");
|
||||
setLoading(false);
|
||||
}, []);
|
||||
const handleSettingsChange = () => {
|
||||
setFilter(defaultFilters);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Skeleton active />;
|
||||
|
||||
@@ -2,11 +2,13 @@ import React, { useMemo } from "react";
|
||||
import { Card, Statistic } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||
|
||||
export const StatisticType = {
|
||||
HOURS: "hours",
|
||||
AMOUNT: "amount",
|
||||
JOBS: "jobs"
|
||||
JOBS: "jobs",
|
||||
TASKS: "tasks"
|
||||
};
|
||||
|
||||
const mergeStatistics = (items, values) => {
|
||||
@@ -122,6 +124,20 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
return parseFloat(total.toFixed(2));
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const tasksInProduction = useMemo(() => {
|
||||
if (!data || !cardSettings.tasksInProduction) return null;
|
||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||
}, [data, cardSettings.tasksInProduction]);
|
||||
|
||||
const tasksOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.tasksOnBoard) return null;
|
||||
return reducerData.lanes.reduce((acc, lane) => {
|
||||
return (
|
||||
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
|
||||
);
|
||||
}, 0);
|
||||
}, [reducerData, cardSettings.tasksOnBoard]);
|
||||
|
||||
const statistics = useMemo(
|
||||
() =>
|
||||
mergeStatistics(statisticsItems, [
|
||||
@@ -134,7 +150,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
|
||||
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
|
||||
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
|
||||
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
|
||||
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS },
|
||||
{ id: 10, value: tasksOnBoard, type: StatisticType.TASKS },
|
||||
{ id: 11, value: tasksInProduction, type: StatisticType.TASKS }
|
||||
]),
|
||||
[
|
||||
totalHrs,
|
||||
@@ -146,7 +164,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
totalAmountOnBoard,
|
||||
totalLABOnBoard,
|
||||
totalLAROnBoard,
|
||||
jobsOnBoard
|
||||
jobsOnBoard,
|
||||
tasksOnBoard,
|
||||
tasksInProduction
|
||||
]
|
||||
);
|
||||
|
||||
@@ -187,37 +207,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
};
|
||||
|
||||
ProductionStatistics.propTypes = {
|
||||
data: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
labhrs: PropTypes.object,
|
||||
larhrs: PropTypes.object,
|
||||
job_totals: PropTypes.object
|
||||
})
|
||||
).isRequired,
|
||||
cardSettings: PropTypes.shape({
|
||||
totalHrs: PropTypes.bool,
|
||||
totalLAB: PropTypes.bool,
|
||||
totalLAR: PropTypes.bool,
|
||||
jobsInProduction: PropTypes.bool,
|
||||
totalAmountInProduction: PropTypes.bool,
|
||||
totalHrsOnBoard: PropTypes.bool,
|
||||
totalLABOnBoard: PropTypes.bool,
|
||||
totalLAROnBoard: PropTypes.bool,
|
||||
jobsOnBoard: PropTypes.bool,
|
||||
totalAmountOnBoard: PropTypes.bool,
|
||||
statisticsOrder: PropTypes.arrayOf(PropTypes.number)
|
||||
}).isRequired,
|
||||
reducerData: PropTypes.shape({
|
||||
lanes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
cards: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
metadata: PropTypes.object
|
||||
})
|
||||
).isRequired
|
||||
})
|
||||
).isRequired
|
||||
})
|
||||
data: PropTypes.array.isRequired,
|
||||
cardSettings: PropTypes.object.isRequired,
|
||||
reducerData: PropTypes.object
|
||||
};
|
||||
|
||||
export default ProductionStatistics;
|
||||
|
||||
@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
|
||||
|
||||
// Function to create board data based on statuses and jobs, with optional filtering
|
||||
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
||||
const { search, employeeId } = filter;
|
||||
const { search, employeeId, alert } = filter;
|
||||
|
||||
const lanes = statuses.map((status) => ({
|
||||
id: status,
|
||||
@@ -52,6 +52,11 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Filter jobs by alert if alert filter is true
|
||||
if (alert) {
|
||||
filteredJobs = filteredJobs.filter((job) => job.production_vars?.alert);
|
||||
}
|
||||
|
||||
const DataGroupedByStatus = groupBy(filteredJobs, "status");
|
||||
|
||||
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {
|
||||
|
||||
@@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => (
|
||||
"sublets",
|
||||
"partsstatus",
|
||||
"estimator",
|
||||
"subtotal"
|
||||
"subtotal",
|
||||
"tasks"
|
||||
].map((item) => (
|
||||
<Col span={4} key={item}>
|
||||
<Form.Item name={item} valuePropName="checked">
|
||||
|
||||
@@ -8,7 +8,9 @@ const statisticsItems = [
|
||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" }
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
|
||||
{ id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
|
||||
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||
];
|
||||
|
||||
const defaultKanbanSettings = {
|
||||
@@ -23,6 +25,7 @@ const defaultKanbanSettings = {
|
||||
scheduled_completion: true,
|
||||
cardcolor: false,
|
||||
orientation: false,
|
||||
tasks: false,
|
||||
cardSize: "small",
|
||||
model_info: true,
|
||||
kiosk: false,
|
||||
@@ -35,6 +38,8 @@ const defaultKanbanSettings = {
|
||||
totalLABOnBoard: false,
|
||||
totalLAROnBoard: false,
|
||||
jobsOnBoard: false,
|
||||
tasksOnBoard: false,
|
||||
tasksInProduction: false,
|
||||
totalAmountOnBoard: true,
|
||||
estimator: false,
|
||||
subtotal: false,
|
||||
@@ -43,4 +48,22 @@ const defaultKanbanSettings = {
|
||||
selectedEstimators: []
|
||||
};
|
||||
|
||||
export { defaultKanbanSettings, statisticsItems };
|
||||
const defaultFilters = { search: "", employeeId: null, alert: false };
|
||||
|
||||
const mergeWithDefaults = (settings) => {
|
||||
// Create a new object that starts with the default settings
|
||||
const mergedSettings = { ...defaultKanbanSettings };
|
||||
|
||||
// Override with the provided settings, if any
|
||||
if (settings) {
|
||||
for (const key in settings) {
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
mergedSettings[key] = settings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergedSettings;
|
||||
};
|
||||
|
||||
export { defaultKanbanSettings, statisticsItems, mergeWithDefaults, defaultFilters };
|
||||
|
||||
@@ -3,13 +3,15 @@ import { Button, Card, Col, Form, notification, Popover, Row, Tabs } from "antd"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
||||
import { defaultKanbanSettings } from "./defaultKanbanSettings.js";
|
||||
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
||||
import LayoutSettings from "./LayoutSettings.jsx";
|
||||
import InformationSettings from "./InformationSettings.jsx";
|
||||
import StatisticsSettings from "./StatisticsSettings.jsx";
|
||||
import FilterSettings from "./FilterSettings.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
import { isFunction } from "lodash";
|
||||
|
||||
export default function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
|
||||
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
||||
const [form] = Form.useForm();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -23,16 +25,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
||||
|
||||
useEffect(() => {
|
||||
if (associationSettings?.kanban_settings) {
|
||||
form.setFieldsValue(associationSettings.kanban_settings);
|
||||
if (associationSettings.kanban_settings.statisticsOrder) {
|
||||
setStatisticsOrder(associationSettings.kanban_settings.statisticsOrder);
|
||||
}
|
||||
if (associationSettings.kanban_settings.selectedMdInsCos) {
|
||||
setSelectedMdInsCos(associationSettings.kanban_settings.selectedMdInsCos);
|
||||
}
|
||||
if (associationSettings.kanban_settings.selectedEstimators) {
|
||||
setSelectedEstimators(associationSettings.kanban_settings.selectedEstimators);
|
||||
}
|
||||
const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
|
||||
form.setFieldsValue(finalSettings);
|
||||
setStatisticsOrder(finalSettings.statisticsOrder);
|
||||
setSelectedMdInsCos(finalSettings.selectedMdInsCos);
|
||||
setSelectedEstimators(finalSettings.selectedEstimators);
|
||||
}
|
||||
}, [form, associationSettings]);
|
||||
|
||||
@@ -65,6 +62,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
parentLoading(false);
|
||||
|
||||
if (onSettingsChange && isFunction(onSettingsChange)) {
|
||||
onSettingsChange(values);
|
||||
}
|
||||
|
||||
setHasChanges(false);
|
||||
};
|
||||
|
||||
@@ -155,3 +157,13 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
ProductionBoardKanbanSettings.propTypes = {
|
||||
associationSettings: PropTypes.object,
|
||||
parentLoading: PropTypes.func.isRequired,
|
||||
bodyshop: PropTypes.object.isRequired,
|
||||
onSettingsChange: PropTypes.func,
|
||||
data: PropTypes.array
|
||||
};
|
||||
|
||||
export default ProductionBoardKanbanSettings;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from "@ant-design/icons";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../../../redux/user/user.selectors.js";
|
||||
import { selectTechnician } from "../../../../redux/tech/tech.selectors.js";
|
||||
import ProductionBoardCard from "../../../production-board-kanban-card/production-board-kanban-card.component.jsx";
|
||||
import ProductionBoardCard from "../../production-board-kanban-card.component.jsx";
|
||||
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
|
||||
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
|
||||
import ListComponent from "../components/ListComponent.jsx";
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { Button, Dropdown } from "antd";
|
||||
import dataSource from "./production-list-columns.data";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
@@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
technician: selectTechnician,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
|
||||
|
||||
export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
// Add any necessary dispatch actions here
|
||||
});
|
||||
|
||||
export function ProductionColumnsComponent({
|
||||
columnState,
|
||||
technician,
|
||||
bodyshop,
|
||||
data,
|
||||
tableState,
|
||||
refetch,
|
||||
onColumnAdd
|
||||
}) {
|
||||
const [columns, setColumns] = columnState;
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const handleAdd = (e) => {
|
||||
setColumns([
|
||||
...columns,
|
||||
...dataSource({
|
||||
bodyshop,
|
||||
technician,
|
||||
state: tableState,
|
||||
data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).filter((i) => i.key === e.key)
|
||||
]);
|
||||
const newColumn = dataSource({
|
||||
bodyshop,
|
||||
technician,
|
||||
state: tableState,
|
||||
data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((i) => i.key === e.key);
|
||||
|
||||
if (newColumn) {
|
||||
const updatedColumns = [...columns, newColumn];
|
||||
setColumns(updatedColumns);
|
||||
|
||||
// Call the onColumnAdd function passed as a prop
|
||||
if (onColumnAdd) {
|
||||
onColumnAdd(newColumn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const columnKeys = columns.map((i) => i.key);
|
||||
@@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
|
||||
);
|
||||
}
|
||||
|
||||
// <Transfer
|
||||
// dataSource={dataSource}
|
||||
// titles={["Source", "Target"]}
|
||||
// targetKeys={columns.map((c) => c.key)}
|
||||
// render={(item) => item.title}
|
||||
// onChange={(nextTargetKeys, direction, moveKeys) => {
|
||||
// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
|
||||
// }}
|
||||
// />
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import { ExclamationCircleFilled, PlusCircleFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button } from "antd";
|
||||
import { Button, Popconfirm } from "antd";
|
||||
import React, { useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -8,6 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
@@ -22,22 +23,24 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
)
|
||||
});
|
||||
|
||||
const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
|
||||
const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => {
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleAlertToggle = useCallback(() => {
|
||||
logImEXEvent("production_toggle_alert");
|
||||
|
||||
const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true;
|
||||
const newAlertState = !!productionVars?.alert ? !productionVars?.alert : true;
|
||||
const finalProductionVars = {
|
||||
...productionVars,
|
||||
alert: newAlertState
|
||||
};
|
||||
|
||||
updateAlert({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
jobId: id,
|
||||
job: {
|
||||
production_vars: {
|
||||
...record.production_vars,
|
||||
alert: newAlertState
|
||||
}
|
||||
production_vars: finalProductionVars
|
||||
}
|
||||
}
|
||||
}).catch((err) => {
|
||||
@@ -45,17 +48,26 @@ const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
jobid: id,
|
||||
operation: AuditTrailMapping.alertToggle(newAlertState),
|
||||
type: "alertToggle"
|
||||
});
|
||||
|
||||
if (record.refetch) record.refetch();
|
||||
}, [updateAlert, insertAuditTrail, record]);
|
||||
if (refetch) refetch();
|
||||
}, [updateAlert, insertAuditTrail, id, productionVars, refetch]);
|
||||
|
||||
if (!record.production_vars?.alert) return null;
|
||||
|
||||
return <Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />;
|
||||
return productionVars?.alert ? (
|
||||
<Popconfirm
|
||||
title={t("general.actions.remove_alert")}
|
||||
onConfirm={handleAlertToggle}
|
||||
okText={t("general.labels.yes")}
|
||||
cancelText={t("general.labels.no")}
|
||||
>
|
||||
<Button className="production-alert" icon={<ExclamationCircleFilled />} />
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<Button className="muted-button" icon={<PlusCircleFilled />} onClick={handleAlertToggle} />
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);
|
||||
|
||||
@@ -2,10 +2,13 @@ import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
||||
import { Checkbox, Space, Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { store } from "../../redux/store";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
@@ -23,10 +26,12 @@ import ProductionListColumnPartsReceived from "./production-list-columns.partsre
|
||||
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
import { store } from "../../redux/store";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
|
||||
const getEmployeeName = (employeeId, employees) => {
|
||||
const employee = employees.find((e) => e.id === employeeId);
|
||||
return employee ? `${employee.first_name} ${employee.last_name}` : "";
|
||||
};
|
||||
|
||||
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
|
||||
const { Enhanced_Payroll } = treatments;
|
||||
@@ -258,7 +263,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
{ text: "True", value: true },
|
||||
{ text: "False", value: false }
|
||||
],
|
||||
onFilter: (value, record) => value.includes(record.special_coverage_policy),
|
||||
onFilter: (value, record) => value === record.special_coverage_policy,
|
||||
render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
|
||||
},
|
||||
|
||||
@@ -349,7 +354,14 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
key: "alert",
|
||||
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
|
||||
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
|
||||
render: (text, record) => <ProductionListColumnAlert record={{ record }} />
|
||||
filters: [
|
||||
{ text: "True", value: true },
|
||||
{ text: "False", value: false }
|
||||
],
|
||||
onFilter: (value, record) => value === (record.production_vars?.alert || false),
|
||||
render: (text, record) => (
|
||||
<ProductionListColumnAlert id={record.id} productionVars={record?.production_vars} refetch={refetch} />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: i18n.t("production.labels.note"),
|
||||
@@ -370,7 +382,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
dataIndex: "tt",
|
||||
key: "tt",
|
||||
render: (text, record) => {
|
||||
return <ProductionlistColumnTouchTime job={record} />;
|
||||
return <ProductionListColumnTouchTime job={record} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -419,8 +431,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
|
||||
getEmployeeName(a.employee_body, bodyshop.employees),
|
||||
getEmployeeName(b.employee_body, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
||||
@@ -433,8 +445,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
|
||||
getEmployeeName(a.employee_prep, bodyshop.employees),
|
||||
getEmployeeName(b.employee_prep, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
||||
@@ -453,8 +465,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
|
||||
getEmployeeName(a.employee_csr, bodyshop.employees),
|
||||
getEmployeeName(b.employee_csr, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
||||
@@ -467,8 +479,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
|
||||
getEmployeeName(a.employee_refinish, bodyshop.employees),
|
||||
getEmployeeName(b.employee_refinish, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Dropdown, Space, TimePicker } from "antd";
|
||||
import { Button, Card, Dropdown, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ProductionListDate({ record, field, time, pastIndicator }) {
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
@@ -57,22 +57,14 @@ export default function ProductionListDate({ record, field, time, pastIndicator
|
||||
label: (
|
||||
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
||||
<Space direction={"vertical"}>
|
||||
<FormDatePicker
|
||||
<DateTimePicker
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
value={(record[field] && dayjs(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
format="MM/DD/YYYY"
|
||||
format={time ? "MM/DD/YYYY hh:mm a" : "MM/DD/YYYY"}
|
||||
isDateOnly={!time}
|
||||
showTime={time ? { format: "hh:mm a", minuteStep: 15 } : false}
|
||||
/>
|
||||
{time && (
|
||||
<TimePicker
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
value={(record[field] && dayjs(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
minuteStep={15}
|
||||
format="hh:mm a"
|
||||
/>
|
||||
)}
|
||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
@@ -148,6 +148,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
||||
) : (
|
||||
<PlusCircleFilled
|
||||
style={iconStyle}
|
||||
className="muted-button"
|
||||
onClick={() => {
|
||||
setAssignment({ operation: type });
|
||||
setVisibility(true);
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { Button, Form, Input, notification, Popover, Space } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) {
|
||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSaveConfig = async (values) => {
|
||||
logImEXEvent("production_save_config");
|
||||
setLoading(true);
|
||||
const result = await updateShop({
|
||||
variables: {
|
||||
id: bodyshop.id,
|
||||
shop: {
|
||||
production_config: [
|
||||
...bodyshop.production_config.filter((b) => b.name !== values.name),
|
||||
//Assign it to the name
|
||||
{
|
||||
name: values.name,
|
||||
columns: {
|
||||
columnKeys: columns.map((i) => {
|
||||
return { key: i.key, width: i.width };
|
||||
}),
|
||||
tableState
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("bodyshop.successes.save") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("bodyshop.errors.saving", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
form.resetFields();
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
};
|
||||
const popMenu = (
|
||||
<div>
|
||||
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
||||
<Form.Item label={t("production.labels.viewname")} name="name" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button type="primary" danger onClick={() => form.submit()} loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={open} content={popMenu}>
|
||||
<Button loading={loading} onClick={() => setOpen(true)}>
|
||||
{t("production.actions.saveconfig")}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListSaveConfigButton);
|
||||
@@ -0,0 +1,505 @@
|
||||
import { DeleteOutlined, ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { isFunction } from "lodash";
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
export function ProductionListConfigManager({
|
||||
refetch,
|
||||
bodyshop,
|
||||
technician,
|
||||
currentUser,
|
||||
state,
|
||||
data,
|
||||
columns,
|
||||
setColumns,
|
||||
setState,
|
||||
onSave,
|
||||
hasUnsavedChanges,
|
||||
setHasUnsavedChanges
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isAddingNewProfile, setIsAddingNewProfile] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [activeView, setActiveView] = useState(() => {
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
return assoc && assoc.default_prod_list_view;
|
||||
});
|
||||
|
||||
const defaultState = {
|
||||
sortedInfo: {
|
||||
columnKey: "ro_number",
|
||||
order: null
|
||||
},
|
||||
filteredInfo: {}
|
||||
};
|
||||
|
||||
const ensureDefaultState = (state) => {
|
||||
return {
|
||||
sortedInfo: state?.sortedInfo || defaultState.sortedInfo,
|
||||
filteredInfo: state?.filteredInfo || defaultState.filteredInfo,
|
||||
...state
|
||||
};
|
||||
};
|
||||
|
||||
const createDefaultView = async () => {
|
||||
const defaultConfig = {
|
||||
name: t("production.constants.main_profile"),
|
||||
columns: {
|
||||
columnKeys: [
|
||||
{ key: "ro_number", width: 100 },
|
||||
{ key: "ownr", width: 100 },
|
||||
{ key: "vehicle", width: 100 },
|
||||
{ key: "ins_co_nm", width: 100 },
|
||||
{ key: "actual_in", width: 100 },
|
||||
{ key: "scheduled_completion", width: 100 },
|
||||
{ key: "labhrs", width: 100 },
|
||||
{ key: "employee_body", width: 100 },
|
||||
{ key: "larhrs", width: 100 },
|
||||
{ key: "employee_refinish", width: 100 },
|
||||
{ key: "tt", width: 100 },
|
||||
{ key: "status", width: 100 },
|
||||
{ key: "sublets", width: 100 },
|
||||
{ key: "viewdetail", width: 100 }
|
||||
],
|
||||
tableState: ensureDefaultState(state)
|
||||
}
|
||||
};
|
||||
|
||||
const result = await updateShop({
|
||||
variables: {
|
||||
id: bodyshop.id,
|
||||
shop: {
|
||||
production_config: [defaultConfig]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
await updateActiveProdView(t("production.constants.main_profile"));
|
||||
window.location.reload(); // Reload the page
|
||||
} else {
|
||||
notification.error({
|
||||
message: t("bodyshop.errors.creatingdefaultview", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
treatments: { Enhanced_Payroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const updateActiveProdView = async (viewName) => {
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
if (assoc) {
|
||||
await updateDefaultProdView({
|
||||
variables: { assocId: assoc.id, view: viewName },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
id: cache.identify(bodyshop),
|
||||
fields: {
|
||||
associations(existingAssociations) {
|
||||
return existingAssociations.map((a) => {
|
||||
if (a.useremail !== currentUser.email) return a;
|
||||
return { ...a, default_prod_list_view: viewName };
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
setActiveView(viewName);
|
||||
setHasUnsavedChanges(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (value) => {
|
||||
if (hasUnsavedChanges) {
|
||||
confirm({
|
||||
title: t("general.labels.unsavedchanges"),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t("general.messages.unsavedchangespopup"),
|
||||
onOk: () => proceedWithSelect(value),
|
||||
onCancel() {
|
||||
// Do nothing if canceled
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await proceedWithSelect(value);
|
||||
}
|
||||
};
|
||||
|
||||
const proceedWithSelect = async (value) => {
|
||||
if (value === "add_new") {
|
||||
setIsAddingNewProfile(true);
|
||||
setOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedConfig = bodyshop.production_config.find((pc) => pc.name === value);
|
||||
|
||||
// If the selected profile doesn't exist, revert to the main profile
|
||||
if (!selectedConfig) {
|
||||
const mainProfileConfig = bodyshop.production_config.find(
|
||||
(pc) => pc.name === t("production.constants.main_profile")
|
||||
);
|
||||
|
||||
if (mainProfileConfig) {
|
||||
await updateActiveProdView(t("production.constants.main_profile"));
|
||||
setColumns(
|
||||
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
refetch,
|
||||
technician,
|
||||
state: ensureDefaultState(state),
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
const newState = ensureDefaultState(mainProfileConfig.columns.tableState);
|
||||
setState(newState);
|
||||
|
||||
if (onSave && isFunction(onSave)) {
|
||||
onSave();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the selected profile exists, proceed as normal
|
||||
if (selectedConfig) {
|
||||
const newColumns = selectedConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
refetch,
|
||||
technician,
|
||||
state: ensureDefaultState(state),
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
});
|
||||
setColumns(newColumns);
|
||||
const newState = ensureDefaultState(selectedConfig.columns.tableState);
|
||||
setState(newState);
|
||||
|
||||
await updateActiveProdView(value);
|
||||
if (onSave && isFunction(onSave)) {
|
||||
onSave();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrash = async (name) => {
|
||||
if (name === t("production.constants.main_profile")) return;
|
||||
|
||||
const remainingConfigs = bodyshop.production_config.filter((b) => b.name !== name);
|
||||
|
||||
await updateShop({
|
||||
variables: {
|
||||
id: bodyshop.id,
|
||||
shop: {
|
||||
production_config: remainingConfigs
|
||||
}
|
||||
},
|
||||
awaitRefetchQueries: true
|
||||
});
|
||||
|
||||
if (name === activeView) {
|
||||
// Only switch profiles if the deleted profile was the active profile
|
||||
if (remainingConfigs.length > 0) {
|
||||
const nextConfig = remainingConfigs[0];
|
||||
await updateActiveProdView(nextConfig.name);
|
||||
setColumns(
|
||||
nextConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state: ensureDefaultState(state),
|
||||
refetch,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
setState(ensureDefaultState(nextConfig.columns.tableState));
|
||||
} else {
|
||||
await updateActiveProdView(null);
|
||||
setColumns([]);
|
||||
setState(defaultState);
|
||||
}
|
||||
} else {
|
||||
// Revert back to the active view and load its columns and state
|
||||
const activeConfig = bodyshop.production_config.find((pc) => pc.name === activeView);
|
||||
if (activeConfig) {
|
||||
await updateActiveProdView(activeView);
|
||||
setColumns(
|
||||
activeConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state: ensureDefaultState(state),
|
||||
refetch,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
setState(ensureDefaultState(activeConfig.columns.tableState));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveConfig = async (values) => {
|
||||
logImEXEvent("production_save_config");
|
||||
setLoading(true);
|
||||
|
||||
const profileName = isAddingNewProfile ? values.name : activeView;
|
||||
|
||||
const result = await updateShop({
|
||||
variables: {
|
||||
id: bodyshop.id,
|
||||
shop: {
|
||||
production_config: [
|
||||
...bodyshop.production_config.filter((b) => b.name !== profileName),
|
||||
{
|
||||
name: profileName,
|
||||
columns: {
|
||||
columnKeys: columns.map((i) => ({ key: i.key, width: i.width })),
|
||||
tableState: ensureDefaultState(state)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification.success({ message: t("bodyshop.successes.save") });
|
||||
if (isAddingNewProfile) {
|
||||
await updateActiveProdView(profileName);
|
||||
}
|
||||
if (onSave && isFunction(onSave)) {
|
||||
onSave();
|
||||
}
|
||||
setHasUnsavedChanges(false);
|
||||
} else {
|
||||
notification.error({
|
||||
message: t("bodyshop.errors.saving", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
form.resetFields();
|
||||
setOpen(false);
|
||||
setLoading(false);
|
||||
setIsAddingNewProfile(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const validateAndSetDefaultView = () => {
|
||||
const configExists = bodyshop.production_config.some((pc) => pc.name === activeView);
|
||||
|
||||
if (!configExists) {
|
||||
// If the default view doesn't exist, revert to the main profile
|
||||
const mainProfileConfig = bodyshop.production_config.find(
|
||||
(pc) => pc.name === t("production.constants.main_profile")
|
||||
);
|
||||
|
||||
if (mainProfileConfig) {
|
||||
setActiveView(t("production.constants.main_profile"));
|
||||
|
||||
setColumns(
|
||||
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
refetch,
|
||||
technician,
|
||||
state: ensureDefaultState(state),
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
setState(ensureDefaultState(mainProfileConfig.columns.tableState));
|
||||
|
||||
updateActiveProdView(t("production.constants.main_profile"));
|
||||
}
|
||||
} else {
|
||||
// If the default view exists, set it as active
|
||||
setActiveView(activeView);
|
||||
}
|
||||
};
|
||||
|
||||
if (!bodyshop.production_config || bodyshop.production_config.length === 0) {
|
||||
createDefaultView().catch((e) => {
|
||||
console.error("Something went wrong saving the production list view Config.");
|
||||
});
|
||||
} else {
|
||||
validateAndSetDefaultView();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeView, bodyshop.production_config]);
|
||||
|
||||
const popMenu = (
|
||||
<div>
|
||||
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
||||
{isAddingNewProfile && (
|
||||
<Form.Item
|
||||
label={t("production.labels.viewname")}
|
||||
name="name"
|
||||
rules={[
|
||||
{ required: true, message: t("production.errors.name_required") },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const nameExists = bodyshop.production_config.some((pc) => pc.name === value);
|
||||
if (nameExists) {
|
||||
return Promise.reject(new Error(t("production.errors.name_exists")));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => form.submit()}
|
||||
loading={loading}
|
||||
disabled={form.getFieldsError().some(({ errors }) => errors.length)}
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
{!isAddingNewProfile && (
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setIsAddingNewProfile(true);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t("general.actions.saveas")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsAddingNewProfile(false);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Button loading={loading} onClick={() => setOpen(true)} disabled={isAddingNewProfile || !hasUnsavedChanges}>
|
||||
{t("production.actions.saveconfig")}
|
||||
</Button>
|
||||
<Popover open={open} content={popMenu} placement="bottom">
|
||||
<Select
|
||||
style={{
|
||||
minWidth: "150px"
|
||||
}}
|
||||
onSelect={handleSelect}
|
||||
placeholder={t("production.labels.selectview")}
|
||||
optionLabelProp="label"
|
||||
popupMatchSelectWidth={false}
|
||||
value={activeView}
|
||||
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||
>
|
||||
{bodyshop.production_config
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
a.name === t("production.constants.main_profile")
|
||||
? -1
|
||||
: b.name === t("production.constants.main_profile")
|
||||
? 1
|
||||
: 0
|
||||
) //
|
||||
.map((config) => (
|
||||
<Select.Option key={config.name} label={config.name}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
maxWidth: "80%",
|
||||
marginRight: "1rem",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
{config.name}
|
||||
</span>
|
||||
{config.name !== t("production.constants.main_profile") && (
|
||||
<Popconfirm
|
||||
placement="right"
|
||||
title={t("general.labels.areyousure")}
|
||||
onConfirm={() => handleTrash(config.name)}
|
||||
onCancel={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
<Select.Option key="add_new" label={t("production.labels.addnewprofile")}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<PlusOutlined style={{ marginRight: "0.5rem" }} />
|
||||
{t("production.labels.addnewprofile")}
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Popover>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Popconfirm, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) {
|
||||
const { t } = useTranslation();
|
||||
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
||||
|
||||
const {
|
||||
treatments: { Enhanced_Payroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const handleSelect = async (value, option) => {
|
||||
setColumns(
|
||||
bodyshop.production_config
|
||||
.filter((pc) => pc.name === value)[0]
|
||||
.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
refetch,
|
||||
technician,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
setState(bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState);
|
||||
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
|
||||
if (assoc) {
|
||||
await updateDefaultProdView({
|
||||
variables: { assocId: assoc.id, view: value },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
id: cache.identify(bodyshop),
|
||||
fields: {
|
||||
associations(existingAssociations, { readField }) {
|
||||
return existingAssociations.map((a) => {
|
||||
if (a.useremail !== currentUser.email) return a;
|
||||
return { ...a, default_prod_list_view: value };
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrash = async (name) => {
|
||||
await updateShop({
|
||||
variables: {
|
||||
id: bodyshop.id,
|
||||
shop: {
|
||||
production_config: bodyshop.production_config.filter((b) => b.name !== name)
|
||||
}
|
||||
},
|
||||
awaitRefetchQueries: true
|
||||
});
|
||||
|
||||
setColumns(
|
||||
bodyshop.production_config[0].columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state,
|
||||
refetch,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setState(bodyshop.production_config[0].columns.tableState);
|
||||
};
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
|
||||
const defaultView = assoc && assoc.default_prod_list_view;
|
||||
return (
|
||||
<div style={{ width: "10rem" }}>
|
||||
<Select
|
||||
onSelect={handleSelect}
|
||||
placeholder={t("production.labels.selectview")}
|
||||
optionLabelProp="label"
|
||||
popupMatchSelectWidth={false}
|
||||
defaultValue={defaultView}
|
||||
>
|
||||
{bodyshop.production_config.map((config) => (
|
||||
<Select.Option key={config.name} label={config.name}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
maxWidth: "80%",
|
||||
marginRight: "1rem",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
{config.name}
|
||||
</span>
|
||||
|
||||
<Popconfirm
|
||||
placement="right"
|
||||
title={t("general.labels.areyousure")}
|
||||
onConfirm={() => handleTrash(config.name)}
|
||||
>
|
||||
<DeleteOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(ProductionListTable);
|
||||
@@ -1,8 +1,6 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import ReactDragListView from "react-drag-listview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -12,10 +10,14 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
|
||||
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
|
||||
import ProductionListPrint from "./production-list-print.component";
|
||||
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
|
||||
import ResizeableTitle from "./production-list-table.resizeable.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import Prompt from "../../utils/prompt.js";
|
||||
import _ from "lodash";
|
||||
import AlertComponent from "../alert/alert.component.jsx";
|
||||
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -25,6 +27,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
|
||||
const {
|
||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||
@@ -35,10 +38,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
});
|
||||
|
||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||
|
||||
const defaultView = assoc && assoc.default_prod_list_view;
|
||||
|
||||
const [state, setState] = useState(
|
||||
const initialStateRef = useRef(
|
||||
(bodyshop.production_config &&
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||
bodyshop.production_config[0]?.columns.tableState || {
|
||||
@@ -47,80 +49,102 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
}
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const matchingColumnConfig = useMemo(() => {
|
||||
return bodyshop.production_config.find((p) => p.name === defaultView);
|
||||
}, [bodyshop.production_config, defaultView]);
|
||||
|
||||
const [columns, setColumns] = useState(
|
||||
(state &&
|
||||
matchingColumnConfig &&
|
||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
refetch,
|
||||
technician,
|
||||
state,
|
||||
data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width ?? 100
|
||||
};
|
||||
})) ||
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns =
|
||||
(state &&
|
||||
matchingColumnConfig &&
|
||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
||||
const initialColumnsRef = useRef(
|
||||
(initialStateRef.current &&
|
||||
bodyshop.production_config
|
||||
.find((p) => p.name === defaultView)
|
||||
?.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
technician,
|
||||
refetch,
|
||||
state,
|
||||
data: data,
|
||||
technician,
|
||||
state: initialStateRef.current,
|
||||
data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width ?? 100
|
||||
};
|
||||
})) ||
|
||||
[];
|
||||
setColumns(newColumns);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
const [state, setState] = useState(initialStateRef.current);
|
||||
const [columns, setColumns] = useState(initialColumnsRef.current);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const matchingColumnConfig = useMemo(() => {
|
||||
return bodyshop.production_config.find((p) => p.name === defaultView);
|
||||
}, [bodyshop.production_config, defaultView]);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns =
|
||||
matchingColumnConfig?.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
technician,
|
||||
refetch,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width ?? 100
|
||||
};
|
||||
}) || [];
|
||||
|
||||
// Only update columns if they haven't been manually changed by the user
|
||||
if (_.isEqual(initialColumnsRef.current, columns)) {
|
||||
setColumns(newColumns);
|
||||
}
|
||||
}, [
|
||||
//state,
|
||||
matchingColumnConfig,
|
||||
bodyshop,
|
||||
technician,
|
||||
data
|
||||
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
|
||||
data,
|
||||
Enhanced_Payroll,
|
||||
Production_List_Status_Colors,
|
||||
refetch,
|
||||
state,
|
||||
columns
|
||||
]);
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({
|
||||
const newState = {
|
||||
...state,
|
||||
filteredInfo: filters,
|
||||
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
|
||||
});
|
||||
};
|
||||
if (!_.isEqual(newState, state)) {
|
||||
setState(newState);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = (fromIndex, toIndex) => {
|
||||
const columnsCopy = columns.slice();
|
||||
const item = columnsCopy.splice(fromIndex, 1)[0];
|
||||
columnsCopy.splice(toIndex, 0, item);
|
||||
setColumns(columnsCopy);
|
||||
if (fromIndex === toIndex) return;
|
||||
|
||||
const columnsCopy = [...columns];
|
||||
const [movedItem] = columnsCopy.splice(fromIndex, 1);
|
||||
columnsCopy.splice(toIndex, 0, movedItem);
|
||||
|
||||
if (!_.isEqual(columnsCopy, columns)) {
|
||||
setColumns(columnsCopy);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const removeColumn = (e) => {
|
||||
const { key } = e;
|
||||
const newColumns = columns.filter((i) => i.key !== key);
|
||||
setColumns(newColumns);
|
||||
|
||||
if (!_.isEqual(newColumns, columns)) {
|
||||
setColumns(newColumns);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResize =
|
||||
@@ -131,9 +155,21 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
...nextColumns[index],
|
||||
width: size.width
|
||||
};
|
||||
setColumns(nextColumns);
|
||||
|
||||
if (!_.isEqual(nextColumns, columns)) {
|
||||
setColumns(nextColumns);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const addColumn = (newColumn) => {
|
||||
const updatedColumns = [...columns, newColumn];
|
||||
if (!_.isEqual(updatedColumns, columns)) {
|
||||
setColumns(updatedColumns);
|
||||
setHasUnsavedChanges(true);
|
||||
}
|
||||
};
|
||||
|
||||
const headerItem = (col) => {
|
||||
const menu = {
|
||||
onClick: removeColumn,
|
||||
@@ -152,29 +188,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
);
|
||||
};
|
||||
|
||||
const dataSource =
|
||||
searchText === ""
|
||||
? data
|
||||
: data.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
const resetChanges = () => {
|
||||
setState(initialStateRef.current);
|
||||
setColumns(initialColumnsRef.current);
|
||||
setHasUnsavedChanges(false);
|
||||
};
|
||||
|
||||
// const handleSelectRecord = (record) => {
|
||||
// if (selected !== record.id) {
|
||||
// setSelected(record.id);
|
||||
// } else {
|
||||
// setSelected(null);
|
||||
// }
|
||||
// };
|
||||
const filterData = (item, searchText) => {
|
||||
const fieldsToSearch = [
|
||||
item.ro_number,
|
||||
item.ownr_co_nm,
|
||||
item.ownr_fn,
|
||||
item.ownr_ln,
|
||||
item.status,
|
||||
item.ins_co_nm,
|
||||
item.clm_no,
|
||||
item.v_model_desc,
|
||||
item.v_make_desc
|
||||
];
|
||||
|
||||
return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
|
||||
};
|
||||
|
||||
const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText));
|
||||
|
||||
if (!!!columns) return <div>No columns found.</div>;
|
||||
|
||||
@@ -186,8 +222,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
.toFixed(1);
|
||||
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
||||
const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Prompt when={hasUnsavedChanges} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
|
||||
{hasUnsavedChanges && (
|
||||
<AlertComponent
|
||||
type="warning"
|
||||
message={
|
||||
<div>
|
||||
<span>{t("general.messages.unsavedchanges")} </span>
|
||||
<span
|
||||
onClick={resetChanges}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
textDecoration: "underline"
|
||||
}}
|
||||
>
|
||||
{t("general.actions.reset")}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
title={
|
||||
<Space>
|
||||
@@ -199,20 +256,37 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch && refetch()}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
refetch && refetch();
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<ProductionListColumnsAdd columnState={[columns, setColumns]} tableState={state} data={data} />
|
||||
<ProductionListSaveConfigButton columns={columns} tableState={state} />
|
||||
|
||||
<ProductionListTableViewSelect
|
||||
state={state}
|
||||
setState={setState}
|
||||
setColumns={setColumns}
|
||||
refetch={refetch}
|
||||
<ProductionListColumnsAdd
|
||||
columnState={[columns, setColumns]}
|
||||
tableState={state}
|
||||
data={data}
|
||||
onColumnAdd={addColumn}
|
||||
/>
|
||||
|
||||
<ProductionListConfigManager
|
||||
columns={columns}
|
||||
setColumns={setColumns}
|
||||
state={state}
|
||||
setState={setState}
|
||||
refetch={refetch}
|
||||
data={data}
|
||||
bodyshop={bodyshop}
|
||||
technician={technician}
|
||||
currentUser={currentUser}
|
||||
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||
hasUnsavedChanges={hasUnsavedChanges}
|
||||
onSave={() => {
|
||||
setHasUnsavedChanges(false);
|
||||
initialStateRef.current = state;
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
placeholder={t("general.labels.search")}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import { generateInternalReflections } from "./report-center-modal-utils";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
|
||||
return (
|
||||
@@ -196,7 +196,8 @@ function FiltersSection({ filters, form, bodyshop }) {
|
||||
// We have a type of date, so we will use a date picker
|
||||
if (type === "date") {
|
||||
return (
|
||||
<FormDatePicker
|
||||
<DateTimePicker
|
||||
isDateOnly
|
||||
disabled={!operator}
|
||||
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@ import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ScoreboardEntryEdit({ entry }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -52,7 +52,7 @@ export default function ScoreboardEntryEdit({ entry }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_VACATION } from "../../graphql/employees.queries";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ShopEmployeeAddVacation({ employee }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -64,7 +64,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employees.fields.vacation.end")}
|
||||
@@ -90,7 +90,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
||||
})
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
|
||||
@@ -21,12 +21,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
||||
import queryString from "query-string";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -266,10 +266,10 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employees.fields.user_email")}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { connect } from "react-redux";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
||||
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -246,7 +245,8 @@ export function TaskUpsertModalComponent({
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
||||
<FormDatePicker
|
||||
<DateTimePicker
|
||||
isDateOnly
|
||||
onlyFuture
|
||||
format="MM/DD/YYYY"
|
||||
presets={generatePresets(selectedJobDetails)}
|
||||
@@ -278,12 +278,7 @@ export function TaskUpsertModalComponent({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerEnhanced
|
||||
onlyFuture
|
||||
showTime
|
||||
minuteStep={15}
|
||||
presets={generatePresets(selectedJobDetails)}
|
||||
/>
|
||||
<DateTimePicker onlyFuture presets={generatePresets(selectedJobDetails)} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -7,9 +7,9 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -71,7 +71,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import { createStructuredSelector } from "reselect";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
@@ -60,8 +60,8 @@ export function TimeTicketModalComponent({
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||
: item.cost_center}
|
||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||
: item.cost_center}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -111,7 +111,7 @@ export function TimeTicketModalComponent({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="employeeid"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AlertOutlined } from "@ant-design/icons";
|
||||
import { Alert, Button, Col, Row, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -12,39 +12,42 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
updateAvailable: selectUpdateAvailable
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
// setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
|
||||
const intervalMS = 10 * 60 * 1000;
|
||||
|
||||
export function UpdateAlert({ updateAvailable }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
offlineReady: [
|
||||
offlineReady //setOfflineReady
|
||||
],
|
||||
needRefresh: [
|
||||
needRefresh //setNeedRefresh
|
||||
],
|
||||
offlineReady: [offlineReady],
|
||||
needRefresh: [needRefresh],
|
||||
updateServiceWorker
|
||||
} = useRegisterSW({
|
||||
onRegistered(r) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
console.log("SW Registered: " + r);
|
||||
r &&
|
||||
setInterval(() => {
|
||||
r.update();
|
||||
}, intervalMS);
|
||||
console.log("SW Registered:", r);
|
||||
if (r) {
|
||||
setInterval(
|
||||
() => {
|
||||
r.update();
|
||||
},
|
||||
10 * 60 * 1000
|
||||
);
|
||||
}
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.log("SW registration error", error);
|
||||
console.error("SW registration error", error);
|
||||
}
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
|
||||
useEffect(() => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
|
||||
}
|
||||
}, [needRefresh, offlineReady]);
|
||||
|
||||
if (!needRefresh) return null;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
message={t("general.messages.newversiontitle", {
|
||||
@@ -69,19 +72,10 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
</Col>
|
||||
<Col sm={24} md={8} lg={6}>
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
window.open("https://imex-online.noticeable.news/", "_blank");
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
|
||||
{i18n.t("general.actions.viewreleasenotes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
updateServiceWorker(true);
|
||||
}}
|
||||
>
|
||||
<Button type="primary" onClick={() => updateServiceWorker(true)}>
|
||||
{i18n.t("general.actions.refresh")}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -93,3 +87,5 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Form, Input } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function VehicleDetailFormComponent({ form, loading }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -102,7 +102,7 @@ export default function VehicleDetailFormComponent({ form, loading }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
|
||||
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
||||
query QUERY_ALL_ACTIVE_JOBS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [jobs_order_by!]
|
||||
@@ -2465,6 +2465,11 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||
query QUERY_JOBS_IN_PRODUCTION {
|
||||
jobs(where: { inproduction: { _eq: true } }) {
|
||||
tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
id
|
||||
updated_at
|
||||
comment
|
||||
|
||||
@@ -12,7 +12,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
|
||||
//import FooterComponent from "../../components/footer/footer.component";
|
||||
//Component Imports
|
||||
import * as Sentry from "@sentry/react";
|
||||
import Joyride from "react-joyride";
|
||||
import TestComponent from "../../components/_test/test.page";
|
||||
import HeaderContainer from "../../components/header/header.container";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
@@ -23,8 +22,6 @@ import { requestForToken } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
||||
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import { setJoyRideFinished } from "../../redux/application/application.actions.js";
|
||||
import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import "./manage.page.styles.scss";
|
||||
|
||||
@@ -105,16 +102,12 @@ const { Content, Footer } = Layout;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
conflict: selectInstanceConflict,
|
||||
bodyshop: selectBodyshop,
|
||||
enableJoyRide: selectEnableJoyRide,
|
||||
joyRideSteps: selectJoyRideSteps
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
|
||||
export function Manage({ conflict, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
|
||||
@@ -578,26 +571,11 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
||||
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
|
||||
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
||||
<UpdateAlert />
|
||||
<HeaderContainer />
|
||||
<Content className="content-container">
|
||||
<Joyride
|
||||
debug
|
||||
run={enableJoyRide}
|
||||
steps={joyRideSteps}
|
||||
continuous
|
||||
hideCloseButton
|
||||
scrollToFirstStep
|
||||
showProgress
|
||||
showSkipButton
|
||||
callback={(props) => {
|
||||
if (props.action === "reset") {
|
||||
setJoyRideFinished();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<PartnerPingComponent />
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
|
||||
{PageContent}
|
||||
|
||||
@@ -67,11 +67,3 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable
|
||||
});
|
||||
export const setJoyRideSteps = (steps) => ({
|
||||
type: ApplicationActionTypes.SET_JOYRIDE_STEPS,
|
||||
payload: steps
|
||||
});
|
||||
export const setJoyRideFinished = () => ({
|
||||
type: ApplicationActionTypes.SET_JOYRIDE_FINISHED
|
||||
//payload: isUpdateAvailable,
|
||||
});
|
||||
|
||||
@@ -14,9 +14,7 @@ const INITIAL_STATE = {
|
||||
error: null
|
||||
},
|
||||
jobReadOnly: false,
|
||||
partnerVersion: null,
|
||||
enableJoyRide: false,
|
||||
joyRideSteps: []
|
||||
partnerVersion: null
|
||||
};
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -89,12 +87,6 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
|
||||
return { ...state, problemJobs: action.payload };
|
||||
}
|
||||
case ApplicationActionTypes.SET_JOYRIDE_STEPS: {
|
||||
return { ...state, enableJoyRide: true, joyRideSteps: action.payload };
|
||||
}
|
||||
case ApplicationActionTypes.SET_JOYRIDE_FINISHED: {
|
||||
return { ...state, enableJoyRide: false, joyRideSteps: [] };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -22,5 +22,3 @@ export const selectJobReadOnly = createSelector([selectApplication], (applicatio
|
||||
export const selectOnline = createSelector([selectApplication], (application) => application.online);
|
||||
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
|
||||
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
|
||||
export const selectEnableJoyRide = createSelector([selectApplication], (application) => application.enableJoyRide);
|
||||
export const selectJoyRideSteps = createSelector([selectApplication], (application) => application.joyRideSteps);
|
||||
|
||||
@@ -12,8 +12,6 @@ const ApplicationActionTypes = {
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
|
||||
SET_JOYRIDE_STEPS: "SET_JOYRIDE_STEPS",
|
||||
SET_JOYRIDE_FINISHED: "SET_JOYRIDE_FINISHED"
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -271,7 +271,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"loading": "Unable to load shop details. Please call technical support.",
|
||||
"saving": "Error encountered while saving. {{message}}"
|
||||
"saving": "Error encountered while saving. {{message}}",
|
||||
"creatingdefaultview": "Error creating default view."
|
||||
},
|
||||
"fields": {
|
||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||
@@ -699,7 +700,10 @@
|
||||
"workingdays": "Working Days"
|
||||
},
|
||||
"successes": {
|
||||
"save": "Shop configuration saved successfully. "
|
||||
"save": "Shop configuration saved successfully. ",
|
||||
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?",
|
||||
"areyousure": "Are you sure you want to continue?",
|
||||
"defaultviewcreated": "Default view created successfully."
|
||||
},
|
||||
"validation": {
|
||||
"centermustexist": "The chosen responsibility center does not exist.",
|
||||
@@ -1160,7 +1164,9 @@
|
||||
"submit": "Submit",
|
||||
"tryagain": "Try Again",
|
||||
"view": "View",
|
||||
"viewreleasenotes": "See What's Changed"
|
||||
"viewreleasenotes": "See What's Changed",
|
||||
"remove_alert": "Are you sure you want to dismiss the alert?",
|
||||
"saveas": "Save As"
|
||||
},
|
||||
"errors": {
|
||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||
@@ -1175,6 +1181,7 @@
|
||||
"vehicle": "Vehicle"
|
||||
},
|
||||
"labels": {
|
||||
"unsavedchanges": "Unsaved changes.",
|
||||
"actions": "Actions",
|
||||
"areyousure": "Are you sure?",
|
||||
"barcode": "Barcode",
|
||||
@@ -1182,6 +1189,8 @@
|
||||
"clear": "Clear",
|
||||
"confirmpassword": "Confirm Password",
|
||||
"created_at": "Created At",
|
||||
"date": "Select Date",
|
||||
"datetime": "Select Date & Time",
|
||||
"email": "Email",
|
||||
"errors": "Errors",
|
||||
"excel": "Excel",
|
||||
@@ -2730,6 +2739,9 @@
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"constants": {
|
||||
"main_profile": "Default"
|
||||
},
|
||||
"options": {
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
@@ -2757,7 +2769,9 @@
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_jobs_on_board": "Jobs on Board"
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -2777,7 +2791,9 @@
|
||||
"errors": {
|
||||
"boardupdate": "Error encountered updating Job. {{message}}",
|
||||
"removing": "Error removing from production board. {{error}}",
|
||||
"settings": "Error saving board settings: {{error}}"
|
||||
"settings": "Error saving board settings: {{error}}",
|
||||
"name_exists": "A Profile with this name already exists. Please choose a different name.",
|
||||
"name_required": "Profile name is required."
|
||||
},
|
||||
"labels": {
|
||||
"kiosk_mode": "Kiosk Mode",
|
||||
@@ -2792,6 +2808,7 @@
|
||||
"model_info": "Vehicle Info",
|
||||
"actual_in": "Actual In",
|
||||
"alert": "Alert",
|
||||
"tasks": "Tasks",
|
||||
"alertoff": "Remove alert from Job",
|
||||
"alerton": "Add alert to Job",
|
||||
"ats": "Alternative Transportation",
|
||||
@@ -2829,7 +2846,9 @@
|
||||
"sublets": "Sublets",
|
||||
"totalhours": "Total Hrs ",
|
||||
"touchtime": "T/T",
|
||||
"viewname": "View Name"
|
||||
"viewname": "View Name",
|
||||
"alerts": "Alerts",
|
||||
"addnewprofile": "Add New Profile"
|
||||
},
|
||||
"successes": {
|
||||
"removed": "Job removed from production."
|
||||
@@ -2845,6 +2864,9 @@
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"tasks": "Tasks",
|
||||
"hours": "Hours",
|
||||
"currency_symbol": "$",
|
||||
"jobs": "Jobs"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -271,7 +271,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
||||
"saving": ""
|
||||
"saving": "",
|
||||
"creatingdefaultview": ""
|
||||
},
|
||||
"fields": {
|
||||
"ReceivableCustomField": "",
|
||||
@@ -699,7 +700,10 @@
|
||||
"workingdays": ""
|
||||
},
|
||||
"successes": {
|
||||
"save": ""
|
||||
"save": "",
|
||||
"unsavedchanges": "",
|
||||
"areyousure": "",
|
||||
"defaultviewcreated": ""
|
||||
},
|
||||
"validation": {
|
||||
"centermustexist": "",
|
||||
@@ -1160,7 +1164,9 @@
|
||||
"submit": "",
|
||||
"tryagain": "",
|
||||
"view": "",
|
||||
"viewreleasenotes": ""
|
||||
"viewreleasenotes": "",
|
||||
"remove_alert": "",
|
||||
"saveas": ""
|
||||
},
|
||||
"errors": {
|
||||
"fcm": "",
|
||||
@@ -1175,6 +1181,7 @@
|
||||
"vehicle": ""
|
||||
},
|
||||
"labels": {
|
||||
"unsavedchanges": "",
|
||||
"actions": "actes",
|
||||
"areyousure": "",
|
||||
"barcode": "code à barre",
|
||||
@@ -1182,6 +1189,8 @@
|
||||
"clear": "",
|
||||
"confirmpassword": "",
|
||||
"created_at": "",
|
||||
"date": "",
|
||||
"datetime": "",
|
||||
"email": "",
|
||||
"errors": "",
|
||||
"excel": "",
|
||||
@@ -2730,6 +2739,9 @@
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"constants": {
|
||||
"main_profile": ""
|
||||
},
|
||||
"options": {
|
||||
"small": "",
|
||||
"medium": "",
|
||||
@@ -2757,7 +2769,9 @@
|
||||
"total_lab_on_board": "",
|
||||
"total_lar_on_board": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_jobs_on_board": ""
|
||||
"total_jobs_on_board": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": ""
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -2777,7 +2791,9 @@
|
||||
"errors": {
|
||||
"boardupdate": "",
|
||||
"removing": "",
|
||||
"settings": ""
|
||||
"settings": "",
|
||||
"name_exists": "",
|
||||
"name_required": ""
|
||||
},
|
||||
"labels": {
|
||||
"kiosk_mode": "",
|
||||
@@ -2792,6 +2808,7 @@
|
||||
"model_info": "",
|
||||
"actual_in": "",
|
||||
"alert": "",
|
||||
"tasks": "",
|
||||
"alertoff": "",
|
||||
"alerton": "",
|
||||
"ats": "",
|
||||
@@ -2829,7 +2846,9 @@
|
||||
"sublets": "",
|
||||
"totalhours": "",
|
||||
"touchtime": "",
|
||||
"viewname": ""
|
||||
"viewname": "",
|
||||
"alerts": "",
|
||||
"addnewprofile": ""
|
||||
},
|
||||
"successes": {
|
||||
"removed": ""
|
||||
@@ -2845,6 +2864,9 @@
|
||||
"total_lar_on_board": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_jobs_on_board": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks": "",
|
||||
"hours": "",
|
||||
"currency_symbol": "",
|
||||
"jobs": ""
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
export const BETA_KEY = "betaSwitchImex";
|
||||
|
||||
export const checkBeta = () => {
|
||||
const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY));
|
||||
return cookie ? cookie.split("=")[1] === "true" : false;
|
||||
};
|
||||
|
||||
export const setBeta = (value) => {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
|
||||
};
|
||||
|
||||
export const handleBeta = () => {
|
||||
// If the current host name does not start with beta or test, then we don't need to do anything.
|
||||
if (window.location.hostname.startsWith("localhost")) {
|
||||
console.log("Not on beta or test, so no need to handle beta.");
|
||||
return;
|
||||
}
|
||||
|
||||
const isBeta = checkBeta();
|
||||
|
||||
const currentHostName = window.location.hostname;
|
||||
|
||||
// Beta is enabled, but the current host name does start with beta.
|
||||
if (isBeta && !currentHostName.startsWith("beta")) {
|
||||
const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
|
||||
// Beta is not enabled, but the current host name does start with beta.
|
||||
else if (!isBeta && currentHostName.startsWith("beta")) {
|
||||
const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
};
|
||||
export default handleBeta;
|
||||
@@ -11,26 +11,37 @@ export const setBeta = (value) => {
|
||||
};
|
||||
|
||||
export const handleBeta = () => {
|
||||
// If the current host name does not start with beta or test, then we don't need to do anything.
|
||||
if (window.location.hostname.startsWith("localhost")) {
|
||||
console.log("Not on beta or test, so no need to handle beta.");
|
||||
return;
|
||||
}
|
||||
|
||||
const isBeta = checkBeta();
|
||||
|
||||
const currentHostName = window.location.hostname;
|
||||
|
||||
// Beta is enabled, but the current host name does start with beta.
|
||||
if (isBeta && !currentHostName.startsWith("beta")) {
|
||||
const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
// Determine if the host name starts with "beta" or "www.beta"
|
||||
const isBetaHost = currentHostName.startsWith("beta.");
|
||||
const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
|
||||
|
||||
// Beta is not enabled, but the current host name does start with beta.
|
||||
else if (!isBeta && currentHostName.startsWith("beta")) {
|
||||
const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
if (isBeta) {
|
||||
// If beta is on and we are not on a beta domain, redirect to the beta version
|
||||
if (!isBetaHost && !isBetaHostWithWWW) {
|
||||
const newHostName = currentHostName.startsWith("www.")
|
||||
? `www.beta.${currentHostName.replace(/^www\./, "")}`
|
||||
: `beta.${currentHostName}`;
|
||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
// Otherwise, if beta is on and we're already on a beta domain, stay there
|
||||
} else {
|
||||
// If beta is off and we are on a beta domain, redirect to the non-beta version
|
||||
if (isBetaHost || isBetaHostWithWWW) {
|
||||
const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
|
||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
// Otherwise, if beta is off and we're not on a beta domain, stay there
|
||||
}
|
||||
};
|
||||
|
||||
export default handleBeta;
|
||||
|
||||
@@ -6,8 +6,6 @@ import * as url from "url";
|
||||
import { defineConfig } from "vite";
|
||||
import { ViteEjsPlugin } from "vite-plugin-ejs";
|
||||
import eslint from "vite-plugin-eslint";
|
||||
|
||||
//import CompressionPlugin from 'vite-plugin-compression';
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
|
||||
|
||||
@@ -16,7 +14,8 @@ process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
|
||||
});
|
||||
|
||||
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
|
||||
function reactVirtualized() {
|
||||
|
||||
function reactVirtualizedFix() {
|
||||
return {
|
||||
name: "flat:react-virtualized",
|
||||
configResolved: async () => {
|
||||
@@ -37,10 +36,7 @@ function reactVirtualized() {
|
||||
export default defineConfig({
|
||||
base: "/",
|
||||
plugins: [
|
||||
ViteEjsPlugin((viteConfig) => {
|
||||
// viteConfig is the current Vite resolved config
|
||||
return { env: viteConfig.env };
|
||||
}),
|
||||
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
|
||||
VitePWA({
|
||||
injectRegister: "auto",
|
||||
registerType: "prompt",
|
||||
@@ -60,7 +56,6 @@ export default defineConfig({
|
||||
description: "The ultimate bodyshop management system.",
|
||||
icons: [
|
||||
{
|
||||
//TODO:AIO Ensure that these are correct for Rome and IO.
|
||||
src: InstanceRenderManager({
|
||||
instance: process.env.VITE_APP_INSTANCE,
|
||||
imex: "favicon.png",
|
||||
@@ -101,7 +96,7 @@ export default defineConfig({
|
||||
gcm_sender_id: "103953800507"
|
||||
}
|
||||
}),
|
||||
reactVirtualized(),
|
||||
reactVirtualizedFix(),
|
||||
react(),
|
||||
eslint()
|
||||
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- UPDATE "public"."masterdata"
|
||||
-- SET value = jsonb_set(
|
||||
-- value::jsonb,
|
||||
-- '{OP20}',
|
||||
-- '{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb,
|
||||
-- true
|
||||
-- );
|
||||
@@ -0,0 +1,7 @@
|
||||
UPDATE "public"."masterdata"
|
||||
SET value = jsonb_set(
|
||||
value::jsonb,
|
||||
'{OP20}',
|
||||
'{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb,
|
||||
true
|
||||
);
|
||||
613
package-lock.json
generated
613
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -19,18 +19,18 @@
|
||||
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.616.0",
|
||||
"@aws-sdk/client-ses": "^3.616.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.616.0",
|
||||
"@opensearch-project/opensearch": "^2.10.0",
|
||||
"aws4": "^1.13.0",
|
||||
"axios": "^1.7.2",
|
||||
"@aws-sdk/client-secrets-manager": "^3.629.0",
|
||||
"@aws-sdk/client-ses": "^3.629.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.629.0",
|
||||
"@opensearch-project/opensearch": "^2.11.0",
|
||||
"aws4": "^1.13.1",
|
||||
"axios": "^1.7.4",
|
||||
"better-queue": "^3.8.12",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"canvas": "^2.11.2",
|
||||
"chart.js": "^4.4.3",
|
||||
"cloudinary": "^2.3.0",
|
||||
"cloudinary": "^2.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "2.8.5",
|
||||
@@ -38,24 +38,24 @@
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"firebase-admin": "^12.2.0",
|
||||
"firebase-admin": "^12.3.1",
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^4.0.2",
|
||||
"intuit-oauth": "^4.1.2",
|
||||
"json-2-csv": "^5.5.4",
|
||||
"json-2-csv": "^5.5.5",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-mailjet": "^6.0.5",
|
||||
"node-persist": "^4.0.2",
|
||||
"node-persist": "^4.0.3",
|
||||
"nodemailer": "^6.9.14",
|
||||
"phone": "^3.1.49",
|
||||
"recursive-diff": "^1.0.9",
|
||||
"rimraf": "^6.0.1",
|
||||
"soap": "^1.1.0",
|
||||
"soap": "^1.1.1",
|
||||
"socket.io": "^4.7.5",
|
||||
"ssh2-sftp-client": "^10.0.3",
|
||||
"twilio": "^4.23.0",
|
||||
|
||||
@@ -30,8 +30,14 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
if (jobline.db_ref === "936007") {
|
||||
hasMashLine = true;
|
||||
}
|
||||
|
||||
//Check if the line is a Towing Line and flag as such.
|
||||
let isTowingLine = false;
|
||||
if (jobline.db_ref === "936001" && jobline.line_desc.includes("Towing")) {
|
||||
isTowingLine = true;
|
||||
}
|
||||
//Parts Lines Mappings.
|
||||
if (jobline.profitcenter_part) {
|
||||
if (!isTowingLine && jobline.profitcenter_part) {
|
||||
//TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify.
|
||||
const discountAmount =
|
||||
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
|
||||
|
||||
@@ -273,7 +273,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
|
||||
return result && result.json && result.json.Bill;
|
||||
} catch (error) {
|
||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
|
||||
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
|
||||
method: "InsertBill"
|
||||
});
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user