Compare commits

...

81 Commits

Author SHA1 Message Date
Allan Carr
2a2d399a98 IO-2894 Null check
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-09-03 08:54:06 -07:00
Allan Carr
f3f16b78d5 IO-2894 Prettier code
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-29 15:00:21 -07:00
Allan Carr
1e855799f8 IO-2894 Modify Shift Memo
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-29 14:59:14 -07:00
Allan Carr
6e8122849a Merged in release/2024-08-23 (pull request #1665)
IO-2890 Update Time
2024-08-24 06:57:41 +00:00
Allan Carr
932979d5fb Merged in feature/IO-2890-Kaizen-Datapump-Cron (pull request #1663)
IO-2890 Update Time
2024-08-24 06:56:34 +00:00
Allan Carr
b04ae84941 IO-2890 Update Time
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-23 23:56:37 -07:00
Dave Richer
f7ef32c58d Merged in release/2024-08-23 (pull request #1662)
Release/2024 08 23
2024-08-24 02:08:03 +00:00
Dave Richer
f7108b4b8c Merged in feature/IO-2834-Enhance-DateTime-Picker (pull request #1660)
- Final DateTimePicker update
2024-08-23 19:59:32 +00:00
Dave Richer
882038a794 - Final DateTimePicker update
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-23 15:55:54 -04:00
Allan Carr
aec23fe46b Merged in feature/IO-2890-Kaizen-Datapump-Cron (pull request #1657)
IO-2890 Kaizen Datapump Cron

Approved-by: Dave Richer
2024-08-23 15:51:58 +00:00
Allan Carr
35ac0b0c6a IO-2890 Kaizen Datapump Cron
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-22 16:36:00 -07:00
Allan Carr
2a2a0f8961 Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1655)
IO-2520 Change Logging back to default and adjust start and end to be default

Approved-by: Dave Richer
2024-08-22 20:28:01 +00:00
Allan Carr
d9902b9744 Merged in feature/IO-2895-Adjustment-to-bottom (pull request #1654)
IO-2895 Adjustment to Bottom Line
2024-08-22 20:12:46 +00:00
Allan Carr
f82478a362 IO-2895 Adjustment to Bottom Line
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-22 13:11:00 -07:00
Allan Carr
bb3d3fbe72 IO-2520 Change Logging back to default and adjust start and end to be default
Datapump will be run daily as per Sofia

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-22 11:35:15 -07:00
Allan Carr
4fa0593bb5 Merge branch 'master-AIO' into feature/IO-2520-Kaizen-Data-Pump
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-22 10:23:57 -07:00
Dave Richer
41517ca7d4 Merged in release/2024-08-23 (pull request #1653)
Release/2024 08 23
2024-08-22 14:44:29 +00:00
Dave Richer
35c9f649ad - Rollback ZOHO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-22 10:41:27 -04:00
Dave Richer
ad2f2e55a5 Merge branch 'feature/IO-2834-Enhance-DateTime-Picker' into release/2024-08-23 2024-08-21 21:09:17 -04:00
Dave Richer
41c446ddb3 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 21:04:52 -04:00
Dave Richer
7d6aa8489d - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 20:49:50 -04:00
Allan Carr
63f1e0f07c IO-2834 Placeholder Translations
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-21 15:44:00 -07:00
Dave Richer
98f4423624 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 17:55:40 -04:00
Dave Richer
1ac4cbb59f Merged in feature/IO-2886-Product-List-Profiles (pull request #1647)
Feature/IO-2886 Product List Profiles
2024-08-21 20:51:47 +00:00
Allan Carr
24ebfbfbf5 Merged in feature/IO-2888-Production-Employee-Sort-Enhancment (pull request #1646)
IO-2888 Production List Employee Sort Enhacement

Approved-by: Dave Richer
2024-08-21 20:50:51 +00:00
Dave Richer
7ff1051d3c - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 16:49:29 -04:00
Dave Richer
8af3364660 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 16:31:42 -04:00
Dave Richer
02f4677aef - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 16:22:56 -04:00
Dave Richer
11785f3b86 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 15:39:16 -04:00
Dave Richer
90532427b6 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 15:32:22 -04:00
Allan Carr
cc9979ff4b IO-2834 Split Date and DateTime formats, remove shorthand and checks
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-21 12:25:38 -07:00
Dave Richer
c89e4f1b41 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 15:14:33 -04:00
Dave Richer
c3e6d3dc48 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 15:09:13 -04:00
Allan Carr
ad1ce7b220 IO-2888 Production List Employee Sort Enhacement
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-21 11:00:59 -07:00
Dave Richer
fd4dbdfb3a Merged in feature/IO-2834-Enhance-DateTime-Picker (pull request #1644)
- enhancements / improvements / stuff
2024-08-21 17:54:39 +00:00
Dave Richer
153cf6a840 - enhancements / improvements / stuff
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 13:53:43 -04:00
Dave Richer
a567d0d6dd - enhancements / improvements / stuff
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 13:46:08 -04:00
Dave Richer
297599a45b Merged in feature/IO-2834-Enhance-DateTime-Picker (pull request #1642)
- enhancements / improvements / stuff
2024-08-21 16:51:50 +00:00
Dave Richer
678ca591c1 - enhancements / improvements / stuff
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-21 12:50:11 -04:00
Allan Carr
c19f8167e8 Merged in feature/IO-2887-Returnfrombill-Parts-Drawer (pull request #1640)
IO-2887 Null out BillData if returnfrombill is not available
2024-08-21 00:12:03 +00:00
Allan Carr
cc2d474fda IO-2887 Null out BillData if returnfrombill is not available
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 17:12:11 -07:00
Dave Richer
9058aca16e - only load chataffix in prod, no more annoying messages / alert dismissals in dev
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 14:44:28 -04:00
Dave Richer
1c186f7fa5 - fix missed FormDatePicker reference
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 14:27:28 -04:00
Dave Richer
46da3285f8 - Revert ZOHO (put back in)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 14:20:58 -04:00
Dave Richer
b419929ad7 Merged in feature/IO-2886-Product-List-Profiles (pull request #1633)
Feature/IO-2886 Product List Profiles
2024-08-20 18:13:13 +00:00
Dave Richer
8018daa2dc - Address changes to profile from call
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 14:12:19 -04:00
Dave Richer
1e7c285fef - Address changes to profile from call
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 13:46:35 -04:00
Dave Richer
0b072e6089 Merged in feature/IO-2834-Enhance-DateTime-Picker (pull request #1632)
- Enhance Date Time Picker
2024-08-20 17:27:24 +00:00
Allan Carr
4fd6203987 Merged in feature/IO-2887-Returnfrombill-Parts-Drawer (pull request #1631)
IO-2887 Returnfrombill Parts Drawer Info

Approved-by: Dave Richer
2024-08-20 17:25:41 +00:00
Dave Richer
51d264098c - Enhance Date Time Picker
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 13:24:26 -04:00
Allan Carr
680a66b156 IO-2887 Returnfrombill Parts Drawer Info
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 10:00:52 -07:00
Dave Richer
481a14e529 Merged in feature/IO-2886-Product-List-Profiles (pull request #1629)
- Improve profile handling in product list view
2024-08-20 14:45:41 +00:00
Dave Richer
f3e43334c4 - Improve profile handling in product list view
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-20 10:44:38 -04:00
Dave Richer
0054b00d01 - Improve profile handling in product list view
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-19 17:45:36 -04:00
Dave Richer
82ecb5533f Merged in release/2024-08-16 (pull request #1625)
Release/2024 08 16

Approved-by: Allan Carr
2024-08-16 23:31:33 +00:00
Allan Carr
d3289d85f1 Merged in feature/IO-2879-936001-TOWING-to-QB (pull request #1627)
IO-2879 Adjust placement of variable
2024-08-16 19:25:14 +00:00
Allan Carr
e628b1364c IO-2879 Adjust placement of variable
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-16 12:27:26 -07:00
Allan Carr
6c421c1447 Merged in feature/IO-2884-Filter-for-Production-Board-Alert (pull request #1620)
IO-2884 Production List Board Filter

Approved-by: Dave Richer
2024-08-16 16:16:53 +00:00
Dave Richer
99369e7040 Merged in feature/IO-2884-Add-Alert-Filter-To-Production-Board (pull request #1622)
- Add Alert Filter to visual production board
2024-08-16 16:15:54 +00:00
Dave Richer
01cbdf14a9 - Add Alert Filter to visual production board
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-16 12:14:52 -04:00
Allan Carr
f691aca241 IO-2884 Production List Board Filter
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-16 09:11:29 -07:00
Dave Richer
85495a11e3 Merged in feature/IO-2884-Add-Alert-Filter-To-Production-Board (pull request #1618)
Feature/IO-2884 Add Alert Filter To Production Board

Approved-by: Allan Carr
2024-08-16 15:38:29 +00:00
Dave Richer
134ce05d27 - Add Alert Filter to visual production board
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-16 11:16:20 -04:00
Dave Richer
3498fbc8f1 - Add Alert Filter to visual production board
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-16 11:14:32 -04:00
Dave Richer
f49f72ce7f - Revert ZOHO Change
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-16 10:49:59 -04:00
Dave Richer
a5e3b6ce33 Merged in feature/IO-2882-Unsaved-Listview-Changes (pull request #1612)
- Production Board List View Unsaved Changes Prompt

Approved-by: Allan Carr
2024-08-16 00:45:48 +00:00
Dave Richer
0fd945b859 - fix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 20:42:18 -04:00
Dave Richer
879eba0247 - Add Alert
- Fix 2 bugs

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 20:16:03 -04:00
Allan Carr
bb49dd77a1 Merged in feature/IO-2879-936001-TOWING-to-QB (pull request #1613)
IO-2879 936001 TOWING to QB

Approved-by: Dave Richer
2024-08-15 21:16:27 +00:00
Allan Carr
ae705322f8 IO-2879 936001 TOWING to QB
Prevent double sending 936001 Towing lines to QB

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-15 13:41:33 -07:00
Dave Richer
36d92d4060 - Production Board List View Unsaved Changes Prompt
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 14:59:47 -04:00
Dave Richer
3ce2b1ab19 - revert (put back in) zoho change
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 13:35:18 -04:00
Dave Richer
52e756a78a Merged in feature/IO-2878-Enhance-Beta-Switch (pull request #1607)
- Improve handle beta code (AIO Version)
2024-08-15 15:21:09 +00:00
Dave Richer
5a36cb7cf1 - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 11:19:59 -04:00
Allan Carr
9138f4be16 Merged in feature/IO-2856-Mark-Exported-Buttons (pull request #1601)
IO-2856 Mark Exported Button

Approved-by: Dave Richer
2024-08-14 18:40:51 +00:00
Allan Carr
df93357cec IO-2856 Mark Exported Button
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-14 11:38:35 -07:00
Dave Richer
8ab23c4ca6 Merged in feature/IO-2878-Enhance-Beta-Switch (pull request #1599)
- Improve handle beta code (AIO Version)
2024-08-14 17:40:50 +00:00
Allan Carr
f179d69420 Merged in feature/IO-2876-Opensearch-Sorters (pull request #1598)
IO-2876 Filtered Search Sorters correction

Approved-by: Dave Richer
2024-08-14 17:39:06 +00:00
Dave Richer
730a7a233d - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-14 13:29:35 -04:00
Dave Richer
84ad10fa9c Merged in feature/IO-2877-Package-Updates-Alert-Update (pull request #1597)
- Remove Joyride and its cause - Package updates (front + back)
2024-08-14 15:56:43 +00:00
Allan Carr
0bce921f69 IO-2876 Filtered Search Sorters correction
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-13 16:00:17 -07:00
64 changed files with 3422 additions and 3014 deletions

View File

@@ -1,20 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %> <% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png"/>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<link rel="icon" href="/ro-favicon.png" /> <link rel="icon" href="/ro-favicon.png"/>
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<link rel="icon" href="/pm/pm-favicon.ico" /> <link rel="icon" href="/pm/pm-favicon.ico"/>
<% } %> <% } %>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#1690ff" /> <meta name="theme-color" content="#1690ff"/>
<!-- <link rel="apple-touch-icon" href="logo192.png" /> --> <!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!-- TODO:AIo Update the individual logos for each.--> <!-- TODO:AIo Update the individual logos for each.-->
<link rel="apple-touch-icon" href="public/logo192.png" /> <link rel="apple-touch-icon" href="public/logo192.png"/>
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF"> <link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
@@ -30,7 +30,7 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %> <% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<meta name="description" content="ImEX Online" /> <meta name="description" content="ImEX Online"/>
<title>ImEX Online</title> <title>ImEX Online</title>
<script type="text/javascript"> <script type="text/javascript">
window.$crisp = []; window.$crisp = [];
@@ -44,12 +44,13 @@
})(); })();
</script> </script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<meta name="description" content="Rome Online" /> <meta name="description" content="Rome Online"/>
<title>Rome Online</title> <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--> <!--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-->
@@ -113,13 +114,13 @@
></call-us>--> ></call-us>-->
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script> <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') { %> <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<title>ProManager</title> <title>ProManager</title>
<meta name="description" content="ProManager" /> <meta name="description" content="ProManager"/>
<% } %> <% } %>
<script> <script>
@@ -143,12 +144,14 @@
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once'); if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
else { else {
var n = (window.noticeable = window.noticeable || []); var n = (window.noticeable = window.noticeable || []);
function t(e) { function t(e) {
return function () { return function () {
var t = Array.prototype.slice.call(arguments); var t = Array.prototype.slice.call(arguments);
return t.unshift(e), n.push(t), n; return t.unshift(e), n.push(t), n;
}; };
} }
!(function () { !(function () {
for (var o = 0; o < e.length; o++) { for (var o = 0; o < e.length; o++) {
var r = e[o]; var r = e[o];

View File

@@ -18,7 +18,7 @@ import { checkUserSession } from "../redux/user/user.actions";
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors"; import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
import PrivateRoute from "../components/PrivateRoute"; import PrivateRoute from "../components/PrivateRoute";
import "./App.styles.scss"; import "./App.styles.scss";
import handleBeta from "../utils/betaHandler"; import handleBeta from "../utils/handleBeta";
import Eula from "../components/eula/eula.component"; import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr"; import InstanceRenderMgr from "../utils/instanceRenderMgr";
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx"; import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";

View File

@@ -13,6 +13,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; 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 OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
@@ -170,13 +171,22 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
extra={ extra={
<Space wrap> <Space wrap>
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && ( {!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
<JobsExportAllButton <>
jobIds={selectedJobs} <JobMarkSelectedExported
disabled={transInProgress || selectedJobs.length === 0} jobIds={selectedJobs}
loadingCallback={setTransInProgress} disabled={transInProgress || selectedJobs.length === 0}
completedCallback={setSelectedJobs} loadingCallback={setTransInProgress}
refetch={refetch} completedCallback={setSelectedJobs}
/> refetch={refetch}
/>
<JobsExportAllButton
jobIds={selectedJobs}
disabled={transInProgress || selectedJobs.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedJobs}
refetch={refetch}
/>
</>
)} )}
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />} {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
<Input.Search <Input.Search

View File

@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.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 FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.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 VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component"; import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility"; import { CalculateBillTotal } from "./bill-form.totals.utility";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -276,7 +276,7 @@ export function BillFormComponent({
}) })
]} ]}
> >
<FormDatePicker disabled={disabled} /> <DateTimePicker isDateOnly disabled={disabled} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bills.fields.is_credit_memo")} label={t("bills.fields.is_credit_memo")}

View File

@@ -8,8 +8,8 @@ import { DateFormatter } from "../../utils/DateFormatter";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component"; import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component"; import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-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 FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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> </Form.Item>
{dlExpiresBeforeReturn && ( {dlExpiresBeforeReturn && (
<Space style={{ color: "tomato" }}> <Space style={{ color: "tomato" }}>
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
<InputPhone /> <InputPhone />
</Form.Item> </Form.Item>
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob"> <Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<ContractsRatesChangeButton form={form} /> <ContractsRatesChangeButton form={form} />

View File

@@ -10,16 +10,12 @@ import { DateFormatter } from "../../utils/DateFormatter";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; 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 CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-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 FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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({ export default function CourtesyCarCreateFormComponent({ form, saveLoading, newCC }) {
form,
saveLoading,
newCC,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -161,16 +157,16 @@ export default function CourtesyCarCreateFormComponent({
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate"> <Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate"> <Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate"> <Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate"> <Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
@@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
</div> </div>
<div> <div>
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate"> <Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}> <Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
{() => { {() => {
@@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
</Form.Item> </Form.Item>
<div> <div>
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires"> <Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}> <Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
{() => { {() => {
@@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}> <Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
{() => { {() => {

View File

@@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; 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() { export default function CourtesyCarReturnModalComponent() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("contracts.fields.kmend")} label={t("contracts.fields.kmend")}

View File

@@ -24,9 +24,9 @@ import i18n from "../../translations/i18n";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -164,7 +164,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}> <Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<Space> <Space>

View File

@@ -4,7 +4,6 @@ import Markdown from "react-markdown";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries"; import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { acceptEula } from "../../redux/user/user.actions"; import { acceptEula } from "../../redux/user/user.actions";
@@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next";
import day from "../../utils/day"; import day from "../../utils/day";
import "./eula.styles.scss"; import "./eula.styles.scss";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const Eula = ({ currentEula, currentUser, acceptEula }) => { const Eula = ({ currentEula, currentUser, acceptEula }) => {
const [formReady, setFormReady] = useState(false); 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> </Form.Item>
</Col> </Col>
</Row> </Row>

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -1,46 +1,105 @@
import React, { forwardRef } from "react"; import { DatePicker } from "antd";
//import DatePicker from "react-datepicker"; import PropTypes from "prop-types";
//import "react-datepicker/src/stylesheets/datepicker.scss"; import React, { useCallback, useState } from "react";
import { Space, TimePicker } from "antd"; import { useTranslation } from "react-i18next";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import { fuzzyMatchDate } from "./formats.js";
//To be used as a form element only.
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => { const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
// const handleChange = (newDate) => { const [isManualInput, setIsManualInput] = useState(false);
// if (value !== newDate && onChange) { const { t } = useTranslation();
// onChange(newDate);
// } const handleChange = useCallback(
// }; (newDate) => {
if (onChange) {
onChange(newDate || null);
}
setIsManualInput(false);
},
[onChange]
);
const handleBlur = useCallback(
(e) => {
// Bail if this is not a manual input
if (!isManualInput) {
return;
}
// Reset manual input flag
setIsManualInput(false);
const v = e?.target?.value;
if (!v) return;
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
if (parsedDate && onChange) {
onChange(parsedDate);
}
},
[isManualInput, isDateOnly, onChange]
);
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 ( return (
<Space direction="vertical" style={{ width: "100%" }} id={id}> <div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
<FormDatePicker <DatePicker
{...restProps} showTime={
{...(onlyFuture && { isDateOnly
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d) ? false
})} : {
value={value} format: "hh:mm a",
onBlur={onBlur} minuteStep: 15,
onChange={onChange} defaultValue: dayjs(dayjs(), "HH:mm:ss")
onlyFuture={onlyFuture} }
isDateOnly={false} }
/> format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
<TimePicker
value={value ? dayjs(value) : null} value={value ? dayjs(value) : null}
{...(onlyFuture && { onChange={handleChange}
disabledDate: (d) => dayjs().isAfter(d) placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
})} onBlur={onBlur || handleBlur}
onChange={onChange} disabledDate={handleDisabledDate}
disableSeconds={true}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps} {...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);

View File

@@ -0,0 +1,63 @@
import dayjs from "../../utils/day";
const dateFormats = [
"MMDDYYYY",
"MMDDYY",
"M/D/YYYY",
"MM/D/YYYY",
"M/DD/YYYY",
"MM/DD/YYYY",
"M/D/YY",
"MM/D/YY",
"M/DD/YY",
"MM/DD/YY"
];
const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"];
const dateTimeFormats = [
...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap(
(dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)
),
...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
"M/D/YYYY",
"MM/D/YYYY",
"M/DD/YYYY",
"MM/DD/YYYY",
"M/D/YY",
"MM/D/YY",
"M/DD/YY",
"MM/DD/YY",
"MMDDYYYY",
"MMDDYY"
];
const sanitizeInput = (input) =>
input
.trim()
.toUpperCase()
.replace(/\s*(am|pm)\s*/i, " $1")
.replaceAll(".", "/")
.replaceAll("-", "/");
export const fuzzyMatchDate = (dateString) => {
const sanitizedInput = sanitizeInput(dateString);
for (const format of dateFormats) {
const parsedDate = dayjs(sanitizedInput, format, true);
if (parsedDate.isValid()) {
return parsedDate;
}
}
for (const format of dateTimeFormats) {
const parsedDateTime = dayjs(sanitizedInput, format, true);
if (parsedDateTime.isValid()) {
return parsedDateTime; // Return the dayjs object
}
}
return null; // If no matching format is found
};

View File

@@ -43,7 +43,7 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler"; import { checkBeta, handleBeta, setBeta } from "../../utils/handleBeta";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";

View File

@@ -10,8 +10,8 @@ import {
QUERY_SCOREBOARD_ENTRY, QUERY_SCOREBOARD_ENTRY,
UPDATE_SCOREBOARD_ENTRY UPDATE_SCOREBOARD_ENTRY
} from "../../graphql/scoreboard.queries"; } from "../../graphql/scoreboard.queries";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.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 }) { export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps })
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("scoreboard.fields.bodyhrs")} label={t("scoreboard.fields.bodyhrs")}

View File

@@ -5,7 +5,6 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; 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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -20,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ 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); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
@@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
<FormFieldsChanged form={form} /> <FormFieldsChanged form={form} />
<LayoutFormRow header={t("jobs.forms.estdates")}> <LayoutFormRow header={t("jobs.forms.estdates")}>
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated"> <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>
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin"> <Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
<DateTimePicker /> <DateTimePicker />

View File

@@ -1,18 +1,9 @@
import { import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
Collapse,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-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 JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no"> <Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
label={t("jobs.fields.regie_number")}
name="regie_number"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm"> <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")} /> <FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date"> <Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin"> <Form.Item label={t("jobs.fields.kmin")} name="kmin">
<Input /> <Input />

View File

@@ -2,9 +2,9 @@ import { Form, Input } from "antd";
import React, { useContext } from "react"; import React, { useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component"; 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 }) { export default function JobsCreateVehicleInfoNewComponent({ form }) {
const [state] = useContext(JobCreateContext); const [state] = useContext(JobCreateContext);
@@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}> <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> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow>

View File

@@ -5,7 +5,6 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormRow from "../layout-form-row/layout-form-row.component"; import FormRow from "../layout-form-row/layout-form-row.component";
@@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<div> <div>
<FormRow header={t("jobs.forms.estdates")}> <FormRow header={t("jobs.forms.estdates")}>
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated"> <Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
<FormDatePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_open")} name="date_open"> <Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} />
@@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<FormRow header={t("jobs.forms.scheddates")}> <FormRow header={t("jobs.forms.scheddates")}>
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled"> <Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
<FormDatePicker disabled={jobRO} /> <DateTimePicker disabled={jobRO} isDateOnly />
</Form.Item> </Form.Item>
<Tooltip title={t("jobs.labels.scheduledinchange")}> <Tooltip title={t("jobs.labels.scheduledinchange")}>
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in"> <Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
@@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
rules={[ rules={[
{ {
required: jobInPostProduction required: jobInPostProduction
//message: t("general.validation.required"),
} }
]} ]}
> >

View File

@@ -5,7 +5,6 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-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 JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component"; import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import FormRow from "../layout-form-row/layout-form-row.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({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
<Input disabled={jobRO} /> <Input disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date"> <Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker disabled={jobRO} /> <DateTimePicker isDateOnly disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use"> <Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
<Input disabled={jobRO} /> <Input disabled={jobRO} />

View File

@@ -11,6 +11,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config"; import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
@@ -37,7 +38,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: 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, sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>{record.ro_number || t("general.labels.na")}</Link> <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", key: "ownr_ln",
ellipsis: true, ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
//sortOrder: sortcolumn === "ownr_ln" && sortorder, //sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
@@ -67,7 +70,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"), title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} /> render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
}, },
@@ -75,7 +77,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph2"), title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2", dataIndex: "ownr_ph2",
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} /> render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
}, },
@@ -85,7 +86,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "status", key: "status",
ellipsis: true, 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, sortOrder: sortcolumn === "status" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.status || t("general.labels.na"); return record.status || t("general.labels.na");
@@ -100,7 +101,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
render: (text, record) => { render: (text, record) => {
return record.vehicleid ? ( return record.vehicleid ? (
@@ -117,7 +117,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "plate_no", dataIndex: "plate_no",
key: "plate_no", key: "plate_no",
ellipsis: true, 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, sortOrder: sortcolumn === "plate_no" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.plate_no ? record.plate_no : ""; return record.plate_no ? record.plate_no : "";
@@ -128,7 +128,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
ellipsis: true, 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, sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}` 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"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",
key: "clm_total", key: "clm_total",
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
sorter: true, //(a, b) => a.clm_total - b.clm_total,
sortOrder: sortcolumn === "clm_total" && sortorder, sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.clm_total ? ( return record.clm_total ? (
@@ -157,7 +156,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"), title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing", dataIndex: "owner_owing",
key: "owner_owing", key: "owner_owing",
render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter> render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
}, },
{ {

View File

@@ -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>
);
}

View File

@@ -23,7 +23,7 @@ export function PartnerPingComponent({ bodyshop }) {
// Execute the created function directly // Execute the created function directly
checkPartnerStatus(bodyshop); checkPartnerStatus(bodyshop);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]); }, [bodyshop?.id]);
return <></>; return <></>;
} }

View File

@@ -8,8 +8,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries"; import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { CalendarFilled } from "@ant-design/icons"; import { CalendarFilled } from "@ant-design/icons";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -62,7 +62,7 @@ export function PartsOrderBackorderEta({
<div> <div>
<Form form={form} onFinish={handleFinish}> <Form form={form} onFinish={handleFinish}>
<Form.Item name="eta"> <Form.Item name="eta">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}

View File

@@ -7,7 +7,7 @@ import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries"; import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
import { selectBodyshop } from "../../redux/user/user.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.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -71,7 +71,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
<div> <div>
<Form form={form} onFinish={handleFinish}> <Form form={form} onFinish={handleFinish}>
<Form.Item name="eta"> <Form.Item name="eta">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("parts_orders.actions.backordered")} {t("parts_orders.actions.backordered")}

View File

@@ -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 { PageHeader } from "@ant-design/pro-layout";
import { useLazyQuery, useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd"; import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -83,47 +82,34 @@ export function PartsOrderListTableDrawerComponent({
sortedInfo: {} sortedInfo: {}
}); });
const [returnfrombill, setReturnFromBill] = useState(); const [billData, setBillData] = useState(null);
const [billData, setBillData] = useState();
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid; const selectedpartsorder = search.partsorderid;
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery; const { refetch } = billsQuery;
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
useEffect(() => { useEffect(() => {
if (returnfrombill === null) { const fetchData = async () => {
setBillData(null); if (selectedPartsOrderRecord?.returnfrombill) {
} else { try {
const fetchData = async () => { const { data } = await billQuery({
const result = await billQuery({ variables: { billid: selectedPartsOrderRecord.returnfrombill }
variables: { billid: returnfrombill } });
}); setBillData(data);
setBillData(result.data); } catch (error) {
}; console.error("Error fetching bill data:", error);
fetchData(); }
} } else setBillData(null);
}, [returnfrombill, billQuery]); };
fetchData();
}, [selectedPartsOrderRecord, billQuery]);
const recordActions = (record, showView = false) => ( const recordActions = (record) => (
<Space direction="horizontal" wrap> <Space direction="horizontal" wrap>
{showView && (
<Button
onClick={() => {
if (record.returnfrombill) {
setReturnFromBill(record.returnfrombill);
} else {
setReturnFromBill(null);
}
handleOnRowClick(record);
}}
>
<EyeFilled />
</Button>
)}
<Button <Button
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid} disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
onClick={() => { onClick={() => {
@@ -133,16 +119,14 @@ export function PartsOrderListTableDrawerComponent({
context: { context: {
jobId: job.id, jobId: job.id,
job: job, job: job,
partsorderlines: record.parts_order_lines.map((pol) => { partsorderlines: record.parts_order_lines.map((pol) => ({
return { joblineid: pol.job_line_id,
joblineid: pol.job_line_id, id: pol.id,
id: pol.id, line_desc: pol.line_desc,
line_desc: pol.line_desc, quantity: pol.quantity,
quantity: pol.quantity, act_price: pol.act_price,
act_price: pol.act_price, oem_partno: pol.oem_partno
oem_partno: pol.oem_partno }))
};
})
} }
}); });
}} }}
@@ -167,7 +151,6 @@ export function PartsOrderListTableDrawerComponent({
disabled={jobRO} disabled={jobRO}
onConfirm={async () => { onConfirm={async () => {
//Delete the parts return.! //Delete the parts return.!
await deletePartsOrder({ await deletePartsOrder({
variables: { partsOrderId: record.id }, variables: { partsOrderId: record.id },
update(cache) { update(cache) {
@@ -191,7 +174,6 @@ export function PartsOrderListTableDrawerComponent({
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid} disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
onClick={() => { onClick={() => {
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
@@ -199,24 +181,20 @@ export function PartsOrderListTableDrawerComponent({
bill: { bill: {
vendorid: record.vendor.id, vendorid: record.vendor.id,
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => { billlines: record.parts_order_lines.map((pol) => ({
return { joblineid: pol.job_line_id || "noline",
joblineid: pol.job_line_id || "noline", line_desc: pol.line_desc,
line_desc: pol.line_desc, quantity: pol.quantity,
quantity: pol.quantity, actual_price: pol.act_price,
cost_center: pol.jobline?.part_type
actual_price: pol.act_price, ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type !== "PAE"
cost_center: pol.jobline?.part_type ? pol.jobline.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid : null
? pol.jobline.part_type !== "PAE" : responsibilityCenters.defaults &&
? pol.jobline.part_type (responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null : 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 }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
const rowExpander = (record) => { const rowExpander = (record) => {
const columns = [ const columns = [
{ {

View File

@@ -6,12 +6,12 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.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({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -74,7 +74,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
]} ]}
label={t("parts_orders.fields.deliver_by")} label={t("parts_orders.fields.deliver_by")}
> >
<FormDatePicker onlyFuture /> <DateTimePicker isDateOnly onlyFuture />
</Form.Item> </Form.Item>
{job && job.special_coverage_policy && ( {job && job.special_coverage_policy && (
<Tag color="tomato"> <Tag color="tomato">

View File

@@ -90,7 +90,7 @@ export function BillMarkSelectedExported({
onConfirm={handleUpdate} onConfirm={handleUpdate}
disabled={disabled} 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")} {t("bills.labels.markexported")}
</Button> </Button>
</Popconfirm> </Popconfirm>

View File

@@ -5,11 +5,11 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; 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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import PaymentFormTotalPayments from "./payment-form.totalpayments.component"; import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -77,7 +77,7 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
} }
]} ]}
> >
<DatePickerFormItem disabled={disabled} /> <DateTimePicker isDateOnly disabled={disabled} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>

View File

@@ -90,7 +90,7 @@ export function PaymentMarkSelectedExported({
onConfirm={handleUpdate} onConfirm={handleUpdate}
disabled={disabled} 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")} {t("bills.labels.markexported")}
</Button> </Button>
</Popconfirm> </Popconfirm>

View File

@@ -1,22 +1,32 @@
import { Input, Space, Spin } from "antd"; import { Button, Input, Space, Spin } from "antd";
import React from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { ExclamationCircleFilled, ExclamationCircleOutlined } from "@ant-design/icons";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component"; import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters); export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters);
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) { export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [alertFilter, setAlertFilter] = useState(false);
const toggleAlertFilter = () => {
const newAlertFilter = !alertFilter;
setAlertFilter(newAlertFilter);
setFilter({ ...filter, alert: newAlertFilter });
};
return ( return (
<Space wrap> <Space wrap>
{loading && <Spin />} {loading && <Spin />}
@@ -35,6 +45,13 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
onChange={(emp) => setFilter({ ...filter, employeeId: emp })} onChange={(emp) => setFilter({ ...filter, employeeId: emp })}
allowClear allowClear
/> />
<Button
type={alertFilter ? "primary" : "default"}
onClick={toggleAlertFilter}
icon={alertFilter ? <ExclamationCircleFilled /> : <ExclamationCircleOutlined />}
>
{t("production.labels.alerts")}
</Button>
</Space> </Space>
); );
} }

View File

@@ -21,7 +21,7 @@ import { createBoardData } from "./production-board-kanban.utils.js";
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx"; import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
import cloneDeep from "lodash/cloneDeep"; import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import { mergeWithDefaults } from "./settings/defaultKanbanSettings.js"; import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) { function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
const [boardLanes, setBoardLanes] = useState({ lanes: [] }); const [boardLanes, setBoardLanes] = useState({ lanes: [] });
const [filter, setFilter] = useState({ search: "", employeeId: null }); const [filter, setFilter] = useState(defaultFilters);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isMoving, setIsMoving] = useState(false); const [isMoving, setIsMoving] = useState(false);
const [orientation, setOrientation] = useState("vertical"); const [orientation, setOrientation] = useState("vertical");
@@ -187,11 +187,9 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
return mergeWithDefaults(kanbanSettings); return mergeWithDefaults(kanbanSettings);
}, [associationSettings]); }, [associationSettings]);
const handleSettingsChange = useCallback((newSettings) => { const handleSettingsChange = () => {
setLoading(true); setFilter(defaultFilters);
setOrientation(newSettings.orientation ? "vertical" : "horizontal"); };
setLoading(false);
}, []);
if (loading) { if (loading) {
return <Skeleton active />; return <Skeleton active />;

View File

@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
// Function to create board data based on statuses and jobs, with optional filtering // Function to create board data based on statuses and jobs, with optional filtering
export const createBoardData = ({ statuses, data, filter, cardSettings }) => { export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
const { search, employeeId } = filter; const { search, employeeId, alert } = filter;
const lanes = statuses.map((status) => ({ const lanes = statuses.map((status) => ({
id: 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"); const DataGroupedByStatus = groupBy(filteredJobs, "status");
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => { Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {

View File

@@ -48,6 +48,8 @@ const defaultKanbanSettings = {
selectedEstimators: [] selectedEstimators: []
}; };
const defaultFilters = { search: "", employeeId: null, alert: false };
const mergeWithDefaults = (settings) => { const mergeWithDefaults = (settings) => {
// Create a new object that starts with the default settings // Create a new object that starts with the default settings
const mergedSettings = { ...defaultKanbanSettings }; const mergedSettings = { ...defaultKanbanSettings };
@@ -64,4 +66,4 @@ const mergeWithDefaults = (settings) => {
return mergedSettings; return mergedSettings;
}; };
export { defaultKanbanSettings, statisticsItems, mergeWithDefaults }; export { defaultKanbanSettings, statisticsItems, mergeWithDefaults, defaultFilters };

View File

@@ -9,8 +9,9 @@ import InformationSettings from "./InformationSettings.jsx";
import StatisticsSettings from "./StatisticsSettings.jsx"; import StatisticsSettings from "./StatisticsSettings.jsx";
import FilterSettings from "./FilterSettings.jsx"; import FilterSettings from "./FilterSettings.jsx";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { isFunction } from "lodash";
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) { function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -61,6 +62,11 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
setOpen(false); setOpen(false);
setLoading(false); setLoading(false);
parentLoading(false); parentLoading(false);
if (onSettingsChange && isFunction(onSettingsChange)) {
onSettingsChange(values);
}
setHasChanges(false); setHasChanges(false);
}; };
@@ -156,6 +162,7 @@ ProductionBoardKanbanSettings.propTypes = {
associationSettings: PropTypes.object, associationSettings: PropTypes.object,
parentLoading: PropTypes.func.isRequired, parentLoading: PropTypes.func.isRequired,
bodyshop: PropTypes.object.isRequired, bodyshop: PropTypes.object.isRequired,
onSettingsChange: PropTypes.func,
data: PropTypes.array data: PropTypes.array
}; };

View File

@@ -2,7 +2,6 @@ import React from "react";
import { Button, Dropdown } from "antd"; import { Button, Dropdown } from "antd";
import dataSource from "./production-list-columns.data"; import dataSource from "./production-list-columns.data";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
@@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician, technician: selectTechnician,
bodyshop: selectBodyshop 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 [columns, setColumns] = columnState;
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
@@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
names: ["Enhanced_Payroll"], names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid splitKey: bodyshop.imexshopid
}); });
const handleAdd = (e) => { const handleAdd = (e) => {
setColumns([ const newColumn = dataSource({
...columns, bodyshop,
...dataSource({ technician,
bodyshop, state: tableState,
technician, data,
state: tableState, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
data, treatments: { Enhanced_Payroll }
activeStatuses: bodyshop.md_ro_statuses.active_statuses, }).find((i) => i.key === e.key);
treatments: { Enhanced_Payroll }
}).filter((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); const columnKeys = columns.map((i) => i.key);
@@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
); );
} }
// <Transfer export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
// 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)));
// }}
// />

View File

@@ -2,10 +2,13 @@ import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
import { Checkbox, Space, Tooltip } from "antd"; import { Checkbox, Space, Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { store } from "../../redux/store";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter"; import { TimeFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
@@ -24,9 +27,11 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
import ProductionListColumnCategory from "./production-list-columns.status.category"; import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component"; import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
import { store } from "../../redux/store";
import { setModalContext } from "../../redux/modals/modals.actions"; const getEmployeeName = (employeeId, employees) => {
import InstanceRenderManager from "../../utils/instanceRenderMgr"; 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 r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
const { Enhanced_Payroll } = treatments; const { Enhanced_Payroll } = treatments;
@@ -258,7 +263,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
{ text: "True", value: true }, { text: "True", value: true },
{ text: "False", value: false } { 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} /> render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
}, },
@@ -349,6 +354,11 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
key: "alert", key: "alert",
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false), sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
filters: [
{ text: "True", value: true },
{ text: "False", value: false }
],
onFilter: (value, record) => value === (record.production_vars?.alert || false),
render: (text, record) => ( render: (text, record) => (
<ProductionListColumnAlert id={record.id} productionVars={record?.production_vars} refetch={refetch} /> <ProductionListColumnAlert id={record.id} productionVars={record?.production_vars} refetch={refetch} />
) )
@@ -421,8 +431,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, getEmployeeName(a.employee_body, bodyshop.employees),
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name getEmployeeName(b.employee_body, bodyshop.employees)
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" /> <ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
@@ -435,8 +445,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, getEmployeeName(a.employee_prep, bodyshop.employees),
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name getEmployeeName(b.employee_prep, bodyshop.employees)
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" /> <ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
@@ -455,8 +465,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, getEmployeeName(a.employee_csr, bodyshop.employees),
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name getEmployeeName(b.employee_csr, bodyshop.employees)
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" /> <ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
@@ -469,8 +479,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name, getEmployeeName(a.employee_refinish, bodyshop.employees),
bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name getEmployeeName(b.employee_refinish, bodyshop.employees)
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" /> <ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />

View File

@@ -1,12 +1,12 @@
import { useMutation } from "@apollo/client"; 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 React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day"; 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 }) { export default function ProductionListDate({ record, field, time, pastIndicator }) {
const [updateAlert] = useMutation(UPDATE_JOB); const [updateAlert] = useMutation(UPDATE_JOB);
@@ -57,22 +57,14 @@ export default function ProductionListDate({ record, field, time, pastIndicator
label: ( label: (
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}> <Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
<Space direction={"vertical"}> <Space direction={"vertical"}>
<FormDatePicker <DateTimePicker
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
value={(record[field] && dayjs(record[field])) || null} value={(record[field] && dayjs(record[field])) || null}
onChange={handleChange} onChange={handleChange}
format="MM/DD/YYYY" format={time ? "MM/DD/YYYY hh:mm a" : "MM/DD/YYYY"}
isDateOnly={!time} 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> <Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
</Space> </Space>
</Card> </Card>

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -1,8 +1,6 @@
import { SyncOutlined } from "@ant-design/icons"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { PageHeader } from "@ant-design/pro-layout";
import React, { useEffect, useMemo, useState } from "react";
import ReactDragListView from "react-drag-listview"; import ReactDragListView from "react-drag-listview";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; 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 ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import ProductionListDetail from "../production-list-detail/production-list-detail.component"; 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 ProductionListPrint from "./production-list-print.component";
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
import ResizeableTitle from "./production-list-table.resizeable.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({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -25,6 +27,7 @@ const mapStateToProps = createStructuredSelector({
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) { export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const { const {
treatments: { Production_List_Status_Colors, Enhanced_Payroll } 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 assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
const defaultView = assoc && assoc.default_prod_list_view; const defaultView = assoc && assoc.default_prod_list_view;
const [state, setState] = useState( const initialStateRef = useRef(
(bodyshop.production_config && (bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
bodyshop.production_config[0]?.columns.tableState || { bodyshop.production_config[0]?.columns.tableState || {
@@ -47,80 +49,102 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
} }
); );
const { t } = useTranslation(); const initialColumnsRef = useRef(
(initialStateRef.current &&
const matchingColumnConfig = useMemo(() => { bodyshop.production_config
return bodyshop.production_config.find((p) => p.name === defaultView); .find((p) => p.name === defaultView)
}, [bodyshop.production_config, defaultView]); ?.columns.columnKeys.map((k) => {
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) => {
return { return {
...ProductionListColumns({ ...ProductionListColumns({
bodyshop, bodyshop,
technician,
refetch, refetch,
state, technician,
data: data, state: initialStateRef.current,
data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Production_List_Status_Colors, Enhanced_Payroll } treatments: { Production_List_Status_Colors, Enhanced_Payroll }
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width ?? 100 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, matchingColumnConfig,
bodyshop, bodyshop,
technician, technician,
data data,
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed. Enhanced_Payroll,
Production_List_Status_Colors,
refetch,
state,
columns
]);
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ const newState = {
...state, ...state,
filteredInfo: filters, filteredInfo: filters,
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order } sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
}); };
if (!_.isEqual(newState, state)) {
setState(newState);
setHasUnsavedChanges(true);
}
}; };
const onDragEnd = (fromIndex, toIndex) => { const onDragEnd = (fromIndex, toIndex) => {
const columnsCopy = columns.slice(); if (fromIndex === toIndex) return;
const item = columnsCopy.splice(fromIndex, 1)[0];
columnsCopy.splice(toIndex, 0, item); const columnsCopy = [...columns];
setColumns(columnsCopy); const [movedItem] = columnsCopy.splice(fromIndex, 1);
columnsCopy.splice(toIndex, 0, movedItem);
if (!_.isEqual(columnsCopy, columns)) {
setColumns(columnsCopy);
setHasUnsavedChanges(true);
}
}; };
const removeColumn = (e) => { const removeColumn = (e) => {
const { key } = e; const { key } = e;
const newColumns = columns.filter((i) => i.key !== key); const newColumns = columns.filter((i) => i.key !== key);
setColumns(newColumns);
if (!_.isEqual(newColumns, columns)) {
setColumns(newColumns);
setHasUnsavedChanges(true);
}
}; };
const handleResize = const handleResize =
@@ -131,9 +155,21 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
...nextColumns[index], ...nextColumns[index],
width: size.width 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 headerItem = (col) => {
const menu = { const menu = {
onClick: removeColumn, onClick: removeColumn,
@@ -152,29 +188,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
); );
}; };
const dataSource = const resetChanges = () => {
searchText === "" setState(initialStateRef.current);
? data setColumns(initialColumnsRef.current);
: data.filter( setHasUnsavedChanges(false);
(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 handleSelectRecord = (record) => { const filterData = (item, searchText) => {
// if (selected !== record.id) { const fieldsToSearch = [
// setSelected(record.id); item.ro_number,
// } else { item.ownr_co_nm,
// setSelected(null); 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>; if (!!!columns) return <div>No columns found.</div>;
@@ -186,8 +222,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
.toFixed(1); .toFixed(1);
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).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); const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
return ( return (
<div> <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 <PageHeader
title={ title={
<Space> <Space>
@@ -199,20 +256,37 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
} }
extra={ extra={
<Space wrap> <Space wrap>
<Button onClick={() => refetch && refetch()}> <Button
onClick={() => {
refetch && refetch();
}}
>
<SyncOutlined /> <SyncOutlined />
</Button> </Button>
<ProductionListColumnsAdd columnState={[columns, setColumns]} tableState={state} data={data} /> <ProductionListColumnsAdd
<ProductionListSaveConfigButton columns={columns} tableState={state} /> columnState={[columns, setColumns]}
tableState={state}
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
refetch={refetch}
data={data} 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 <Input
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier"; import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { generateInternalReflections } from "./report-center-modal-utils"; 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 }) { export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
return ( return (
@@ -196,7 +196,8 @@ function FiltersSection({ filters, form, bodyshop }) {
// We have a type of date, so we will use a date picker // We have a type of date, so we will use a date picker
if (type === "date") { if (type === "date") {
return ( return (
<FormDatePicker <DateTimePicker
isDateOnly
disabled={!operator} disabled={!operator}
onChange={(date) => form.setFieldValue(fieldPath, date)} onChange={(date) => form.setFieldValue(fieldPath, date)}
/> />

View File

@@ -4,7 +4,7 @@ import dayjs from "../../utils/day";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; 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 }) { export default function ScoreboardEntryEdit({ entry }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -52,7 +52,7 @@ export default function ScoreboardEntryEdit({ entry }) {
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("scoreboard.fields.bodyhrs")} label={t("scoreboard.fields.bodyhrs")}

View File

@@ -5,7 +5,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_VACATION } from "../../graphql/employees.queries"; 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 }) { export default function ShopEmployeeAddVacation({ employee }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -64,7 +64,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("employees.fields.vacation.end")} label={t("employees.fields.vacation.end")}
@@ -90,7 +90,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
}) })
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>

View File

@@ -21,12 +21,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect"; import CiecaSelect from "../../utils/Ciecaselect";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; 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 FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component"; import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
import queryString from "query-string"; import queryString from "query-string";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -266,10 +266,10 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("employees.fields.termination_date")} name="termination_date"> <Form.Item label={t("employees.fields.termination_date")} name="termination_date">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("employees.fields.user_email")} label={t("employees.fields.user_email")}

View File

@@ -1,14 +1,13 @@
import { Col, Form, Input, Row, Select, Switch } from "antd"; import { Col, Form, Input, Row, Select, Switch } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { connect } from "react-redux"; import { connect } from "react-redux";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import JobSearchSelectComponent from "../job-search-select/job-search-select.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({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -246,7 +245,8 @@ export function TaskUpsertModalComponent({
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item label={t("tasks.fields.due_date")} name="due_date"> <Form.Item label={t("tasks.fields.due_date")} name="due_date">
<FormDatePicker <DateTimePicker
isDateOnly
onlyFuture onlyFuture
format="MM/DD/YYYY" format="MM/DD/YYYY"
presets={generatePresets(selectedJobDetails)} presets={generatePresets(selectedJobDetails)}
@@ -278,12 +278,7 @@ export function TaskUpsertModalComponent({
} }
]} ]}
> >
<FormDateTimePickerEnhanced <DateTimePicker onlyFuture presets={generatePresets(selectedJobDetails)} />
onlyFuture
showTime
minuteStep={15}
presets={generatePresets(selectedJobDetails)}
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>

View File

@@ -1,5 +1,4 @@
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd"; import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import DatePIckerRanges from "../../utils/DatePickerRanges"; import DatePIckerRanges from "../../utils/DatePickerRanges";
import dayjs from "../../utils/day";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectTechnician,
technician: selectTechnician technician: selectTechnician
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets); export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
export function TechJobPrintTickets({ technician, event, attendacePrint }) { export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
subject: subject:
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
}, },
values.sendby // === "email" ? "e" : "p" values.sendby,
bodyshop
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -7,9 +7,9 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries"; import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; 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 JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -71,7 +71,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>

View File

@@ -1,6 +1,6 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons"; import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Checkbox, Space, Table } from "antd"; import { Button, Card, Checkbox, Space, Table } from "antd";
import dayjs from "../../utils/day";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import dayjs from "../../utils/day";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -165,7 +165,7 @@ export function TimeTicketList({
key: "memo", key: "memo",
sorter: (a, b) => alphaSort(a.memo, b.memo), sorter: (a, b) => alphaSort(a.memo, b.memo),
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo) render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
}, },
...(Enhanced_Payroll.treatment === "on" ...(Enhanced_Payroll.treatment === "on"
? [ ? [

View File

@@ -1,4 +1,5 @@
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Form, Input, InputNumber, Select, Switch } from "antd"; import { Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import {
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; default as DateTimePicker,
default as FormDateTimePicker
} from "../form-date-time-picker/form-date-time-picker.component";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -60,8 +62,8 @@ export function TimeTicketModalComponent({
{item.cost_center === "timetickets.labels.shift" {item.cost_center === "timetickets.labels.shift"
? t(item.cost_center) ? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" : bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`) ? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
: item.cost_center} : item.cost_center}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
}; };
const MemoInput = ({ value, ...props }) => { const MemoInput = ({ value, ...props }) => {
return ( return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
<Input
value={value?.startsWith("timetickets.") ? t(value) : value}
{...props}
disabled={value?.startsWith("timetickets.") || disabled}
/>
);
}; };
return ( return (
@@ -111,7 +107,7 @@ export function TimeTicketModalComponent({
} }
]} ]}
> >
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="employeeid" name="employeeid"

View File

@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
renderItem={(ticket) => ( renderItem={(ticket) => (
<List.Item> <List.Item>
<Card <Card
title={t(ticket.memo)} title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
actions={[ actions={[
<TechClockOffButton <TechClockOffButton
jobId={ticket.jobid} jobId={ticket.jobid}

View File

@@ -1,9 +1,9 @@
import { Form, Input } from "antd"; import { Form, Input } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; 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 FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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 }) { export default function VehicleDetailFormComponent({ form, loading }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -102,7 +102,7 @@ export default function VehicleDetailFormComponent({ form, loading }) {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt"> <Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
<FormDatePicker /> <DateTimePicker isDateOnly />
</Form.Item> </Form.Item>
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}> <Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( query QUERY_ALL_ACTIVE_JOBS_PAGINATED(
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]

View File

@@ -571,7 +571,7 @@ export function Manage({ conflict, bodyshop }) {
return ( return (
<> <>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} /> {import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
<Layout style={{ minHeight: "100vh" }} className="layout-container"> <Layout style={{ minHeight: "100vh" }} className="layout-container">
<UpdateAlert /> <UpdateAlert />
<HeaderContainer /> <HeaderContainer />

View File

@@ -271,7 +271,8 @@
}, },
"errors": { "errors": {
"loading": "Unable to load shop details. Please call technical support.", "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": { "fields": {
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}", "ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
@@ -699,7 +700,10 @@
"workingdays": "Working Days" "workingdays": "Working Days"
}, },
"successes": { "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": { "validation": {
"centermustexist": "The chosen responsibility center does not exist.", "centermustexist": "The chosen responsibility center does not exist.",
@@ -1161,7 +1165,8 @@
"tryagain": "Try Again", "tryagain": "Try Again",
"view": "View", "view": "View",
"viewreleasenotes": "See What's Changed", "viewreleasenotes": "See What's Changed",
"remove_alert": "Are you sure you want to dismiss the alert?" "remove_alert": "Are you sure you want to dismiss the alert?",
"saveas": "Save As"
}, },
"errors": { "errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.", "fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -1176,6 +1181,7 @@
"vehicle": "Vehicle" "vehicle": "Vehicle"
}, },
"labels": { "labels": {
"unsavedchanges": "Unsaved changes.",
"actions": "Actions", "actions": "Actions",
"areyousure": "Are you sure?", "areyousure": "Are you sure?",
"barcode": "Barcode", "barcode": "Barcode",
@@ -1183,6 +1189,8 @@
"clear": "Clear", "clear": "Clear",
"confirmpassword": "Confirm Password", "confirmpassword": "Confirm Password",
"created_at": "Created At", "created_at": "Created At",
"date": "Select Date",
"datetime": "Select Date & Time",
"email": "Email", "email": "Email",
"errors": "Errors", "errors": "Errors",
"excel": "Excel", "excel": "Excel",
@@ -2731,6 +2739,9 @@
} }
}, },
"production": { "production": {
"constants": {
"main_profile": "Default"
},
"options": { "options": {
"small": "Small", "small": "Small",
"medium": "Medium", "medium": "Medium",
@@ -2780,7 +2791,9 @@
"errors": { "errors": {
"boardupdate": "Error encountered updating Job. {{message}}", "boardupdate": "Error encountered updating Job. {{message}}",
"removing": "Error removing from production board. {{error}}", "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": { "labels": {
"kiosk_mode": "Kiosk Mode", "kiosk_mode": "Kiosk Mode",
@@ -2833,7 +2846,9 @@
"sublets": "Sublets", "sublets": "Sublets",
"totalhours": "Total Hrs ", "totalhours": "Total Hrs ",
"touchtime": "T/T", "touchtime": "T/T",
"viewname": "View Name" "viewname": "View Name",
"alerts": "Alerts",
"addnewprofile": "Add New Profile"
}, },
"successes": { "successes": {
"removed": "Job removed from production." "removed": "Job removed from production."

File diff suppressed because it is too large Load Diff

View File

@@ -271,7 +271,8 @@
}, },
"errors": { "errors": {
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.", "loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
"saving": "" "saving": "",
"creatingdefaultview": ""
}, },
"fields": { "fields": {
"ReceivableCustomField": "", "ReceivableCustomField": "",
@@ -699,7 +700,10 @@
"workingdays": "" "workingdays": ""
}, },
"successes": { "successes": {
"save": "" "save": "",
"unsavedchanges": "",
"areyousure": "",
"defaultviewcreated": ""
}, },
"validation": { "validation": {
"centermustexist": "", "centermustexist": "",
@@ -1161,7 +1165,8 @@
"tryagain": "", "tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "", "viewreleasenotes": "",
"remove_alert": "" "remove_alert": "",
"saveas": ""
}, },
"errors": { "errors": {
"fcm": "", "fcm": "",
@@ -1176,6 +1181,7 @@
"vehicle": "" "vehicle": ""
}, },
"labels": { "labels": {
"unsavedchanges": "",
"actions": "actes", "actions": "actes",
"areyousure": "", "areyousure": "",
"barcode": "code à barre", "barcode": "code à barre",
@@ -1183,6 +1189,8 @@
"clear": "", "clear": "",
"confirmpassword": "", "confirmpassword": "",
"created_at": "", "created_at": "",
"date": "",
"datetime": "",
"email": "", "email": "",
"errors": "", "errors": "",
"excel": "", "excel": "",
@@ -2731,6 +2739,9 @@
} }
}, },
"production": { "production": {
"constants": {
"main_profile": ""
},
"options": { "options": {
"small": "", "small": "",
"medium": "", "medium": "",
@@ -2780,7 +2791,9 @@
"errors": { "errors": {
"boardupdate": "", "boardupdate": "",
"removing": "", "removing": "",
"settings": "" "settings": "",
"name_exists": "",
"name_required": ""
}, },
"labels": { "labels": {
"kiosk_mode": "", "kiosk_mode": "",
@@ -2833,7 +2846,9 @@
"sublets": "", "sublets": "",
"totalhours": "", "totalhours": "",
"touchtime": "", "touchtime": "",
"viewname": "" "viewname": "",
"alerts": "",
"addnewprofile": ""
}, },
"successes": { "successes": {
"removed": "" "removed": ""

View File

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

View File

@@ -11,26 +11,37 @@ export const setBeta = (value) => {
}; };
export const handleBeta = () => { 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")) { if (window.location.hostname.startsWith("localhost")) {
console.log("Not on beta or test, so no need to handle beta."); console.log("Not on beta or test, so no need to handle beta.");
return; return;
} }
const isBeta = checkBeta(); const isBeta = checkBeta();
const currentHostName = window.location.hostname; const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta. // Determine if the host name starts with "beta" or "www.beta"
if (isBeta && !currentHostName.startsWith("beta")) { const isBetaHost = currentHostName.startsWith("beta.");
const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
window.location.replace(href);
}
// Beta is not enabled, but the current host name does start with beta. if (isBeta) {
else if (!isBeta && currentHostName.startsWith("beta")) { // If beta is on and we are not on a beta domain, redirect to the beta version
const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`; if (!isBetaHost && !isBetaHostWithWWW) {
window.location.replace(href); 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; export default handleBeta;

View File

@@ -1,3 +1,11 @@
- name: Kaizen Data Pump
webhook: '{{HASURA_API_URL}}/data/kaizen'
schedule: 30 5 * * *
include_in_metadata: true
payload: {}
headers:
- name: x-imex-auth
value_from_env: DATAPUMP_AUTH
- name: Task Reminders - name: Task Reminders
webhook: '{{HASURA_API_URL}}/tasks-remind-handler' webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
schedule: '*/15 * * * *' schedule: '*/15 * * * *'

View File

@@ -30,8 +30,14 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
if (jobline.db_ref === "936007") { if (jobline.db_ref === "936007") {
hasMashLine = true; 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. //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. //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 = const discountAmount =
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) && ((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&

View File

@@ -56,8 +56,8 @@ exports.default = async (req, res) => {
try { try {
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, { const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"), start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("hours") }) ...(end && { end: moment(end).endOf("day") })
}); });
const kaizenObject = { const kaizenObject = {
@@ -176,24 +176,19 @@ exports.default = async (req, res) => {
} finally { } finally {
sftp.end(); sftp.end();
} }
// sendServerEmail({ sendServerEmail({
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
// Uploaded: ${JSON.stringify( Uploaded: ${JSON.stringify(
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
// null, null,
// 2 2
// )} )}
// `, `
// }); });
res.sendStatus(200); res.sendStatus(200);
} catch (error) { } catch (error) {
res.status(200).json(error); res.status(200).json(error);
sendServerEmail({
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
text: `Errors: JSON.stringify(error)}
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`
});
} }
}; };

View File

@@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) {
} }
}); });
if (job.adjustment_bottom_line) { if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) })); for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
const percent_of_adjustment = if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) {
Math.round( //This amount is taxable for this type.
subtotal_before_adjustment.toUnit() / taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1) Dinero({
) / 100; amount: Math.round(job.adjustment_bottom_line * 100)
})
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { );
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
if (job.adjustment_bottom_line > 0) {
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
} else {
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment);
} }
}); }
} }
const remainingTaxableAmounts = taxableAmountsByTier; const remainingTaxableAmounts = taxableAmountsByTier;