- Merge client update into test-beta

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-18 19:20:08 -05:00
696 changed files with 92291 additions and 107075 deletions

View File

@@ -183,6 +183,26 @@ jobs:
- jira/notify
app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://imex-online-beta/"
- jira/notify
test-hasura-migrate:
docker:
- image: cimg/node:16.15.0
@@ -233,6 +253,27 @@ jobs:
to: "s3://imex-online-test/"
- jira/notify
test-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test
- aws-s3/sync:
from: build
to: "s3://imex-online-test-beta/"
- jira/notify
admin-app-build:
docker:
- image: cimg/node:16.15.0
@@ -274,6 +315,10 @@ workflows:
filters:
branches:
only: master
- app-beta-build:
filters:
branches:
only: master-beta
- hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
@@ -296,6 +341,10 @@ workflows:
filters:
branches:
only: test
- test-app-beta-build:
filters:
branches:
only: test-beta
- test-hasura-migrate:
secret: ${HASURA_TEST_SECRET}
filters:

1
client/.npmrc Normal file
View File

@@ -0,0 +1 @@
legacy-peer-deps=true

View File

@@ -2,6 +2,14 @@
const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
const {convertLegacyToken} = require('@ant-design/compatible/lib');
const {theme} = require('antd/lib');
const {defaultAlgorithm, defaultSeed} = theme;
const mapToken = defaultAlgorithm(defaultSeed);
const v4Token = convertLegacyToken(mapToken);
module.exports = {
plugins: [
@@ -26,8 +34,10 @@ module.exports = {
lessLoaderOptions: {
lessOptions: {
modifyVars: {
...v4Token,
// TODO: This will no longer work in AntD 5.0
...(process.env.NODE_ENV === "development"
? { "@primary-color": "#B22234" }
? {"colorPrimary": "#B22234"}
: {
//"@primary-color": "#1DA57A"
}),
@@ -53,8 +63,14 @@ module.exports = {
},
],
webpack: {
configure: (webpackConfig) => ({
configure: (webpackConfig) => {
return {
...webpackConfig,
// Required for Dev Server
devServer: {
...webpackConfig.devServer,
allowedHosts: 'all',
},
optimization: {
...webpackConfig.optimization,
// Workaround for CircleCI bug caused by the number of CPUs shown
@@ -67,7 +83,8 @@ module.exports = {
return item;
}),
},
}),
};
},
},
devtool: "source-map",
};

17
client/cypress.config.js Normal file
View File

@@ -0,0 +1,17 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
experimentalStudio: true,
env: {
FIREBASE_USERNAME: 'cypress@imex.test',
FIREBASE_PASSWORD: 'cypress',
},
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:3000',
},
})

View File

@@ -1,8 +0,0 @@
{
"baseUrl": "http://localhost:3000",
"experimentalStudio": true,
"env": {
"FIREBASE_USERNAME": "cypress@imex.test",
"FIREBASE_PASSWORD": "cypress"
}
}

View File

@@ -8872,13 +8872,13 @@
│ ├─ email: luis@luisrudge.net
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes/LICENSE
├─ postcss-focus-visible@4.0.0
├─ postcss-focus-open@4.0.0
│ ├─ licenses: CC0-1.0
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-visible
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-open
│ ├─ publisher: Jonathan Neal
│ ├─ email: jonathantneal@hotmail.com
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible/LICENSE.md
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open/LICENSE.md
├─ postcss-focus-within@3.0.0
│ ├─ licenses: CC0-1.0
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-within

15965
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,85 +4,83 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.7.9",
"@ant-design/compatible": "^5.1.2",
"@ant-design/pro-layout": "^7.17.16",
"@apollo/client": "^3.8.10",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@craco/craco": "^7.1.0",
"@fingerprintjs/fingerprintjs": "^4.2.1",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.40.0",
"@splitsoftware/splitio-react": "^1.8.1",
"@tanem/react-nprogress": "^5.0.8",
"antd": "^4.24.8",
"@reduxjs/toolkit": "^2.0.1",
"@sentry/react": "^7.93.0",
"@sentry/tracing": "^7.93.0",
"@splitsoftware/splitio-react": "^1.11.0",
"@tanem/react-nprogress": "^5.0.51",
"antd": "^5.12.8",
"apollo-link-logger": "^2.0.1",
"axios": "^1.3.4",
"craco-less": "^2.0.0",
"axios": "^1.6.5",
"craco-less": "^3.0.1",
"dayjs": "^1.11.10",
"dayjs-business-days2": "^1.2.2",
"dinero.js": "^1.9.1",
"dotenv": "^16.0.1",
"dotenv": "^16.3.1",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.17.1",
"firebase": "^10.7.2",
"graphql": "^16.6.0",
"i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1",
"jsoneditor": "^9.9.0",
"i18next": "^23.7.16",
"i18next-browser-languagedetector": "^7.0.2",
"jsoneditor": "^10.0.0",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.21",
"logrocket": "^3.0.1",
"markerjs2": "^2.28.1",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.41",
"libphonenumber-js": "^1.10.53",
"logrocket": "^7.0.0",
"markerjs2": "^2.31.4",
"normalize-url": "^8.0.0",
"phone": "^3.1.35",
"phone": "^3.1.42",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.3",
"query-string": "^8.1.0",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^1.6.8",
"react": "^18.2.0",
"react-big-calendar": "^1.8.6",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.2.1",
"react-cookie": "^7.0.1",
"react-dom": "^18.2.0",
"react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.0",
"react-grid-layout": "^1.3.4",
"react-i18next": "^12.2.0",
"react-icons": "^4.7.1",
"react-grid-layout": "1.3.4",
"react-i18next": "^14.0.0",
"react-icons": "^5.0.1",
"react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.4.3",
"react-number-format": "^5.1.3",
"react-redux": "^8.0.5",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
"react-intersection-observer": "^9.5.3",
"react-number-format": "^5.1.4",
"react-redux": "^9.1.0",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.21.3",
"react-scripts": "^5.0.1",
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.4.3",
"redux": "^4.2.1",
"react-virtualized": "^9.22.5",
"recharts": "^2.10.4",
"redux": "^5.0.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4",
"reselect": "^4.1.7",
"sass": "^1.58.3",
"socket.io-client": "^4.6.1",
"styled-components": "^5.3.6",
"reselect": "^5.1.0",
"sass": "^1.70.0",
"socket.io-client": "^4.7.4",
"styled-components": "^6.1.8",
"subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.5.3",
"workbox-expiration": "^6.5.3",
"workbox-google-analytics": "^6.5.3",
"workbox-navigation-preload": "^6.5.3",
"workbox-precaching": "^6.5.3",
"workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3",
"terser-webpack-plugin": "^5.3.10",
"web-vitals": "^3.5.1",
"workbox-core": "^7.0.0",
"workbox-expiration": "^7.0.0",
"workbox-navigation-preload": "^7.0.0",
"workbox-precaching": "^7.0.0",
"workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0",
"yauzl": "^2.10.0"
},
"scripts": {
@@ -119,12 +117,13 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.20.0",
"@testing-library/cypress": "^8.0.3",
"cypress": "^10.3.1",
"eslint-plugin-cypress": "^2.12.1",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@sentry/webpack-plugin": "^2.10.2",
"@testing-library/cypress": "^10.0.1",
"cypress": "^13.6.3",
"eslint-plugin-cypress": "^2.15.1",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
"source-map-explorer": "^2.5.3"
}
}

View File

@@ -190,7 +190,7 @@ This package contains the following license and notice below:
# @firebase/logger
This package serves as the base of all logging in the JS SDK. Any logging that
is intended to be visible to Firebase end developers should go through this
is intended to be open to Firebase end developers should go through this
module.
## Basic Usage
@@ -9375,7 +9375,7 @@ parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
to the extent that it includes a convenient and prominently open
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the

View File

@@ -1029,7 +1029,7 @@ The following NPM packages may be included in this product:
- postcss-dir-pseudo-class@5.0.0
- postcss-double-position-gradients@1.0.0
- postcss-env-function@2.0.2
- postcss-focus-visible@4.0.0
- postcss-focus-open@4.0.0
- postcss-focus-within@3.0.0
- postcss-gap-properties@2.0.0
- postcss-image-set-function@3.0.1
@@ -1699,7 +1699,7 @@ This package contains the following license and notice below:
# @firebase/logger
This package serves as the base of all logging in the JS SDK. Any logging that
is intended to be visible to Firebase end developers should go through this
is intended to be open to Firebase end developers should go through this
module.
## Basic Usage
@@ -24029,7 +24029,7 @@ parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
to the extent that it includes a convenient and prominently open
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the

View File

@@ -2,14 +2,15 @@ import { ApolloProvider } from "@apollo/client";
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import moment from "moment";
import dayjs from "../utils/day";
import 'dayjs/locale/en';
import React from "react";
import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient";
import App from "./App";
moment.locale("en-US");
dayjs.locale("en");
export const factory = SplitSdk({
core: {

View File

@@ -1,11 +1,11 @@
import { useClient } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd";
import {useSplitClient} from "@splitsoftware/splitio-react";
import {Button, Result} from "antd";
import LogRocket from "logrocket";
import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Route, Routes} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
//Component Imports
@@ -13,15 +13,13 @@ import LoadingSpinner from "../components/loading-spinner/loading-spinner.compon
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
import LandingPage from "../pages/landing/landing.page";
import TechPageContainer from "../pages/tech/tech.page.container";
import { setOnline } from "../redux/application/application.actions";
import { selectOnline } from "../redux/application/application.selectors";
import { checkUserSession } from "../redux/user/user.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../redux/user/user.selectors";
import PrivateRoute from "../utils/private-route";
import {setOnline} from "../redux/application/application.actions";
import {selectOnline} from "../redux/application/application.selectors";
import {checkUserSession} from "../redux/user/user.actions";
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
import PrivateRoute from "../components/PrivateRoute";
import "./App.styles.scss";
import handleBeta from "../utils/betaHandler";
const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
@@ -33,7 +31,6 @@ const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() =>
import("../pages/mobile-payment/mobile-payment.container")
);
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
online: selectOnline,
@@ -44,14 +41,12 @@ const mapDispatchToProps = (dispatch) => ({
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
});
export function App({
bodyshop,
checkUserSession,
currentUser,
online,
setOnline,
}) {
const client = useClient();
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
const client = useSplitClient().client;
const [listenersAdded, setListenersAdded] = useState(false)
const {t} = useTranslation();
useEffect(() => {
if (!navigator.onLine) {
@@ -64,30 +59,49 @@ export function App({
//const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b);
const { t } = useTranslation();
window.addEventListener("offline", function (e) {
// Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => {
const offlineListener = (e) => {
setOnline(false);
});
}
window.addEventListener("online", function (e) {
const onlineListener = (e) => {
setOnline(true);
});
}
if (!listenersAdded) {
console.log('Added events for offline and online');
window.addEventListener("offline", offlineListener);
window.addEventListener("online", onlineListener);
setListenersAdded(true);
}
return () => {
window.removeEventListener("offline", offlineListener);
window.removeEventListener("online", onlineListener);
}
}, [setOnline, listenersAdded]);
useEffect(() => {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
LogRocket.init("rome-online/rome-online");
if (client.getTreatment("LogRocket_Tracking") === "on") {
if (
client.getTreatment("LogRocket_Tracking") === "on" ||
window.location.hostname === 'beta.romeonline.io'
) {
console.log("LR Start");
LogRocket.init("rome-online/rome-online");
}
}
}, [bodyshop, client, currentUser.authorized]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")} />;
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
}
handleBeta();
if (!online)
return (
<Result
@@ -107,54 +121,27 @@ export function App({
/>
);
// Any route that is not assigned and matched will default to the Landing Page component
return (
<Switch>
<Suspense fallback={<LoadingSpinner />}>
<ErrorBoundary>
<Route exact path="/" component={LandingPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/signin" component={SignInPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/resetpassword" component={ResetPassword} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/csi/:surveyId" component={CsiPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route exact path="/disclaimer" component={DisclaimerPage} />
</ErrorBoundary>
<ErrorBoundary>
<Route
exact
path="/mp/:paymentIs"
component={MobilePaymentContainer}
/>
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/manage"
component={ManagePage}
/>
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/tech"
component={TechPageContainer}
/>
</ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/edit"
component={DocumentEditorContainer}
/>
</ErrorBoundary>
<Suspense fallback={<LoadingSpinner message="Rome Online"/>}>
<Routes>
<Route path="*" element={<ErrorBoundary><LandingPage/></ErrorBoundary>}/>
<Route path="/signin" element={<ErrorBoundary><SignInPage/></ErrorBoundary>}/>
<Route path="/resetpassword" element={<ErrorBoundary><ResetPassword/></ErrorBoundary>}/>
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
<Route path="/manage/*" element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<ManagePage/>}/>
</Route>
<Route path="/tech/*" element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
<Route path="*" element={<TechPageContainer/>}/>
</Route>
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
<Route path="*" element={<DocumentEditorContainer/>}/>
</Route>
</Routes>
</Suspense>
</Switch>
);
}

View File

@@ -1,6 +1,14 @@
//Global Styles.
@import "react-big-calendar/lib/sass/styles";
.ant-menu-item-divider {
border-bottom: 1px solid #74695c !important;
}
.ant-menu-dark .ant-menu-item:hover {
background-color: #1890ff !important;
}
.imex-table-header {
display: flex;
flex-wrap: wrap;

View File

@@ -0,0 +1,17 @@
import React, {useEffect} from "react";
import {Outlet, useLocation, useNavigate} from "react-router-dom";
function PrivateRoute({component: Component, isAuthorized, ...rest}) {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (!isAuthorized) {
navigate(`/signin?redirect=${location.pathname}`);
}
}, [isAuthorized, navigate, location]);
return <Outlet/>;
}
export default PrivateRoute;

View File

@@ -1,17 +1,17 @@
import { Button } from "antd";
import {Button} from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {setModalContext} from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setRefundPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "refund_payment" })),
dispatch(setModalContext({context: context, modal: "refund_payment"})),
});
function Test({ setRefundPaymentContext, refundPaymentModal }) {
function Test({setRefundPaymentContext, refundPaymentModal}) {
console.log("refundPaymentModal", refundPaymentModal);
return (
<div>

View File

@@ -1,18 +1,18 @@
import { Input, Table, Checkbox, Card, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import {Card, Checkbox, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {Link} from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import {alphaSort, dateSort} from "../../utils/sorters";
import PayableExportButton from "../payable-export-button/payable-export-button.component";
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
import { DateFormatter } from "../../utils/DateFormatter";
import {DateFormatter} from "../../utils/DateFormatter";
import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils";
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";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
import {pageLimit} from "../../utils/config";
@@ -35,8 +35,8 @@ export function AccountingPayablesTableComponent({
loading,
bills,
refetch,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [selectedBills, setSelectedBills] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
const [state, setState] = useState({
@@ -45,7 +45,7 @@ export function AccountingPayablesTableComponent({
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const columns = [
@@ -60,7 +60,7 @@ export function AccountingPayablesTableComponent({
<Link
to={{
pathname: `/manage/shop/vendors`,
search: queryString.stringify({ selectedvendor: record.vendor.id }),
search: queryString.stringify({selectedvendor: record.vendor.id}),
}}
>
{record.vendor.name}
@@ -131,7 +131,7 @@ export function AccountingPayablesTableComponent({
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => (
<Checkbox disabled checked={record.is_credit_memo} />
<Checkbox disabled checked={record.is_credit_memo}/>
),
},
{
@@ -140,7 +140,7 @@ export function AccountingPayablesTableComponent({
key: "attempts",
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
<ExportLogsCountDisplay logs={record.exportlogs}/>
),
},
{
@@ -162,7 +162,7 @@ export function AccountingPayablesTableComponent({
];
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
setState({...state, search: e.target.value});
logImEXEvent("accounting_payables_table_search");
};
@@ -197,7 +197,7 @@ export function AccountingPayablesTableComponent({
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
<QboAuthorizeComponent/>
)}
<Input
value={state.search}
@@ -211,7 +211,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{position: "top", pageSize: pageLimit}}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -1,14 +1,14 @@
import { Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {Card, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import {DateFormatter, DateTimeFormatter} from "../../utils/DateFormatter";
import {alphaSort, dateSort} from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
@@ -35,8 +35,8 @@ export function AccountingPayablesTableComponent({
loading,
payments,
refetch,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [selectedPayments, setSelectedPayments] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
const [state, setState] = useState({
@@ -45,7 +45,7 @@ export function AccountingPayablesTableComponent({
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const columns = [
@@ -81,11 +81,11 @@ export function AccountingPayablesTableComponent({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
<OwnerNameDisplay ownerObject={record.job} />
<OwnerNameDisplay ownerObject={record.job}/>
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record.job} />
<OwnerNameDisplay ownerObject={record.job}/>
</span>
);
},
@@ -130,7 +130,7 @@ export function AccountingPayablesTableComponent({
key: "attempts",
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
<ExportLogsCountDisplay logs={record.exportlogs}/>
),
},
{
@@ -152,7 +152,7 @@ export function AccountingPayablesTableComponent({
];
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
setState({...state, search: e.target.value});
logImEXEvent("account_payments_table_search");
};
@@ -196,7 +196,7 @@ export function AccountingPayablesTableComponent({
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
<QboAuthorizeComponent/>
)}
<Input
value={state.search}
@@ -210,7 +210,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{position: "top", pageSize: pageLimit}}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -1,18 +1,18 @@
import { Button, Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {Button, Card, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {Link} from "react-router-dom";
import {logImEXEvent} from "../../firebase/firebase.utils";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import {alphaSort, dateSort} from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter";
import {DateFormatter} from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
@@ -32,8 +32,8 @@ export function AccountingReceivablesTableComponent({
loading,
jobs,
refetch,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
@@ -43,7 +43,7 @@ export function AccountingReceivablesTableComponent({
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const columns = [
@@ -89,11 +89,11 @@ export function AccountingReceivablesTableComponent({
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
<OwnerNameDisplay ownerObject={record} />
<OwnerNameDisplay ownerObject={record}/>
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
<OwnerNameDisplay ownerObject={record}/>
</span>
);
},
@@ -142,7 +142,7 @@ export function AccountingReceivablesTableComponent({
dataIndex: "attempts",
key: "attempts",
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
<ExportLogsCountDisplay logs={record.exportlogs}/>
),
},
{
@@ -167,7 +167,7 @@ export function AccountingReceivablesTableComponent({
];
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
setState({...state, search: e.target.value});
logImEXEvent("accounting_receivables_search");
};
@@ -211,7 +211,7 @@ export function AccountingReceivablesTableComponent({
/>
)}
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
<QboAuthorizeComponent/>
)}
<Input.Search
value={state.search}
@@ -225,7 +225,7 @@ export function AccountingReceivablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top" }}
pagination={{position: "top"}}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -1,4 +1,4 @@
import { Alert } from "antd";
import {Alert} from "antd";
import React from "react";
export default function AlertComponent(props) {

View File

@@ -1,4 +1,4 @@
import { shallow } from "enzyme";
import {shallow} from "enzyme";
import React from "react";
import Alert from "./alert.component";

View File

@@ -1,9 +1,9 @@
import { Select, Button, Popover, InputNumber } from "antd";
import {Button, InputNumber, Popover, Select} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -16,11 +16,11 @@ export function AllocationsAssignmentComponent({
setAssignment,
visibilityState,
maxHours
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const onChange = e => {
setAssignment({ ...assignment, employeeid: e });
setAssignment({...assignment, employeeid: e});
};
const [visibility, setVisibility] = visibilityState;
@@ -29,7 +29,7 @@ export function AllocationsAssignmentComponent({
<div>
<Select id="employeeSelector"
showSearch
style={{ width: 200 }}
style={{width: 200}}
placeholder='Select a person'
optionFilterProp='children'
onChange={onChange}
@@ -47,7 +47,7 @@ export function AllocationsAssignmentComponent({
placeholder={t("joblines.fields.mod_lb_hrs")}
max={parseFloat(maxHours)}
min={0}
onChange={e => setAssignment({ ...assignment, hours: e })}
onChange={e => setAssignment({...assignment, hours: e})}
/>
<Button
@@ -61,7 +61,7 @@ export function AllocationsAssignmentComponent({
);
return (
<Popover content={popContent} visible={visibility}>
<Popover content={popContent} open={visibility}>
<Button onClick={() => setVisibility(true)}>
{t("allocations.actions.assign")}
</Button>

View File

@@ -1,7 +1,7 @@
import { mount } from "enzyme";
import {mount} from "enzyme";
import React from "react";
import { MockBodyshop } from "../../utils/TestingHelpers";
import { AllocationsAssignmentComponent } from "./allocations-assignment.component";
import {MockBodyshop} from "../../utils/TestingHelpers";
import {AllocationsAssignmentComponent} from "./allocations-assignment.component";
describe("AllocationsAssignmentComponent component", () => {
let wrapper;

View File

@@ -1,17 +1,17 @@
import React, { useState } from "react";
import React, {useState} from "react";
import AllocationsAssignmentComponent from "./allocations-assignment.component";
import { useMutation } from "@apollo/client";
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
import { useTranslation } from "react-i18next";
import { notification } from "antd";
import {useMutation} from "@apollo/client";
import {INSERT_ALLOCATION} from "../../graphql/allocations.queries";
import {useTranslation} from "react-i18next";
import {notification} from "antd";
export default function AllocationsAssignmentContainer({
jobLineId,
hours,
refetch,
}) {
}) {
const visibilityState = useState(false);
const { t } = useTranslation();
const {t} = useTranslation();
const [assignment, setAssignment] = useState({
joblineid: jobLineId,
hours: parseFloat(hours),
@@ -20,7 +20,7 @@ export default function AllocationsAssignmentContainer({
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
const handleAssignment = () => {
insertAllocation({ variables: { alloc: { ...assignment } } })
insertAllocation({variables: {alloc: {...assignment}}})
.then((r) => {
notification["success"]({
message: t("allocations.successes.save"),
@@ -30,7 +30,7 @@ export default function AllocationsAssignmentContainer({
})
.catch((error) => {
notification["error"]({
message: t("employees.errors.saving", { message: error.message }),
message: t("employees.errors.saving", {message: error.message}),
});
});
};

View File

@@ -1,9 +1,9 @@
import { Button, Popover, Select } from "antd";
import {Button, Popover, Select} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -19,11 +19,11 @@ export default connect(
assignment,
setAssignment,
visibilityState,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const onChange = (e) => {
setAssignment({ ...assignment, employeeid: e });
setAssignment({...assignment, employeeid: e});
};
const [visibility, setVisibility] = visibilityState;
@@ -32,7 +32,7 @@ export default connect(
<div>
<Select
showSearch
style={{ width: 200 }}
style={{width: 200}}
placeholder="Select a person"
optionFilterProp="children"
onChange={onChange}
@@ -59,7 +59,7 @@ export default connect(
);
return (
<Popover content={popContent} visible={visibility}>
<Popover content={popContent} open={visibility}>
<Button disabled={disabled} onClick={() => setVisibility(true)}>
{t("allocations.actions.assign")}
</Button>

View File

@@ -1,16 +1,16 @@
import React, { useState } from "react";
import React, {useState} from "react";
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
import { useMutation } from "@apollo/client";
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
import { useTranslation } from "react-i18next";
import { notification } from "antd";
import {useMutation} from "@apollo/client";
import {INSERT_ALLOCATION} from "../../graphql/allocations.queries";
import {useTranslation} from "react-i18next";
import {notification} from "antd";
export default function AllocationsBulkAssignmentContainer({
jobLines,
refetch,
}) {
}) {
const visibilityState = useState(false);
const { t } = useTranslation();
const {t} = useTranslation();
const [assignment, setAssignment] = useState({
employeeid: null,
});
@@ -26,7 +26,7 @@ export default function AllocationsBulkAssignmentContainer({
return acc;
}, []);
insertAllocation({ variables: { alloc: allocs } }).then((r) => {
insertAllocation({variables: {alloc: allocs}}).then((r) => {
notification["success"]({
message: t("employees.successes.save"),
});

View File

@@ -1,17 +1,17 @@
import Icon from "@ant-design/icons";
import React from "react";
import { MdRemoveCircleOutline } from "react-icons/md";
import {MdRemoveCircleOutline} from "react-icons/md";
export default function AllocationsLabelComponent({ allocation, handleClick }) {
export default function AllocationsLabelComponent({allocation, handleClick}) {
return (
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{display: "flex", alignItems: "center"}}>
<span>
{`${allocation.employee.first_name || ""} ${
allocation.employee.last_name || ""
} (${allocation.hours || ""})`}
</span>
<Icon
style={{ color: "red", padding: "0px 4px" }}
style={{color: "red", padding: "0px 4px"}}
component={MdRemoveCircleOutline}
onClick={handleClick}
/>

View File

@@ -1,17 +1,17 @@
import React from "react";
import { useMutation } from "@apollo/client";
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
import {useMutation} from "@apollo/client";
import {DELETE_ALLOCATION} from "../../graphql/allocations.queries";
import AllocationsLabelComponent from "./allocations-employee-label.component";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import {notification} from "antd";
import {useTranslation} from "react-i18next";
export default function AllocationsLabelContainer({ allocation, refetch }) {
export default function AllocationsLabelContainer({allocation, refetch}) {
const [deleteAllocation] = useMutation(DELETE_ALLOCATION);
const { t } = useTranslation();
const {t} = useTranslation();
const handleClick = (e) => {
e.preventDefault();
deleteAllocation({ variables: { id: allocation.id } })
deleteAllocation({variables: {id: allocation.id}})
.then((r) => {
notification["success"]({
message: t("allocations.successes.deleted"),
@@ -19,7 +19,7 @@ export default function AllocationsLabelContainer({ allocation, refetch }) {
if (refetch) refetch();
})
.catch((error) => {
notification["error"]({ message: t("allocations.errors.deleting") });
notification["error"]({message: t("allocations.errors.deleting")});
});
};

View File

@@ -1,17 +1,17 @@
import React, { useState } from "react";
import { Table } from "antd";
import { alphaSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next";
import React, {useState} from "react";
import {Table} from "antd";
import {alphaSort} from "../../utils/sorters";
import {DateTimeFormatter} from "../../utils/DateFormatter";
import {useTranslation} from "react-i18next";
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
import {pageLimit} from "../../utils/config";
export default function AuditTrailListComponent({ loading, data }) {
export default function AuditTrailListComponent({loading, data}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
});
const { t } = useTranslation();
const {t} = useTranslation();
const columns = [
{
title: t("audit.fields.created"),
@@ -59,23 +59,23 @@ export default function AuditTrailListComponent({ loading, data }) {
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 },
xs: {span: 12},
sm: {span: 5},
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
xs: {span: 24},
sm: {span: 12},
},
};
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
return (
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{position: "top", defaultPageSize: pageLimit}}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -1,24 +1,24 @@
import React from "react";
import AuditTrailListComponent from "./audit-trail-list.component";
import { useQuery } from "@apollo/client";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import {useQuery} from "@apollo/client";
import {QUERY_AUDIT_TRAIL} from "../../graphql/audit_trail.queries";
import AlertComponent from "../alert/alert.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {logImEXEvent} from "../../firebase/firebase.utils";
import EmailAuditTrailListComponent from "./email-audit-trail-list.component";
import { Card, Row } from "antd";
import {Card, Row} from "antd";
export default function AuditTrailListContainer({ recordId }) {
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { id: recordId },
export default function AuditTrailListContainer({recordId}) {
const {loading, error, data} = useQuery(QUERY_AUDIT_TRAIL, {
variables: {id: recordId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
logImEXEvent("audittrail_view", { recordId });
logImEXEvent("audittrail_view", {recordId});
return (
<div>
{error ? (
<AlertComponent type="error" message={error.message} />
<AlertComponent type="error" message={error.message}/>
) : (
<Row gutter={[16, 16]}>
<Card>

View File

@@ -1,16 +1,16 @@
import { Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import {Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {DateTimeFormatter} from "../../utils/DateFormatter";
import {alphaSort} from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function EmailAuditTrailListComponent({ loading, data }) {
export default function EmailAuditTrailListComponent({loading, data}) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
});
const { t } = useTranslation();
const {t} = useTranslation();
const columns = [
{
title: t("audit.fields.created"),
@@ -38,23 +38,23 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 },
xs: {span: 12},
sm: {span: 5},
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
xs: {span: 24},
sm: {span: 12},
},
};
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
return (
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{position: "top", defaultPageSize: pageLimit}}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -1,13 +1,14 @@
import React from "react";
import { List } from "antd";
import {List} from "antd";
import Icon from "@ant-design/icons";
import { FaArrowRight } from "react-icons/fa";
export default function AuditTrailValuesComponent({ oldV, newV }) {
import {FaArrowRight} from "react-icons/fa";
export default function AuditTrailValuesComponent({oldV, newV}) {
if (!oldV && !newV) return <div></div>;
if (!oldV && newV)
return (
<List style={{ width: "800px" }} bordered size='small'>
<List style={{width: "800px"}} bordered size='small'>
{Object.keys(newV).map((key, idx) => (
<List.Item key={idx} value={key}>
{key}: {JSON.stringify(newV[key])}
@@ -17,10 +18,10 @@ export default function AuditTrailValuesComponent({ oldV, newV }) {
);
return (
<List style={{ width: "800px" }} bordered size='small'>
<List style={{width: "800px"}} bordered size='small'>
{Object.keys(oldV).map((key, idx) => (
<List.Item key={idx}>
{key}: {oldV[key]} <Icon component={FaArrowRight} />
{key}: {oldV[key]} <Icon component={FaArrowRight}/>
{JSON.stringify(newV[key])}
</List.Item>
))}

View File

@@ -1,9 +1,10 @@
import { Tag, Popover } from "antd";
import {Popover, Tag} from "antd";
import React from "react";
import Barcode from "react-barcode";
import { useTranslation } from "react-i18next";
export default function BarcodePopupComponent({ value, children }) {
const { t } = useTranslation();
import {useTranslation} from "react-i18next";
export default function BarcodePopupComponent({value, children}) {
const {t} = useTranslation();
return (
<div>
<Popover

View File

@@ -1,14 +1,15 @@
import { Checkbox, Form, Skeleton, Typography } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import {Checkbox, Form, Skeleton, Typography} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
import "./bill-cm-returns-table.styles.scss";
export default function BillCmdReturnsTableComponent({
form,
returnLoading,
returnData,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
useEffect(() => {
if (returnData) {
@@ -34,11 +35,11 @@ export default function BillCmdReturnsTableComponent({
return null;
}
if (returnLoading) return <Skeleton />;
if (returnLoading) return <Skeleton/>;
return (
<Form.List name="outstanding_returns">
{(fields, { add, remove, move }) => {
{(fields, {add, remove, move}) => {
return (
<>
<Typography.Title level={4}>
@@ -64,7 +65,7 @@ export default function BillCmdReturnsTableComponent({
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
@@ -75,7 +76,7 @@ export default function BillCmdReturnsTableComponent({
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -85,7 +86,7 @@ export default function BillCmdReturnsTableComponent({
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -95,7 +96,7 @@ export default function BillCmdReturnsTableComponent({
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
<td>
@@ -105,7 +106,7 @@ export default function BillCmdReturnsTableComponent({
key={`${index}cost`}
name={[field.name, "cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
@@ -117,7 +118,7 @@ export default function BillCmdReturnsTableComponent({
name={[field.name, "cm_received"]}
valuePropName="checked"
>
<Checkbox />
<Checkbox/>
</Form.Item>
</td>
</tr>

View File

@@ -1,30 +1,30 @@
import { DeleteFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DELETE_BILL } from "../../graphql/bills.queries";
import {DeleteFilled} from "@ant-design/icons";
import {useMutation} from "@apollo/client";
import {Button, notification, Popconfirm} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {DELETE_BILL} from "../../graphql/bills.queries";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function BillDeleteButton({ bill, callback }) {
export default function BillDeleteButton({bill, callback}) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const {t} = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL);
const handleDelete = async () => {
setLoading(true);
const result = await deleteBill({
variables: { billId: bill.id },
update(cache, { errors }) {
variables: {billId: bill.id},
update(cache, {errors}) {
if (errors) return;
cache.modify({
fields: {
bills(existingBills, { readField }) {
bills(existingBills, {readField}) {
return existingBills.filter(
(billref) => bill.id !== readField("id", billref)
);
},
search_bills(existingBills, { readField }) {
search_bills(existingBills, {readField}) {
return existingBills.filter(
(billref) => bill.id !== readField("id", billref)
);
@@ -35,7 +35,7 @@ export default function BillDeleteButton({ bill, callback }) {
});
if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") });
notification["success"]({message: t("bills.successes.deleted")});
if (callback && typeof callback === "function") callback(bill.id);
} else {
@@ -72,7 +72,7 @@ export default function BillDeleteButton({ bill, callback }) {
// onClick={handleDelete}
loading={loading}
>
<DeleteFilled />
<DeleteFilled/>
</Button>
</Popconfirm>
</RbacWrapper>

View File

@@ -1,21 +1,17 @@
import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
import moment from "moment";
import {useMutation, useQuery} from "@apollo/client";
import {Button, Form, Popconfirm, Space} from "antd";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE} from "../../graphql/bill-lines.queries";
import {QUERY_BILL_BY_PK, UPDATE_BILL} from "../../graphql/bills.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
@@ -26,15 +22,16 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditReturn from "./bill-detail-edit-return.component";
import {PageHeader} from "@ant-design/pro-layout";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
dispatch(setModalContext({context: context, modal: "partsOrder"})),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export default connect(
@@ -42,29 +39,27 @@ export default connect(
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail, bodyshop,}) {
const search = queryString.parse(useLocation().search);
const { t } = useTranslation();
const {t} = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
const {loading, error, data, refetch} = useQuery(QUERY_BILL_BY_PK, {
variables: {billid: search.billid},
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
// ... rest of the code remains the same
const handleSave = () => {
//It's got a previously deducted bill line!
if (
@@ -72,7 +67,7 @@ export function BillDetailEditcontainer({
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
setOpen(true);
else {
form.submit();
}
@@ -82,11 +77,11 @@ export function BillDetailEditcontainer({
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const {billlines, upload, ...bill} = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
variables: {billId: search.billid, bill: bill},
})
);
@@ -105,11 +100,11 @@ export function BillDetailEditcontainer({
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
updates.push(deleteBillLine({variables: {id: d.id}}));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
const {deductedfromlbr, inventories, jobline, ...il} = billline;
delete il.__typename;
if (il.id) {
@@ -155,18 +150,18 @@ export function BillDetailEditcontainer({
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setOpen(false);
setUpdateLoading(false);
};
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<>
{loading && <LoadingSkeleton />}
{loading && <LoadingSkeleton/>}
{data && (
<>
<PageHeader
@@ -176,13 +171,13 @@ export function BillDetailEditcontainer({
}
extra={
<Space>
<BillDetailEditReturn data={data} />
<BillPrintButton billid={search.billid} />
<BillDetailEditReturn data={data}/>
<BillPrintButton billid={search.billid}/>
<Popconfirm
visible={visible}
open={open}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
onCancel={() => setOpen(false)}
okButtonProps={{loading: updateLoading}}
title={t("bills.labels.editadjwarning")}
>
<Button
@@ -195,8 +190,8 @@ export function BillDetailEditcontainer({
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
<BillReeportButtonComponent bill={data && data.bills_by_pk}/>
<BillMarkExportedButton bill={data && data.bills_by_pk}/>
</Space>
}
/>
@@ -206,11 +201,11 @@ export function BillDetailEditcontainer({
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
<BillFormContainer form={form} billEdit disabled={exported}/>
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
job={{id: data ? data.bills_by_pk.jobid : null}}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
vendorid={data ? data.bills_by_pk.vendorid : null}
/>
@@ -246,7 +241,7 @@ const transformData = (data) => {
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
date: data.bills_by_pk ? dayjs(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -1,13 +1,13 @@
import { Button, Checkbox, Form, Modal } from "antd";
import {Button, Checkbox, Form, Modal} from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
const mapStateToProps = createStructuredSelector({
@@ -15,9 +15,9 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
dispatch(setModalContext({context: context, modal: "partsOrder"})),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export default connect(
@@ -31,14 +31,14 @@ export function BillDetailEditReturn({
bodyshop,
data,
disabled,
}) {
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const history = useNavigate();
const {t} = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const handleFinish = ({ billlines }) => {
const handleFinish = ({billlines}) => {
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
setPartsOrderContext({
@@ -67,18 +67,18 @@ export function BillDetailEditReturn({
});
delete search.billid;
history.push({ search: queryString.stringify(search) });
setVisible(false);
history({search: queryString.stringify(search)});
setOpen(false);
};
useEffect(() => {
if (visible === false) form.resetFields();
}, [visible, form]);
if (open === false) form.resetFields();
}, [open, form]);
return (
<>
<Modal
visible={visible}
onCancel={() => setVisible(false)}
open={open}
onCancel={() => setOpen(false)}
destroyOnClose
title={t("bills.actions.return")}
onOk={() => form.submit()}
@@ -89,9 +89,9 @@ export function BillDetailEditReturn({
form={form}
>
<Form.List name={["billlines"]}>
{(fields, { add, remove, move }) => {
{(fields, {add, remove, move}) => {
return (
<table style={{ tableLayout: "auto", width: "100%" }}>
<table style={{tableLayout: "auto", width: "100%"}}>
<thead>
<tr>
<td>
@@ -124,7 +124,7 @@ export function BillDetailEditReturn({
name={[field.name, "selected"]}
valuePropName="checked"
>
<Checkbox />
<Checkbox/>
</Form.Item>
</td>
<td>
@@ -133,7 +133,7 @@ export function BillDetailEditReturn({
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -142,7 +142,7 @@ export function BillDetailEditReturn({
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -151,7 +151,7 @@ export function BillDetailEditReturn({
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
<td>
@@ -160,7 +160,7 @@ export function BillDetailEditReturn({
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
</tr>
@@ -175,7 +175,7 @@ export function BillDetailEditReturn({
<Button
disabled={data.bills_by_pk.is_credit_memo || disabled}
onClick={() => {
setVisible(true);
setOpen(true);
}}
>
{t("bills.actions.return")}

View File

@@ -1,12 +1,12 @@
import { Drawer, Grid } from "antd";
import {Drawer, Grid} from "antd";
import queryString from "query-string";
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import {useLocation, useNavigate} from "react-router-dom";
import BillDetailEditComponent from "./bill-detail-edit-component";
export default function BillDetailEditcontainer() {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const history = useNavigate();
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -29,12 +29,12 @@ export default function BillDetailEditcontainer() {
width={drawerPercentage}
onClose={() => {
delete search.billid;
history.push({ search: queryString.stringify(search) });
history({search: queryString.stringify(search)});
}}
destroyOnClose
visible={search.billid}
open={search.billid}
>
<BillDetailEditComponent />
<BillDetailEditComponent/>
</Drawer>
);
}

View File

@@ -1,35 +1,29 @@
import { useApolloClient, useMutation } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
import {useApolloClient, useMutation} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Button, Checkbox, Form, Modal, notification, Space} from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import {
QUERY_JOB_LBR_ADJUSTMENTS,
UPDATE_JOB,
} from "../../graphql/jobs.queries";
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import React, {useEffect, useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_NEW_BILL} from "../../graphql/bills.queries";
import {UPDATE_INVENTORY_LINES} from "../../graphql/inventory.queries";
import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries";
import {QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB,} from "../../graphql/jobs.queries";
import {MUTATION_MARK_RETURN_RECEIVED} from "../../graphql/parts-orders.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectBillEnterModal} from "../../redux/modals/modals.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
import confirmDialog from "../../utils/asyncConfirm";
import useLocalStorage from "../../utils/useLocalStorage";
import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
import {CalculateBillTotal} from "../bill-form/bill-form.totals.utility";
import {handleUpload as handleLocalUpload} from "../documents-local-upload/documents-local-upload.utility";
import {handleUpload} from "../documents-upload/documents-upload.utility";
const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal,
@@ -38,8 +32,8 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, billid, operation }) =>
dispatch(insertAuditTrail({ jobid, billid, operation })),
insertAuditTrail: ({jobid, billid, operation}) =>
dispatch(insertAuditTrail({jobid, billid, operation})),
});
const Templates = TemplateList("job_special");
@@ -50,9 +44,9 @@ function BillEnterModalContainer({
bodyshop,
currentUser,
insertAuditTrail,
}) {
}) {
const [form] = Form.useForm();
const { t } = useTranslation();
const {t} = useTranslation();
const [enterAgain, setEnterAgain] = useState(false);
const [insertBill] = useMutation(INSERT_NEW_BILL);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
@@ -64,11 +58,13 @@ function BillEnterModalContainer({
"enter_bill_generate_label",
false
);
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
@@ -222,7 +218,7 @@ function BillEnterModalContainer({
mutation: UPDATE_JOB,
variables: {
jobId: values.jobid,
job: { lbr_adjustments: newAdjustments },
job: {lbr_adjustments: newAdjustments},
},
});
if (!!jobUpdate.errors) {
@@ -241,7 +237,7 @@ function BillEnterModalContainer({
if (markPolReceived && markPolReceived.length > 0) {
const r2 = await updatePartsOrderLines({
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
variables: {partsLineIds: markPolReceived.map((p) => p.id)},
});
if (!!r2.errors) {
setLoading(false);
@@ -321,7 +317,7 @@ function BillEnterModalContainer({
if (bodyshop.uselocalmediaserver) {
upload.forEach((u) => {
handleLocalUpload({
ev: { file: u.originFileObj },
ev: {file: u.originFileObj},
context: {
jobid: values.jobid,
invoice_number: remainingValues.invoice_number,
@@ -332,7 +328,7 @@ function BillEnterModalContainer({
} else {
upload.forEach((u) => {
handleUpload(
{ file: u.originFileObj },
{file: u.originFileObj},
{
bodyshop: bodyshop,
uploaded_by: currentUser.email,
@@ -399,18 +395,18 @@ function BillEnterModalContainer({
}, [enterAgain, form]);
useEffect(() => {
if (billEnterModal.visible) {
if (billEnterModal.open) {
form.setFieldsValue(formValues);
} else {
form.resetFields();
}
}, [billEnterModal.visible, form, formValues]);
}, [billEnterModal.open, form, formValues]);
return (
<Modal
title={t("bills.labels.new")}
width={"98%"}
visible={billEnterModal.visible}
open={billEnterModal.open}
okText={t("general.actions.save")}
keyboard="false"
onOk={() => form.submit()}

View File

@@ -1,18 +1,19 @@
import { Form, Input, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {Form, Input, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import {alphaSort} from "../../utils/sorters";
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
export default function BillFormLinesExtended({
lineData,
discount,
form,
responsibilityCenters,
disabled,
}) {
}) {
const [search, setSearch] = useState("");
const { t } = useTranslation();
const {t} = useTranslation();
const columns = [
{
title: t("joblines.fields.line_desc"),
@@ -79,7 +80,7 @@ export default function BillFormLinesExtended({
{record.part_qty ? `(x ${record.part_qty})` : null}
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
style={{marginLeft: ".2rem"}}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
@@ -122,7 +123,7 @@ export default function BillFormLinesExtended({
return (
<Form.Item noStyle name="billlineskeys">
<button onClick={() => console.log(form.getFieldsValue())}>form</button>
<Input onChange={(e) => setSearch(e.target.value)} allowClear />
<Input onChange={(e) => setSearch(e.target.value)} allowClear/>
<Table
pagination={false}
size="small"

View File

@@ -1,15 +1,11 @@
import React from "react";
import {
PlusCircleFilled,
MinusCircleFilled,
WarningOutlined,
} from "@ant-design/icons";
import { Form, Button, InputNumber, Input, Select, Switch, Space } from "antd";
import { useTranslation } from "react-i18next";
import {MinusCircleFilled, PlusCircleFilled, WarningOutlined,} from "@ant-design/icons";
import {Button, Form, Input, InputNumber, Select, Space, Switch} from "antd";
import {useTranslation} from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import CiecaSelect from "../../utils/Ciecaselect";
@@ -33,10 +29,10 @@ export function BillFormItemsExtendedFormItem({
disabled,
responsibilityCenters,
discount,
}) {
}) {
// const { billlineskeys } = form.getFieldsValue("billlineskeys");
const { t } = useTranslation();
const {t} = useTranslation();
if (!value)
return (
<Button
@@ -64,7 +60,7 @@ export function BillFormItemsExtendedFormItem({
});
}}
>
<PlusCircleFilled />
<PlusCircleFilled/>
</Button>
);
@@ -74,13 +70,13 @@ export function BillFormItemsExtendedFormItem({
label={t("billlines.fields.line_desc")}
name={["billlineskeys", record.id, "line_desc"]}
>
<Input disabled={disabled} />
<Input disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("billlines.fields.quantity")}
name={["billlineskeys", record.id, "quantity"]}
>
<InputNumber precision={0} min={0} disabled={disabled} />
<InputNumber precision={0} min={0} disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("billlines.fields.actual_price")}
@@ -90,7 +86,7 @@ export function BillFormItemsExtendedFormItem({
min={0}
disabled={disabled}
onBlur={(e) => {
const { billlineskeys } = form.getFieldsValue("billlineskeys");
const {billlineskeys} = form.getFieldsValue("billlineskeys");
form.setFieldsValue({
billlineskeys: {
...billlineskeys,
@@ -113,7 +109,7 @@ export function BillFormItemsExtendedFormItem({
label={t("billlines.fields.actual_cost")}
name={["billlineskeys", record.id, "actual_cost"]}
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
@@ -124,15 +120,15 @@ export function BillFormItemsExtendedFormItem({
Math.round((line.actual_cost / line.actual_price) * 100) / 100
).toPrecision(2);
if (lineDiscount - discount === 0) return <div />;
return <WarningOutlined style={{ color: "red" }} />;
if (lineDiscount - discount === 0) return <div/>;
return <WarningOutlined style={{color: "red"}}/>;
}}
</Form.Item>
<Form.Item
label={t("billlines.fields.cost_center")}
name={["billlineskeys", record.id, "cost_center"]}
>
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
<Select showSearch style={{minWidth: "3rem"}} disabled={disabled}>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? CiecaSelect(true, false)
: responsibilityCenters.costs.map((item) => (
@@ -157,9 +153,9 @@ export function BillFormItemsExtendedFormItem({
name={["billlineskeys", record.id, "deductedfromlbr"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
<Switch disabled={disabled}/>
</Form.Item>
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
<Form.Item shouldUpdate style={{display: "inline-block"}}>
{() => {
if (
form.getFieldsValue("billlineskeys").billlineskeys[record.id]
@@ -238,7 +234,7 @@ export function BillFormItemsExtendedFormItem({
},
]}
>
<InputNumber precision={2} min={0.01} />
<InputNumber precision={2} min={0.01}/>
</Form.Item>
</div>
);
@@ -251,21 +247,21 @@ export function BillFormItemsExtendedFormItem({
name={["billlineskeys", record.id, "applicable_taxes", "federal"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
<Switch disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("billlines.fields.state_tax_applicable")}
name={["billlineskeys", record.id, "applicable_taxes", "state"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
<Switch disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("billlines.fields.local_tax_applicable")}
name={["billlineskeys", record.id, "applicable_taxes", "local"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
<Switch disabled={disabled}/>
</Form.Item>
<Button
@@ -281,7 +277,7 @@ export function BillFormItemsExtendedFormItem({
});
}}
>
<MinusCircleFilled />
<MinusCircleFilled/>
</Button>
</Space>
);

View File

@@ -1,26 +1,16 @@
import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Alert,
Divider,
Form,
Input,
Select,
Space,
Statistic,
Switch,
Upload,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Icon, {UploadOutlined} from "@ant-design/icons";
import {useApolloClient} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
import dayjs from "../../utils/day";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {MdOpenInNew} from "react-icons/md";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {CHECK_BILL_INVOICE_NUMBER} from "../../graphql/bills.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
@@ -30,7 +20,7 @@ import JobSearchSelect from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import {CalculateBillTotal} from "./bill-form.totals.utility";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -50,21 +40,19 @@ export function BillFormComponent({
job,
loadOutstandingReturns,
loadInventory,
preferredMake,
}) {
const { t } = useTranslation();
preferredMake
}) {
const {t} = useTranslation();
const client = useApolloClient();
const [discount, setDiscount] = useState(0);
const { Extended_Bill_Posting } = useTreatments(
["Extended_Bill_Posting"],
{},
bodyshop.imexshopid
);
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const {treatments: {Extended_Bill_Posting, ClosingPeriod}} = useSplitTreatments({
attributes: {},
names: ["Extended_Bill_Posting", "ClosingPeriod"],
splitKey: bodyshop.imexshopid,
});
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
@@ -109,7 +97,7 @@ export function BillFormComponent({
}
const jobId = form.getFieldValue("jobid");
if (jobId) {
loadLines({ variables: { id: jobId } });
loadLines({variables: {id: jobId}});
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
loadOutstandingReturns({
variables: {
@@ -136,13 +124,13 @@ export function BillFormComponent({
return (
<div>
<FormFieldsChanged form={form} />
<FormFieldsChanged form={form}/>
<Form.Item
style={{ display: "none" }}
style={{display: "none"}}
name="isinhouse"
valuePropName="checked"
>
<Switch />
<Switch/>
</Form.Item>
<LayoutFormRow grow>
<Form.Item
@@ -161,7 +149,7 @@ export function BillFormComponent({
notExported={false}
onBlur={() => {
if (form.getFieldValue("jobid") !== null) {
loadLines({ variables: { id: form.getFieldValue("jobid") } });
loadLines({variables: {id: form.getFieldValue("jobid")}});
if (form.getFieldValue("vendorid") !== null) {
loadOutstandingReturns({
variables: {
@@ -183,7 +171,7 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
value &&
@@ -222,7 +210,7 @@ export function BillFormComponent({
>
<Space>
{iou.ro_number}
<Icon component={MdOpenInNew} />
<Icon component={MdOpenInNew}/>
</Space>
</Link>
</Space>
@@ -240,7 +228,7 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
async validator(rule, value) {
const vendorid = getFieldValue("vendorid");
if (vendorid && value) {
@@ -271,7 +259,7 @@ export function BillFormComponent({
}),
]}
>
<Input disabled={disabled || disableInvNumber} />
<Input disabled={disabled || disableInvNumber}/>
</Form.Item>
<Form.Item
label={t("bills.fields.date")}
@@ -281,24 +269,24 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value)
dayjs(value)
.startOf("day")
.isSameOrAfter(
moment(
dayjs(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value)
dayjs(value)
.startOf("day")
.isSameOrBefore(
moment(
dayjs(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
@@ -314,14 +302,14 @@ export function BillFormComponent({
}),
]}
>
<FormDatePicker disabled={disabled} />
<FormDatePicker disabled={disabled}/>
</Form.Item>
<Form.Item
label={t("bills.fields.is_credit_memo")}
name="is_credit_memo"
valuePropName="checked"
rules={[
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
value === true &&
@@ -353,7 +341,7 @@ export function BillFormComponent({
}),
]}
>
<Switch />
<Switch/>
</Form.Item>
<Form.Item
label={t("bills.fields.total")}
@@ -365,11 +353,11 @@ export function BillFormComponent({
},
]}
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
{!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
<Select style={{width: "10rem"}} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
@@ -394,29 +382,24 @@ export function BillFormComponent({
label={t("bills.fields.state_tax_rate")}
name="state_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput min={0} disabled={disabled}/>
</Form.Item>
{
// <Form.Item
// span={3}
// label={t("bills.fields.local_tax_rate")}
// name="local_tax_rate"
// >
// <CurrencyInput min={0} />
// </Form.Item>
}
{
//Removed as a part of the merge to Rome Online. Federal tax not applicable.
// bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
// <Form.Item
// span={2}
// label={t("bills.labels.federal_tax_exempt")}
// name="federal_tax_exempt"
// >
// <Switch onChange={handleFederalTaxExemptSwitchToggle} />
// </Form.Item>
// ) : null
}
{/*<Form.Item*/}
{/* span={3}*/}
{/* label={t("bills.fields.local_tax_rate")}*/}
{/* name="local_tax_rate"*/}
{/*>*/}
{/* <CurrencyInput min={0} />*/}
{/*</Form.Item>*/}
{/* {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (*/}
{/* <Form.Item*/}
{/* span={2}*/}
{/* label={t("bills.labels.federal_tax_exempt")}*/}
{/* name="federal_tax_exempt"*/}
{/* >*/}
{/* <Switch onChange={handleFederalTaxExemptSwitchToggle} />*/}
{/* </Form.Item>*/}
{/* ) : null}*/}
<Form.Item shouldUpdate span={13}>
{() => {
const values = form.getFieldsValue([
@@ -519,7 +502,7 @@ export function BillFormComponent({
<Form.Item
name="upload"
label="Upload"
style={{ display: billEdit ? "none" : null }}
style={{display: billEdit ? "none" : null}}
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
@@ -536,7 +519,7 @@ export function BillFormComponent({
>
<>
<p className="ant-upload-drag-icon">
<UploadOutlined />
<UploadOutlined/>
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.

View File

@@ -1,13 +1,13 @@
import { useLazyQuery, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import {useLazyQuery, useQuery} from "@apollo/client";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {QUERY_OUTSTANDING_INVENTORY} from "../../graphql/inventory.queries";
import {GET_JOB_LINES_TO_ENTER_BILL} from "../../graphql/jobs-lines.queries";
import {QUERY_UNRECEIVED_LINES} from "../../graphql/parts-orders.queries";
import {SEARCH_VENDOR_AUTOCOMPLETE} from "../../graphql/vendors.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import BillFormComponent from "./bill-form.component";
@@ -22,25 +22,25 @@ export function BillFormContainer({
billEdit,
disabled,
disableInvNumber,
}) {
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
{},
bodyshop && bodyshop.imexshopid
);
}) {
const {treatments: {Simple_Inventory}} = useSplitTreatments({
attributes: {},
names: ["Simple_Inventory"],
splitKey: bodyshop && bodyshop.imexshopid,
});
const { data: VendorAutoCompleteData } = useQuery(
const {data: VendorAutoCompleteData} = useQuery(
SEARCH_VENDOR_AUTOCOMPLETE,
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
{fetchPolicy: "network-only", nextFetchPolicy: "network-only"}
);
const [loadLines, { data: lineData }] = useLazyQuery(
const [loadLines, {data: lineData}] = useLazyQuery(
GET_JOB_LINES_TO_ENTER_BILL
);
const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] =
const [loadOutstandingReturns, {loading: returnLoading, data: returnData}] =
useLazyQuery(QUERY_UNRECEIVED_LINES);
const [loadInventory, { loading: inventoryLoading, data: inventoryData }] =
const [loadInventory, {loading: inventoryLoading, data: inventoryData}] =
useLazyQuery(QUERY_OUTSTANDING_INVENTORY);
return (
@@ -79,4 +79,5 @@ export function BillFormContainer({
</>
);
}
export default connect(mapStateToProps, null)(BillFormContainer);

View File

@@ -1,22 +1,11 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Checkbox,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip,
} from "antd";
import {DeleteFilled, DollarCircleFilled} from "@ant-design/icons";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip,} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
@@ -39,20 +28,16 @@ export function BillEnterModalLinesComponent({
responsibilityCenters,
billEdit,
billid,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
{},
bodyshop && bodyshop.imexshopid
);
}) {
const {t} = useTranslation();
const {setFieldsValue, getFieldsValue, getFieldValue} = form;
const {treatments: {Simple_Inventory, Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Simple_Inventory", "Enhanced_Payroll"],
splitKey: bodyshop && bodyshop.imexshopid,
});
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const columns = (remove) => {
return [
@@ -90,7 +75,7 @@ export function BillEnterModalLinesComponent({
<BillLineSearchSelect
disabled={disabled}
options={lineData}
style={{ width: "100%", minWidth: "10rem" }}
style={{width: "100%", minWidth: "10rem"}}
allowRemoved={form.getFieldValue("is_credit_memo") || false}
onSelect={(value, opt) => {
setFieldsValue({
@@ -142,7 +127,7 @@ export function BillEnterModalLinesComponent({
],
};
},
formInput: (record, index) => <Input disabled={disabled} />,
formInput: (record, index) => <Input disabled={disabled}/>,
},
{
title: t("billlines.fields.quantity"),
@@ -159,7 +144,7 @@ export function BillEnterModalLinesComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
({getFieldValue}) => ({
validator(rule, value) {
if (
value &&
@@ -181,7 +166,7 @@ export function BillEnterModalLinesComponent({
};
},
formInput: (record, index) => (
<InputNumber precision={0} min={1} disabled={disabled} />
<InputNumber precision={0} min={1} disabled={disabled}/>
),
},
{
@@ -255,7 +240,7 @@ export function BillEnterModalLinesComponent({
valuePropName="checked"
name={[record.name, "create_ppc"]}
>
<Checkbox />
<Checkbox/>
</Form.Item>
{t("joblines.fields.create_ppc")}
</Space>
@@ -360,7 +345,7 @@ export function BillEnterModalLinesComponent({
};
},
formInput: (record, index) => (
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
<Select showSearch style={{minWidth: "3rem"}} disabled={disabled}>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? CiecaSelect(true, false)
: responsibilityCenters.costs.map((item) => (
@@ -405,9 +390,9 @@ export function BillEnterModalLinesComponent({
name: [field.name, "deductedfromlbr"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
formInput: (record, index) => <Switch disabled={disabled}/>,
additional: (record, index) => (
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
<Form.Item shouldUpdate noStyle style={{display: "inline-block"}}>
{() => {
const price = getFieldValue([
"billlines",
@@ -529,7 +514,7 @@ export function BillEnterModalLinesComponent({
},
]}
>
<InputNumber precision={2} min={0.01} />
<InputNumber precision={2} min={0.01}/>
</Form.Item>
)}
@@ -572,7 +557,7 @@ export function BillEnterModalLinesComponent({
name: [field.name, "applicable_taxes", "state"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
formInput: (record, index) => <Switch disabled={disabled}/>,
},
// {
// title: t("billlines.fields.local_tax_applicable"),
@@ -604,7 +589,7 @@ export function BillEnterModalLinesComponent({
}
onClick={() => remove(record.name)}
>
<DeleteFilled />
<DeleteFilled/>
</Button>
{Simple_Inventory.treatment === "on" && (
<BilllineAddInventory
@@ -656,7 +641,7 @@ export function BillEnterModalLinesComponent({
},
]}
>
{(fields, { add, remove, move }) => {
{(fields, {add, remove, move}) => {
return (
<>
<Table
@@ -669,7 +654,7 @@ export function BillEnterModalLinesComponent({
bordered
dataSource={fields}
columns={mergedColumns(remove)}
scroll={{ x: true }}
scroll={{x: true}}
pagination={false}
rowClassName="editable-row"
/>
@@ -679,7 +664,7 @@ export function BillEnterModalLinesComponent({
onClick={() => {
add();
}}
style={{ width: "100%" }}
style={{width: "100%"}}
>
{t("billlines.actions.newline")}
</Button>
@@ -708,14 +693,14 @@ const EditableCell = ({
additional,
wrapper,
...restProps
}) => {
}) => {
if (additional)
return (
<td {...restProps}>
<div size="small">
<Form.Item
name={dataIndex}
labelCol={{ span: 0 }}
labelCol={{span: 0}}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.name)) || children}
@@ -729,7 +714,7 @@ const EditableCell = ({
<wrapper>
<td {...restProps}>
<Form.Item
labelCol={{ span: 0 }}
labelCol={{span: 0}}
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>
@@ -741,7 +726,7 @@ const EditableCell = ({
return (
<td {...restProps}>
<Form.Item
labelCol={{ span: 0 }}
labelCol={{span: 0}}
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>

View File

@@ -1,14 +1,14 @@
import Dinero from "dinero.js";
export const CalculateBillTotal = (invoice) => {
const { total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate } =
const {total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate} =
invoice;
//TODO Determine why this recalculates so many times.
let subtotal = Dinero({ amount: 0 });
let federalTax = Dinero({ amount: 0 });
let stateTax = Dinero({ amount: 0 });
let localTax = Dinero({ amount: 0 });
let subtotal = Dinero({amount: 0});
let federalTax = Dinero({amount: 0});
let stateTax = Dinero({amount: 0});
let localTax = Dinero({amount: 0});
if (!!!billlines) return null;
@@ -31,7 +31,7 @@ export const CalculateBillTotal = (invoice) => {
}
});
const invoiceTotal = Dinero({ amount: Math.round((total || 0) * 100) });
const invoiceTotal = Dinero({amount: Math.round((total || 0) * 100)});
const enteredTotal = subtotal.add(federalTax).add(stateTax).add(localTax);
const discrepancy = enteredTotal.subtract(invoiceTotal);

View File

@@ -1,13 +1,13 @@
import { Checkbox, Form, Skeleton, Typography } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import {Checkbox, Form, Skeleton, Typography} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
import "./bill-inventory-table.styles.scss";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {selectBillEnterModal} from "../../redux/modals/modals.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -25,8 +25,8 @@ export function BillInventoryTable({
billEdit,
inventoryLoading,
inventoryData,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
useEffect(() => {
if (inventoryData && inventoryData.inventory) {
@@ -55,11 +55,11 @@ export function BillInventoryTable({
return null;
}
if (inventoryLoading) return <Skeleton />;
if (inventoryLoading) return <Skeleton/>;
return (
<Form.List name="inventory">
{(fields, { add, remove, move }) => {
{(fields, {add, remove, move}) => {
return (
<>
<Typography.Title level={4}>
@@ -86,7 +86,7 @@ export function BillInventoryTable({
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
@@ -103,7 +103,7 @@ export function BillInventoryTable({
"name",
]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -113,7 +113,7 @@ export function BillInventoryTable({
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
<td>
@@ -123,7 +123,7 @@ export function BillInventoryTable({
key={`${index}act_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
<td>
@@ -133,7 +133,7 @@ export function BillInventoryTable({
key={`${index}cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
<ReadOnlyFormItemComponent type="currency"/>
</Form.Item>
</td>
<td>
@@ -143,7 +143,7 @@ export function BillInventoryTable({
key={`${index}comment`}
name={[field.name, "comment"]}
>
<ReadOnlyFormItemComponent />
<ReadOnlyFormItemComponent/>
</Form.Item>
</td>
@@ -155,7 +155,7 @@ export function BillInventoryTable({
name={[field.name, "consumefrominventory"]}
valuePropName="checked"
>
<Checkbox />
<Checkbox/>
</Form.Item>
</td>
</tr>

View File

@@ -1,21 +1,22 @@
import { Select } from "antd";
import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next";
import {Select} from "antd";
import React, {forwardRef} from "react";
import {useTranslation} from "react-i18next";
//To be used as a form element only.
const { Option } = Select;
const {Option} = Select;
const BillLineSearchSelect = (
{ options, disabled, allowRemoved, ...restProps },
{options, disabled, allowRemoved, ...restProps},
ref
) => {
const { t } = useTranslation();
const {t} = useTranslation();
return (
<Select
disabled={disabled}
ref={ref}
showSearch
dropdownMatchSelectWidth={false}
popupMatchSelectWidth={false}
optionLabelProp={"name"}
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
return (
@@ -55,8 +56,11 @@ const BillLineSearchSelect = (
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {}),
...(item.removed ? {textDecoration: "line-through"} : {}),
}}
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
@@ -64,12 +68,12 @@ const BillLineSearchSelect = (
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
{item.act_price === 0 && item.mod_lb_hrs > 0 && (
<span style={{ float: "right", paddingleft: "1rem" }}>
<span style={{float: "right", paddingleft: "1rem"}}>
{`${item.mod_lb_hrs} units`}
</span>
)}
<span style={{ float: "right", paddingleft: "1rem" }}>
<span style={{float: "right", paddingleft: "1rem"}}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`
: ``}

View File

@@ -1,18 +1,14 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {gql, useMutation} from "@apollo/client";
import {Button, notification} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectAuthLevel, selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component";
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
@@ -32,8 +28,8 @@ export function BillMarkExportedButton({
bodyshop,
authLevel,
bill,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
@@ -52,7 +48,7 @@ export function BillMarkExportedButton({
const handleUpdate = async () => {
setLoading(true);
const result = await updateBill({
variables: { billId: bill.id },
variables: {billId: bill.id},
});
await insertExportLog({

View File

@@ -1,11 +1,11 @@
import { Button, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {Button, Space} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
export default function BillPrintButton({ billid }) {
const { t } = useTranslation();
export default function BillPrintButton({billid}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const Templates = TemplateList("job_special");

View File

@@ -1,16 +1,13 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {gql, useMutation} from "@apollo/client";
import {Button, notification} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectAuthLevel, selectBodyshop,} from "../../redux/user/user.selectors";
import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
@@ -24,8 +21,8 @@ export default connect(
mapDispatchToProps
)(BillMarkForReexportButton);
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
const { t } = useTranslation();
export function BillMarkForReexportButton({bodyshop, authLevel, bill}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const [updateBill] = useMutation(gql`
@@ -43,7 +40,7 @@ export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
const handleUpdate = async () => {
setLoading(true);
const result = await updateBill({
variables: { billId: bill.id },
variables: {billId: bill.id},
});
if (!result.errors) {

View File

@@ -1,19 +1,16 @@
import { FileAddFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, notification, Tooltip } from "antd";
import { t } from "i18next";
import moment from "moment";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import {FileAddFilled} from "@ant-design/icons";
import {useMutation} from "@apollo/client";
import {Button, notification, Tooltip} from "antd";
import {t} from "i18next";
import dayjs from "./../../utils/day";
import React, {useState} from "react";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_INVENTORY_AND_CREDIT} from "../../graphql/inventory.queries";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {CalculateBillTotal} from "../bill-form/bill-form.totals.utility";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
import {useLocation} from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -33,10 +30,9 @@ export function BilllineAddInventory({
billline,
disabled,
jobid,
}) {
}) {
const [loading, setLoading] = useState(false);
const { billid } = queryString.parse(useLocation().search);
const {billid} = queryString.parse(useLocation().search);
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
const addToInventory = async () => {
@@ -50,7 +46,7 @@ export function BilllineAddInventory({
jobid: jobid,
isinhouse: true,
is_credit_memo: true,
date: moment().format("YYYY-MM-DD"),
date: dayjs().format("YYYY-MM-DD"),
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
@@ -88,11 +84,11 @@ export function BilllineAddInventory({
quantity: billline.quantity,
line_desc: billline.line_desc,
},
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
cm: {...cm, billlines: {data: cm.billlines}}, //Fix structure for apollo insert.
pol: {
returnfrombill: billid,
vendorid: bodyshop.inhousevendorid,
deliver_by: moment().format("YYYY-MM-DD"),
deliver_by: dayjs().format("YYYY-MM-DD"),
parts_order_lines: {
data: [
{
@@ -145,7 +141,7 @@ export function BilllineAddInventory({
}
onClick={addToInventory}
>
<FileAddFilled />
<FileAddFilled/>
{billline?.inventories?.length > 0 && (
<div>({billline?.inventories?.length} in inv)</div>
)}

View File

@@ -1,16 +1,16 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {EditFilled, SyncOutlined} from "@ant-design/icons";
import {Button, Card, Checkbox, Input, Space, Table} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectJobReadOnly} from "../../redux/application/application.selectors";
import {setModalContext} from "../../redux/modals/modals.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort, dateSort} from "../../utils/sorters";
import {TemplateList} from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
@@ -22,11 +22,11 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
dispatch(setModalContext({context: context, modal: "partsOrder"})),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
dispatch(setModalContext({context: context, modal: "billEnter"})),
setReconciliationContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reconciliation" })),
dispatch(setModalContext({context: context, modal: "reconciliation"})),
});
export function BillsListTableComponent({
@@ -38,8 +38,8 @@ export function BillsListTableComponent({
setPartsOrderContext,
setBillEnterContext,
setReconciliationContext,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
@@ -50,17 +50,17 @@ export function BillsListTableComponent({
const Templates = TemplateList("bill");
const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery;
const {refetch} = billsQuery;
const recordActions = (record, showView = false) => (
<Space wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<EditFilled />
<EditFilled/>
</Button>
)}
<BillDeleteButton bill={record} />
<BillDeleteButton bill={record}/>
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
data={{bills_by_pk: {...record, jobid: job.id}}}
disabled={
record.is_credit_memo ||
record.vendorid === bodyshop.inhousevendorid ||
@@ -72,9 +72,9 @@ export function BillsListTableComponent({
<PrintWrapperComponent
templateObject={{
name: Templates.inhouse_invoice.key,
variables: { id: record.id },
variables: {id: record.id},
}}
messageObject={{ subject: Templates.inhouse_invoice.subject }}
messageObject={{subject: Templates.inhouse_invoice.subject}}
/>
)}
</Space>
@@ -126,7 +126,7 @@ export function BillsListTableComponent({
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.is_credit_memo} />,
render: (text, record) => <Checkbox checked={record.is_credit_memo}/>,
},
{
title: t("bills.fields.exported"),
@@ -135,7 +135,7 @@ export function BillsListTableComponent({
sorter: (a, b) => a.exported - b.exported,
sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.exported} />,
render: (text, record) => <Checkbox checked={record.exported}/>,
},
{
title: t("general.labels.actions"),
@@ -146,7 +146,7 @@ export function BillsListTableComponent({
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const filteredBills = bills
@@ -173,14 +173,14 @@ export function BillsListTableComponent({
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
<SyncOutlined/>
</Button>
{job && job.converted ? (
<>
<Button
onClick={() => {
setBillEnterContext({
actions: { refetch: billsQuery.refetch },
actions: {refetch: billsQuery.refetch},
context: {
job,
},
@@ -192,7 +192,7 @@ export function BillsListTableComponent({
<Button
onClick={() => {
setReconciliationContext({
actions: { refetch: billsQuery.refetch },
actions: {refetch: billsQuery.refetch},
context: {
job,
bills: (billsQuery.data && billsQuery.data.bills) || [],
@@ -229,6 +229,7 @@ export function BillsListTableComponent({
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -1,23 +1,23 @@
import React, { useState } from "react";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
import { useQuery } from "@apollo/client";
import React, {useState} from "react";
import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries";
import {useQuery} from "@apollo/client";
import queryString from "query-string";
import { useHistory, useLocation } from "react-router-dom";
import { Table, Input } from "antd";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import {useLocation, useNavigate} from "react-router-dom";
import {Input, Table} from "antd";
import {useTranslation} from "react-i18next";
import {alphaSort} from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
export default function BillsVendorsList() {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const history = useNavigate();
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, {
const {loading, error, data} = useQuery(QUERY_ALL_VENDORS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
const {t} = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
@@ -25,7 +25,7 @@ export default function BillsVendorsList() {
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
setState({...state, filteredInfo: filters, sortedInfo: sorter});
};
const columns = [
@@ -57,19 +57,19 @@ export default function BillsVendorsList() {
delete search.billid;
if (record.id) {
search.vendorid = record.id;
history.push({ search: queryString.stringify(search) });
history.push({search: queryString.stringify(search)});
}
} else {
delete search.vendorid;
history.push({ search: queryString.stringify(search) });
history.push({search: queryString.stringify(search)});
}
};
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
setState({...state, search: e.target.value});
};
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error"/>;
const dataSource = state.search
? data.vendors.filter(
@@ -98,7 +98,7 @@ export default function BillsVendorsList() {
);
}}
dataSource={dataSource}
pagination={{ position: "top" }}
pagination={{position: "top"}}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -1,54 +1,64 @@
import { HomeFilled } from "@ant-design/icons";
import { Breadcrumb, Row, Col } from "antd";
import {HomeFilled} from "@ant-design/icons";
import {Breadcrumb, Col, Row} from "antd";
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {selectBreadcrumbs} from "../../redux/application/application.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs,
bodyshop: selectBodyshop,
});
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
export function BreadCrumbs({breadcrumbs, bodyshop}) {
const {treatments: {OpenSearch}} = useSplitTreatments({
attributes: {},
names: ["OpenSearch"],
splitKey: bodyshop && bodyshop.imexshopid,
});
// TODO - Client Update - Technically key is not doing anything here
return (
<Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}>
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to={`/manage`}>
<HomeFilled />{" "}
<Breadcrumb
separator=">"
items={[
{
key: "home",
title: (
<Link to={`/manage/`}>
<HomeFilled/>{" "}
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
""}
</Link>
</Breadcrumb.Item>
{breadcrumbs.map((item) =>
item.link ? (
<Breadcrumb.Item key={item.label}>
<Link to={item.link}>{item.label} </Link>
</Breadcrumb.Item>
) : (
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
)
)}
</Breadcrumb>
),
},
...breadcrumbs.map((item) =>
item.link
? {
key: item.label,
title: <Link to={item.link}>{item.label}</Link>,
}
: {
key: item.label,
title: item.label,
}
),
]}
/>
</Col>
<Col xs={24} sm={24} md={8}>
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
{OpenSearch.treatment === "on" ? <GlobalSearchOs/> : <GlobalSearch/>}
</Col>
</Row>
);
}
export default connect(mapStateToProps, null)(BreadCrumbs);

View File

@@ -1,13 +1,13 @@
import { Button, Form, Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCaBcEtfTableConvert } from "../../redux/modals/modals.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {Button, Form, Modal} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectCaBcEtfTableConvert} from "../../redux/modals/modals.selectors";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
import CaBcEtfTableModalComponent from "./ca-bc-etf-table.modal.component";
const mapStateToProps = createStructuredSelector({
@@ -22,10 +22,10 @@ const mapDispatchToProps = (dispatch) => ({
export function ContractsFindModalContainer({
caBcEtfTableModal,
toggleModalVisible,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const { visible } = caBcEtfTableModal;
const {open} = caBcEtfTableModal;
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
@@ -35,7 +35,7 @@ export function ContractsFindModalContainer({
setLoading(true);
const claimNumbers = [];
values.table.split("\n").forEach((row, idx, arr) => {
const { 1: claim, 2: shortclaim, 4: amount } = row.split("\t");
const {1: claim, 2: shortclaim, 4: amount} = row.split("\t");
if (!claim || !shortclaim) return;
const trimmedShortClaim = shortclaim.trim();
// const trimmedClaim = claim.trim();
@@ -63,14 +63,14 @@ export function ContractsFindModalContainer({
};
useEffect(() => {
if (visible) {
if (open) {
form.resetFields();
}
}, [visible, form]);
}, [open, form]);
return (
<Modal
visible={visible}
open={open}
width="70%"
title={t("payments.labels.findermodal")}
onCancel={() => toggleModalVisible()}
@@ -84,7 +84,7 @@ export function ContractsFindModalContainer({
autoComplete="no"
onFinish={handleFinish}
>
<CaBcEtfTableModalComponent form={form} />
<CaBcEtfTableModalComponent form={form}/>
<Button onClick={() => form.submit()} type="primary" loading={loading}>
{t("general.labels.search")}
</Button>

View File

@@ -1,9 +1,9 @@
import { Form, Input, Radio } from "antd";
import {Form, Input, Radio} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -11,8 +11,8 @@ const mapStateToProps = createStructuredSelector({
export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
export function PartsReceiveModalComponent({ bodyshop, form }) {
const { t } = useTranslation();
export function PartsReceiveModalComponent({bodyshop, form}) {
const {t} = useTranslation();
return (
<div>
@@ -25,7 +25,7 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
},
]}
>
<Input.TextArea rows={8} />
<Input.TextArea rows={8}/>
</Form.Item>
<Form.Item
label={t("general.labels.sendby")}

View File

@@ -1,30 +1,31 @@
import React, { useState } from "react";
import { Button, Form, InputNumber, Popover } from "antd";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { useTranslation } from "react-i18next";
import { CalculatorFilled } from "@ant-design/icons";
export default function CABCpvrtCalculator({ disabled, form }) {
import React, {useState} from "react";
import {Button, Form, InputNumber, Popover} from "antd";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {useTranslation} from "react-i18next";
import {CalculatorFilled} from "@ant-design/icons";
export default function CABCpvrtCalculator({disabled, form}) {
const [visibility, setVisibility] = useState(false);
const { t } = useTranslation();
const {t} = useTranslation();
const handleFinish = async (values) => {
logImEXEvent("job_ca_bc_pvrt_calculate");
form.setFieldsValue({
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
});
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
form.setFields([{name: "ca_bc_pvrt", touched: true}]);
setVisibility(false);
};
const popContent = (
<div>
<Form onFinish={handleFinish} initialValues={{ rate: 1.5 }}>
<Form onFinish={handleFinish} initialValues={{rate: 1.5}}>
<Form.Item name="rate" label={t("jobs.labels.ca_bc_pvrt.rate")}>
<InputNumber precision={2} min={0} />
<InputNumber precision={2} min={0}/>
</Form.Item>
<Form.Item name="days" label={t("jobs.labels.ca_bc_pvrt.days")}>
<InputNumber precision={0} min={0} />
<InputNumber precision={0} min={0}/>
</Form.Item>
<Button type="primary" htmlType="submit">
{t("general.actions.calculate")}
@@ -38,11 +39,11 @@ export default function CABCpvrtCalculator({ disabled, form }) {
<Popover
destroyTooltipOnHide
content={popContent}
visible={visibility}
open={visibility}
disabled={disabled}
>
<Button disabled={disabled} onClick={() => setVisibility(true)}>
<CalculatorFilled />
<CalculatorFilled/>
</Button>
</Popover>
);

View File

@@ -1,32 +1,18 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Col,
Form,
Input,
Row,
Space,
Spin,
Statistic,
notification,
} from "antd";
import {DeleteFilled} from "@ant-design/icons";
import {useLazyQuery, useMutation} from "@apollo/client";
import {Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic,} from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS,} from "../../graphql/payment_response.queries";
import {INSERT_NEW_PAYMENT} from "../../graphql/payments.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectCardPayment} from "../../redux/modals/modals.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
@@ -37,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
@@ -47,20 +33,20 @@ const CardPaymentModalComponent = ({
cardPaymentModal,
toggleModalVisible,
insertAuditTrail,
}) => {
const { context } = cardPaymentModal;
}) => {
const {context} = cardPaymentModal;
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const {t} = useTranslation();
const [, { data, refetch, queryLoading }] = useLazyQuery(
const [, {data, refetch, queryLoading}] = useLazyQuery(
QUERY_RO_AND_OWNER_BY_JOB_PKS,
{
variables: { jobids: [context.jobid] },
variables: {jobids: [context.jobid]},
skip: true,
}
);
@@ -82,7 +68,7 @@ const CardPaymentModalComponent = ({
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
const {payments} = form.getFieldsValue();
await insertPaymentResponse({
variables: {
@@ -117,7 +103,7 @@ const CardPaymentModalComponent = ({
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: moment(Date.now()),
date: dayjs(Date.now()),
payment_responses: {
data: [
{
@@ -141,7 +127,7 @@ const CardPaymentModalComponent = ({
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
message: t("payments.errors.inserting", {error: error.message}),
});
} finally {
setLoading(false);
@@ -195,11 +181,11 @@ const CardPaymentModalComponent = ({
form={form}
layout="vertical"
initialValues={{
payments: context.jobid ? [{ jobid: context.jobid }] : [],
payments: context.jobid ? [{jobid: context.jobid}] : [],
}}
>
<Form.List name={["payments"]}>
{(fields, { add, remove, move }) => {
{(fields, {add, remove, move}) => {
return (
<div>
{fields.map((field, index) => (
@@ -235,12 +221,12 @@ const CardPaymentModalComponent = ({
},
]}
>
<CurrencyFormItemComponent />
<CurrencyFormItemComponent/>
</Form.Item>
</Col>
<Col span={2}>
<DeleteFilled
style={{ margin: "1rem" }}
style={{margin: "1rem"}}
onClick={() => {
remove(field.name);
}}
@@ -255,7 +241,7 @@ const CardPaymentModalComponent = ({
onClick={() => {
add();
}}
style={{ width: "100%" }}
style={{width: "100%"}}
>
{t("general.actions.add")}
</Button>
@@ -274,13 +260,13 @@ const CardPaymentModalComponent = ({
{() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
const {payments} = form.getFieldsValue();
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) });
refetch({jobids: payments.map((p) => p.jobid)});
}
console.log(
"Acc info",
@@ -324,13 +310,13 @@ const CardPaymentModalComponent = ({
}
>
{() => {
const { payments } = form.getFieldsValue();
const {payments} = form.getFieldsValue();
const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0);
return (
<Space style={{ float: "right" }}>
<Space style={{float: "right"}}>
<Statistic
title="Amount To Charge"
value={totalAmountToCharge}
@@ -360,7 +346,7 @@ const CardPaymentModalComponent = ({
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
<Input type="hidden"/>
</Form.Item>
</Form>
</Spin>

View File

@@ -1,11 +1,11 @@
import { Button, Modal } from "antd";
import {Button, Modal} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectCardPayment} from "../../redux/modals/modals.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CardPaymentModalComponent from "./card-payment-modal.component.";
const mapStateToProps = createStructuredSelector({
@@ -21,9 +21,9 @@ function CardPaymentModalContainer({
cardPaymentModal,
toggleModalVisible,
bodyshop,
}) {
const { visible } = cardPaymentModal;
const { t } = useTranslation();
}) {
const {open} = cardPaymentModal;
const {t} = useTranslation();
const handleCancel = () => {
toggleModalVisible();
@@ -35,7 +35,7 @@ function CardPaymentModalContainer({
return (
<Modal
open={visible}
open={open}
onOk={handleOK}
onCancel={handleCancel}
footer={[
@@ -46,7 +46,7 @@ function CardPaymentModalContainer({
width="80%"
destroyOnClose
>
<CardPaymentModalComponent />
<CardPaymentModalComponent/>
</Modal>
);
}

View File

@@ -1,25 +1,16 @@
import { useApolloClient } from "@apollo/client";
import { getToken, onMessage } from "@firebase/messaging";
import { Button, notification, Space } from "antd";
import {useApolloClient} from "@apollo/client";
import {getToken, onMessage} from "@firebase/messaging";
import {Button, notification, Space} from "antd";
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { messaging, requestForToken } from "../../firebase/firebase.utils";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import {messaging, requestForToken} from "../../firebase/firebase.utils";
import FcmHandler from "../../utils/fcm-handler";
import ChatPopupComponent from "../chat-popup/chat-popup.component";
import "./chat-affix.styles.scss";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
chatVisible: selectChatVisible,
});
export function ChatAffixContainer({ bodyshop, chatVisible }) {
const { t } = useTranslation();
export function ChatAffixContainer({bodyshop, chatVisible}) {
const {t} = useTranslation();
const client = useApolloClient();
useEffect(() => {
if (!bodyshop || !bodyshop.messagingservicesid) return;
@@ -47,7 +38,6 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
<Button
onClick={async () => {
await requestForToken();
SubscribeToTopic();
}}
>
@@ -81,16 +71,17 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
payload: (payload && payload.data && payload.data.data) || payload.data,
});
}
let stopMessageListenr, channel;
let stopMessageListener, channel;
try {
stopMessageListenr = onMessage(messaging, handleMessage);
stopMessageListener = onMessage(messaging, handleMessage);
channel = new BroadcastChannel("imex-sw-messages");
channel.addEventListener("message", handleMessage);
} catch (error) {
console.log("Unable to set event listeners.");
}
return () => {
stopMessageListenr && stopMessageListenr();
stopMessageListener && stopMessageListener();
channel && channel.removeEventListener("message", handleMessage);
};
}, [client]);
@@ -99,8 +90,9 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
return (
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent/> : null}
</div>
);
}
export default connect(mapStateToProps, null)(ChatAffixContainer);
export default ChatAffixContainer;

View File

@@ -1,18 +1,18 @@
import { useMutation } from "@apollo/client";
import { Button } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
import {useMutation} from "@apollo/client";
import {Button} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {TOGGLE_CONVERSATION_ARCHIVE} from "../../graphql/conversations.queries";
export default function ChatArchiveButton({ conversation }) {
export default function ChatArchiveButton({conversation}) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const {t} = useTranslation();
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
const handleToggleArchive = async () => {
setLoading(true);
await updateConversation({
variables: { id: conversation.id, archived: !conversation.archived },
variables: {id: conversation.id, archived: !conversation.archived},
refetchQueries: ["CONVERSATION_LIST_QUERY"],
});

View File

@@ -1,19 +1,14 @@
import { Badge, List, Tag } from "antd";
import {Badge, Card, List, Space, Tag} from "antd";
import React from "react";
import { connect } from "react-redux";
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List as VirtualizedList,
} from "react-virtualized";
import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import {connect} from "react-redux";
import {AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList,} from "react-virtualized";
import {createStructuredSelector} from "reselect";
import {setSelectedConversation} from "../../redux/messaging/messaging.actions";
import {selectSelectedConversation} from "../../redux/messaging/messaging.selectors";
import {TimeAgoFormatter} from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
import _ from "lodash";
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -30,14 +25,44 @@ function ChatConversationListComponent({
selectedConversation,
setSelectedConversation,
loadMoreConversations,
}) {
}) {
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 60,
});
const rowRenderer = ({ index, key, style, parent }) => {
const rowRenderer = ({index, key, style, parent}) => {
const item = conversationList[index];
const cardContentRight =
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft = item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx}>{j.job.ro_number}</Tag>
))
: null;
const names = <>{_.uniq(item.job_conversations.map((j, idx) =>
OwnerNameDisplayFunction(j.job)
))}</>
const cardTitle = <>
{item.label && <Tag color="blue">{item.label}</Tag>}
{item.job_conversations.length > 0 ? (
<Space direction="vertical">
{names}
</Space>
) : (
<Space>
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
</Space>
)}
</>
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0}/>
const getCardStyle = () =>
item.id === selectedConversation
? {backgroundColor: 'rgba(128, 128, 128, 0.2)'}
: {backgroundColor: index % 2 === 0 ? '#f0f2f5' : '#ffffff'};
return (
<CellMeasurer
@@ -49,44 +74,21 @@ function ChatConversationListComponent({
>
<List.Item
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
style={style}
className={`chat-list-item
${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
style={style}
>
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
<div style={{display: 'inline-block', width: '70%', textAlign: 'left'}}>
{cardContentLeft}
</div>
<div
style={{
display: "inline-block",
}}
>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div style={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
style={{display: 'inline-block', width: '30%', textAlign: 'right'}}>{cardContentRight}</div>
</Card>
</List.Item>
</CellMeasurer>
);
@@ -95,14 +97,14 @@ function ChatConversationListComponent({
return (
<div className="chat-list-container">
<AutoSizer>
{({ height, width }) => (
{({height, width}) => (
<VirtualizedList
height={height}
width={width}
rowCount={conversationList.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
onScroll={({scrollTop, scrollHeight, clientHeight}) => {
if (scrollTop + clientHeight === scrollHeight) {
loadMoreConversations();
}

View File

@@ -1,27 +1,16 @@
.chat-list-selected-conversation {
background-color: rgba(128, 128, 128, 0.2);
}
.chat-list-container {
flex: 1;
overflow: hidden;
height: 100%;
border: 1px solid gainsboro;
}
.chat-list-item {
display: flex;
flex-direction: row;
.ant-card-head {
border: none;
}
&:hover {
cursor: pointer;
color: #ff7a00;
}
.chat-name {
flex: 1;
display: inline;
}
.ro-number-tag {
align-self: baseline;
}
padding: 12px 24px;
border-bottom: 1px solid gainsboro;
}

View File

@@ -1,12 +1,12 @@
import { useMutation } from "@apollo/client";
import { Tag } from "antd";
import {useMutation} from "@apollo/client";
import {Tag} from "antd";
import React from "react";
import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import {Link} from "react-router-dom";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {REMOVE_CONVERSATION_TAG} from "../../graphql/job-conversations.queries";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ChatConversationTitleTags({ jobConversations }) {
export default function ChatConversationTitleTags({jobConversations}) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
const handleRemoveTag = (jobId) => {
@@ -19,7 +19,7 @@ export default function ChatConversationTitleTags({ jobConversations }) {
},
update(cache) {
cache.modify({
id: cache.identify({ id: convId, __typename: "conversations" }),
id: cache.identify({id: convId, __typename: "conversations"}),
fields: {
job_conversations(ex) {
return ex.filter((e) => e.jobid !== jobId);
@@ -42,12 +42,12 @@ export default function ChatConversationTitleTags({ jobConversations }) {
key={item.job.id}
closable
color="blue"
style={{ cursor: "pointer" }}
style={{cursor: "pointer"}}
onClose={() => handleRemoveTag(item.job.id)}
>
<Link to={`/manage/jobs/${item.job.id}`}>
{`${item.job.ro_number || "?"} | `}
<OwnerNameDisplay ownerObject={item.job} />
<OwnerNameDisplay ownerObject={item.job}/>
</Link>
</Tag>
))}

View File

@@ -1,4 +1,4 @@
import { Space } from "antd";
import {Space} from "antd";
import React from "react";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
@@ -7,21 +7,21 @@ import ChatLabelComponent from "../chat-label/chat-label.component";
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
export default function ChatConversationTitle({ conversation }) {
export default function ChatConversationTitle({conversation}) {
return (
<Space wrap>
<PhoneNumberFormatter>
{conversation && conversation.phone_num}
</PhoneNumberFormatter>
<ChatLabelComponent conversation={conversation} />
<ChatPrintButton conversation={conversation} />
<ChatLabelComponent conversation={conversation}/>
<ChatPrintButton conversation={conversation}/>
<ChatConversationTitleTags
jobConversations={
(conversation && conversation.job_conversations) || []
}
/>
<ChatTagRoContainer conversation={conversation || []} />
<ChatArchiveButton conversation={conversation} />
<ChatTagRoContainer conversation={conversation || []}/>
<ChatArchiveButton conversation={conversation}/>
</Space>
);
}

View File

@@ -11,11 +11,11 @@ export default function ChatConversationComponent({
conversation,
messages,
handleMarkConversationAsRead,
}) {
}) {
const [loading, error] = subState;
if (loading) return <LoadingSkeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSkeleton/>;
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<div
@@ -23,9 +23,9 @@ export default function ChatConversationComponent({
onMouseDown={handleMarkConversationAsRead}
onKeyDown={handleMarkConversationAsRead}
>
<ChatConversationTitle conversation={conversation} />
<ChatMessageListComponent messages={messages} />
<ChatSendMessage conversation={conversation} />
<ChatConversationTitle conversation={conversation}/>
<ChatMessageListComponent messages={messages}/>
<ChatSendMessage conversation={conversation}/>
</div>
);
}

View File

@@ -1,16 +1,14 @@
import { useMutation, useQuery, useSubscription } from "@apollo/client";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
CONVERSATION_SUBSCRIPTION_BY_PK,
GET_CONVERSATION_DETAILS,
} from "../../graphql/conversations.queries";
import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import {useMutation, useQuery, useSubscription} from "@apollo/client";
import React, {useState} from "react";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS,} from "../../graphql/conversations.queries";
import {MARK_MESSAGES_AS_READ_BY_CONVERSATION} from "../../graphql/messages.queries";
import {selectSelectedConversation} from "../../redux/messaging/messaging.selectors";
import ChatConversationComponent from "./chat-conversation.component";
import axios from "axios";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
bodyshop: selectBodyshop,
@@ -18,21 +16,21 @@ const mapStateToProps = createStructuredSelector({
export default connect(mapStateToProps, null)(ChatConversationContainer);
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
export function ChatConversationContainer({bodyshop, selectedConversation}) {
const {
loading: convoLoading,
error: convoError,
data: convoData,
} = useQuery(GET_CONVERSATION_DETAILS, {
variables: { conversationId: selectedConversation },
variables: {conversationId: selectedConversation},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const { loading, error, data } = useSubscription(
const {loading, error, data} = useSubscription(
CONVERSATION_SUBSCRIPTION_BY_PK,
{
variables: { conversationId: selectedConversation },
variables: {conversationId: selectedConversation},
}
);
@@ -41,7 +39,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
const [markConversationRead] = useMutation(
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{
variables: { conversationId: selectedConversation },
variables: {conversationId: selectedConversation},
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
update(cache) {
cache.modify({
@@ -51,7 +49,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: 0 } };
return {aggregate: {count: 0}};
},
},
});

View File

@@ -1,22 +1,23 @@
import { PlusOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Input, notification, Spin, Tag, Tooltip } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
export default function ChatLabel({ conversation }) {
import {PlusOutlined} from "@ant-design/icons";
import {useMutation} from "@apollo/client";
import {Input, notification, Spin, Tag, Tooltip} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {UPDATE_CONVERSATION_LABEL} from "../../graphql/conversations.queries";
export default function ChatLabel({conversation}) {
const [loading, setLoading] = useState(false);
const [editing, setEditing] = useState(false);
const [value, setValue] = useState(conversation.label);
const { t } = useTranslation();
const {t} = useTranslation();
const [updateLabel] = useMutation(UPDATE_CONVERSATION_LABEL);
const handleSave = async () => {
setLoading(true);
try {
const response = await updateLabel({
variables: { id: conversation.id, label: value },
variables: {id: conversation.id, label: value},
});
if (response.errors) {
notification["error"]({
@@ -47,18 +48,18 @@ export default function ChatLabel({ conversation }) {
onBlur={handleSave}
allowClear
/>
{loading && <Spin size="small" />}
{loading && <Spin size="small"/>}
</div>
);
} else {
return conversation.label && conversation.label.trim() !== "" ? (
<Tag style={{ cursor: "pointer" }} onClick={() => setEditing(true)}>
<Tag style={{cursor: "pointer"}} onClick={() => setEditing(true)}>
{conversation.label}
</Tag>
) : (
<Tooltip title={t("messaging.labels.addlabel")}>
<PlusOutlined
style={{ cursor: "pointer" }}
style={{cursor: "pointer"}}
onClick={() => setEditing(true)}
/>
</Tooltip>

View File

@@ -1,15 +1,16 @@
import { PictureFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Badge, Popover } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {PictureFilled} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Badge, Popover} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {GET_DOCUMENTS_BY_JOB} from "../../graphql/documents.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import JobDocumentsLocalGalleryExternal
from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({
@@ -25,11 +26,11 @@ export function ChatMediaSelector({
selectedMedia,
setSelectedMedia,
conversation,
}) {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
}) {
const {t} = useTranslation();
const [open, setOpen] = useState(false);
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
const {loading, error, data} = useQuery(GET_DOCUMENTS_BY_JOB, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
@@ -39,13 +40,13 @@ export function ChatMediaSelector({
},
skip:
!visible ||
!open ||
!conversation.job_conversations ||
conversation.job_conversations.length === 0,
});
const handleVisibleChange = (visible) => {
setVisible(visible);
const handleVisibleChange = (change) => {
setOpen(change);
};
useEffect(() => {
@@ -54,10 +55,10 @@ export function ChatMediaSelector({
const content = (
<div>
{loading && <LoadingSpinner />}
{error && <AlertComponent message={error.message} type="error" />}
{loading && <LoadingSpinner/>}
{error && <AlertComponent message={error.message} type="error"/>}
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
<div style={{color: "red"}}>{t("messaging.labels.maxtenimages")}</div>
) : null}
{!bodyshop.uselocalmediaserver && data && (
<JobDocumentsGalleryExternal
@@ -65,7 +66,7 @@ export function ChatMediaSelector({
externalMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
{bodyshop.uselocalmediaserver && visible && (
{bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={
@@ -88,11 +89,11 @@ export function ChatMediaSelector({
}
title={t("messaging.labels.selectmedia")}
trigger="click"
visible={visible}
onVisibleChange={handleVisibleChange}
open={open}
onOpenChange={handleVisibleChange}
>
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
<PictureFilled style={{ margin: "0 .5rem" }} />
<PictureFilled style={{margin: "0 .5rem"}}/>
</Badge>
</Popover>
);

View File

@@ -1,19 +1,14 @@
import Icon from "@ant-design/icons";
import { Tooltip } from "antd";
import {Tooltip} from "antd";
import i18n from "i18next";
import moment from "moment";
import React, { useEffect, useRef } from "react";
import { MdDone, MdDoneAll } from "react-icons/md";
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from "react-virtualized";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import React, {useEffect, useRef} from "react";
import {MdDone, MdDoneAll} from "react-icons/md";
import {AutoSizer, CellMeasurer, CellMeasurerCache, List,} from "react-virtualized";
import {DateTimeFormatter} from "../../utils/DateFormatter";
import "./chat-message-list.styles.scss";
export default function ChatMessageListComponent({ messages }) {
export default function ChatMessageListComponent({messages}) {
const virtualizedListRef = useRef(null);
const _cache = new CellMeasurerCache({
@@ -32,10 +27,10 @@ export default function ChatMessageListComponent({ messages }) {
useEffect(scrollToBottom, [messages]);
const _rowRenderer = ({ index, key, parent, style }) => {
const _rowRenderer = ({index, key, parent, style}) => {
return (
<CellMeasurer cache={_cache} key={key} rowIndex={index} parent={parent}>
{({ measure, registerChild }) => (
{({measure, registerChild}) => (
<div
ref={registerChild}
onLoad={measure}
@@ -49,10 +44,10 @@ export default function ChatMessageListComponent({ messages }) {
{StatusRender(messages[index].status)}
</div>
{messages[index].isoutbound && (
<div style={{ fontSize: 10 }}>
<div style={{fontSize: 10}}>
{i18n.t("messaging.labels.sentby", {
by: messages[index].userid,
time: moment(messages[index].created_at).format(
time: dayjs(messages[index].created_at).format(
"MM/DD/YYYY @ hh:mm a"
),
})}
@@ -67,7 +62,7 @@ export default function ChatMessageListComponent({ messages }) {
return (
<div className="chat">
<AutoSizer>
{({ height, width }) => (
{({height, width}) => (
<List
ref={virtualizedListRef}
width={width}
@@ -87,16 +82,16 @@ export default function ChatMessageListComponent({ messages }) {
const MessageRender = (message) => {
return (
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
<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" }}
style={{display: "flex", justifyContent: "center"}}
>
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={i} />
<img alt="Received" className="message-img" src={i}/>
</a>
</div>
))}
@@ -109,9 +104,9 @@ const MessageRender = (message) => {
const StatusRender = (status) => {
switch (status) {
case "sent":
return <Icon component={MdDone} className="message-icon" />;
return <Icon component={MdDone} className="message-icon"/>;
case "delivered":
return <Icon component={MdDoneAll} className="message-icon" />;
return <Icon component={MdDoneAll} className="message-icon"/>;
default:
return null;
}

View File

@@ -44,6 +44,7 @@
.yours {
align-items: flex-start;
}
.msgmargin {
margin-top: 0.1rem;
margin-bottom: 0.1rem;
@@ -66,6 +67,7 @@
background: #eee;
border-bottom-right-radius: 15px;
}
.yours .message.last:after {
content: "";
position: absolute;

View File

@@ -1,13 +1,11 @@
import { PlusCircleFilled } from "@ant-design/icons";
import { Button, Form, Popover } from "antd";
import {PlusCircleFilled} from "@ant-design/icons";
import {Button, Form, Popover} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
import PhoneFormItem, {
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {openChatByPhone} from "../../redux/messaging/messaging.actions";
import PhoneFormItem, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -16,11 +14,11 @@ const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
});
export function ChatNewConversation({ openChatByPhone }) {
const { t } = useTranslation();
export function ChatNewConversation({openChatByPhone}) {
const {t} = useTranslation();
const [form] = Form.useForm();
const handleFinish = (values) => {
openChatByPhone({ phone_num: values.phoneNumber });
openChatByPhone({phone_num: values.phoneNumber});
form.resetFields();
};
@@ -31,11 +29,11 @@ export function ChatNewConversation({ openChatByPhone }) {
label={t("messaging.labels.phonenumber")}
name="phoneNumber"
rules={[
({ getFieldValue }) =>
({getFieldValue}) =>
PhoneItemFormatterValidation(getFieldValue, "phoneNumber"),
]}
>
<PhoneFormItem />
<PhoneFormItem/>
</Form.Item>
<Button type="primary" htmlType="submit">
{t("messaging.actions.new")}
@@ -46,7 +44,7 @@ export function ChatNewConversation({ openChatByPhone }) {
return (
<Popover trigger="click" content={popContent}>
<PlusCircleFilled />
<PlusCircleFilled/>
</Popover>
);
}

View File

@@ -1,14 +1,15 @@
import { notification } from "antd";
import {notification} from "antd";
import parsePhoneNumber from "libphonenumber-js";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {openChatByPhone} from "../../redux/messaging/messaging.actions";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {searchingForConversation} from "../../redux/messaging/messaging.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
searchingForConversation: searchingForConversation,
@@ -24,8 +25,8 @@ export function ChatOpenButton({
phone,
jobid,
openChatByPhone,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
if (!phone) return <></>;
if (!bodyshop.messagingservicesid)
@@ -39,9 +40,9 @@ export function ChatOpenButton({
const p = parsePhoneNumber(phone, "CA");
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
if (p && p.isValid()) {
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
openChatByPhone({phone_num: p.formatInternational(), jobid: jobid});
} else {
notification["error"]({ message: t("messaging.error.invalidphone") });
notification["error"]({message: t("messaging.error.invalidphone")});
}
}}
>
@@ -49,4 +50,5 @@ export function ChatOpenButton({
</a>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatOpenButton);

View File

@@ -1,24 +1,13 @@
import {
InfoCircleOutlined,
MessageOutlined,
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useLazyQuery, useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
CONVERSATION_LIST_QUERY,
UNREAD_CONVERSATION_COUNT,
} from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import {
selectChatVisible,
selectSelectedConversation,
} from "../../redux/messaging/messaging.selectors";
import {InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined,} from "@ant-design/icons";
import {useLazyQuery, useQuery} from "@apollo/client";
import {Badge, Card, Col, Row, Space, Tag, Tooltip, Typography} from "antd";
import React, {useCallback, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {CONVERSATION_LIST_QUERY, UNREAD_CONVERSATION_COUNT,} from "../../graphql/conversations.queries";
import {toggleChatVisible} from "../../redux/messaging/messaging.actions";
import {selectChatVisible, selectSelectedConversation,} from "../../redux/messaging/messaging.selectors";
import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component";
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
@@ -37,22 +26,22 @@ export function ChatPopupComponent({
chatVisible,
selectedConversation,
toggleChatVisible,
}) {
const { t } = useTranslation();
}) {
const {t} = useTranslation();
const [pollInterval, setpollInterval] = useState(0);
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
const {data: unreadData} = useQuery(UNREAD_CONVERSATION_COUNT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
...(pollInterval > 0 ? { pollInterval } : {}),
...(pollInterval > 0 ? {pollInterval} : {}),
});
const [getConversations, { loading, data, refetch, fetchMore }] =
const [getConversations, {loading, data, refetch, fetchMore}] =
useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}),
...(pollInterval > 0 ? {pollInterval} : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -94,12 +83,12 @@ export function ChatPopupComponent({
<Typography.Title level={4}>
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<ChatNewConversation/>
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
<InfoCircleOutlined/>
</Tooltip>
<SyncOutlined
style={{ cursor: "pointer" }}
style={{cursor: "pointer"}}
onClick={() => refetch()}
/>
{pollInterval > 0 && (
@@ -108,13 +97,13 @@ export function ChatPopupComponent({
</Space>
<ShrinkOutlined
onClick={() => toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
style={{position: "absolute", right: ".5rem", top: ".5rem"}}
/>
<Row gutter={[8, 8]} className="chat-popup-content">
<Col span={8}>
{loading ? (
<LoadingSpinner />
<LoadingSpinner/>
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
@@ -123,16 +112,16 @@ export function ChatPopupComponent({
)}
</Col>
<Col span={16}>
{selectedConversation ? <ChatConversationContainer /> : null}
{selectedConversation ? <ChatConversationContainer/> : null}
</Col>
</Row>
</div>
) : (
<div
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}
style={{cursor: "pointer"}}
>
<MessageOutlined className="chat-popup-info-icon" />
<MessageOutlined className="chat-popup-info-icon"/>
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}
@@ -140,4 +129,5 @@ export function ChatPopupComponent({
</Badge>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent);

View File

@@ -13,6 +13,7 @@
height: 100%;
}
}
.chat-popup-info-icon {
margin-right: 5px;
}

View File

@@ -1,10 +1,10 @@
import { PlusCircleOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd";
import {PlusCircleOutlined} from "@ant-design/icons";
import {Dropdown} from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setMessage } from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {setMessage} from "../../redux/messaging/messaging.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -15,25 +15,23 @@ const mapDispatchToProps = (dispatch) => ({
setMessage: (message) => dispatch(setMessage(message)),
});
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
const menu = (
<Menu>
{bodyshop.md_messaging_presets.map((i, idx) => (
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
{i.label}
</Menu.Item>
))}
</Menu>
);
export function ChatPresetsComponent({bodyshop, setMessage, className}) {
const items = bodyshop.md_messaging_presets.map((i, idx) => ({
key: idx,
label: (i.label),
onClick: () => setMessage(i.text),
}));
return (
<div className={className}>
<Dropdown trigger={["click"]} overlay={menu}>
<PlusCircleOutlined />
<Dropdown trigger={["click"]} menu={{items}}>
<PlusCircleOutlined/>
</Dropdown>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -1,11 +1,11 @@
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import { Space, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {MailOutlined, PrinterOutlined} from "@ant-design/icons";
import {Space, Spin} from "antd";
import React, {useState} from "react";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {setEmailOptions} from "../../redux/email/email.actions";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({});
@@ -13,47 +13,34 @@ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export function ChatPrintButton({ conversation }) {
export function ChatPrintButton({conversation}) {
const [loading, setLoading] = useState(false);
const generateDocument = (type) => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: {id: conversation.id},
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
type,
conversation.id
).catch(e => {
console.warn('Something went wrong generating a document.');
});
setLoading(false);
}
return (
<Space wrap>
<PrinterOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"p",
conversation.id
);
setLoading(false);
}}
/>
<MailOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"e",
conversation.id
);
setLoading(false);
}}
/>
{loading && <Spin />}
<PrinterOutlined onClick={() => generateDocument('p')}/>
<MailOutlined onClick={() => generateDocument('e')}/>
{loading && <Spin/>}
</Space>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);

View File

@@ -1,19 +1,13 @@
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Input, Spin } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
sendMessage,
setMessage,
} from "../../redux/messaging/messaging.actions";
import {
selectIsSending,
selectMessage,
} from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {LoadingOutlined, SendOutlined} from "@ant-design/icons";
import {Input, Spin} from "antd";
import React, {useEffect, useRef, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {sendMessage, setMessage,} from "../../redux/messaging/messaging.actions";
import {selectIsSending, selectMessage,} from "../../redux/messaging/messaging.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
@@ -35,14 +29,14 @@ function ChatSendMessageComponent({
isSending,
message,
setMessage,
}) {
}) {
const inputArea = useRef(null);
const [selectedMedia, setSelectedMedia] = useState([]);
useEffect(() => {
inputArea.current.focus();
}, [isSending, setMessage]);
const { t } = useTranslation();
const {t} = useTranslation();
const handleEnter = () => {
const selectedImages = selectedMedia.filter((i) => i.isSelected);
@@ -60,27 +54,27 @@ function ChatSendMessageComponent({
});
setSelectedMedia(
selectedMedia.map((i) => {
return { ...i, isSelected: false };
return {...i, isSelected: false};
})
);
}
};
return (
<div className="imex-flex-row" style={{ width: "100%" }}>
<ChatPresetsComponent className="imex-flex-row__margin" />
<div className="imex-flex-row" style={{width: "100%"}}>
<ChatPresetsComponent className="imex-flex-row__margin"/>
<ChatMediaSelector
conversation={conversation}
selectedMedia={selectedMedia}
setSelectedMedia={setSelectedMedia}
/>
<span style={{ flex: 1 }}>
<span style={{flex: 1}}>
<Input.TextArea
className="imex-flex-row__margin imex-flex-row__grow"
allowClear
autoFocus
ref={inputArea}
autoSize={{ minRows: 1, maxRows: 4 }}
autoSize={{minRows: 1, maxRows: 4}}
value={message}
disabled={isSending}
placeholder={t("messaging.labels.typeamessage")}
@@ -97,7 +91,7 @@ function ChatSendMessageComponent({
onClick={handleEnter}
/>
<Spin
style={{ display: `${isSending ? "" : "none"}` }}
style={{display: `${isSending ? "" : "none"}`}}
indicator={
<LoadingOutlined
style={{
@@ -110,6 +104,7 @@ function ChatSendMessageComponent({
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -1,30 +1,30 @@
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import { Empty, Select, Space } from "antd";
import {CloseCircleOutlined, LoadingOutlined} from "@ant-design/icons";
import {Empty, Select, Space} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import {useTranslation} from "react-i18next";
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
export default function ChatTagRoComponent({
roOptions,
loading,
handleSearch,
handleInsertTag,
setVisible,
}) {
const { t } = useTranslation();
setOpen,
}) {
const {t} = useTranslation();
return (
<Space flex>
<div style={{ width: "15rem" }}>
<Space>
<div style={{width: "15rem"}}>
<Select
showSearch
autoFocus
dropdownMatchSelectWidth
popupMatchSelectWidth
placeholder={t("general.labels.search")}
filterOption={false}
onSearch={handleSearch}
onSelect={handleInsertTag}
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
notFoundContent={loading ? <LoadingOutlined/> : <Empty/>}
>
{roOptions.map((item, idx) => (
<Select.Option key={item.id || idx}>
@@ -33,12 +33,12 @@ export default function ChatTagRoComponent({
))}
</Select>
</div>
{loading ? <LoadingOutlined /> : null}
{loading ? <LoadingOutlined/> : null}
{loading ? (
<LoadingOutlined />
<LoadingOutlined/>
) : (
<CloseCircleOutlined onClick={() => setVisible(false)} />
<CloseCircleOutlined onClick={() => setOpen(false)}/>
)}
</Space>
);

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