Compare commits
288 Commits
feature/cd
...
feature/oe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5db43dd065 | ||
|
|
53be0bbc1a | ||
|
|
bfcc03850b | ||
|
|
65402c1420 | ||
|
|
b462b2fa03 | ||
|
|
0c26b90591 | ||
|
|
f8f2384c54 | ||
|
|
ef18cf0718 | ||
|
|
9d35fc85ad | ||
|
|
e98f9763fd | ||
|
|
95e5385cd1 | ||
|
|
46abf01366 | ||
|
|
87f06425e1 | ||
|
|
45ab7543d5 | ||
|
|
120e4fc94c | ||
|
|
fed16efd10 | ||
|
|
ea6277c586 | ||
|
|
49bf461c36 | ||
|
|
696781c857 | ||
|
|
5540872f62 | ||
|
|
dac53a56c3 | ||
|
|
69690f0184 | ||
|
|
f11a4c93ac | ||
|
|
d74870812e | ||
|
|
9eed33b5f2 | ||
|
|
55b7715e1c | ||
|
|
c129b5ba8c | ||
|
|
efc2844d99 | ||
|
|
ab38f85d38 | ||
|
|
81eccba393 | ||
|
|
b3b49fd4ca | ||
|
|
b7a08db4e7 | ||
|
|
2d63c3d576 | ||
|
|
5f4a2392af | ||
|
|
f6fe8be7c4 | ||
|
|
89af6d23e8 | ||
|
|
7e1431d65e | ||
|
|
d50e845ba0 | ||
|
|
6796c35e5b | ||
|
|
4a68a10005 | ||
|
|
17088b3025 | ||
|
|
4e3c659b6d | ||
|
|
6afcf82cc4 | ||
|
|
f4ddb40bde | ||
|
|
6017e2172e | ||
|
|
bef9e64bf8 | ||
|
|
e8d95bdb68 | ||
|
|
10fb7d9d96 | ||
|
|
2ec196e664 | ||
|
|
1f38b98bfd | ||
|
|
a7c76386bc | ||
|
|
042b67c531 | ||
|
|
2e72ed3698 | ||
|
|
bc01f46388 | ||
|
|
74f791541f | ||
|
|
84c7fdba5a | ||
|
|
630fa23f6c | ||
|
|
8f58f09c8c | ||
|
|
a0922f2944 | ||
|
|
b8f001625b | ||
|
|
53394efebf | ||
|
|
d8296e1d01 | ||
|
|
2c00e5ee79 | ||
|
|
34e220dcad | ||
|
|
fa1fffd8b9 | ||
|
|
67eb430ff9 | ||
|
|
ff53355b4a | ||
|
|
331b2c517b | ||
|
|
05d9f20a66 | ||
|
|
9e3a2e920d | ||
|
|
1511b87959 | ||
|
|
9c3e4b7b83 | ||
|
|
b718f49071 | ||
|
|
e7157119ae | ||
|
|
09187bdb7f | ||
|
|
2aed392fbe | ||
|
|
1a0054a911 | ||
|
|
6e6f3d3d3e | ||
|
|
29df140680 | ||
|
|
f37c67a122 | ||
|
|
e53c9aab72 | ||
|
|
385ad06adc | ||
|
|
d1e2d943a9 | ||
|
|
90aa3557b7 | ||
|
|
8891167183 | ||
|
|
6631e645df | ||
|
|
b7f202969b | ||
|
|
1dc3353ecc | ||
|
|
b1a3f1a7b8 | ||
|
|
89f3a26635 | ||
|
|
506fe9b1af | ||
|
|
37c898d3ce | ||
|
|
e4eac5714a | ||
|
|
9d4a59ca16 | ||
|
|
8ec524061a | ||
|
|
434ed46b5a | ||
|
|
3ece5e0ba2 | ||
|
|
52a383ffb7 | ||
|
|
8ad1d5929a | ||
|
|
445c01499b | ||
|
|
0b05be841d | ||
|
|
6bcb5f2af5 | ||
|
|
7482751c5b | ||
|
|
7a025fff42 | ||
|
|
602fe36638 | ||
|
|
da08fc74f1 | ||
|
|
14af45baf0 | ||
|
|
420a88c505 | ||
|
|
f3c44f8dd1 | ||
|
|
c72111e18b | ||
|
|
1ca2870912 | ||
|
|
db9744e1e5 | ||
|
|
e700095551 | ||
|
|
99196a77ed | ||
|
|
289a8222a0 | ||
|
|
dc10f8d35b | ||
|
|
f448232fe7 | ||
|
|
4baf4b4afa | ||
|
|
1785093023 | ||
|
|
0d65f8d894 | ||
|
|
3d8c390291 | ||
|
|
f0a13856bc | ||
|
|
ad6394783d | ||
|
|
16e9843298 | ||
|
|
a4c949c376 | ||
|
|
0e0d5316b7 | ||
|
|
60cb6ee8fb | ||
|
|
7d3279d21a | ||
|
|
402d13ad99 | ||
|
|
e803f5a2d4 | ||
|
|
3989d0f1e2 | ||
|
|
161d476ab3 | ||
|
|
ce84a89cf3 | ||
|
|
1d210a9e52 | ||
|
|
660f463aea | ||
|
|
ad9f01111c | ||
|
|
1b885e4114 | ||
|
|
b56742bcb2 | ||
|
|
40d4d69a9a | ||
|
|
b54d5beb76 | ||
|
|
5cbcd440f5 | ||
|
|
1cdc34249a | ||
|
|
8bbb218777 | ||
|
|
755ac7f657 | ||
|
|
d7b884ff86 | ||
|
|
fda3620ed0 | ||
|
|
61ad9f0d58 | ||
|
|
804c8ad40a | ||
|
|
0ddf009f8f | ||
|
|
14309b5c96 | ||
|
|
57d9de469a | ||
|
|
404ade396c | ||
|
|
bdad6da6d9 | ||
|
|
e7ef3b94c1 | ||
|
|
c19c92ab7e | ||
|
|
944229bae3 | ||
|
|
661b05d9e3 | ||
|
|
4d1d471a66 | ||
|
|
3e84fbbaf4 | ||
|
|
c4e59c1a5e | ||
|
|
9ee8e9007a | ||
|
|
4d52a5c44a | ||
|
|
fff9073f9d | ||
|
|
71f0b8a005 | ||
|
|
d2b965f79e | ||
|
|
a02aa71a95 | ||
|
|
7562bf5c95 | ||
|
|
f9521483e2 | ||
|
|
ca85858885 | ||
|
|
c7b3a94533 | ||
|
|
b010c9ecb0 | ||
|
|
0b98d04bac | ||
|
|
e25e388e59 | ||
|
|
10654b7916 | ||
|
|
ff049ad3e8 | ||
|
|
3572fff2c1 | ||
|
|
774e4cdf94 | ||
|
|
65ade5cab8 | ||
|
|
bf21a073fb | ||
|
|
851f1c265f | ||
|
|
51f3b5927b | ||
|
|
42c779f368 | ||
|
|
c104ee4fd9 | ||
|
|
0b2efa31b5 | ||
|
|
17baa8fcb2 | ||
|
|
e550baf59d | ||
|
|
fd53eb92e6 | ||
|
|
6521c0bfb8 | ||
|
|
50489dd682 | ||
|
|
220afa5add | ||
|
|
9d549b02fe | ||
|
|
ca2ded047b | ||
|
|
c423e61ce8 | ||
|
|
65550c7bf4 | ||
|
|
cdb4da9e5f | ||
|
|
b59a5303c6 | ||
|
|
9a72abbed0 | ||
|
|
762a5ff01b | ||
|
|
2f6571e703 | ||
|
|
80c90f1819 | ||
|
|
4b0c2c60a2 | ||
|
|
ee7d7d2f6a | ||
|
|
0ad670013b | ||
|
|
064a66aa66 | ||
|
|
f46c19b152 | ||
|
|
ea2c583b26 | ||
|
|
7bd2a90141 | ||
|
|
632549dd9d | ||
|
|
70ef274821 | ||
|
|
7d9759fda4 | ||
|
|
0fd06d5e4e | ||
|
|
4995e44e06 | ||
|
|
fd43d7d56d | ||
|
|
96f292f61c | ||
|
|
e899e4545f | ||
|
|
c62f5e3911 | ||
|
|
9f1d184081 | ||
|
|
29e596eedb | ||
|
|
6e28afda67 | ||
|
|
eca7e9fba1 | ||
|
|
aa2ac2b296 | ||
|
|
ce57752b95 | ||
|
|
63163c6459 | ||
|
|
2712ee5c0b | ||
|
|
2e28a4a790 | ||
|
|
4179943df6 | ||
|
|
8bb8eee384 | ||
|
|
85d79a8d7f | ||
|
|
d38dd67c2f | ||
|
|
e73d082eab | ||
|
|
2637538d9a | ||
|
|
0eacf9a840 | ||
|
|
eb58274f90 | ||
|
|
aa410d6847 | ||
|
|
97f1be9d6f | ||
|
|
a7c9dde5e3 | ||
|
|
8ec5bc049b | ||
|
|
1f2bec06ef | ||
|
|
1cad57bec2 | ||
|
|
10f3c01677 | ||
|
|
fc68b669db | ||
|
|
6e22091b81 | ||
|
|
545db54c14 | ||
|
|
aa69fef9ba | ||
|
|
1e30642d28 | ||
|
|
c1dfba949e | ||
|
|
dfa9592755 | ||
|
|
8f2b1f0f78 | ||
|
|
cf91ec14c0 | ||
|
|
891aa649e8 | ||
|
|
dfe0f99bea | ||
|
|
591022c097 | ||
|
|
a8a0167123 | ||
|
|
c76da54b93 | ||
|
|
186f6101ff | ||
|
|
e95cf0a026 | ||
|
|
3eaa3a0189 | ||
|
|
0ba445aad2 | ||
|
|
637c718b05 | ||
|
|
a01019b1b3 | ||
|
|
f6e2393a40 | ||
|
|
715857a587 | ||
|
|
186ad2d7a2 | ||
|
|
c64c49ab6e | ||
|
|
3e067e2103 | ||
|
|
4cc1f77b14 | ||
|
|
334dd5fe94 | ||
|
|
d73f37cacc | ||
|
|
c6a7e3dcb1 | ||
|
|
4bdc02d22c | ||
|
|
afa8e04008 | ||
|
|
990c1bb2bf | ||
|
|
a5a59c526c | ||
|
|
2d5bef4b7b | ||
|
|
5893b1e3b3 | ||
|
|
0af452b8a4 | ||
|
|
3d82198c90 | ||
|
|
01bca360c7 | ||
|
|
59e994ac29 | ||
|
|
d5f3105341 | ||
|
|
92920f69d4 | ||
|
|
61ac520192 | ||
|
|
45176cc2e2 | ||
|
|
7a515c35d2 | ||
|
|
1942103985 | ||
|
|
4b55719f86 | ||
|
|
5387ff207c | ||
|
|
e3804b103b |
1
.gitignore
vendored
@@ -112,3 +112,4 @@ firebase/.env
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
logs/oAuthClient-log.log
|
||||
|
||||
@@ -2,86 +2,87 @@
|
||||
"name": "bodyshop",
|
||||
"version": "0.1.1",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:5000",
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.4.10",
|
||||
"@craco/craco": "^6.2.0",
|
||||
"@apollo/client": "^3.4.17",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^6.4.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.0",
|
||||
"@lourenci/react-kanban": "^2.1.0",
|
||||
"@openreplay/tracker": "^3.3.1",
|
||||
"@openreplay/tracker-assist": "^3.1.1",
|
||||
"@openreplay/tracker": "^3.4.7",
|
||||
"@openreplay/tracker-assist": "^3.4.4",
|
||||
"@openreplay/tracker-graphql": "^3.0.0",
|
||||
"@openreplay/tracker-redux": "^3.0.0",
|
||||
"@sentry/react": "^6.11.0",
|
||||
"@sentry/tracing": "^6.11.0",
|
||||
"@stripe/react-stripe-js": "^1.4.0",
|
||||
"@stripe/stripe-js": "^1.17.1",
|
||||
"@tanem/react-nprogress": "^3.0.79",
|
||||
"@sentry/react": "^6.14.3",
|
||||
"@sentry/tracing": "^6.14.3",
|
||||
"@splitsoftware/splitio-react": "^1.3.0",
|
||||
"@stripe/react-stripe-js": "^1.6.0",
|
||||
"@stripe/stripe-js": "^1.21.1",
|
||||
"@tanem/react-nprogress": "^3.0.82",
|
||||
"antd": "^4.16.13",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^0.24.0",
|
||||
"craco-less": "^1.20.0",
|
||||
"dinero.js": "^1.9.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.0.0",
|
||||
"graphql": "^15.5.3",
|
||||
"i18next": "^20.4.0",
|
||||
"firebase": "^9.4.1",
|
||||
"graphql": "^16.0.1",
|
||||
"i18next": "^21.4.2",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"jsoneditor": "^9.5.4",
|
||||
"jsoneditor": "^9.5.7",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.26",
|
||||
"logrocket": "^2.0.0",
|
||||
"markerjs2": "^2.11.2",
|
||||
"libphonenumber-js": "^1.9.42",
|
||||
"logrocket": "^2.1.1",
|
||||
"markerjs2": "^2.17.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^3.1.6",
|
||||
"phone": "^3.1.9",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^7.0.1",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.1",
|
||||
"react-big-calendar": "^0.35.0",
|
||||
"react-big-calendar": "^0.38.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-drag-listview": "^0.1.8",
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.3.0",
|
||||
"react-i18next": "^11.11.4",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-number-format": "^4.7.3",
|
||||
"react-redux": "^7.2.5",
|
||||
"react-i18next": "^11.14.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-number-format": "^4.8.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.2",
|
||||
"redux": "^4.1.1",
|
||||
"recharts": "^2.1.6",
|
||||
"redux": "^4.1.2",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.38.2",
|
||||
"socket.io-client": "^4.2.0",
|
||||
"styled-components": "^5.3.1",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"web-vitals": "^2.1.0",
|
||||
"workbox-background-sync": "^6.2.4",
|
||||
"workbox-broadcast-update": "^6.2.4",
|
||||
"workbox-cacheable-response": "^6.2.4",
|
||||
"workbox-core": "^6.2.4",
|
||||
"workbox-expiration": "^6.2.4",
|
||||
"workbox-google-analytics": "^6.2.4",
|
||||
"workbox-navigation-preload": "^6.2.4",
|
||||
"workbox-precaching": "^6.2.4",
|
||||
"workbox-range-requests": "^6.2.4",
|
||||
"workbox-routing": "^6.2.4",
|
||||
"workbox-strategies": "^6.2.4",
|
||||
"workbox-streams": "^6.2.4"
|
||||
"reselect": "^4.1.2",
|
||||
"sass": "^1.43.4",
|
||||
"socket.io-client": "^4.3.2",
|
||||
"styled-components": "^5.3.3",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"web-vitals": "^2.1.2",
|
||||
"workbox-background-sync": "^6.3.0",
|
||||
"workbox-broadcast-update": "^6.3.0",
|
||||
"workbox-cacheable-response": "^6.3.0",
|
||||
"workbox-core": "^6.3.0",
|
||||
"workbox-expiration": "^6.3.0",
|
||||
"workbox-google-analytics": "^6.3.0",
|
||||
"workbox-navigation-preload": "^6.3.0",
|
||||
"workbox-precaching": "^6.3.0",
|
||||
"workbox-range-requests": "^6.3.0",
|
||||
"workbox-routing": "^6.3.0",
|
||||
"workbox-strategies": "^6.3.0",
|
||||
"workbox-streams": "^6.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
@@ -114,7 +115,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.17.1",
|
||||
"@sentry/webpack-plugin": "^1.18.3",
|
||||
"patch-package": "^6.4.7",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
//import trackerRedux from "@openreplay/tracker-redux";
|
||||
import Tracker from "@openreplay/tracker";
|
||||
import trackerGraphQL from "@openreplay/tracker-graphql";
|
||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import LogRocket from "logrocket";
|
||||
@@ -6,12 +10,11 @@ import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
//import trackerAssist from "@openreplay/tracker-assist";
|
||||
import { getCurrentUser } from "../firebase/firebase.utils";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import trackerGraphQL from "@openreplay/tracker-graphql";
|
||||
//import trackerRedux from "@openreplay/tracker-redux";
|
||||
import Tracker from "@openreplay/tracker";
|
||||
//import trackerAssist from "@openreplay/tracker-assist";
|
||||
|
||||
moment.locale("en-US");
|
||||
|
||||
export const tracker = new Tracker({
|
||||
@@ -21,7 +24,10 @@ export const tracker = new Tracker({
|
||||
? { __DISABLE_SECURE_MODE: true }
|
||||
: {}),
|
||||
// beaconSize: 10485760,
|
||||
onStart: ({ sessionID }) => console.log("ORS SESSION ", sessionID),
|
||||
onStart: async ({ sessionID }) => {
|
||||
const user = await getCurrentUser();
|
||||
if (user) tracker.setUserID(user.email);
|
||||
},
|
||||
});
|
||||
|
||||
// tracker.use(
|
||||
@@ -31,6 +37,13 @@ export const recordGraphQL = tracker.use(trackerGraphQL());
|
||||
tracker.start();
|
||||
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
|
||||
export const factory = SplitSdk({
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
});
|
||||
|
||||
export default function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -48,7 +61,9 @@ export default function AppContainer() {
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<App />
|
||||
<SplitFactory factory={factory}>
|
||||
<App />
|
||||
</SplitFactory>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,6 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
window.addEventListener("offline", function (e) {
|
||||
console.log("Internet connection lost.");
|
||||
setOnline(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -124,3 +124,13 @@
|
||||
z-index: 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.react-kanban-column {
|
||||
background-color: #ddd !important;
|
||||
}
|
||||
|
||||
.production-list-table {
|
||||
td.ant-table-column-sort {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
|
||||
24
client/src/assets/C2QB_composite_English.svg
Normal file
|
After Width: | Height: | Size: 51 KiB |
24
client/src/assets/C2QB_transparent_English.svg
Normal file
|
After Width: | Height: | Size: 51 KiB |
4
client/src/assets/qbo/C2QB_green_btn_med_default.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
5
client/src/assets/qbo/C2QB_green_btn_med_hover.svg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
4
client/src/assets/qbo/C2QB_green_btn_short_default.svg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
5
client/src/assets/qbo/C2QB_green_btn_short_hover.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
4
client/src/assets/qbo/C2QB_green_btn_tall_default.svg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
5
client/src/assets/qbo/C2QB_green_btn_tall_hover.svg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
5
client/src/assets/qbo/C2QB_transparent_btn_med_hover.svg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
@@ -9,8 +9,25 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import queryString from "query-string";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AccountingPayablesTableComponent);
|
||||
|
||||
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedBills, setSelectedBills] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
@@ -166,6 +183,9 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedBills}
|
||||
/>
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -8,8 +8,26 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
export default function AccountingPayablesTableComponent({
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AccountingPayablesTableComponent);
|
||||
|
||||
export function AccountingPayablesTableComponent({
|
||||
bodyshop,
|
||||
loading,
|
||||
payments,
|
||||
}) {
|
||||
@@ -163,6 +181,9 @@ export default function AccountingPayablesTableComponent({
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedPayments}
|
||||
/>
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -11,6 +11,7 @@ import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-butto
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -198,7 +199,7 @@ export function AccountingReceivablesTableComponent({
|
||||
<Card
|
||||
extra={
|
||||
<Space wrap>
|
||||
{!bodyshop.cdk_dealerid && (
|
||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
@@ -206,6 +207,9 @@ export function AccountingReceivablesTableComponent({
|
||||
completedCallback={setSelectedJobs}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input.Search
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -18,8 +18,10 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -48,7 +50,31 @@ function BillEnterModalContainer({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const client = useApolloClient();
|
||||
|
||||
const formValues = useMemo(() => {
|
||||
return {
|
||||
...billEnterModal.context.bill,
|
||||
jobid:
|
||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||
federal_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
|
||||
0,
|
||||
state_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
|
||||
0,
|
||||
local_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
|
||||
0,
|
||||
};
|
||||
}, [billEnterModal, bodyshop]);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
let totals = CalculateBillTotal(values);
|
||||
if (totals.discrepancy.getAmount() !== 0) {
|
||||
if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const { upload, location, ...remainingValues } = values;
|
||||
|
||||
@@ -190,7 +216,7 @@ function BillEnterModalContainer({
|
||||
|
||||
if (enterAgain) {
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ billlines: [] });
|
||||
form.setFieldsValue({ ...form.getFieldsValue(), billlines: [] });
|
||||
} else {
|
||||
toggleModalVisible();
|
||||
}
|
||||
@@ -208,23 +234,6 @@ function BillEnterModalContainer({
|
||||
if (enterAgain) form.submit();
|
||||
}, [enterAgain, form]);
|
||||
|
||||
const formValues = useMemo(() => {
|
||||
return {
|
||||
...billEnterModal.context.bill,
|
||||
jobid:
|
||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||
federal_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
|
||||
0,
|
||||
state_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
|
||||
0,
|
||||
local_tax_rate:
|
||||
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
|
||||
0,
|
||||
};
|
||||
}, [billEnterModal, bodyshop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (billEnterModal.visible) {
|
||||
form.setFieldsValue(formValues);
|
||||
|
||||
@@ -156,18 +156,7 @@ export function BillEnterModalLinesComponent({
|
||||
setFieldsValue({
|
||||
billlines: getFieldsValue("billlines").billlines.map(
|
||||
(item, idx) => {
|
||||
console.log("Checking", index, idx);
|
||||
if (idx === index) {
|
||||
console.log(
|
||||
"Found and setting.",
|
||||
!!item.actual_cost
|
||||
? item.actual_cost
|
||||
: Math.round(
|
||||
(parseFloat(e.target.value) * (1 - discount) +
|
||||
Number.EPSILON) *
|
||||
100
|
||||
) / 100
|
||||
);
|
||||
return {
|
||||
...item,
|
||||
actual_cost: !!item.actual_cost
|
||||
@@ -502,9 +491,9 @@ const EditableCell = ({
|
||||
labelCol={{ span: 0 }}
|
||||
{...(formItemProps && formItemProps(record))}
|
||||
>
|
||||
{(formInput && formInput(record, record.key)) || children}
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.key)}
|
||||
{additional && additional(record, record.name)}
|
||||
</Space>
|
||||
</td>
|
||||
);
|
||||
@@ -516,7 +505,7 @@ const EditableCell = ({
|
||||
name={dataIndex}
|
||||
{...(formItemProps && formItemProps(record))}
|
||||
>
|
||||
{(formInput && formInput(record, record.key)) || children}
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,18 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
optionFilterProp="line_desc"
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
return (
|
||||
(option.line_desc &&
|
||||
option.line_desc
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.oem_partno &&
|
||||
option.oem_partno.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
);
|
||||
}}
|
||||
notFoundContent={"Removed."}
|
||||
{...restProps}
|
||||
>
|
||||
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
|
||||
@@ -21,14 +32,19 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
|
||||
{options
|
||||
? options.map((item) => (
|
||||
<Option
|
||||
disabled={item.removed}
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
cost={item.act_price ? item.act_price : 0}
|
||||
part_type={item.part_type}
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
||||
}}
|
||||
>
|
||||
{`${item.line_desc}${
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}`}
|
||||
</Option>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
@@ -60,13 +61,18 @@ export function ChatConversationListComponent({
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
<div sryle={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
CellMeasurerCache,
|
||||
List,
|
||||
} from "react-virtualized";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import "./chat-message-list.styles.scss";
|
||||
|
||||
export default function ChatMessageListComponent({ messages }) {
|
||||
@@ -85,17 +87,22 @@ export default function ChatMessageListComponent({ messages }) {
|
||||
|
||||
const MessageRender = (message) => {
|
||||
return (
|
||||
<div>
|
||||
{message.image_path &&
|
||||
message.image_path.map((i, idx) => (
|
||||
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<a href={i} target="__blank">
|
||||
<img alt="Received" className="message-img" src={i} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
<div>{message.text}</div>
|
||||
</div>
|
||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
||||
<div>
|
||||
{message.image_path &&
|
||||
message.image_path.map((i, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{ display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
<a href={i} target="__blank">
|
||||
<img alt="Received" className="message-img" src={i} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
<div>{message.text}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ export function ChatNewConversation({ openChatByPhone }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const handleFinish = (values) => {
|
||||
console.log("values :>> ", values);
|
||||
openChatByPhone({ phone_num: values.phoneNumber });
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ShrinkOutlined } from "@ant-design/icons";
|
||||
import { Col, Row, Typography } from "antd";
|
||||
import { ShrinkOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Col, Row, Tooltip, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -31,6 +31,9 @@ export function ChatPopupComponent({
|
||||
{t("messaging.labels.messaging")}
|
||||
</Typography.Title>
|
||||
<ChatNewConversation />
|
||||
<Tooltip title={t("messaging.labels.recentonly")}>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ShrinkOutlined
|
||||
onClick={() => toggleChatVisible()}
|
||||
|
||||
@@ -53,6 +53,7 @@ export function ContractConvertToRo({
|
||||
const billingLines = [];
|
||||
if (contractLength > 0)
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 1,
|
||||
line_no: 1,
|
||||
line_ref: 1,
|
||||
@@ -70,6 +71,7 @@ export function ContractConvertToRo({
|
||||
contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength;
|
||||
if (mileageDiff > 0) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 2,
|
||||
line_no: 2,
|
||||
line_ref: 2,
|
||||
@@ -86,6 +88,7 @@ export function ContractConvertToRo({
|
||||
|
||||
if (values.refuelqty > 0) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 3,
|
||||
line_no: 3,
|
||||
line_ref: 3,
|
||||
@@ -101,6 +104,7 @@ export function ContractConvertToRo({
|
||||
}
|
||||
if (values.applyCleanupCharge) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 4,
|
||||
line_no: 4,
|
||||
line_ref: 4,
|
||||
@@ -117,6 +121,7 @@ export function ContractConvertToRo({
|
||||
if (contract.damagewaiver) {
|
||||
//Add for cleanup fee.
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 5,
|
||||
line_no: 5,
|
||||
line_ref: 5,
|
||||
|
||||
@@ -42,7 +42,6 @@ export default function ContractFormComponent({
|
||||
<ContractStatusSelector />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t("contracts.fields.start")}
|
||||
name="start"
|
||||
|
||||
@@ -31,6 +31,9 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
|
||||
<Option value="courtesycars.status.out">
|
||||
{t("courtesycars.status.out")}
|
||||
</Option>
|
||||
<Option value="courtesycars.status.sold">
|
||||
{t("courtesycars.status.sold")}
|
||||
</Option>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -45,6 +45,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
text: t("courtesycars.status.out"),
|
||||
value: "courtesycars.status.out",
|
||||
},
|
||||
{
|
||||
text: t("courtesycars.status.sold"),
|
||||
value: "courtesycars.status.sold",
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
sortOrder:
|
||||
|
||||
@@ -86,10 +86,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
const handleRemoveComponent = (key) => {
|
||||
logImEXEvent("dashboard_remove_component", { name: key });
|
||||
const idxToRemove = state.items.findIndex((i) => i.i === key);
|
||||
console.log(
|
||||
"🚀 ~ file: dashboard-grid.component.jsx ~ line 81 ~ idxToRemove",
|
||||
idxToRemove
|
||||
);
|
||||
|
||||
const items = _.cloneDeep(state.items);
|
||||
|
||||
items.splice(idxToRemove, 1);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Button } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -19,11 +19,11 @@ export function DmsCdkMakesRefetch({ bodyshop, form, socket }) {
|
||||
const { t } = useTranslation();
|
||||
const handleRefetch = async () => {
|
||||
setLoading(true);
|
||||
const response = await axios.post("/cdk/getvehicles", {
|
||||
await axios.post("/cdk/getvehicles", {
|
||||
cdk_dealerid: bodyshop.cdk_dealerid,
|
||||
bodyshopid: bodyshop.id,
|
||||
});
|
||||
console.log(response);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Table, Col , Checkbox} from "antd";
|
||||
import { Button, Table, Col, Checkbox } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -23,15 +23,26 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
const [dmsType, setDmsType] = useState("cdk");
|
||||
|
||||
socket.on("cdk-select-customer", (customerList, callback) => {
|
||||
setVisible(true);
|
||||
setDmsType("cdk");
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
socket.on("pbs-select-customer", (customerList, callback) => {
|
||||
setVisible(true);
|
||||
setDmsType("pbs");
|
||||
setcustomerList(customerList);
|
||||
console.log(
|
||||
"🚀 ~ file: dms-customer-selector.component.jsx ~ line 37 ~ socket.on ~ customerList",
|
||||
customerList
|
||||
);
|
||||
});
|
||||
|
||||
const onUseSelected = () => {
|
||||
setVisible(false);
|
||||
socket.emit("cdk-selected-customer", selectedCustomer);
|
||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
@@ -50,7 +61,7 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
setSelectedCustomer(null);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
const cdkColumns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.id"),
|
||||
dataIndex: ["id", "value"],
|
||||
@@ -60,13 +71,14 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
title: t("jobs.fields.dms.vinowner"),
|
||||
dataIndex: "vinOwner",
|
||||
key: "vinOwner",
|
||||
render: (text, record) => <Checkbox disabled checked={record.vinOwner}/>
|
||||
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.name1"),
|
||||
dataIndex: ["name1", "fullName"],
|
||||
key: "name1",
|
||||
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
|
||||
sorter: (a, b) =>
|
||||
alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName),
|
||||
},
|
||||
|
||||
{
|
||||
@@ -74,11 +86,43 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
//dataIndex: ["name2", "fullName"],
|
||||
key: "address",
|
||||
render: (record, value) =>
|
||||
`${record?.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
|
||||
`${record.address && record.address.addressLine[0]}, ${
|
||||
record.address && record.address.city
|
||||
} ${record.address && record.address.stateOrProvince} ${
|
||||
record.address && record.address.postalCode
|
||||
}`,
|
||||
},
|
||||
];
|
||||
|
||||
if (!visible) return <></>;
|
||||
const pbsColumns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.id"),
|
||||
dataIndex: "ContactId",
|
||||
key: "ContactId",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.vinowner"),
|
||||
dataIndex: "vinOwner",
|
||||
key: "vinOwner",
|
||||
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.name1"),
|
||||
key: "name1",
|
||||
sorter: (a, b) => alphaSort(a.LastName, b.LastName),
|
||||
render: (text, record) =>
|
||||
`${record.FirstName || ""} ${record.LastName || ""}`,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.dms.address"),
|
||||
key: "address",
|
||||
render: (record, value) =>
|
||||
`${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`,
|
||||
},
|
||||
];
|
||||
|
||||
if (!visible) return null;
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Table
|
||||
@@ -104,13 +148,17 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.id.value}
|
||||
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
|
||||
rowKey={(record) =>
|
||||
dmsType === "cdk" ? record.id.value : record.ContactId
|
||||
}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (props) => {
|
||||
setSelectedCustomer(props.id.value);
|
||||
onSelect: (record) => {
|
||||
setSelectedCustomer(
|
||||
dmsType === "cdk" ? record.id.value : record.ContactId
|
||||
);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCustomer],
|
||||
|
||||
@@ -27,7 +27,7 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
|
||||
@@ -119,33 +119,35 @@ export function DmsPostForm({ bodyshop, socket, job }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow style={{ justifyContent: "center" }} grow>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model"
|
||||
label={t("jobs.fields.dms.dms_model")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
{bodyshop.cdk_dealerid && (
|
||||
<LayoutFormRow style={{ justifyContent: "center" }} grow>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model"
|
||||
label={t("jobs.fields.dms.dms_model")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
</LayoutFormRow>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<Form.Item
|
||||
name="story"
|
||||
label={t("jobs.fields.dms.story")}
|
||||
@@ -157,6 +159,7 @@ export function DmsPostForm({ bodyshop, socket, job }) {
|
||||
>
|
||||
<Input.TextArea maxLength={240} />
|
||||
</Form.Item>
|
||||
|
||||
<Divider />
|
||||
<Form.List name={["payers"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
@@ -259,7 +262,6 @@ export function DmsPostForm({ bodyshop, socket, job }) {
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
disabled={!(fields.length < 3)}
|
||||
onClick={() => {
|
||||
if (fields.length < 3) add();
|
||||
|
||||
@@ -58,11 +58,9 @@ export function DocumentEditorComponent({ currentUser, bodyshop, document }) {
|
||||
if (imgRef.current !== null) {
|
||||
// create a marker.js MarkerArea
|
||||
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
|
||||
console.log(`markerArea.current`, markerArea.current);
|
||||
|
||||
// attach an event handler to assign annotated image back to our image element
|
||||
markerArea.current.addCloseEventListener((closeEvent) => {
|
||||
console.log("Close Event", closeEvent);
|
||||
});
|
||||
markerArea.current.addCloseEventListener((closeEvent) => {});
|
||||
|
||||
markerArea.current.addRenderEventListener((dataUrl) => {
|
||||
imgRef.current.src = dataUrl;
|
||||
|
||||
@@ -14,8 +14,6 @@ var cleanAxios = axios.create();
|
||||
cleanAxios.interceptors.request.eject(axiosAuthInterceptorId);
|
||||
|
||||
export const handleUpload = (ev, context) => {
|
||||
console.log("Handling Upload", ev);
|
||||
|
||||
logImEXEvent("document_upload", { filetype: ev.file.type });
|
||||
|
||||
const { onError, onSuccess, onProgress } = ev;
|
||||
@@ -61,7 +59,7 @@ export const uploadToCloudinary = async (
|
||||
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
||||
|
||||
//Get the signed url.
|
||||
console.log("fileType", fileType);
|
||||
|
||||
const upload_preset = fileType.startsWith("video")
|
||||
? "incoming_upload_video"
|
||||
: "incoming_upload";
|
||||
@@ -74,7 +72,6 @@ export const uploadToCloudinary = async (
|
||||
});
|
||||
|
||||
if (signedURLResponse.status !== 200) {
|
||||
console.log("Error Getting Signed URL", signedURLResponse.statusText);
|
||||
if (!!onError) onError(signedURLResponse.statusText);
|
||||
notification["error"]({
|
||||
message: i18n.t("documents.errors.getpresignurl", {
|
||||
@@ -113,13 +110,8 @@ export const uploadToCloudinary = async (
|
||||
...options,
|
||||
}
|
||||
);
|
||||
console.log("Upload Response", cloudinaryUploadResponse.data);
|
||||
|
||||
if (cloudinaryUploadResponse.status !== 200) {
|
||||
console.log(
|
||||
"Error uploading to cloudinary.",
|
||||
cloudinaryUploadResponse.statusText
|
||||
);
|
||||
if (!!onError) onError(cloudinaryUploadResponse.statusText);
|
||||
notification["error"]({
|
||||
message: i18n.t("documents.errors.insert", {
|
||||
|
||||
@@ -35,10 +35,6 @@ export function EmailDocumentsComponent({
|
||||
},
|
||||
skip: !emailConfig.jobid,
|
||||
});
|
||||
console.log(
|
||||
"🚀 ~ file: email-documents.component.jsx ~ line 38 ~ emailConfig",
|
||||
emailConfig
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1,15 +1,71 @@
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { Divider, Form, Input, Select, Tabs, Upload } from "antd";
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Tabs,
|
||||
Upload,
|
||||
Space,
|
||||
Menu,
|
||||
Dropdown,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
import _ from "lodash";
|
||||
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
|
||||
)(EmailOverlayComponent);
|
||||
|
||||
export default function EmailOverlayComponent({ form, selectedMediaState }) {
|
||||
export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({ to: _.uniq([...form.getFieldValue("to"), email]) });
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("emails.fields.to")}
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.to")}
|
||||
<Dropdown overlay={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="to"
|
||||
rules={[
|
||||
{
|
||||
|
||||
@@ -51,7 +51,9 @@ export function EmailOverlayContainer({
|
||||
|
||||
const defaultEmailFrom = {
|
||||
from: {
|
||||
name: `${currentUser.displayName} @ ${bodyshop.shopname}`,
|
||||
name: currentUser.displayName
|
||||
? `${currentUser.displayName} @ ${bodyshop.shopname}`
|
||||
: bodyshop.shopname,
|
||||
address: EmailSettings.fromAddress,
|
||||
},
|
||||
ReplyTo: {
|
||||
@@ -109,7 +111,6 @@ export function EmailOverlayContainer({
|
||||
notification["success"]({ message: t("emails.successes.sent") });
|
||||
toggleEmailOverlayVisible();
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify(error));
|
||||
notification["error"]({
|
||||
message: t("emails.errors.notsent", { message: error.message }),
|
||||
});
|
||||
@@ -147,6 +148,18 @@ export function EmailOverlayContainer({
|
||||
html: response.data,
|
||||
fileList: [],
|
||||
});
|
||||
|
||||
if (
|
||||
bodyshop.md_email_cc[emailConfig.template.name] &&
|
||||
bodyshop.md_email_cc[emailConfig.template.name].length > 0
|
||||
) {
|
||||
form.setFieldsValue({
|
||||
cc: [
|
||||
...(form.getFieldValue("cc") || []),
|
||||
...bodyshop.md_email_cc[emailConfig.template.name],
|
||||
],
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import moment from "moment";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
});
|
||||
@@ -19,14 +21,24 @@ export function EmailTestComponent({ currentUser, setEmailOptions }) {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleFinish = (values) => {
|
||||
console.log("values", values);
|
||||
GenerateDocument(
|
||||
{
|
||||
name: values.key,
|
||||
variables: {
|
||||
...(values.start
|
||||
? {
|
||||
start: moment(values.start).startOf("day").format("YYYY-MM-DD"),
|
||||
}
|
||||
: {}),
|
||||
...(values.end
|
||||
? { end: moment(values.end).endOf("day").format("YYYY-MM-DD") }
|
||||
: {}),
|
||||
...(values.start
|
||||
? { starttz: moment(values.start).startOf("day") }
|
||||
: {}),
|
||||
...(values.end ? { endtz: moment(values.end).endOf("day") } : {}),
|
||||
|
||||
...(values.id ? { id: values.id } : {}),
|
||||
...(values.start ? { start: values.start } : {}),
|
||||
...(values.end ? { end: values.end } : {}),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { tracker } from "../../App/App.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -36,6 +37,7 @@ class ErrorBoundary extends React.Component {
|
||||
componentDidCatch(error, info) {
|
||||
console.log("Exception Caught by Error Boundary.", error, info);
|
||||
this.setState({ ...this.state, error, info });
|
||||
tracker.event("error_boundary", error, true);
|
||||
}
|
||||
|
||||
handleErrorSubmit = () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ export const PhoneItemFormatterValidation = (getFieldValue, name) => ({
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
const p = parsePhoneNumber(value, "CA");
|
||||
if (p.isValid()) {
|
||||
if (p && p.isValid()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(i18n.t("general.validation.invalidphone"));
|
||||
|
||||
@@ -62,13 +62,16 @@ export default function GlobalSearch() {
|
||||
}`,
|
||||
label: (
|
||||
<Link to={`/manage/owners/${owner.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<Space size="small" split={<Divider type="vertical" />} wrap>
|
||||
<span>{`${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
|
||||
owner.ownr_co_nm || ""
|
||||
}`}</span>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph1}
|
||||
</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph2}
|
||||
</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
|
||||
@@ -46,12 +46,16 @@ import {
|
||||
} from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
recentItems: selectRecentItems,
|
||||
selectedHeader: selectSelectedHeader,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -69,6 +73,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
function Header({
|
||||
handleMenuClick,
|
||||
currentUser,
|
||||
bodyshop,
|
||||
selectedHeader,
|
||||
signOutStart,
|
||||
setBillEnterContext,
|
||||
@@ -237,16 +242,26 @@ function Header({
|
||||
{t("menus.header.accounting-receivables")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="payables">
|
||||
<Link to="/manage/accounting/payables">
|
||||
{t("menus.header.accounting-payables")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="payments">
|
||||
<Link to="/manage/accounting/payments">
|
||||
{t("menus.header.accounting-payments")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
) && (
|
||||
<Menu.Item key="payables">
|
||||
<Link to="/manage/accounting/payables">
|
||||
{t("menus.header.accounting-payables")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
) && (
|
||||
<Menu.Item key="payments">
|
||||
<Link to="/manage/accounting/payments">
|
||||
{t("menus.header.accounting-payments")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key="export-logs">
|
||||
<Link to="/manage/accounting/exportlogs">
|
||||
{t("menus.header.export-logs")}
|
||||
|
||||
@@ -10,15 +10,11 @@ export default function HelpRescue() {
|
||||
var bodyFormData = new FormData();
|
||||
bodyFormData.append("Code", code);
|
||||
bodyFormData.append("hostederrorhandling", 1);
|
||||
const res1 = await fetch(
|
||||
"https://secure.logmeinrescue.com/Customer/Code.aspx",
|
||||
{
|
||||
mode: "no-cors",
|
||||
method: "POST",
|
||||
body: bodyFormData,
|
||||
}
|
||||
);
|
||||
console.log("handleClick -> res1", await res1.text());
|
||||
await fetch("https://secure.logmeinrescue.com/Customer/Code.aspx", {
|
||||
mode: "no-cors",
|
||||
method: "POST",
|
||||
body: bodyFormData,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -40,7 +36,6 @@ export default function HelpRescue() {
|
||||
method="post"
|
||||
id="logmeinsupport"
|
||||
onSubmit={(...props) => {
|
||||
console.log(`props`, props);
|
||||
alert();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -44,7 +44,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
|
||||
};
|
||||
const handleFinish = (values) => {
|
||||
const { sendtype, ...restVals } = values;
|
||||
console.log(restVals);
|
||||
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("job_special").thirdpartypayer.key,
|
||||
|
||||
@@ -56,7 +56,7 @@ export function ScheduleEventColor({ bodyshop, event }) {
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
console.log(`event`, event);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
|
||||
@@ -106,6 +106,12 @@ export function ScheduleEventComponent({
|
||||
jobid={event.job.id}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ph2")}>
|
||||
<ChatOpenButton
|
||||
phone={event.job && event.job.ownr_ph2}
|
||||
jobid={event.job.id}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
@@ -170,7 +176,7 @@ export function ScheduleEventComponent({
|
||||
t("appointments.labels.reminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
date: moment(event.start).format("MM/DD/YYYY"),
|
||||
time: moment(event.start).format("HH:MM a"),
|
||||
time: moment(event.start).format("HH:mm a"),
|
||||
})
|
||||
);
|
||||
setVisible(false);
|
||||
@@ -203,6 +209,9 @@ export function ScheduleEventComponent({
|
||||
jobId: event.job.id,
|
||||
job: event.job,
|
||||
previousEvent: event.id,
|
||||
color: event.color,
|
||||
alt_transport: event.job && event.job.alt_transport,
|
||||
note: event.note,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -55,7 +55,6 @@ export default function JobBillsTotalComponent({
|
||||
);
|
||||
}
|
||||
if (il.deductedfromlbr) {
|
||||
console.log(i, "Deducting from labor.");
|
||||
lbrAdjustments = lbrAdjustments.add(
|
||||
Dinero({
|
||||
amount: Math.round((il.actual_price || 0) * 100),
|
||||
|
||||
@@ -167,7 +167,11 @@ export function JobChecklistForm({
|
||||
title={t("checklist.labels.checklist")}
|
||||
extra={
|
||||
!readOnly && (
|
||||
<Button loading={loading} onClick={() => form.submit()}>
|
||||
<Button
|
||||
loading={loading}
|
||||
type="primary"
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t("general.actions.submit")}
|
||||
</Button>
|
||||
)
|
||||
@@ -182,12 +186,14 @@ export function JobChecklistForm({
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
),
|
||||
(job.labbrs && job.larhrs
|
||||
? moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
)
|
||||
: null),
|
||||
scheduled_delivery: job && job.scheduled_delivery,
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function JobIntakeTemplateList({ templates }) {
|
||||
const renderAllTemplates = async () => {
|
||||
logImEXEvent("checklist_render_all_templates");
|
||||
setLoading(true);
|
||||
console.log("templates :>> ", templates);
|
||||
|
||||
await GenerateDocuments(
|
||||
templates.map((key) => {
|
||||
return { name: key, variables: { id: jobId } };
|
||||
|
||||
@@ -35,7 +35,7 @@ export function JobCostingModalContainer({
|
||||
async function getData() {
|
||||
if (jobId && visible) {
|
||||
const { data } = await axios.post("/job/costing", { jobid: jobId });
|
||||
console.log(data);
|
||||
|
||||
setCostingData(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,6 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
||||
.includes(searchText.toLowerCase())
|
||||
);
|
||||
|
||||
console.log("data :>> ", data);
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
|
||||
@@ -112,60 +112,46 @@ export function JobDetailCards({ setPrintCenterContext }) {
|
||||
<Divider type="horizontal" />
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Timeline } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
@@ -26,80 +26,86 @@ export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
) ? (
|
||||
<div>{t("jobs.errors.nodates")}</div>
|
||||
) : null}
|
||||
{data.date_last_contacted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_open ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateFormatter>{data.date_open}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_estimated ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateFormatter>{data.date_estimated}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_scheduled ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateFormatter>{data.date_scheduled}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateFormatter>{data.scheduled_in}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateFormatter>{data.actual_in}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateFormatter>{data.scheduled_completion}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateFormatter>{data.actual_completion}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateFormatter>{data.scheduled_delivery}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateFormatter>{data.actual_delivery}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_invoiced ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateFormatter>{data.date_invoiced}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_exported ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateFormatter>{data.date_exported}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
</Timeline>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Carousel } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,15 +22,7 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
{data.documents.length > 0 ? (
|
||||
<Carousel autoplay>
|
||||
{data.documents.map((item) => (
|
||||
<img
|
||||
key={item.id}
|
||||
src={`${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(item.type)}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${item.key}`}
|
||||
alt={item.name}
|
||||
/>
|
||||
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
|
||||
@@ -12,11 +12,13 @@ export default function JobDetailCardTemplate({
|
||||
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
|
||||
return (
|
||||
<Card
|
||||
size='small'
|
||||
className='job-card'
|
||||
size="small"
|
||||
className="job-card"
|
||||
title={title}
|
||||
loading={loading}
|
||||
{...extra}>
|
||||
style={{ height: "100%" }}
|
||||
{...extra}
|
||||
>
|
||||
{otherProps.children}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
|
||||
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import _ from "lodash";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -161,7 +162,11 @@ export function JobLinesComponent({
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<>
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
<CurrencyFormatter>
|
||||
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||
? record.prt_dsmk_m
|
||||
: record.act_price}
|
||||
</CurrencyFormatter>
|
||||
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||
<span
|
||||
style={{ marginLeft: ".2rem" }}
|
||||
@@ -295,18 +300,17 @@ export function JobLinesComponent({
|
||||
onClick={async () => {
|
||||
await deleteJobLine({
|
||||
variables: { joblineId: record.id },
|
||||
// update(cache) {
|
||||
// cache.modify({
|
||||
// id: cache.identify(job),
|
||||
// fields: {
|
||||
// joblines(existingJobLines, { readField }) {
|
||||
// return existingJobLines.filter(
|
||||
// (jlRef) => record.id !== readField("id", jlRef)
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
joblines(existingJobLines, { readField }) {
|
||||
return existingJobLines.filter(
|
||||
(jlRef) => record.id !== readField("id", jlRef)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
await axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
@@ -334,10 +338,12 @@ export function JobLinesComponent({
|
||||
const markedTypes = [e.key];
|
||||
if (e.key === "PAN") markedTypes.push("PAP");
|
||||
if (e.key === "PAS") markedTypes.push("PASL");
|
||||
setSelectedLines([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
]);
|
||||
setSelectedLines(
|
||||
_.uniq([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -400,7 +406,7 @@ export function JobLinesComponent({
|
||||
setState({
|
||||
...state,
|
||||
filteredInfo: {
|
||||
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL"],
|
||||
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL,PAG"],
|
||||
},
|
||||
});
|
||||
}}
|
||||
@@ -435,7 +441,7 @@ export function JobLinesComponent({
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={false}
|
||||
dataSource={jobLines}
|
||||
onChange={handleTableChange}
|
||||
scroll={{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, Input, InputNumber, Modal, Select } from "antd";
|
||||
import { Form, Input, InputNumber, Modal, Select, Switch } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InputCurrency from "../form-items-formatted/currency-form-item.component";
|
||||
@@ -184,9 +184,9 @@ export default function JobLinesUpsertModalComponent({
|
||||
>
|
||||
<InputNumber precision={0} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.db_price")} name="db_price">
|
||||
{/* <Form.Item label={t("joblines.fields.db_price")} name="db_price">
|
||||
<InputCurrency precision={2} min={0} />
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
label={t("joblines.fields.act_price")}
|
||||
name="act_price"
|
||||
@@ -222,6 +222,14 @@ export default function JobLinesUpsertModalComponent({
|
||||
>
|
||||
<InputNumber precision={0} min={0} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.tax_part")}
|
||||
name="tax_part"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -44,6 +44,7 @@ function JobLinesUpsertModalContainer({
|
||||
},
|
||||
],
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
||||
});
|
||||
if (!r.errors) {
|
||||
await Axios.post("/job/totalsssu", {
|
||||
@@ -69,6 +70,7 @@ function JobLinesUpsertModalContainer({
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: values,
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
||||
});
|
||||
if (!r.errors) {
|
||||
notification["success"]({
|
||||
|
||||
@@ -21,7 +21,8 @@ export default function JobReconciliationBillsTable({
|
||||
title: t("billlines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
width: "35%",
|
||||
ellipsis: true,
|
||||
minWidth: "65rem",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
@@ -30,7 +31,7 @@ export default function JobReconciliationBillsTable({
|
||||
title: t("billlines.labels.from"),
|
||||
dataIndex: "from",
|
||||
key: "from",
|
||||
width: "20%",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) =>
|
||||
`${record.bill.vendor && record.bill.vendor.name} / ${
|
||||
@@ -42,6 +43,7 @@ export default function JobReconciliationBillsTable({
|
||||
dataIndex: "actual_price",
|
||||
key: "actual_price",
|
||||
sorter: (a, b) => a.actual_price - b.actual_price,
|
||||
width: "7rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_price" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
@@ -53,6 +55,7 @@ export default function JobReconciliationBillsTable({
|
||||
dataIndex: "actual_cost",
|
||||
key: "actual_cost",
|
||||
sorter: (a, b) => a.actual_cost - b.actual_cost,
|
||||
width: "7rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_cost" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
@@ -64,6 +67,7 @@ export default function JobReconciliationBillsTable({
|
||||
dataIndex: "quantity",
|
||||
key: "quantity",
|
||||
sorter: (a, b) => a.quantity - b.quantity,
|
||||
width: "4rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
|
||||
},
|
||||
@@ -72,9 +76,11 @@ export default function JobReconciliationBillsTable({
|
||||
dataIndex: "is_credit_memo",
|
||||
key: "is_credit_memo",
|
||||
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
|
||||
width: "8rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "is_credit_memo" &&
|
||||
state.sortedInfo.order,
|
||||
|
||||
render: (text, record) => (
|
||||
<Checkbox disabled checked={record.bill.is_credit_memo} />
|
||||
),
|
||||
@@ -94,7 +100,7 @@ export default function JobReconciliationBillsTable({
|
||||
<Table
|
||||
pagination={false}
|
||||
size="small"
|
||||
scroll={{ y: "80vh", x: true }}
|
||||
scroll={{ y: "60vh" }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={invoiceLineData}
|
||||
@@ -102,6 +108,9 @@ export default function JobReconciliationBillsTable({
|
||||
rowSelection={{
|
||||
onChange: handleOnRowClick,
|
||||
selectedRowKeys: selectedLines,
|
||||
getCheckboxProps: (record) => {
|
||||
return { disabled: record.deductedfromlbr };
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.imex-reconciliation-modal {
|
||||
top: 20px;
|
||||
.ant-modal-content {
|
||||
height: 95vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.ant-modal-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
// .ant-modal-content {
|
||||
// height: 95vh;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// .ant-modal-body {
|
||||
// display: flex;
|
||||
// flex: 1;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function JobReconcilitionPartsTable({
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
ellipses: true,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
},
|
||||
@@ -57,6 +58,7 @@ export default function JobReconcilitionPartsTable({
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
sorter: (a, b) => a.act_price - b.act_price,
|
||||
width: "7rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||
|
||||
@@ -68,10 +70,12 @@ export default function JobReconcilitionPartsTable({
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
width: "4rem",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.total"),
|
||||
dataIndex: "total",
|
||||
width: "7rem",
|
||||
key: "total",
|
||||
sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty,
|
||||
sortOrder:
|
||||
@@ -89,6 +93,7 @@ export default function JobReconcilitionPartsTable({
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
width: "6rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
},
|
||||
@@ -108,7 +113,7 @@ export default function JobReconcilitionPartsTable({
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
size="small"
|
||||
scroll={{ y: "80vh", x: true }}
|
||||
scroll={{ y: "60vh" }}
|
||||
rowKey="id"
|
||||
dataSource={jobLineData}
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const reconcileByAssocLine = (
|
||||
const [selectedJobLines, setSelectedJobLines] = jobLineState;
|
||||
|
||||
const allJoblinesFromBills = billLines
|
||||
.filter((bl) => bl.joblineid && !(bl.jobline && bl.jobline.removed))
|
||||
.filter((bl) => bl.joblineid && bl.jobline && !bl.jobline.removed)
|
||||
.map((bl) => bl.joblineid);
|
||||
|
||||
const duplicatedJobLinesbyInvoiceId = _.filter(
|
||||
@@ -73,7 +73,7 @@ export const reconcileByPrice = (
|
||||
|
||||
jobLines.forEach((jl) => {
|
||||
const matchingBillLineIds = billLines
|
||||
.filter((bl) => bl.actual_price === jl.act_price && !jl.removed)
|
||||
.filter((bl) => bl.actual_price === jl.act_price && bl.quantity === jl.part_qty && !jl.removed)
|
||||
.map((bl) => bl.id);
|
||||
|
||||
if (matchingBillLineIds.length > 1) {
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, InputNumber, notification, Popover } from "antd";
|
||||
import { useMutation, useLazyQuery } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
||||
import {
|
||||
INSERT_SCOREBOARD_ENTRY,
|
||||
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";
|
||||
|
||||
export default function ScoreboardAddButton({
|
||||
job,
|
||||
@@ -14,17 +27,44 @@ export default function ScoreboardAddButton({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY);
|
||||
const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
|
||||
QUERY_SCOREBOARD_ENTRY
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visibility) {
|
||||
callQuery({ variables: { jobid: job.id } });
|
||||
}
|
||||
}, [visibility, job.id, callQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
|
||||
form.setFieldsValue(entryData.scoreboard[0]);
|
||||
}
|
||||
}, [entryData, form]);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_close_add_to_scoreboard");
|
||||
|
||||
setLoading(true);
|
||||
const result = await insertScoreboardEntry({
|
||||
variables: { sbInput: [{ jobid: job.id, ...values }] },
|
||||
});
|
||||
let result;
|
||||
|
||||
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
|
||||
result = await updateScoreboardEntry({
|
||||
variables: {
|
||||
sbId: entryData.scoreboard[0].id,
|
||||
sbInput: values,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
result = await insertScoreboardEntry({
|
||||
variables: { sbInput: [{ jobid: job.id, ...values }] },
|
||||
});
|
||||
}
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
@@ -44,53 +84,62 @@ export default function ScoreboardAddButton({
|
||||
const overlay = (
|
||||
<Card>
|
||||
<div>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{}}
|
||||
>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
{entryLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{}}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
name="bodyhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.painthrs")}
|
||||
name="painthrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
name="bodyhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.painthrs")}
|
||||
name="painthrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Form>
|
||||
<Space wrap>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisibility(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
@@ -99,7 +148,7 @@ export default function ScoreboardAddButton({
|
||||
setLoading(true);
|
||||
const v = job.joblines.reduce(
|
||||
(acc, val) => {
|
||||
if (val.mod_lbr_ty === "LAB")
|
||||
if (val.mod_lbr_ty !== "LAR")
|
||||
acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs };
|
||||
if (val.mod_lbr_ty === "LAR")
|
||||
acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs };
|
||||
|
||||
@@ -22,7 +22,6 @@ export function JobsAdminClass({ bodyshop, job }) {
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
console.log(values);
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: values },
|
||||
|
||||
@@ -13,7 +13,6 @@ export default function JobsAdminDatesChange({ job }) {
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
console.log(values);
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: values },
|
||||
@@ -74,7 +73,12 @@ export default function JobsAdminDatesChange({ job }) {
|
||||
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_last_contacted")}
|
||||
name="date_last_contacted"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
name="scheduled_completion"
|
||||
|
||||
@@ -10,7 +10,6 @@ export default function JobAdminOwnerReassociate({ job }) {
|
||||
const [form] = Form.useForm();
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const handleFinish = async (values) => {
|
||||
console.log(values);
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: { ownerid: values.ownerid } },
|
||||
|
||||
@@ -10,7 +10,6 @@ export default function JobAdminOwnerReassociate({ job }) {
|
||||
const [form] = Form.useForm();
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
const handleFinish = async (values) => {
|
||||
console.log(values);
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: { vehicleid: values.vehicleid } },
|
||||
|
||||
@@ -3,8 +3,6 @@ import { gql } from "@apollo/client";
|
||||
import _ from "lodash";
|
||||
|
||||
export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
console.log("-----Begin Supplement-----");
|
||||
|
||||
const {
|
||||
data: { joblines: existingLinesFromDb },
|
||||
} = await client.query({
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||
@@ -98,8 +99,8 @@ export function JobsAvailableContainer({
|
||||
return;
|
||||
}
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(estData, bodyshop);
|
||||
|
||||
await CheckTaxRates(estData.est_data, bodyshop);
|
||||
console.log(estData);
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: {
|
||||
@@ -139,16 +140,17 @@ export function JobsAvailableContainer({
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (selectedOwner) {
|
||||
newJob.ownerid = selectedOwner;
|
||||
delete newJob.owner;
|
||||
}
|
||||
if (newJob.vehicleid) {
|
||||
delete newJob.vehicle;
|
||||
}
|
||||
|
||||
insertNewJob({
|
||||
variables: {
|
||||
job: selectedOwner
|
||||
? Object.assign(
|
||||
{},
|
||||
newJob,
|
||||
{ owner: null },
|
||||
{ ownerid: selectedOwner }
|
||||
)
|
||||
: newJob,
|
||||
job: newJob,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
@@ -198,11 +200,10 @@ export function JobsAvailableContainer({
|
||||
message: t("jobs.errors.creating", { error: "No job data present." }),
|
||||
});
|
||||
} else {
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(estData, bodyshop);
|
||||
|
||||
//create upsert job
|
||||
let supp = replaceEmpty({ ...estData.est_data });
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(supp, bodyshop);
|
||||
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
@@ -390,109 +391,119 @@ function replaceEmpty(someObj, replaceValue = null) {
|
||||
value === "" ? replaceValue || null : value;
|
||||
//^ because you seem to want to replace (strings) "null" or "undefined" too
|
||||
const temp = JSON.stringify(someObj, replacer);
|
||||
console.log("Parsed", JSON.parse(temp));
|
||||
return JSON.parse(temp);
|
||||
}
|
||||
|
||||
function confirmDialog(msg) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let confirmed = window.confirm(msg);
|
||||
|
||||
return confirmed ? resolve(true) : resolve(false);
|
||||
});
|
||||
}
|
||||
|
||||
async function CheckTaxRates(estData, bodyshop) {
|
||||
//LKQ Check
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAL ||
|
||||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAL ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAL) {
|
||||
estData.est_data.parts_tax_rates.PAL = {
|
||||
if (!estData.parts_tax_rates.PAL) {
|
||||
estData.parts_tax_rates.PAL = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAL",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAL.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAC Check
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAC ||
|
||||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAC ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAC) {
|
||||
estData.est_data.parts_tax_rates.PAC = {
|
||||
if (!estData.parts_tax_rates.PAC) {
|
||||
estData.parts_tax_rates.PAC = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAC",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAC.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAC.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAM Check
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAM ||
|
||||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAM ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAM) {
|
||||
estData.est_data.parts_tax_rates.PAM = {
|
||||
if (!estData.parts_tax_rates.PAM) {
|
||||
estData.parts_tax_rates.PAM = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAM",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAM.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAM.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAR ||
|
||||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAR ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAR) {
|
||||
estData.est_data.parts_tax_rates.PAR = {
|
||||
if (!estData.parts_tax_rates.PAR) {
|
||||
estData.parts_tax_rates.PAR = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAR",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAR.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAR.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
|
||||
//Currently limited to SK shops only.
|
||||
//if (bodyshop.region_config === "CA_SK") {
|
||||
estData.joblines.data.forEach((jl, index) => {
|
||||
if (
|
||||
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
|
||||
jl.lbr_op !== "OP11"
|
||||
) {
|
||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||
}
|
||||
|
||||
//Set markup lines and tax lines as taxable.
|
||||
//900510 is a mark up. 900510 is a discount.
|
||||
if (jl.db_ref === "900510") {
|
||||
estData.joblines.data[index].tax_part = true;
|
||||
}
|
||||
});
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
);
|
||||
};
|
||||
|
||||
const overlay = bodyshop.cdk_dealerid && (
|
||||
const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
|
||||
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
|
||||
@@ -69,7 +69,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return bodyshop.cdk_dealerid ? (
|
||||
return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
|
||||
<Dropdown overlay={overlay}>
|
||||
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
|
||||
</Dropdown>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -34,55 +35,69 @@ export function JobsCloseExportButton({
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleQbxml = async () => {
|
||||
if (bodyshop.cdk_dealerid) {
|
||||
//Check if it's a CDK setup.
|
||||
if (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) {
|
||||
history.push(`/manage/dms?jobId=${jobId}`);
|
||||
return;
|
||||
}
|
||||
logImEXEvent("jobs_close_export");
|
||||
|
||||
setLoading(true);
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [jobId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if it's a QBO Setup.
|
||||
let PartnerResponse;
|
||||
try {
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
`/qbo/receivables`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
jobIds: [jobId],
|
||||
},
|
||||
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [jobId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("PartnerResponse", PartnerResponse);
|
||||
|
||||
@@ -31,6 +31,7 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
<th>{t("joblines.fields.line_desc")}</th>
|
||||
<th>{t("joblines.fields.part_type")}</th>
|
||||
<th>{t("joblines.fields.act_price")}</th>
|
||||
<th>{t("joblines.fields.prt_dsmk_m")}</th>
|
||||
<th>{t("joblines.fields.op_code_desc")}</th>
|
||||
<th>{t("joblines.fields.mod_lbr_ty")}</th>
|
||||
<th>{t("joblines.fields.mod_lb_hrs")}</th>
|
||||
@@ -70,6 +71,16 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
<ReadOnlyFormItem type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
// label={t("joblines.fields.prt_dsmk_m")}
|
||||
key={`${index}prt_dsmk_m`}
|
||||
name={[field.name, "prt_dsmk_m"]}
|
||||
>
|
||||
<ReadOnlyFormItem type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
@@ -108,7 +119,9 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
labelCol={{ span: 0 }}
|
||||
rules={[
|
||||
{
|
||||
required: !!job.joblines[index].act_price,
|
||||
required:
|
||||
!!job.joblines[index].act_price ||
|
||||
!!job.joblines[index].prt_dsmk_m,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
@@ -34,6 +35,7 @@ export function JobsConvertButton({
|
||||
refetch,
|
||||
jobRO,
|
||||
insertAuditTrail,
|
||||
parentFormIsFieldsTouched,
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -42,6 +44,10 @@ export function JobsConvertButton({
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleConvert = async (values) => {
|
||||
if (parentFormIsFieldsTouched()) {
|
||||
alert(t("jobs.labels.savebeforeconversion"));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const res = await mutationConvertJob({
|
||||
variables: { jobId: job.id, ...values },
|
||||
@@ -112,24 +118,32 @@ export function JobsConvertButton({
|
||||
</Form.Item>
|
||||
)}
|
||||
{bodyshop.enforce_referral && (
|
||||
<Form.Item
|
||||
name={"referral_source"}
|
||||
label={t("jobs.fields.referralsource")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.enforce_referral,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_referral_sources.map((s) => (
|
||||
<Select.Option key={s} value={s}>
|
||||
{s}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<>
|
||||
<Form.Item
|
||||
name={"referral_source"}
|
||||
label={t("jobs.fields.referralsource")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.enforce_referral,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_referral_sources.map((s) => (
|
||||
<Select.Option key={s} value={s}>
|
||||
{s}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
label={t("jobs.fields.ca_gst_registrant")}
|
||||
|
||||
@@ -150,6 +150,9 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -181,6 +184,12 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel
|
||||
@@ -191,7 +200,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<JobsMarkPstExempt form={form} />
|
||||
<LayoutFormRow>
|
||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||
<Select>
|
||||
|
||||
@@ -129,6 +129,19 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph2")}
|
||||
name={["owner", "data", "ownr_ph2"]}
|
||||
rules={[
|
||||
({ getFieldValue }) =>
|
||||
PhoneItemFormatterValidation(
|
||||
getFieldValue,
|
||||
"owner.data.ownr_ph2"
|
||||
),
|
||||
]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
|
||||
@@ -84,6 +84,18 @@ export default function JobsCreateOwnerInfoSearchComponent({
|
||||
tableState.sortedInfo.columnKey === "ownr_ph1" &&
|
||||
tableState.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
),
|
||||
sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2),
|
||||
sortOrder:
|
||||
tableState.sortedInfo.columnKey === "ownr_ph2" &&
|
||||
tableState.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const est = item.props.value;
|
||||
form.setFieldsValue(est);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_estimators.map((est, idx) => (
|
||||
<Menu.Item value={est} key={idx}>
|
||||
{`${est.est_ct_fn} ${est.est_ct_ln}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailChangeEstimator);
|
||||
@@ -0,0 +1,43 @@
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const est = item.props.value;
|
||||
form.setFieldsValue(est);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_filehandlers.map((est, idx) => (
|
||||
<Menu.Item value={est} key={idx}>
|
||||
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailChangeFilehandler);
|
||||
@@ -69,6 +69,18 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
</Tooltip>
|
||||
</FormRow>
|
||||
<FormRow header={t("jobs.forms.repairdates")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_last_contacted")}
|
||||
name="date_last_contacted"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_next_contact")}
|
||||
name="date_next_contact"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
name="scheduled_completion"
|
||||
@@ -93,7 +105,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_delivery")}
|
||||
name="scheduled_delivery"
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Col, Form, Input, InputNumber, Row, Select, Switch } from "antd";
|
||||
import {
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -12,8 +22,9 @@ import FormItemPhone, {
|
||||
PhoneItemFormatterValidation,
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -44,7 +55,14 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
||||
<Select disabled={jobRO}>
|
||||
{bodyshop.md_ded_notes.map((n, index) => (
|
||||
<Select.Option key={index}>{n}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||
<Input disabled={jobRO} />
|
||||
@@ -68,7 +86,15 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ct_ln")} name="ins_ct_ln">
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("jobs.fields.ins_ct_ln")}
|
||||
<JobsDetailChangeFileHandler form={form} disabled={jobRO} />
|
||||
</Space>
|
||||
}
|
||||
name="ins_ct_ln"
|
||||
>
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
|
||||
@@ -114,6 +140,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
||||
<Select disabled={jobRO} allowClear>
|
||||
{bodyshop.appt_alt_transport.map((s) => (
|
||||
@@ -133,6 +165,9 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||
<InputNumber precision={0} min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
@@ -185,12 +220,27 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider
|
||||
orientation="left"
|
||||
type="horizontal"
|
||||
style={{ marginTop: ".8rem", float: "right" }}
|
||||
>
|
||||
{t("jobs.forms.appraiserinfo")}
|
||||
</Divider>
|
||||
|
||||
<FormRow header={t("jobs.forms.appraiserinfo")}>
|
||||
<FormRow noDivider>
|
||||
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ct_fn")} name="est_ct_fn">
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("jobs.fields.est_ct_fn")}
|
||||
<JobsDetailChangeEstimator form={form} disabled={jobRO} />
|
||||
</Space>
|
||||
}
|
||||
name="est_ct_fn"
|
||||
>
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln">
|
||||
|
||||
@@ -101,6 +101,7 @@ export function JobsDetailHeaderActions({
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
alt_transport: job.alt_transport,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function JobsDetailHeaderCsi({
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList("job").csi_invitation.key,
|
||||
name: TemplateList("job_special").csi_invitation_action.key,
|
||||
variables: {
|
||||
id: result.data.insert_csi.returning[0].id,
|
||||
},
|
||||
@@ -147,7 +147,7 @@ export function JobsDetailHeaderCsi({
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList("job").csi_invitation.key,
|
||||
name: TemplateList("job_special").csi_invitation_action.key,
|
||||
variables: {
|
||||
id: job.csiinvites[0].id,
|
||||
},
|
||||
|
||||
@@ -19,7 +19,6 @@ export default async function DuplicateJob(
|
||||
variables: { id: jobId },
|
||||
});
|
||||
|
||||
console.log("res", res);
|
||||
const { jobs_by_pk } = res.data;
|
||||
const existingJob = _.cloneDeep(jobs_by_pk);
|
||||
delete existingJob.__typename;
|
||||
|
||||
@@ -21,47 +21,57 @@ export function JobsDetailHeaderActionexportCustomerData({
|
||||
|
||||
const handleExportCustData = async (e) => {
|
||||
logImEXEvent("job_export_cust_data");
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [job.id], custDataOnly: true },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
jobIds: [job.id],
|
||||
custDataOnly: true,
|
||||
});
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
return;
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [job.id], custDataOnly: true },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Check to see if any of them failed. If they didn't don't execute the update.
|
||||
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||
|
||||
@@ -16,6 +16,7 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -70,12 +71,21 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
{job.production_vars && job.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
||||
job.scheduled_in ? (
|
||||
<Tag>
|
||||
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>
|
||||
{job.ins_co_nm}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
|
||||
{job.po_number}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.repairtotal")}>
|
||||
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
||||
<span style={{ margin: "0rem .5rem" }}>/</span>
|
||||
@@ -138,6 +148,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel key="3" label={t("owners.fields.address")}>
|
||||
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
||||
job.ownr_city || ""
|
||||
|
||||
@@ -38,7 +38,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
label={t("jobs.fields.depreciation_taxes")}
|
||||
name="depreciation_taxes"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
|
||||
<Form.Item
|
||||
@@ -47,6 +47,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
>
|
||||
<CurrencyInput
|
||||
disabled={jobRO}
|
||||
min={0}
|
||||
max={
|
||||
Math.round(
|
||||
(job.job_totals &&
|
||||
@@ -61,19 +62,19 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
label={t("jobs.fields.other_amount_payable")}
|
||||
name="other_amount_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.towing_payable")}
|
||||
name="towing_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.storage_payable")}
|
||||
name="storage_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.adjustment_bottom_line")}
|
||||
@@ -83,7 +84,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
</Form.Item>
|
||||
<Space align="end">
|
||||
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<CABCpvrtCalculator form={form} disabled={jobRO} />
|
||||
</Space>
|
||||
@@ -123,7 +124,6 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
{t("jobs.forms.laborrates")}
|
||||
</Divider>
|
||||
<Space>
|
||||
<div></div>
|
||||
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
|
||||
<JobsMarkPstExempt form={form} />
|
||||
</Space>
|
||||
@@ -135,72 +135,72 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
{
|
||||
// <Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
|
||||
// <CurrencyInput disabled={jobRO} />
|
||||
// <CurrencyInput min={0}disabled={jobRO} />
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
|
||||
// <CurrencyInput disabled={jobRO} />
|
||||
// <CurrencyInput min={0}disabled={jobRO} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
<JobsDetailRatesParts form={form} />
|
||||
|
||||
@@ -40,7 +40,6 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
|
||||
|
||||
const updateImage = async (i, jobid) => {
|
||||
//Move the cloudinary image
|
||||
console.log(i);
|
||||
|
||||
//Update it in the database.
|
||||
const result = await updateDocument({
|
||||
|
||||
@@ -29,20 +29,6 @@ function JobsDocumentsComponent({
|
||||
setIndex(index);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Added event listening for reteching.");
|
||||
// window.addEventListener("storage", (ev) => {
|
||||
// if (ev.key === "refetch" && ev.newValue === true) {
|
||||
// refetch && refetch();
|
||||
// localStorage.setItem("refetch", false);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// window.removeEventListener("storage");
|
||||
// };
|
||||
// }, [refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
let documents = data.reduce(
|
||||
(acc, value) => {
|
||||
|
||||
@@ -34,53 +34,60 @@ export function JobsExportAllButton({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleQbxml = async () => {
|
||||
logImEXEvent("jobs_export_all");
|
||||
|
||||
setLoading(true);
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: jobIds },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
setLoading(true);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
jobIds: jobIds,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: jobIds },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log("PartnerResponse", PartnerResponse);
|
||||
const groupedData = _.groupBy(PartnerResponse.data, "id");
|
||||
const groupedData = _.groupBy(
|
||||
PartnerResponse.data,
|
||||
bodyshop.accountingconfig.qbo ? "jobid" : "id"
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(groupedData).map(async (key) => {
|
||||
@@ -157,16 +164,12 @@ export function JobsExportAllButton({
|
||||
|
||||
if (!!completedCallback) completedCallback([]);
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.export")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -69,6 +69,20 @@ export default function JobsFindModalComponent({
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.ownr_ph2 ? (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
|
||||
@@ -73,6 +73,16 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
@@ -130,13 +140,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.clm_no ? (
|
||||
<span>{record.clm_no}</span>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
},
|
||||
render: (text, record) =>
|
||||
`${record.clm_no || ""}${
|
||||
record.po_number ? ` (PO: ${record.po_number})` : ""
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
|
||||
@@ -145,6 +145,16 @@ export function JobsList({ bodyshop }) {
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
@@ -208,6 +218,10 @@ export function JobsList({ bodyshop }) {
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.clm_no || ""}${
|
||||
record.po_number ? ` (PO: ${record.po_number})` : ""
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
|
||||
@@ -3,15 +3,28 @@ import { notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
//import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
DELETE_NOTE,
|
||||
QUERY_NOTES_BY_JOB_PK,
|
||||
} from "../../graphql/notes.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import JobNotesComponent from "./jobs.notes.component";
|
||||
|
||||
export default function JobNotesContainer({ jobId }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer);
|
||||
|
||||
export function JobNotesContainer({ jobId, insertAuditTrail }) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_NOTES_BY_JOB_PK, {
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only",
|
||||
@@ -32,6 +45,10 @@ export default function JobNotesContainer({ jobId }) {
|
||||
notification["success"]({
|
||||
message: t("notes.successes.deleted"),
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: jobId,
|
||||
operation: AuditTrailMapping.jobnotedeleted(),
|
||||
});
|
||||
});
|
||||
setDeleteLoading(false);
|
||||
};
|
||||
@@ -46,6 +63,7 @@ export default function JobNotesContainer({ jobId }) {
|
||||
refetch={refetch}
|
||||
deleteLoading={deleteLoading}
|
||||
handleNoteDelete={handleNoteDelete}
|
||||
ro_number={data ? data.jobs_by_pk.ro_number : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||