Compare commits

...

57 Commits

Author SHA1 Message Date
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
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
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
Patrick Fic
b1cda41f56 - Remove Joyride and its cause
- Package updates (front + back)

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-14 11:54:52 -04: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
Dave Richer
97282740f5 Merged in release/2024-08-09-no-zoho (pull request #1596)
Release/2024 08 09 no zoho
2024-08-13 21:10:30 +00:00
Patrick Fic
150ae02978 - revert change to zoho
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-13 17:06:40 -04:00
69 changed files with 2266 additions and 1980 deletions

1280
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,22 +8,22 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@ant-design/pro-layout": "^7.19.11",
"@apollo/client": "^3.10.8",
"@ant-design/pro-layout": "^7.19.12",
"@apollo/client": "^3.11.4",
"@emotion/is-prop-valid": "^1.3.0",
"@fingerprintjs/fingerprintjs": "^4.4.3",
"@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.2.6",
"@sentry/cli": "^2.32.2",
"@reduxjs/toolkit": "^2.2.7",
"@sentry/cli": "^2.33.1",
"@sentry/react": "^7.114.0",
"@splitsoftware/splitio-react": "^1.12.0",
"@splitsoftware/splitio-react": "^1.12.1",
"@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1",
"antd": "^5.19.3",
"antd": "^5.20.1",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1",
"axios": "^1.6.8",
"axios": "^1.7.4",
"classnames": "^2.5.1",
"css-box-model": "^1.2.1",
"dayjs": "^1.11.12",
@@ -32,45 +32,44 @@
"dotenv": "^16.4.5",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^10.12.4",
"firebase": "^10.12.5",
"graphql": "^16.9.0",
"i18next": "^23.12.2",
"i18next": "^23.12.3",
"i18next-browser-languagedetector": "^8.0.0",
"immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.11.4",
"logrocket": "^8.1.1",
"libphonenumber-js": "^1.11.5",
"logrocket": "^8.1.2",
"markerjs2": "^2.32.1",
"memoize-one": "^6.0.0",
"normalize-url": "^8.0.1",
"object-hash": "^3.0.0",
"prop-types": "^15.8.1",
"query-string": "^9.0.0",
"query-string": "^9.1.0",
"raf-schd": "^4.0.3",
"react": "^18.3.1",
"react-big-calendar": "^1.13.1",
"react-big-calendar": "^1.13.2",
"react-color": "^2.19.3",
"react-cookie": "^7.1.4",
"react-cookie": "^7.2.0",
"react-dom": "^18.3.1",
"react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1",
"react-grid-layout": "1.3.4",
"react-i18next": "^14.1.3",
"react-icons": "^5.2.1",
"react-icons": "^5.3.0",
"react-image-lightbox": "^5.1.4",
"react-joyride": "^2.8.2",
"react-markdown": "^9.0.1",
"react-number-format": "^5.4.0",
"react-popopo": "^2.1.9",
"react-product-fruits": "^2.2.6",
"react-redux": "^9.1.2",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.25.1",
"react-router-dom": "^6.26.0",
"react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.7.12",
"react-virtuoso": "^4.10.1",
"recharts": "^2.12.7",
"redux": "^5.0.1",
"redux-actions": "^3.0.0",
"redux-actions": "^3.0.3",
"redux-persist": "^6.0.0",
"redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4",
@@ -80,7 +79,7 @@
"styled-components": "^6.1.12",
"subscriptions-transport-ws": "^0.11.0",
"use-memo-one": "^1.1.3",
"userpilot": "^1.3.2",
"userpilot": "^1.3.5",
"vite-plugin-ejs": "^1.7.0",
"web-vitals": "^3.5.2"
},
@@ -131,29 +130,29 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.24.7",
"@dotenvx/dotenvx": "^1.6.4",
"@dotenvx/dotenvx": "^1.7.0",
"@emotion/babel-plugin": "^11.12.0",
"@emotion/react": "^11.12.0",
"@sentry/webpack-plugin": "^2.21.1",
"@emotion/react": "^11.13.0",
"@sentry/webpack-plugin": "^2.22.2",
"@testing-library/cypress": "^10.0.2",
"browserslist": "^4.23.2",
"browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1",
"cross-env": "^7.0.3",
"cypress": "^13.13.1",
"cypress": "^13.13.3",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1",
"memfs": "^4.9.3",
"memfs": "^4.11.1",
"os-browserify": "^0.3.0",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3",
"vite": "^5.3.4",
"vite": "^5.4.0",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-pwa": "^0.20.0",
"vite-plugin-pwa": "^0.20.1",
"vite-plugin-style-import": "^2.0.0"
}
}

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
@@ -22,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -276,7 +276,7 @@ export function BillFormComponent({
})
]}
>
<FormDatePicker disabled={disabled} />
<DateTimePicker isDateOnly disabled={disabled} />
</Form.Item>
<Form.Item
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 ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -196,7 +196,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
}
]}
>
<FormDatePicker />
<DateTimePicker isDateOnly />
</Form.Item>
{dlExpiresBeforeReturn && (
<Space style={{ color: "tomato" }}>
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
<InputPhone />
</Form.Item>
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
<FormDatePicker />
<DateTimePicker isDateOnly />
</Form.Item>
</LayoutFormRow>
<ContractsRatesChangeButton form={form} />

View File

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

View File

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

View File

@@ -36,9 +36,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
};
});
console.log("Scheduled Out Today");
console.dir(scheduledOutToday);
const tvFontSize = 18;
const tvFontWeight = "bold";

View File

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

View File

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

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,136 @@
import React, { forwardRef } from "react";
//import DatePicker from "react-datepicker";
//import "react-datepicker/src/stylesheets/datepicker.scss";
import { Space, TimePicker } from "antd";
import React, { useCallback, useState } from "react";
import { DatePicker } from "antd";
import dayjs from "../../utils/day";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only.
import { formats, shorthandFormats } from "./formats.js";
import PropTypes from "prop-types";
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
// const handleChange = (newDate) => {
// if (value !== newDate && onChange) {
// onChange(newDate);
// }
// };
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
const [isManualInput, setIsManualInput] = useState(false);
const handleChange = useCallback(
(newDate) => {
if (newDate === null && onChange) {
onChange(null);
} else if (newDate && onChange) {
onChange(newDate);
}
setIsManualInput(false);
},
[onChange]
);
const handleBlur = useCallback(
(e) => {
if (!isManualInput) {
return;
}
setIsManualInput(false);
const v = e.target.value;
if (!v) return;
const upperV = v.toUpperCase();
let _a;
for (const format of shorthandFormats) {
_a = dayjs(upperV, format);
if (_a.isValid()) break;
}
if (!_a || !_a.isValid()) {
for (const format of formats) {
_a = dayjs(upperV, format);
if (_a.isValid()) break;
}
}
if (_a && _a.isValid()) {
if (isDateOnly) {
_a = _a.startOf("day");
}
if (value && value.isValid && value.isValid()) {
_a.set({
hours: value.hours(),
minutes: value.minutes(),
seconds: value.seconds(),
milliseconds: value.milliseconds()
});
}
if (onlyFuture) {
if (dayjs().subtract(1, "day").isBefore(_a)) {
onChange(_a);
} else {
onChange(dayjs().startOf("day"));
}
} else {
onChange(_a);
}
}
},
[isManualInput, isDateOnly, onlyFuture, onChange, value]
);
const handleKeyDown = useCallback(
(e) => {
setIsManualInput(true);
if (e.key.toLowerCase() === "t" && onChange) {
setIsManualInput(false);
onChange(dayjs());
} else if (e.key.toLowerCase() === "enter") {
handleBlur(e);
}
},
[onChange, handleBlur]
);
const handleDisabledDate = useCallback(
(current) => {
if (onlyToday) {
return !dayjs().isSame(current, "day");
} else if (onlyFuture) {
return dayjs().subtract(1, "day").isAfter(current);
}
return false;
},
[onlyToday, onlyFuture]
);
return (
<Space direction="vertical" style={{ width: "100%" }} id={id}>
<FormDatePicker
{...restProps}
{...(onlyFuture && {
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
})}
value={value}
onBlur={onBlur}
onChange={onChange}
onlyFuture={onlyFuture}
isDateOnly={false}
/>
<TimePicker
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
<DatePicker
showTime={
isDateOnly
? false
: {
format: "hh:mm a",
minuteStep: 15,
defaultValue: dayjs(dayjs(), "HH:mm:ss")
}
}
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
value={value ? dayjs(value) : null}
{...(onlyFuture && {
disabledDate: (d) => dayjs().isAfter(d)
})}
onChange={onChange}
disableSeconds={true}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
onChange={handleChange}
onBlur={onBlur || handleBlur}
disabledDate={handleDisabledDate}
{...restProps}
/>
</Space>
</div>
);
};
export default forwardRef(DateTimePicker);
DateTimePicker.propTypes = {
value: PropTypes.any,
onChange: PropTypes.func,
onBlur: PropTypes.func,
id: PropTypes.string,
onlyFuture: PropTypes.bool,
onlyToday: PropTypes.bool,
isDateOnly: PropTypes.bool
};
export default React.memo(DateTimePicker);

View File

@@ -0,0 +1,93 @@
export const shorthandFormats = [
"M/D/YY hA",
"M/D/YY h:mmA",
"M/D/YYYY hA",
"M/D/YYYY h:mmA",
"M/D/YY ha",
"M/D/YY h:mma",
"M/D/YYYY ha",
"M/D/YYYY h:mma"
];
export 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",
"MMDDYY hh:mm A",
"MMDDYYYY hh:mm A",
"MM/DD/YY hh:mm A",
"MM/DD/YYYY hh:mm A",
"M/DD/YY hh:mm A",
"M/DD/YYYY hh:mm A",
"MM/D/YY hh:mm A",
"MM/D/YYYY hh:mm A",
"M/D/YY hh:mm A",
"M/D/YYYY hh:mm A",
"D/MM/YY hh:mm A",
"D/MM/YYYY hh:mm A",
"DD/M/YY hh:mm A",
"DD/M/YYYY hh:mm A",
"D/M/YY hh:mm A",
"D/M/YYYY hh:mm A",
"MMDDYY hh:mm:ss A",
"MMDDYYYY hh:mm:ss A",
"MM/DD/YY hh:mm:ss A",
"MM/DD/YYYY hh:mm:ss A",
"M/DD/YY hh:mm:ss A",
"M/DD/YYYY hh:mm:ss A",
"MM/D/YY hh:mm:ss A",
"MM/D/YYYY hh:mm:ss A",
"M/D/YY hh:mm:ss A",
"M/D/YYYY hh:mm:ss A",
"D/MM/YY hh:mm:ss A",
"D/MM/YYYY hh:mm:ss A",
"DD/M/YY hh:mm:ss A",
"DD/M/YYYY hh:mm:ss A",
"D/M/YY hh:mm:ss A",
"D/M/YYYY hh:mm:ss A",
"MMDDYY HH:mm",
"MMDDYYYY HH:mm",
"MM/DD/YY HH:mm",
"MM/DD/YYYY HH:mm",
"M/DD/YY HH:mm",
"M/DD/YYYY HH:mm",
"MM/D/YY HH:mm",
"MM/D/YYYY HH:mm",
"M/D/YY HH:mm",
"M/D/YYYY HH:mm",
"D/MM/YY HH:mm",
"D/MM/YYYY HH:mm",
"DD/M/YY HH:mm",
"DD/M/YYYY HH:mm",
"D/M/YY HH:mm",
"D/M/YYYY HH:mm",
"MMDDYY HH:mm:ss",
"MMDDYYYY HH:mm:ss",
"MM/DD/YY HH:mm:ss",
"MM/DD/YYYY HH:mm:ss",
"M/DD/YY HH:mm:ss",
"M/DD/YYYY HH:mm:ss",
"MM/D/YY HH:mm:ss",
"MM/D/YYYY HH:mm:ss",
"M/D/YY HH:mm:ss",
"M/D/YYYY HH:mm:ss",
"D/MM/YY HH:mm:ss",
"D/MM/YYYY HH:mm:ss",
"DD/M/YY HH:mm:ss",
"DD/M/YYYY HH:mm:ss",
"D/M/YY HH:mm:ss",
"D/M/YYYY HH:mm:ss"
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,18 +16,15 @@ import useLocalStorage from "../../utils/useLocalStorage";
import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { setJoyRideSteps } from "../../redux/application/application.actions";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
});
const mapDispatchToProps = (dispatch) => ({});
export function JobsList({ bodyshop, setJoyRideSteps }) {
export function JobsList({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())

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
checkPartnerStatus(bodyshop);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]);
}, [bodyshop?.id]);
return <></>;
}

View File

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

View File

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

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

View File

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

View File

@@ -90,7 +90,7 @@ export function BillMarkSelectedExported({
onConfirm={handleUpdate}
disabled={disabled}
>
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
{t("bills.labels.markexported")}
</Button>
</Popconfirm>

View File

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

View File

@@ -90,7 +90,7 @@ export function PaymentMarkSelectedExported({
onConfirm={handleUpdate}
disabled={disabled}
>
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
{t("bills.labels.markexported")}
</Button>
</Popconfirm>

View File

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

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 cloneDeep from "lodash/cloneDeep";
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";
const mapStateToProps = createStructuredSelector({
@@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
const [filter, setFilter] = useState({ search: "", employeeId: null });
const [filter, setFilter] = useState(defaultFilters);
const [loading, setLoading] = useState(true);
const [isMoving, setIsMoving] = useState(false);
const [orientation, setOrientation] = useState("vertical");
@@ -187,11 +187,9 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
return mergeWithDefaults(kanbanSettings);
}, [associationSettings]);
const handleSettingsChange = useCallback((newSettings) => {
setLoading(true);
setOrientation(newSettings.orientation ? "vertical" : "horizontal");
setLoading(false);
}, []);
const handleSettingsChange = () => {
setFilter(defaultFilters);
};
if (loading) {
return <Skeleton active />;

View File

@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
// Function to create board data based on statuses and jobs, with optional filtering
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
const { search, employeeId } = filter;
const { search, employeeId, alert } = filter;
const lanes = statuses.map((status) => ({
id: status,
@@ -52,6 +52,11 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
);
}
// Filter jobs by alert if alert filter is true
if (alert) {
filteredJobs = filteredJobs.filter((job) => job.production_vars?.alert);
}
const DataGroupedByStatus = groupBy(filteredJobs, "status");
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {

View File

@@ -48,6 +48,8 @@ const defaultKanbanSettings = {
selectedEstimators: []
};
const defaultFilters = { search: "", employeeId: null, alert: false };
const mergeWithDefaults = (settings) => {
// Create a new object that starts with the default settings
const mergedSettings = { ...defaultKanbanSettings };
@@ -64,4 +66,4 @@ const mergeWithDefaults = (settings) => {
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 FilterSettings from "./FilterSettings.jsx";
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 [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
@@ -61,6 +62,11 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
setOpen(false);
setLoading(false);
parentLoading(false);
if (onSettingsChange && isFunction(onSettingsChange)) {
onSettingsChange(values);
}
setHasChanges(false);
};
@@ -156,6 +162,7 @@ ProductionBoardKanbanSettings.propTypes = {
associationSettings: PropTypes.object,
parentLoading: PropTypes.func.isRequired,
bodyshop: PropTypes.object.isRequired,
onSettingsChange: PropTypes.func,
data: PropTypes.array
};

View File

@@ -2,7 +2,6 @@ import React from "react";
import { Button, Dropdown } from "antd";
import dataSource from "./production-list-columns.data";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
@@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) {
const mapDispatchToProps = (dispatch) => ({
// Add any necessary dispatch actions here
});
export function ProductionColumnsComponent({
columnState,
technician,
bodyshop,
data,
tableState,
refetch,
onColumnAdd
}) {
const [columns, setColumns] = columnState;
const { t } = useTranslation();
const {
@@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid
});
const handleAdd = (e) => {
setColumns([
...columns,
...dataSource({
bodyshop,
technician,
state: tableState,
data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Enhanced_Payroll }
}).filter((i) => i.key === e.key)
]);
const newColumn = dataSource({
bodyshop,
technician,
state: tableState,
data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Enhanced_Payroll }
}).find((i) => i.key === e.key);
if (newColumn) {
const updatedColumns = [...columns, newColumn];
setColumns(updatedColumns);
// Call the onColumnAdd function passed as a prop
if (onColumnAdd) {
onColumnAdd(newColumn);
}
}
};
const columnKeys = columns.map((i) => i.key);
@@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
);
}
// <Transfer
// dataSource={dataSource}
// titles={["Source", "Target"]}
// targetKeys={columns.map((c) => c.key)}
// render={(item) => item.title}
// onChange={(nextTargetKeys, direction, moveKeys) => {
// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
// }}
// />
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);

View File

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

View File

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

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 { useSplitTreatments } from "@splitsoftware/splitio-react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React, { useEffect, useMemo, useState } from "react";
import ReactDragListView from "react-drag-listview";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -12,10 +10,14 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
import ProductionListPrint from "./production-list-print.component";
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
import ResizeableTitle from "./production-list-table.resizeable.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { SyncOutlined } from "@ant-design/icons";
import Prompt from "../../utils/prompt.js";
import _ from "lodash";
import AlertComponent from "../alert/alert.component.jsx";
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -25,6 +27,7 @@ const mapStateToProps = createStructuredSelector({
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
const [searchText, setSearchText] = useState("");
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const {
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
@@ -35,10 +38,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
});
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
const defaultView = assoc && assoc.default_prod_list_view;
const [state, setState] = useState(
const initialStateRef = useRef(
(bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
bodyshop.production_config[0]?.columns.tableState || {
@@ -47,80 +49,102 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
);
const { t } = useTranslation();
const matchingColumnConfig = useMemo(() => {
return bodyshop.production_config.find((p) => p.name === defaultView);
}, [bodyshop.production_config, defaultView]);
const [columns, setColumns] = useState(
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
refetch,
technician,
state,
data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
}).find((e) => e.key === k.key),
width: k.width ?? 100
};
})) ||
[]
);
useEffect(() => {
const newColumns =
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
const initialColumnsRef = useRef(
(initialStateRef.current &&
bodyshop.production_config
.find((p) => p.name === defaultView)
?.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
refetch,
state,
data: data,
technician,
state: initialStateRef.current,
data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
}).find((e) => e.key === k.key),
width: k.width ?? 100
};
})) ||
[];
setColumns(newColumns);
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const [state, setState] = useState(initialStateRef.current);
const [columns, setColumns] = useState(initialColumnsRef.current);
const { t } = useTranslation();
const matchingColumnConfig = useMemo(() => {
return bodyshop.production_config.find((p) => p.name === defaultView);
}, [bodyshop.production_config, defaultView]);
useEffect(() => {
const newColumns =
matchingColumnConfig?.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
refetch,
state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
}).find((e) => e.key === k.key),
width: k.width ?? 100
};
}) || [];
// Only update columns if they haven't been manually changed by the user
if (_.isEqual(initialColumnsRef.current, columns)) {
setColumns(newColumns);
}
}, [
//state,
matchingColumnConfig,
bodyshop,
technician,
data
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
data,
Enhanced_Payroll,
Production_List_Status_Colors,
refetch,
state,
columns
]);
const handleTableChange = (pagination, filters, sorter) => {
setState({
const newState = {
...state,
filteredInfo: filters,
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
});
};
if (!_.isEqual(newState, state)) {
setState(newState);
setHasUnsavedChanges(true);
}
};
const onDragEnd = (fromIndex, toIndex) => {
const columnsCopy = columns.slice();
const item = columnsCopy.splice(fromIndex, 1)[0];
columnsCopy.splice(toIndex, 0, item);
setColumns(columnsCopy);
if (fromIndex === toIndex) return;
const columnsCopy = [...columns];
const [movedItem] = columnsCopy.splice(fromIndex, 1);
columnsCopy.splice(toIndex, 0, movedItem);
if (!_.isEqual(columnsCopy, columns)) {
setColumns(columnsCopy);
setHasUnsavedChanges(true);
}
};
const removeColumn = (e) => {
const { key } = e;
const newColumns = columns.filter((i) => i.key !== key);
setColumns(newColumns);
if (!_.isEqual(newColumns, columns)) {
setColumns(newColumns);
setHasUnsavedChanges(true);
}
};
const handleResize =
@@ -131,9 +155,21 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
...nextColumns[index],
width: size.width
};
setColumns(nextColumns);
if (!_.isEqual(nextColumns, columns)) {
setColumns(nextColumns);
setHasUnsavedChanges(true);
}
};
const addColumn = (newColumn) => {
const updatedColumns = [...columns, newColumn];
if (!_.isEqual(updatedColumns, columns)) {
setColumns(updatedColumns);
setHasUnsavedChanges(true);
}
};
const headerItem = (col) => {
const menu = {
onClick: removeColumn,
@@ -152,29 +188,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
);
};
const dataSource =
searchText === ""
? data
: data.filter(
(j) =>
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
);
const resetChanges = () => {
setState(initialStateRef.current);
setColumns(initialColumnsRef.current);
setHasUnsavedChanges(false);
};
// const handleSelectRecord = (record) => {
// if (selected !== record.id) {
// setSelected(record.id);
// } else {
// setSelected(null);
// }
// };
const filterData = (item, searchText) => {
const fieldsToSearch = [
item.ro_number,
item.ownr_co_nm,
item.ownr_fn,
item.ownr_ln,
item.status,
item.ins_co_nm,
item.clm_no,
item.v_model_desc,
item.v_make_desc
];
return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
};
const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText));
if (!!!columns) return <div>No columns found.</div>;
@@ -186,8 +222,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
.toFixed(1);
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
return (
<div>
<Prompt when={hasUnsavedChanges} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
{hasUnsavedChanges && (
<AlertComponent
type="warning"
message={
<div>
<span>{t("general.messages.unsavedchanges")} </span>
<span
onClick={resetChanges}
style={{
cursor: "pointer",
textDecoration: "underline"
}}
>
{t("general.actions.reset")}
</span>
</div>
}
/>
)}
<PageHeader
title={
<Space>
@@ -199,20 +256,37 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
extra={
<Space wrap>
<Button onClick={() => refetch && refetch()}>
<Button
onClick={() => {
refetch && refetch();
}}
>
<SyncOutlined />
</Button>
<ProductionListColumnsAdd columnState={[columns, setColumns]} tableState={state} data={data} />
<ProductionListSaveConfigButton columns={columns} tableState={state} />
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
refetch={refetch}
<ProductionListColumnsAdd
columnState={[columns, setColumns]}
tableState={state}
data={data}
onColumnAdd={addColumn}
/>
<ProductionListConfigManager
columns={columns}
setColumns={setColumns}
state={state}
setState={setState}
refetch={refetch}
data={data}
bodyshop={bodyshop}
technician={technician}
currentUser={currentUser}
setHasUnsavedChanges={setHasUnsavedChanges}
hasUnsavedChanges={hasUnsavedChanges}
onSave={() => {
setHasUnsavedChanges(false);
initialStateRef.current = state;
}}
/>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,8 +7,8 @@ import { createStructuredSelector } from "reselect";
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
@@ -60,8 +60,8 @@ export function TimeTicketModalComponent({
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
: item.cost_center}
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
: item.cost_center}
</Select.Option>
))}
</Select>
@@ -111,7 +111,7 @@ export function TimeTicketModalComponent({
}
]}
>
<FormDatePicker />
<DateTimePicker isDateOnly />
</Form.Item>
<Form.Item
name="employeeid"

View File

@@ -1,7 +1,7 @@
import { AlertOutlined } from "@ant-design/icons";
import { Alert, Button, Col, Row, Space } from "antd";
import i18n from "i18next";
import React from "react";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -12,39 +12,42 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
updateAvailable: selectUpdateAvailable
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
// setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
const intervalMS = 10 * 60 * 1000;
export function UpdateAlert({ updateAvailable }) {
const { t } = useTranslation();
const {
offlineReady: [
offlineReady //setOfflineReady
],
needRefresh: [
needRefresh //setNeedRefresh
],
offlineReady: [offlineReady],
needRefresh: [needRefresh],
updateServiceWorker
} = useRegisterSW({
onRegistered(r) {
// eslint-disable-next-line prefer-template
console.log("SW Registered: " + r);
r &&
setInterval(() => {
r.update();
}, intervalMS);
console.log("SW Registered:", r);
if (r) {
setInterval(
() => {
r.update();
},
10 * 60 * 1000
);
}
},
onRegisterError(error) {
console.log("SW registration error", error);
console.error("SW registration error", error);
}
});
if (import.meta.env.DEV) console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
useEffect(() => {
if (import.meta.env.DEV) {
console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
}
}, [needRefresh, offlineReady]);
if (!needRefresh) return null;
return (
<Alert
message={t("general.messages.newversiontitle", {
@@ -69,19 +72,10 @@ export function UpdateAlert({ updateAvailable }) {
</Col>
<Col sm={24} md={8} lg={6}>
<Space wrap>
<Button
onClick={async () => {
window.open("https://imex-online.noticeable.news/", "_blank");
}}
>
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
{i18n.t("general.actions.viewreleasenotes")}
</Button>
<Button
type="primary"
onClick={async () => {
updateServiceWorker(true);
}}
>
<Button type="primary" onClick={() => updateServiceWorker(true)}>
{i18n.t("general.actions.refresh")}
</Button>
</Space>
@@ -93,3 +87,5 @@ export function UpdateAlert({ updateAvailable }) {
/>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);

View File

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

View File

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

View File

@@ -12,7 +12,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
//import FooterComponent from "../../components/footer/footer.component";
//Component Imports
import * as Sentry from "@sentry/react";
import Joyride from "react-joyride";
import TestComponent from "../../components/_test/test.page";
import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
@@ -23,8 +22,6 @@ import { requestForToken } from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import { setJoyRideFinished } from "../../redux/application/application.actions.js";
import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss";
@@ -105,16 +102,12 @@ const { Content, Footer } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
enableJoyRide: selectEnableJoyRide,
joyRideSteps: selectJoyRideSteps
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
});
const mapDispatchToProps = (dispatch) => ({});
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
export function Manage({ conflict, bodyshop }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
@@ -578,26 +571,11 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
return (
<>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
<Layout style={{ minHeight: "100vh" }} className="layout-container">
<UpdateAlert />
<HeaderContainer />
<Content className="content-container">
<Joyride
debug
run={enableJoyRide}
steps={joyRideSteps}
continuous
hideCloseButton
scrollToFirstStep
showProgress
showSkipButton
callback={(props) => {
if (props.action === "reset") {
setJoyRideFinished();
}
}}
/>
<PartnerPingComponent />
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
{PageContent}

View File

@@ -67,11 +67,3 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable
});
export const setJoyRideSteps = (steps) => ({
type: ApplicationActionTypes.SET_JOYRIDE_STEPS,
payload: steps
});
export const setJoyRideFinished = () => ({
type: ApplicationActionTypes.SET_JOYRIDE_FINISHED
//payload: isUpdateAvailable,
});

View File

@@ -14,9 +14,7 @@ const INITIAL_STATE = {
error: null
},
jobReadOnly: false,
partnerVersion: null,
enableJoyRide: false,
joyRideSteps: []
partnerVersion: null
};
const applicationReducer = (state = INITIAL_STATE, action) => {
@@ -89,12 +87,6 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return { ...state, problemJobs: action.payload };
}
case ApplicationActionTypes.SET_JOYRIDE_STEPS: {
return { ...state, enableJoyRide: true, joyRideSteps: action.payload };
}
case ApplicationActionTypes.SET_JOYRIDE_FINISHED: {
return { ...state, enableJoyRide: false, joyRideSteps: [] };
}
default:
return state;
}

View File

@@ -22,5 +22,3 @@ export const selectJobReadOnly = createSelector([selectApplication], (applicatio
export const selectOnline = createSelector([selectApplication], (application) => application.online);
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
export const selectEnableJoyRide = createSelector([selectApplication], (application) => application.enableJoyRide);
export const selectJoyRideSteps = createSelector([selectApplication], (application) => application.joyRideSteps);

View File

@@ -12,8 +12,6 @@ const ApplicationActionTypes = {
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
SET_JOYRIDE_STEPS: "SET_JOYRIDE_STEPS",
SET_JOYRIDE_FINISHED: "SET_JOYRIDE_FINISHED"
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
};
export default ApplicationActionTypes;

View File

@@ -271,7 +271,8 @@
},
"errors": {
"loading": "Unable to load shop details. Please call technical support.",
"saving": "Error encountered while saving. {{message}}"
"saving": "Error encountered while saving. {{message}}",
"creatingdefaultview": "Error creating default view."
},
"fields": {
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
@@ -699,7 +700,10 @@
"workingdays": "Working Days"
},
"successes": {
"save": "Shop configuration saved successfully. "
"save": "Shop configuration saved successfully. ",
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?",
"areyousure": "Are you sure you want to continue?",
"defaultviewcreated": "Default view created successfully."
},
"validation": {
"centermustexist": "The chosen responsibility center does not exist.",
@@ -1161,7 +1165,8 @@
"tryagain": "Try Again",
"view": "View",
"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": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -1176,6 +1181,7 @@
"vehicle": "Vehicle"
},
"labels": {
"unsavedchanges": "Unsaved changes.",
"actions": "Actions",
"areyousure": "Are you sure?",
"barcode": "Barcode",
@@ -2731,6 +2737,9 @@
}
},
"production": {
"constants":{
"main_profile": "Default"
},
"options": {
"small": "Small",
"medium": "Medium",
@@ -2780,7 +2789,9 @@
"errors": {
"boardupdate": "Error encountered updating Job. {{message}}",
"removing": "Error removing from production board. {{error}}",
"settings": "Error saving board settings: {{error}}"
"settings": "Error saving board settings: {{error}}",
"name_exists": "A Profile with this name already exists. Please choose a different name.",
"name_required": "Profile name is required."
},
"labels": {
"kiosk_mode": "Kiosk Mode",
@@ -2833,7 +2844,9 @@
"sublets": "Sublets",
"totalhours": "Total Hrs ",
"touchtime": "T/T",
"viewname": "View Name"
"viewname": "View Name",
"alerts": "Alerts",
"addnewprofile": "Add New Profile"
},
"successes": {
"removed": "Job removed from production."

View File

@@ -271,7 +271,8 @@
},
"errors": {
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.",
"saving": ""
"saving": "",
"creatingdefaultview": ""
},
"fields": {
"ReceivableCustomField": "",
@@ -699,8 +700,11 @@
"workingdays": ""
},
"successes": {
"save": ""
},
"save": "",
"unsavedchanges": "",
"areyousure": "",
"defaultviewcreated": ""
},
"validation": {
"centermustexist": "",
"larsplit": "",
@@ -1161,7 +1165,8 @@
"tryagain": "",
"view": "",
"viewreleasenotes": "",
"remove_alert": ""
"remove_alert": "",
"saveas": ""
},
"errors": {
"fcm": "",
@@ -1176,6 +1181,7 @@
"vehicle": ""
},
"labels": {
"unsavedchanges": "",
"actions": "Comportamiento",
"areyousure": "",
"barcode": "código de barras",
@@ -2731,6 +2737,9 @@
}
},
"production": {
"constants":{
"main_profile": ""
},
"options": {
"small": "",
"medium": "",
@@ -2780,7 +2789,9 @@
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
"settings": "",
"name_exists": "",
"name_required": ""
},
"labels": {
"kiosk_mode": "",
@@ -2833,7 +2844,9 @@
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
"viewname": "",
"alerts": "",
"addnewprofile": ""
},
"successes": {
"removed": ""

View File

@@ -271,7 +271,8 @@
},
"errors": {
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
"saving": ""
"saving": "",
"creatingdefaultview": ""
},
"fields": {
"ReceivableCustomField": "",
@@ -699,7 +700,10 @@
"workingdays": ""
},
"successes": {
"save": ""
"save": "",
"unsavedchanges": "",
"areyousure": "",
"defaultviewcreated": ""
},
"validation": {
"centermustexist": "",
@@ -1161,7 +1165,8 @@
"tryagain": "",
"view": "",
"viewreleasenotes": "",
"remove_alert": ""
"remove_alert": "",
"saveas": ""
},
"errors": {
"fcm": "",
@@ -1176,6 +1181,7 @@
"vehicle": ""
},
"labels": {
"unsavedchanges": "",
"actions": "actes",
"areyousure": "",
"barcode": "code à barre",
@@ -2731,6 +2737,9 @@
}
},
"production": {
"constants":{
"main_profile": ""
},
"options": {
"small": "",
"medium": "",
@@ -2780,7 +2789,9 @@
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
"settings": "",
"name_exists": "",
"name_required": ""
},
"labels": {
"kiosk_mode": "",
@@ -2833,7 +2844,9 @@
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
"viewname": "",
"alerts": "",
"addnewprofile": ""
},
"successes": {
"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 = () => {
// If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith("localhost")) {
console.log("Not on beta or test, so no need to handle beta.");
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta.
if (isBeta && !currentHostName.startsWith("beta")) {
const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Determine if the host name starts with "beta" or "www.beta"
const isBetaHost = currentHostName.startsWith("beta.");
const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
// Beta is not enabled, but the current host name does start with beta.
else if (!isBeta && currentHostName.startsWith("beta")) {
const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
if (isBeta) {
// If beta is on and we are not on a beta domain, redirect to the beta version
if (!isBetaHost && !isBetaHostWithWWW) {
const newHostName = currentHostName.startsWith("www.")
? `www.beta.${currentHostName.replace(/^www\./, "")}`
: `beta.${currentHostName}`;
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Otherwise, if beta is on and we're already on a beta domain, stay there
} else {
// If beta is off and we are on a beta domain, redirect to the non-beta version
if (isBetaHost || isBetaHostWithWWW) {
const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Otherwise, if beta is off and we're not on a beta domain, stay there
}
};
export default handleBeta;

View File

@@ -6,8 +6,6 @@ import * as url from "url";
import { defineConfig } from "vite";
import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from "vite-plugin-eslint";
//import CompressionPlugin from 'vite-plugin-compression';
import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
@@ -16,7 +14,8 @@ process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
});
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
function reactVirtualized() {
function reactVirtualizedFix() {
return {
name: "flat:react-virtualized",
configResolved: async () => {
@@ -37,10 +36,7 @@ function reactVirtualized() {
export default defineConfig({
base: "/",
plugins: [
ViteEjsPlugin((viteConfig) => {
// viteConfig is the current Vite resolved config
return { env: viteConfig.env };
}),
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
VitePWA({
injectRegister: "auto",
registerType: "prompt",
@@ -60,7 +56,6 @@ export default defineConfig({
description: "The ultimate bodyshop management system.",
icons: [
{
//TODO:AIO Ensure that these are correct for Rome and IO.
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "favicon.png",
@@ -101,7 +96,7 @@ export default defineConfig({
gcm_sender_id: "103953800507"
}
}),
reactVirtualized(),
reactVirtualizedFix(),
react(),
eslint()
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.

613
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,18 +19,18 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.616.0",
"@aws-sdk/client-ses": "^3.616.0",
"@aws-sdk/credential-provider-node": "^3.616.0",
"@opensearch-project/opensearch": "^2.10.0",
"aws4": "^1.13.0",
"axios": "^1.7.2",
"@aws-sdk/client-secrets-manager": "^3.629.0",
"@aws-sdk/client-ses": "^3.629.0",
"@aws-sdk/credential-provider-node": "^3.629.0",
"@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1",
"axios": "^1.7.4",
"better-queue": "^3.8.12",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
"canvas": "^2.11.2",
"chart.js": "^4.4.3",
"cloudinary": "^2.3.0",
"cloudinary": "^2.4.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "2.8.5",
@@ -38,24 +38,24 @@
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"firebase-admin": "^12.2.0",
"firebase-admin": "^12.3.1",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
"inline-css": "^4.0.2",
"intuit-oauth": "^4.1.2",
"json-2-csv": "^5.5.4",
"json-2-csv": "^5.5.5",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.5",
"node-persist": "^4.0.2",
"node-persist": "^4.0.3",
"nodemailer": "^6.9.14",
"phone": "^3.1.49",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"soap": "^1.1.0",
"soap": "^1.1.1",
"socket.io": "^4.7.5",
"ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0",

View File

@@ -30,8 +30,14 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
if (jobline.db_ref === "936007") {
hasMashLine = true;
}
//Check if the line is a Towing Line and flag as such.
let isTowingLine = false;
if (jobline.db_ref === "936001" && jobline.line_desc.includes("Towing")) {
isTowingLine = true;
}
//Parts Lines Mappings.
if (jobline.profitcenter_part) {
if (!isTowingLine && jobline.profitcenter_part) {
//TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify.
const discountAmount =
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&