Compare commits
1 Commits
feature/IO
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e63f8c7c45 |
13
README.MD
13
README.MD
@@ -1,3 +1,14 @@
|
|||||||
|
Yarn Dependency Management:
|
||||||
|
To force upgrades for some packages:
|
||||||
|
yarn upgrade-interactive --latest
|
||||||
|
|
||||||
|
To Start Hasura CLI:
|
||||||
|
npx hasura console
|
||||||
|
|
||||||
|
Migrating to Staging:
|
||||||
|
npx hasura migrate apply --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||||
|
npx hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret 'Test-ImEXOnlineBySnaptSoftware!'
|
||||||
|
|
||||||
NGROK TEsting:
|
NGROK TEsting:
|
||||||
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
||||||
|
|
||||||
@@ -10,4 +21,4 @@ hasura migrate apply --version "1620771761757" --skip-execution --endpoint https
|
|||||||
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||||
|
|
||||||
Generate the license file:
|
Generate the license file:
|
||||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,6 @@ REACT_APP_CLOUDINARY_API_KEY=957865933348715
|
|||||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
GENERATE_SOURCEMAP=false
|
|
||||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
||||||
REACT_APP_GA_CODE=231103507
|
REACT_APP_GA_CODE=231103507
|
||||||
|
|||||||
@@ -4,70 +4,69 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.7.9",
|
"@apollo/client": "^3.6.9",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.0.0",
|
"@craco/craco": "^6.4.5",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@sentry/react": "^7.40.0",
|
"@sentry/react": "^7.28.1",
|
||||||
"@sentry/tracing": "^7.40.0",
|
"@sentry/tracing": "^7.28.1",
|
||||||
"@splitsoftware/splitio-react": "^1.8.1",
|
"@splitsoftware/splitio-react": "^1.6.0",
|
||||||
"@tanem/react-nprogress": "^5.0.8",
|
"@tanem/react-nprogress": "^5.0.8",
|
||||||
"antd": "^4.24.8",
|
"antd": "^4.22.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^0.27.2",
|
||||||
"craco-less": "^2.0.0",
|
"craco-less": "^1.20.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^9.17.1",
|
"firebase": "^9.9.1",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.5.0",
|
||||||
"i18next": "^22.4.10",
|
"i18next": "^21.8.14",
|
||||||
"i18next-browser-languagedetector": "^7.0.1",
|
"i18next-browser-languagedetector": "^6.1.4",
|
||||||
"jsoneditor": "^9.9.0",
|
"jsoneditor": "^9.9.0",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"jsreport-browser-client-dist": "^1.3.0",
|
||||||
"libphonenumber-js": "^1.10.21",
|
"libphonenumber-js": "^1.10.9",
|
||||||
"logrocket": "^3.0.1",
|
"logrocket": "^3.0.1",
|
||||||
"markerjs2": "^2.28.1",
|
"markerjs2": "^2.22.0",
|
||||||
"moment-business-days": "^1.2.0",
|
"moment-business-days": "^1.2.0",
|
||||||
"moment-timezone": "^0.5.41",
|
"moment-timezone": "^0.5.34",
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^7.0.3",
|
||||||
"phone": "^3.1.35",
|
"phone": "^3.1.23",
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.1",
|
||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-big-calendar": "^1.6.8",
|
"react-big-calendar": "^1.5.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^4.1.1",
|
"react-cookie": "^4.1.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-drag-listview": "^0.2.1",
|
"react-drag-listview": "^0.2.1",
|
||||||
"react-grid-gallery": "^1.0.0",
|
"react-grid-gallery": "^0.5.5",
|
||||||
"react-grid-layout": "^1.3.4",
|
"react-grid-layout": "^1.3.4",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^11.18.1",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.4.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-number-format": "^4.9.3",
|
||||||
"react-number-format": "^5.1.3",
|
"react-redux": "^7.2.8",
|
||||||
"react-redux": "^8.0.5",
|
|
||||||
"react-resizable": "^3.0.4",
|
"react-resizable": "^3.0.4",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^4.0.3",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-sublime-video": "^0.2.5",
|
"react-sublime-video": "^0.2.5",
|
||||||
"react-virtualized": "^9.22.3",
|
"react-virtualized": "^9.22.3",
|
||||||
"recharts": "^2.4.3",
|
"recharts": "^2.1.12",
|
||||||
"redux": "^4.2.1",
|
"redux": "^4.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.2.2",
|
"redux-saga": "^1.1.3",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^4.1.7",
|
"reselect": "^4.1.6",
|
||||||
"sass": "^1.58.3",
|
"sass": "^1.54.0",
|
||||||
"socket.io-client": "^4.6.1",
|
"socket.io-client": "^4.5.1",
|
||||||
"styled-components": "^5.3.6",
|
"styled-components": "^5.3.5",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"workbox-background-sync": "^6.5.3",
|
"workbox-background-sync": "^6.5.3",
|
||||||
|
|||||||
@@ -143,16 +143,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update row highlighting on production board.
|
|
||||||
|
//Update row highlighting on production board.
|
||||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||||
background: #eaeaea !important;
|
background: #eaeaea !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-line-manual {
|
.job-line-manual{
|
||||||
color: tomato;
|
color: tomato;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.ant-table-column-sort {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
|
|
||||||
export default function BillDeleteButton({ bill, callback }) {
|
export default function BillDeleteButton({ bill }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [deleteBill] = useMutation(DELETE_BILL);
|
const [deleteBill] = useMutation(DELETE_BILL);
|
||||||
@@ -36,8 +36,6 @@ export default function BillDeleteButton({ bill, callback }) {
|
|||||||
|
|
||||||
if (!!!result.errors) {
|
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 {
|
} else {
|
||||||
//Check if it's an fkey violation.
|
//Check if it's an fkey violation.
|
||||||
const error = JSON.stringify(result.errors);
|
const error = JSON.stringify(result.errors);
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import GlobalSearch from "../global-search/global-search.component";
|
import GlobalSearch from "../global-search/global-search.component";
|
||||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
|
||||||
import "./breadcrumbs.styles.scss";
|
import "./breadcrumbs.styles.scss";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
breadcrumbs: selectBreadcrumbs,
|
breadcrumbs: selectBreadcrumbs,
|
||||||
@@ -17,12 +15,6 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||||
const { OpenSearch } = useTreatments(
|
|
||||||
["OpenSearch"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="breadcrumb-container">
|
<Row className="breadcrumb-container">
|
||||||
<Col xs={24} sm={24} md={16}>
|
<Col xs={24} sm={24} md={16}>
|
||||||
@@ -46,7 +38,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
|||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={8}>
|
<Col xs={24} sm={24} md={8}>
|
||||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
<GlobalSearch />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
logImEXEvent("job_ca_bc_pvrt_calculate");
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
|
||||||
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
|
|
||||||
});
|
|
||||||
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
|
|
||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
|
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
|
||||||
{
|
{
|
||||||
variables: { conversationId: selectedConversation },
|
variables: { conversationId: selectedConversation },
|
||||||
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
|
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.modify({
|
cache.modify({
|
||||||
id: cache.identify({
|
id: cache.identify({
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries";
|
||||||
CONVERSATION_LIST_QUERY,
|
|
||||||
UNREAD_CONVERSATION_COUNT,
|
|
||||||
} from "../../graphql/conversations.queries";
|
|
||||||
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||||
import {
|
import {
|
||||||
selectChatVisible,
|
selectChatVisible,
|
||||||
@@ -40,17 +37,9 @@ export function ChatPopupComponent({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [pollInterval, setpollInterval] = useState(0);
|
const [pollInterval, setpollInterval] = useState(0);
|
||||||
|
|
||||||
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
|
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
skip: !chatVisible,
|
|
||||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,14 +57,12 @@ export function ChatPopupComponent({
|
|||||||
if (called && chatVisible) refetch();
|
if (called && chatVisible) refetch();
|
||||||
}, [chatVisible, called, refetch]);
|
}, [chatVisible, called, refetch]);
|
||||||
|
|
||||||
// const unreadCount = data
|
const unreadCount = data
|
||||||
// ? data.conversations.reduce(
|
? data.conversations.reduce(
|
||||||
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
|
(acc, val) => val.messages_aggregate.aggregate.count + acc,
|
||||||
// 0
|
0
|
||||||
// )
|
)
|
||||||
// : 0;
|
: 0;
|
||||||
|
|
||||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge count={unreadCount}>
|
<Badge count={unreadCount}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import moment from "moment";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
|
import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
|
||||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
@@ -165,9 +165,7 @@ export default function ContractFormComponent({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{
|
<ContractLicenseDecodeButton form={form} />
|
||||||
//<ContractLicenseDecodeButton form={form} />
|
|
||||||
}
|
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
|
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
import { AutoComplete, Divider, Input, Space } from "antd";
|
|
||||||
import axios from "axios";
|
|
||||||
import _ from "lodash";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Link, useHistory } from "react-router-dom";
|
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|
||||||
import OwnerNameDisplay, {
|
|
||||||
OwnerNameDisplayFunction,
|
|
||||||
} from "../owner-name-display/owner-name-display.component";
|
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
|
||||||
|
|
||||||
export default function GlobalSearchOs() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const history = useHistory();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [data, setData] = useState(false);
|
|
||||||
|
|
||||||
const executeSearch = async (v) => {
|
|
||||||
if (v && v && v !== "" && v.length >= 3) {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const searchData = await axios.post("/search", {
|
|
||||||
search: v,
|
|
||||||
});
|
|
||||||
|
|
||||||
const resultsByType = {
|
|
||||||
payments: [],
|
|
||||||
jobs: [],
|
|
||||||
bills: [],
|
|
||||||
owners: [],
|
|
||||||
vehicles: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
searchData.data.hits.hits.forEach((hit) => {
|
|
||||||
resultsByType[hit._index].push(hit._source);
|
|
||||||
});
|
|
||||||
setData([
|
|
||||||
{
|
|
||||||
label: renderTitle(t("menus.header.search.jobs")),
|
|
||||||
options: resultsByType.jobs.map((job) => {
|
|
||||||
return {
|
|
||||||
key: job.id,
|
|
||||||
value: job.ro_number || "N/A",
|
|
||||||
label: (
|
|
||||||
<Link to={`/manage/jobs/${job.id}`}>
|
|
||||||
<Space size="small" split={<Divider type="vertical" />}>
|
|
||||||
<strong>{job.ro_number || t("general.labels.na")}</strong>
|
|
||||||
<span>{`${job.status || ""}`}</span>
|
|
||||||
<span>
|
|
||||||
<OwnerNameDisplay ownerObject={job} />
|
|
||||||
</span>
|
|
||||||
<span>{`${job.v_model_yr || ""} ${
|
|
||||||
job.v_make_desc || ""
|
|
||||||
} ${job.v_model_desc || ""}`}</span>
|
|
||||||
<span>{`${job.clm_no || ""}`}</span>
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: renderTitle(t("menus.header.search.owners")),
|
|
||||||
options: resultsByType.owners.map((owner) => {
|
|
||||||
return {
|
|
||||||
key: owner.id,
|
|
||||||
value: OwnerNameDisplayFunction(owner),
|
|
||||||
label: (
|
|
||||||
<Link to={`/manage/owners/${owner.id}`}>
|
|
||||||
<Space
|
|
||||||
size="small"
|
|
||||||
split={<Divider type="vertical" />}
|
|
||||||
wrap
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<OwnerNameDisplay ownerObject={owner} />
|
|
||||||
</span>
|
|
||||||
<PhoneNumberFormatter>
|
|
||||||
{owner.ownr_ph1}
|
|
||||||
</PhoneNumberFormatter>
|
|
||||||
<PhoneNumberFormatter>
|
|
||||||
{owner.ownr_ph2}
|
|
||||||
</PhoneNumberFormatter>
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: renderTitle(t("menus.header.search.vehicles")),
|
|
||||||
options: resultsByType.vehicles.map((vehicle) => {
|
|
||||||
return {
|
|
||||||
key: vehicle.id,
|
|
||||||
value: `${vehicle.v_model_yr || ""} ${
|
|
||||||
vehicle.v_make_desc || ""
|
|
||||||
} ${vehicle.v_model_desc || ""}`,
|
|
||||||
label: (
|
|
||||||
<Link to={`/manage/vehicles/${vehicle.id}`}>
|
|
||||||
<Space size="small" split={<Divider type="vertical" />}>
|
|
||||||
<span>
|
|
||||||
{`${vehicle.v_model_yr || ""} ${
|
|
||||||
vehicle.v_make_desc || ""
|
|
||||||
} ${vehicle.v_model_desc || ""}`}
|
|
||||||
</span>
|
|
||||||
<span>{vehicle.plate_no || ""}</span>
|
|
||||||
<span>
|
|
||||||
<VehicleVinDisplay>
|
|
||||||
{vehicle.v_vin || ""}
|
|
||||||
</VehicleVinDisplay>
|
|
||||||
</span>
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: renderTitle(t("menus.header.search.payments")),
|
|
||||||
options: resultsByType.payments.map((payment) => {
|
|
||||||
return {
|
|
||||||
key: payment.id,
|
|
||||||
value: `${payment.job?.ro_number} ${payment.amount}`,
|
|
||||||
label: (
|
|
||||||
<Link to={`/manage/jobs/${payment.job?.id}`}>
|
|
||||||
<Space size="small" split={<Divider type="vertical" />}>
|
|
||||||
<span>{payment.paymentnum}</span>
|
|
||||||
<span>{payment.job?.ro_number}</span>
|
|
||||||
<span>{payment.memo || ""}</span>
|
|
||||||
<span>{payment.amount || ""}</span>
|
|
||||||
<span>{payment.transactionid || ""}</span>
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: renderTitle(t("menus.header.search.bills")),
|
|
||||||
options: resultsByType.bills.map((bill) => {
|
|
||||||
return {
|
|
||||||
key: bill.id,
|
|
||||||
value: `${bill.invoice_number} - ${bill.vendor.name}`,
|
|
||||||
label: (
|
|
||||||
<Link to={`/manage/bills?billid=${bill.id}`}>
|
|
||||||
<Space size="small" split={<Divider type="vertical" />}>
|
|
||||||
<span>{bill.invoice_number}</span>
|
|
||||||
<span>{bill.vendor.name}</span>
|
|
||||||
<span>{bill.date}</span>
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: renderTitle(t("menus.header.search.phonebook")),
|
|
||||||
// options: resultsByType.search_phonebook.map((pb) => {
|
|
||||||
// return {
|
|
||||||
// key: pb.id,
|
|
||||||
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
|
|
||||||
// pb.company || ""
|
|
||||||
// }`,
|
|
||||||
// label: (
|
|
||||||
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
|
|
||||||
// <Space size="small" split={<Divider type="vertical" />}>
|
|
||||||
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
|
|
||||||
// pb.company || ""
|
|
||||||
// }`}</span>
|
|
||||||
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
|
|
||||||
// <span>{pb.email}</span>
|
|
||||||
// </Space>
|
|
||||||
// </Link>
|
|
||||||
// ),
|
|
||||||
// };
|
|
||||||
// }),
|
|
||||||
// },
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error while fetching search results", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
|
||||||
debouncedExecuteSearch(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTitle = (title) => {
|
|
||||||
return <span>{title}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AutoComplete
|
|
||||||
options={data}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
defaultActiveFirstOption
|
|
||||||
onSelect={(val, opt) => {
|
|
||||||
history.push(opt.label.props.to);
|
|
||||||
}}
|
|
||||||
onClear={() => setData([])}
|
|
||||||
>
|
|
||||||
<Input.Search
|
|
||||||
size="large"
|
|
||||||
placeholder={t("general.labels.globalsearch")}
|
|
||||||
enterButton
|
|
||||||
allowClear
|
|
||||||
loading={loading}
|
|
||||||
/>
|
|
||||||
</AutoComplete>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
import { AutoComplete, Divider, Input, Space } from "antd";
|
import { LoadingOutlined } from "@ant-design/icons";
|
||||||
|
import { AutoComplete, Divider, Space } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -18,18 +19,11 @@ export default function GlobalSearch() {
|
|||||||
useLazyQuery(GLOBAL_SEARCH_QUERY);
|
useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
if (
|
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
|
||||||
v &&
|
|
||||||
v.variables.search &&
|
|
||||||
v.variables.search !== "" &&
|
|
||||||
v.variables.search.length >= 3
|
|
||||||
)
|
|
||||||
callSearch(v);
|
|
||||||
};
|
};
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
console.log("Handle Search");
|
|
||||||
debouncedExecuteSearch({ variables: { search: value } });
|
debouncedExecuteSearch({ variables: { search: value } });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,7 +38,7 @@ export default function GlobalSearch() {
|
|||||||
options: data.search_jobs.map((job) => {
|
options: data.search_jobs.map((job) => {
|
||||||
return {
|
return {
|
||||||
key: job.id,
|
key: job.id,
|
||||||
value: job.ro_number || "N/A",
|
value: job.ro_number,
|
||||||
label: (
|
label: (
|
||||||
<Link to={`/manage/jobs/${job.id}`}>
|
<Link to={`/manage/jobs/${job.id}`}>
|
||||||
<Space size="small" split={<Divider type="vertical" />}>
|
<Space size="small" split={<Divider type="vertical" />}>
|
||||||
@@ -184,18 +178,13 @@ export default function GlobalSearch() {
|
|||||||
<AutoComplete
|
<AutoComplete
|
||||||
options={options}
|
options={options}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
|
suffixIcon={loading && <LoadingOutlined spin />}
|
||||||
defaultActiveFirstOption
|
defaultActiveFirstOption
|
||||||
|
placeholder={t("general.labels.globalsearch")}
|
||||||
|
allowClear
|
||||||
onSelect={(val, opt) => {
|
onSelect={(val, opt) => {
|
||||||
history.push(opt.label.props.to);
|
history.push(opt.label.props.to);
|
||||||
}}
|
}}
|
||||||
>
|
></AutoComplete>
|
||||||
<Input.Search
|
|
||||||
size="large"
|
|
||||||
placeholder={t("general.labels.globalsearch")}
|
|
||||||
enterButton
|
|
||||||
allowClear
|
|
||||||
loading={loading}
|
|
||||||
/>
|
|
||||||
</AutoComplete>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Form,
|
|
||||||
Menu,
|
Menu,
|
||||||
notification,
|
notification,
|
||||||
Popover,
|
Popover,
|
||||||
Select,
|
|
||||||
Space,
|
Space,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
@@ -61,10 +59,7 @@ export function ScheduleEventComponent({
|
|||||||
|
|
||||||
const blockContent = (
|
const blockContent = (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
|
||||||
onClick={() => handleCancel({ id: event.id })}
|
|
||||||
disabled={event.arrived}
|
|
||||||
>
|
|
||||||
{t("appointments.actions.cancel")}
|
{t("appointments.actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,46 +203,10 @@ export function ScheduleEventComponent({
|
|||||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : null}
|
) : null}
|
||||||
<Popover
|
|
||||||
trigger="click"
|
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
|
||||||
disabled={event.arrived}
|
{t("appointments.actions.cancel")}
|
||||||
content={
|
</Button>
|
||||||
<Form
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={({ lost_sale_reason }) => {
|
|
||||||
handleCancel({ id: event.id, lost_sale_reason });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name="lost_sale_reason"
|
|
||||||
label={t("jobs.fields.lost_sale_reason")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
|
||||||
label: lsr,
|
|
||||||
value: lsr,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Button htmlType="submit">
|
|
||||||
{t("appointments.actions.cancel")}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
// onClick={() => handleCancel(event.id)}
|
|
||||||
disabled={event.arrived}
|
|
||||||
>
|
|
||||||
{t("appointments.actions.cancel")}
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
{event.isintake ? (
|
{event.isintake ? (
|
||||||
<Button
|
<Button
|
||||||
disabled={event.arrived}
|
disabled={event.arrived}
|
||||||
@@ -290,7 +249,7 @@ export function ScheduleEventComponent({
|
|||||||
const RegularEvent = event.isintake ? (
|
const RegularEvent = event.isintake ? (
|
||||||
<Space
|
<Space
|
||||||
wrap
|
wrap
|
||||||
size="small"
|
size='small'
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
event.color && event.color.hex ? event.color.hex : event.color,
|
event.color && event.color.hex ? event.color.hex : event.color,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
const handleCancel = async ({ id, lost_sale_reason }) => {
|
const handleCancel = async (id) => {
|
||||||
logImEXEvent("schedule_cancel_appt");
|
logImEXEvent("schedule_cancel_appt");
|
||||||
|
|
||||||
const cancelAppt = await cancelAppointment({
|
const cancelAppt = await cancelAppointment({
|
||||||
@@ -38,8 +38,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
|||||||
job: {
|
job: {
|
||||||
date_scheduled: null,
|
date_scheduled: null,
|
||||||
scheduled_in: null,
|
scheduled_in: null,
|
||||||
scheduled_completion: null,
|
scheduled_completion:null,
|
||||||
lost_sale_reason,
|
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -103,12 +103,7 @@ export function JobLinesComponent({
|
|||||||
fixed: "left",
|
fixed: "left",
|
||||||
key: "line_desc",
|
key: "line_desc",
|
||||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||||
onCell: (record) => ({
|
onCell: (record) => ({ className: record.manual_line && "job-line-manual" }),
|
||||||
className: record.manual_line && "job-line-manual",
|
|
||||||
style: {
|
|
||||||
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
@@ -348,7 +343,7 @@ export function JobLinesComponent({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setJobLineEditContext({
|
setJobLineEditContext({
|
||||||
actions: { refetch: refetch, submit: form && form.submit },
|
actions: { refetch: refetch, submit: form && form.submit },
|
||||||
context: { ...record, jobid: job.id },
|
context: record,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -22,20 +22,9 @@ export function JoblinePresetButton({ bodyshop, form }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu
|
<Menu>
|
||||||
style={{
|
|
||||||
columnCount: Math.max(
|
|
||||||
Math.floor(bodyshop.md_jobline_presets.length / 15),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{bodyshop.md_jobline_presets.map((i, idx) => (
|
{bodyshop.md_jobline_presets.map((i, idx) => (
|
||||||
<Menu.Item
|
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
|
||||||
onClick={() => handleSelect(i)}
|
|
||||||
key={idx}
|
|
||||||
style={{ breakInside: "avoid" }}
|
|
||||||
>
|
|
||||||
{i.label}
|
{i.label}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ export function JobLinesUpsertModalComponent({
|
|||||||
{},
|
{},
|
||||||
bodyshop.imexshopid
|
bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
const { Autohouse_Detail_line } = useTreatments(
|
|
||||||
["Autohouse_Detail_line"],
|
|
||||||
{},
|
|
||||||
bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -160,40 +155,6 @@ export function JobLinesUpsertModalComponent({
|
|||||||
>
|
>
|
||||||
<InputNumber precision={1} />
|
<InputNumber precision={1} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{Autohouse_Detail_line.treatment === "on" && (
|
|
||||||
<Form.Item
|
|
||||||
label={t("joblines.fields.ah_detail_line")}
|
|
||||||
name="ah_detail_line"
|
|
||||||
valuePropName="checked"
|
|
||||||
dependencies={["mod_lbr_ty"]}
|
|
||||||
initialValue={false}
|
|
||||||
rules={[
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
value === false ||
|
|
||||||
value === undefined ||
|
|
||||||
value === null
|
|
||||||
)
|
|
||||||
return Promise.resolve();
|
|
||||||
if (
|
|
||||||
value === true &&
|
|
||||||
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
|
|
||||||
getFieldValue("mod_lbr_ty")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return Promise.reject(
|
|
||||||
t("joblines.validations.ahdetailonlyonuserdefinedtypes")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
||||||
@@ -257,6 +218,7 @@ export function JobLinesUpsertModalComponent({
|
|||||||
rules={[
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
|
console.log(value);
|
||||||
if (!value || getFieldValue("part_type") !== "PAE") {
|
if (!value || getFieldValue("part_type") !== "PAE") {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -267,6 +229,7 @@ export function JobLinesUpsertModalComponent({
|
|||||||
}),
|
}),
|
||||||
({ getFieldValue }) => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
|
console.log(value, !!value);
|
||||||
if (
|
if (
|
||||||
!!getFieldValue("part_type") === (!!value || value === 0)
|
!!getFieldValue("part_type") === (!!value || value === 0)
|
||||||
) {
|
) {
|
||||||
@@ -289,7 +252,7 @@ export function JobLinesUpsertModalComponent({
|
|||||||
name="prt_dsmk_p"
|
name="prt_dsmk_p"
|
||||||
initialValue={0}
|
initialValue={0}
|
||||||
>
|
>
|
||||||
<InputNumber precision={0} min={-100} max={100} />
|
<InputNumber precision={0} min={0} max={100} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("joblines.fields.tax_part")}
|
label={t("joblines.fields.tax_part")}
|
||||||
|
|||||||
@@ -13,13 +13,8 @@ import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
|||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobLineEditModal: selectJobLineEditModal,
|
jobLineEditModal: selectJobLineEditModal,
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
|
||||||
@@ -28,13 +23,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
function JobLinesUpsertModalContainer({
|
function JobLinesUpsertModalContainer({
|
||||||
jobLineEditModal,
|
jobLineEditModal,
|
||||||
toggleModalVisible,
|
toggleModalVisible,
|
||||||
bodyshop,
|
|
||||||
}) {
|
}) {
|
||||||
const { CriticalPartsScanning } = useTreatments(
|
|
||||||
["CriticalPartsScanning"],
|
|
||||||
{},
|
|
||||||
bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
||||||
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||||
@@ -51,15 +40,7 @@ function JobLinesUpsertModalContainer({
|
|||||||
manual_line: !(
|
manual_line: !(
|
||||||
jobLineEditModal.context && jobLineEditModal.context.id
|
jobLineEditModal.context && jobLineEditModal.context.id
|
||||||
),
|
),
|
||||||
...UndefinedToNull({
|
...UndefinedToNull(values),
|
||||||
...values,
|
|
||||||
prt_dsmk_m: Dinero({
|
|
||||||
amount: Math.round((values.act_price || 0) * 100),
|
|
||||||
})
|
|
||||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
|
||||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
|
||||||
.toFormat(0.0),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -87,15 +68,7 @@ function JobLinesUpsertModalContainer({
|
|||||||
const r = await updateJobLine({
|
const r = await updateJobLine({
|
||||||
variables: {
|
variables: {
|
||||||
lineId: jobLineEditModal.context.id,
|
lineId: jobLineEditModal.context.id,
|
||||||
line: {
|
line: values,
|
||||||
...values,
|
|
||||||
prt_dsmk_m: Dinero({
|
|
||||||
amount: Math.round(values.act_price * 100),
|
|
||||||
})
|
|
||||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
|
||||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
|
||||||
.toFormat(0.0),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
||||||
});
|
});
|
||||||
@@ -119,9 +92,6 @@ function JobLinesUpsertModalContainer({
|
|||||||
}
|
}
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}
|
}
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
|
||||||
CriticalPartsScan(jobLineEditModal.context.jobid);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import {
|
|||||||
useApolloClient,
|
useApolloClient,
|
||||||
useLazyQuery,
|
useLazyQuery,
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery
|
useQuery,
|
||||||
} from "@apollo/client";
|
} from "@apollo/client";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { Col, notification, Row } from "antd";
|
import { Col, notification, Row } from "antd";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
@@ -20,7 +19,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import {
|
import {
|
||||||
DELETE_AVAILABLE_JOB,
|
DELETE_AVAILABLE_JOB,
|
||||||
QUERY_AVAILABLE_JOBS,
|
QUERY_AVAILABLE_JOBS,
|
||||||
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
|
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
|
||||||
} from "../../graphql/available-jobs.queries";
|
} from "../../graphql/available-jobs.queries";
|
||||||
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
|
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
|
||||||
@@ -28,11 +27,10 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
|
|||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import confirmDialog from "../../utils/asyncConfirm";
|
import confirmDialog from "../../utils/asyncConfirm";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||||
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||||
@@ -55,11 +53,6 @@ export function JobsAvailableContainer({
|
|||||||
currentUser,
|
currentUser,
|
||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const { CriticalPartsScanning } = useTreatments(
|
|
||||||
["CriticalPartsScanning"],
|
|
||||||
{},
|
|
||||||
bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
|
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
@@ -162,9 +155,6 @@ export function JobsAvailableContainer({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
|
||||||
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
|
|
||||||
}
|
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("jobs.successes.created"),
|
message: t("jobs.successes.created"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -251,9 +241,7 @@ export function JobsAvailableContainer({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
|
||||||
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
|
||||||
}
|
|
||||||
if (updateResult.errors) {
|
if (updateResult.errors) {
|
||||||
//error while inserting
|
//error while inserting
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
|
|||||||
@@ -43,22 +43,14 @@ export function JobsConvertButton({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
const handleConvert = async (values) => {
|
||||||
if (parentFormIsFieldsTouched()) {
|
if (parentFormIsFieldsTouched()) {
|
||||||
alert(t("jobs.labels.savebeforeconversion"));
|
alert(t("jobs.labels.savebeforeconversion"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await mutationConvertJob({
|
const res = await mutationConvertJob({
|
||||||
variables: {
|
variables: { jobId: job.id, ...values },
|
||||||
jobId: job.id,
|
|
||||||
job: {
|
|
||||||
converted: true,
|
|
||||||
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
|
|
||||||
...(bodyshop.enforce_conversion_category ? { category } : {}),
|
|
||||||
...values,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (values.ca_gst_registrant) {
|
if (values.ca_gst_registrant) {
|
||||||
@@ -95,7 +87,6 @@ export function JobsConvertButton({
|
|||||||
driveable: true,
|
driveable: true,
|
||||||
towin: false,
|
towin: false,
|
||||||
employee_csr: job.employee_csr,
|
employee_csr: job.employee_csr,
|
||||||
category: job.category,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -109,8 +100,8 @@ export function JobsConvertButton({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
{bodyshop.md_ins_cos.map((s, i) => (
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
<Select.Option key={i} value={s.name}>
|
<Select.Option key={s.name} value={s.name}>
|
||||||
{s.name}
|
{s.name}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
@@ -199,35 +190,13 @@ export function JobsConvertButton({
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{bodyshop.enforce_conversion_category && (
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.fields.ca_gst_registrant")}
|
||||||
name={"category"}
|
name="ca_gst_registrant"
|
||||||
label={t("jobs.fields.category")}
|
valuePropName="checked"
|
||||||
rules={[
|
>
|
||||||
{
|
<Switch />
|
||||||
required: bodyshop.enforce_conversion_category,
|
</Form.Item>
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select allowClear>
|
|
||||||
{bodyshop.md_categories.map((s) => (
|
|
||||||
<Select.Option key={s} value={s}>
|
|
||||||
{s}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.ca_gst_registrant")}
|
|
||||||
name="ca_gst_registrant"
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.driveable")}
|
label={t("jobs.fields.driveable")}
|
||||||
name="driveable"
|
name="driveable"
|
||||||
|
|||||||
@@ -224,15 +224,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
>
|
>
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.fields.ca_gst_registrant")}
|
||||||
label={t("jobs.fields.ca_gst_registrant")}
|
name="ca_gst_registrant"
|
||||||
name="ca_gst_registrant"
|
valuePropName="checked"
|
||||||
valuePropName="checked"
|
>
|
||||||
>
|
<Switch />
|
||||||
<Switch />
|
</Form.Item>
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.other_amount_payable")}
|
label={t("jobs.fields.other_amount_payable")}
|
||||||
name="other_amount_payable"
|
name="other_amount_payable"
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ const colSpan = {
|
|||||||
lg: { span: 12 },
|
lg: { span: 12 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function JobsCreateVehicleInfoComponent({
|
export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) {
|
||||||
loading,
|
|
||||||
vehicles,
|
|
||||||
form,
|
|
||||||
}) {
|
|
||||||
const [state, setState] = useContext(JobCreateContext);
|
const [state, setState] = useContext(JobCreateContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
@@ -62,7 +58,7 @@ export default function JobsCreateVehicleInfoComponent({
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...colSpan}>
|
<Col {...colSpan}>
|
||||||
<JobsCreateVehicleInfoNewComponent form={form}/>
|
<JobsCreateVehicleInfoNewComponent />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
|
|||||||
<JobsCreateVehicleInfoComponent
|
<JobsCreateVehicleInfoComponent
|
||||||
loading={loading}
|
loading={loading}
|
||||||
vehicles={data ? data.search_vehicles : null}
|
vehicles={data ? data.search_vehicles : null}
|
||||||
form={form}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
|
||||||
|
|
||||||
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
export default function JobsCreateVehicleInfoNewComponent() {
|
||||||
const [state] = useContext(JobCreateContext);
|
const [state] = useContext(JobCreateContext);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -26,7 +25,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
<Input disabled={!state.vehicle.new} />
|
<Input disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow noDivider>
|
<LayoutFormRow grow>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("vehicles.fields.v_color")}
|
label={t("vehicles.fields.v_color")}
|
||||||
name={["vehicle", "data", "v_color"]}
|
name={["vehicle", "data", "v_color"]}
|
||||||
@@ -53,9 +52,8 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<LayoutFormRow grow noDivider>
|
<LayoutFormRow grow>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
span={10}
|
|
||||||
label={t("vehicles.fields.v_make_desc")}
|
label={t("vehicles.fields.v_make_desc")}
|
||||||
name={["vehicle", "data", "v_make_desc"]}
|
name={["vehicle", "data", "v_make_desc"]}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -68,7 +66,6 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
<Input disabled={!state.vehicle.new} />
|
<Input disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
span={11}
|
|
||||||
label={t("vehicles.fields.v_model_desc")}
|
label={t("vehicles.fields.v_model_desc")}
|
||||||
name={["vehicle", "data", "v_model_desc"]}
|
name={["vehicle", "data", "v_model_desc"]}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -80,11 +77,6 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
>
|
>
|
||||||
<Input disabled={!state.vehicle.new} />
|
<Input disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<JobsCreateVehicleInfoPredefined
|
|
||||||
disabled={!state.vehicle.new}
|
|
||||||
form={form}
|
|
||||||
span={1}
|
|
||||||
/>
|
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<LayoutFormRow header={t("vehicles.forms.registration")} grow>
|
<LayoutFormRow header={t("vehicles.forms.registration")} grow>
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
|
|
||||||
import { Button, Input, Popover, Table } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import PredefinedVehicles from "./predefined-vehicles.js";
|
|
||||||
|
|
||||||
export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const handleOpenChange = (newOpen) => {
|
|
||||||
setOpen(newOpen);
|
|
||||||
setSearch("");
|
|
||||||
};
|
|
||||||
const filteredPredefinedVehicles =
|
|
||||||
search === ""
|
|
||||||
? PredefinedVehicles
|
|
||||||
: PredefinedVehicles.filter(
|
|
||||||
(v) =>
|
|
||||||
v.make.toLowerCase().includes(search.toLowerCase()) ||
|
|
||||||
v.model.toLowerCase().includes(search.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
const popContent = () => (
|
|
||||||
<div>
|
|
||||||
<Table
|
|
||||||
size="small"
|
|
||||||
title={() => <Input.Search onSearch={(value) => setSearch(value)} />}
|
|
||||||
dataSource={filteredPredefinedVehicles}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
dataIndex: "make",
|
|
||||||
key: "make",
|
|
||||||
title: t("vehicles.fields.v_make_desc"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: "model",
|
|
||||||
key: "model",
|
|
||||||
title: t("vehicles.fields.v_model_desc"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: "select",
|
|
||||||
key: "select",
|
|
||||||
title: t("general.labels.actions"),
|
|
||||||
render: (value, record) => (
|
|
||||||
<Button
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={() => {
|
|
||||||
form.setFieldsValue({
|
|
||||||
vehicle: {
|
|
||||||
data: {
|
|
||||||
v_make_desc: record.make,
|
|
||||||
v_model_desc: record.model,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setOpen(false);
|
|
||||||
setSearch("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlusOutlined />
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
content={popContent}
|
|
||||||
trigger="click"
|
|
||||||
open={open}
|
|
||||||
placement="left"
|
|
||||||
onOpenChange={handleOpenChange}
|
|
||||||
destroyTooltipOnHide
|
|
||||||
>
|
|
||||||
<SearchOutlined style={{ cursor: "pointer" }} />
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -256,7 +256,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow header={t("jobs.forms.other")}>
|
<FormRow header={t("jobs.forms.other")}>
|
||||||
<Form.Item label={t("jobs.fields.category")} name="category">
|
<Form.Item label={t("jobs.fields.category")} name="category">
|
||||||
<Select disabled={jobRO} allowClear>
|
<Select disabled={jobRO}>
|
||||||
{bodyshop.md_categories.map((s) => (
|
{bodyshop.md_categories.map((s) => (
|
||||||
<Select.Option key={s} value={s}>
|
<Select.Option key={s} value={s}>
|
||||||
{s}
|
{s}
|
||||||
@@ -289,12 +289,6 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
>
|
>
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.lost_sale_reason")}
|
|
||||||
name="lost_sale_reason"
|
|
||||||
>
|
|
||||||
<Input disabled={jobRO} allowClear />
|
|
||||||
</Form.Item>
|
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { DownCircleFilled } from "@ant-design/icons";
|
import { DownCircleFilled } from "@ant-design/icons";
|
||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
import {
|
import { Button, Dropdown, Menu, notification, Popconfirm } from "antd";
|
||||||
Button,
|
|
||||||
Dropdown,
|
|
||||||
Form,
|
|
||||||
Menu,
|
|
||||||
notification,
|
|
||||||
Popconfirm,
|
|
||||||
Popover,
|
|
||||||
Select,
|
|
||||||
} from "antd";
|
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -136,63 +127,35 @@ export function JobsDetailHeaderActions({
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
||||||
>
|
>
|
||||||
<Popover
|
<Popconfirm
|
||||||
trigger="click"
|
title={t("general.labels.areyousure")}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
||||||
content={
|
onConfirm={async () => {
|
||||||
<Form
|
const jobUpdate = await cancelAllAppointments({
|
||||||
layout="vertical"
|
variables: {
|
||||||
onFinish={async ({ lost_sale_reason }) => {
|
jobid: job.id,
|
||||||
const jobUpdate = await cancelAllAppointments({
|
job: {
|
||||||
variables: {
|
date_scheduled: null,
|
||||||
jobid: job.id,
|
scheduled_in: null,
|
||||||
job: {
|
scheduled_completion: null,
|
||||||
date_scheduled: null,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
scheduled_in: null,
|
},
|
||||||
scheduled_completion: null,
|
},
|
||||||
lost_sale_reason,
|
});
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
if (!jobUpdate.errors) {
|
||||||
},
|
notification["success"]({
|
||||||
},
|
message: t("appointments.successes.canceled"),
|
||||||
});
|
});
|
||||||
if (!jobUpdate.errors) {
|
return;
|
||||||
notification["success"]({
|
}
|
||||||
message: t("appointments.successes.canceled"),
|
}}
|
||||||
});
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
return;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name="lost_sale_reason"
|
|
||||||
label={t("jobs.fields.lost_sale_reason")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
|
||||||
label: lsr,
|
|
||||||
value: lsr,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Button
|
|
||||||
htmlType="submit"
|
|
||||||
disabled={
|
|
||||||
job.status !== bodyshop.md_ro_statuses.default_scheduled
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("appointments.actions.cancel")}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{t("menus.jobsactions.cancelallappointments")}
|
{t("menus.jobsactions.cancelallappointments")}
|
||||||
</Popover>
|
</Popconfirm>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
disabled={
|
disabled={
|
||||||
|
|||||||
@@ -40,26 +40,24 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<CurrencyInput disabled={jobRO} min={0} />
|
<CurrencyInput disabled={jobRO} min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
|
||||||
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.fields.ca_customer_gst")}
|
||||||
label={t("jobs.fields.ca_customer_gst")}
|
name="ca_customer_gst"
|
||||||
name="ca_customer_gst"
|
>
|
||||||
>
|
<CurrencyInput
|
||||||
<CurrencyInput
|
disabled={jobRO}
|
||||||
disabled={jobRO}
|
min={0}
|
||||||
min={0}
|
max={
|
||||||
max={
|
Math.round(
|
||||||
Math.round(
|
(job.job_totals &&
|
||||||
(job.job_totals &&
|
job.job_totals.totals.federal_tax.amount) ||
|
||||||
job.job_totals.totals.federal_tax.amount) ||
|
0
|
||||||
0
|
) / 100
|
||||||
) / 100
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.other_amount_payable")}
|
label={t("jobs.fields.other_amount_payable")}
|
||||||
name="other_amount_payable"
|
name="other_amount_payable"
|
||||||
@@ -84,14 +82,12 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
|
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.region_config === "CA_BC" && (
|
<Space align="end">
|
||||||
<Space align="center">
|
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
|
||||||
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
|
<CurrencyInput disabled={jobRO} min={0} />
|
||||||
<CurrencyInput disabled={jobRO} min={0} />
|
</Form.Item>
|
||||||
</Form.Item>
|
<CABCpvrtCalculator form={form} disabled={jobRO} />
|
||||||
<CABCpvrtCalculator form={form} disabled={jobRO} />
|
</Space>
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.auto_add_ats")}
|
label={t("jobs.fields.auto_add_ats")}
|
||||||
name="auto_add_ats"
|
name="auto_add_ats"
|
||||||
@@ -145,15 +141,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
|
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.fields.ca_gst_registrant")}
|
||||||
label={t("jobs.fields.ca_gst_registrant")}
|
name="ca_gst_registrant"
|
||||||
name="ca_gst_registrant"
|
valuePropName="checked"
|
||||||
valuePropName="checked"
|
>
|
||||||
>
|
<Switch disabled={jobRO} />
|
||||||
<Switch disabled={jobRO} />
|
</Form.Item>
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="left"
|
orientation="left"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import cleanAxios from "../../utils/CleanAxios";
|
import cleanAxios from "../../utils/CleanAxios";
|
||||||
import formatBytes from "../../utils/formatbytes";
|
import formatBytes from "../../utils/formatbytes";
|
||||||
//import yauzl from "yauzl";
|
import yauzl from "yauzl";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -69,44 +69,44 @@ export function JobsDocumentsDownloadButton({
|
|||||||
setDownload(null);
|
setDownload(null);
|
||||||
if (Direct_Media_Download.treatment === "on") {
|
if (Direct_Media_Download.treatment === "on") {
|
||||||
try {
|
try {
|
||||||
// const parentDir = await window.showDirectoryPicker({
|
const parentDir = await window.showDirectoryPicker({
|
||||||
// id: "media",
|
id: "media",
|
||||||
// startIn: "downloads",
|
startIn: "downloads",
|
||||||
// });
|
});
|
||||||
|
|
||||||
// const directory = await parentDir.getDirectoryHandle(identifier, {
|
const directory = await parentDir.getDirectoryHandle(identifier, {
|
||||||
// create: true,
|
create: true,
|
||||||
// });
|
});
|
||||||
|
|
||||||
// yauzl.fromBuffer(
|
yauzl.fromBuffer(
|
||||||
// Buffer.from(theDownloadedZip.data),
|
Buffer.from(theDownloadedZip.data),
|
||||||
// {},
|
{},
|
||||||
// (err, zipFile) => {
|
(err, zipFile) => {
|
||||||
// if (err) throw err;
|
if (err) throw err;
|
||||||
// zipFile.on("entry", (entry) => {
|
zipFile.on("entry", (entry) => {
|
||||||
// zipFile.openReadStream(entry, async (readErr, readStream) => {
|
zipFile.openReadStream(entry, async (readErr, readStream) => {
|
||||||
// if (readErr) {
|
if (readErr) {
|
||||||
// zipFile.close();
|
zipFile.close();
|
||||||
// throw readErr;
|
throw readErr;
|
||||||
// }
|
}
|
||||||
// if (err) throw err;
|
if (err) throw err;
|
||||||
// let fileSystemHandle = await directory.getFileHandle(
|
let fileSystemHandle = await directory.getFileHandle(
|
||||||
// entry.fileName,
|
entry.fileName,
|
||||||
// {
|
{
|
||||||
// create: true,
|
create: true,
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
// const writable = await fileSystemHandle.createWritable();
|
const writable = await fileSystemHandle.createWritable();
|
||||||
// readStream.on("data", async function (chunk) {
|
readStream.on("data", async function (chunk) {
|
||||||
// await writable.write(chunk);
|
await writable.write(chunk);
|
||||||
// });
|
});
|
||||||
// readStream.on("end", async function () {
|
readStream.on("end", async function () {
|
||||||
// await writable.close();
|
await writable.close();
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
standardMediaDownload(theDownloadedZip.data);
|
standardMediaDownload(theDownloadedZip.data);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Col, Row, Space } from "antd";
|
import { Button, Card, Col, Row, Space } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import Gallery from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
|
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
|
||||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||||
@@ -11,9 +11,6 @@ import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.compo
|
|||||||
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
|
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
|
||||||
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component";
|
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component";
|
||||||
|
|
||||||
import Lightbox from "react-image-lightbox";
|
|
||||||
import "react-image-lightbox/style.css";
|
|
||||||
|
|
||||||
function JobsDocumentsComponent({
|
function JobsDocumentsComponent({
|
||||||
data,
|
data,
|
||||||
jobId,
|
jobId,
|
||||||
@@ -26,7 +23,11 @@ function JobsDocumentsComponent({
|
|||||||
}) {
|
}) {
|
||||||
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
|
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalState, setModalState] = useState({ open: false, index: 0 });
|
const [index, setIndex] = useState(0);
|
||||||
|
|
||||||
|
const onCurrentImageChange = (index) => {
|
||||||
|
setIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let documents = data.reduce(
|
let documents = data.reduce(
|
||||||
@@ -34,16 +35,14 @@ function JobsDocumentsComponent({
|
|||||||
const fileType = DetermineFileType(value.type);
|
const fileType = DetermineFileType(value.type);
|
||||||
if (value.type.startsWith("image")) {
|
if (value.type.startsWith("image")) {
|
||||||
acc.images.push({
|
acc.images.push({
|
||||||
// src: GenerateSrcUrl(value),
|
src: GenerateSrcUrl(value),
|
||||||
src: GenerateThumbUrl(value),
|
thumbnail: GenerateThumbUrl(value),
|
||||||
// src: GenerateSrcUrl(value),
|
thumbnailHeight: 225,
|
||||||
// thumbnail: GenerateThumbUrl(value),
|
thumbnailWidth: 225,
|
||||||
fullsize: GenerateSrcUrl(value),
|
|
||||||
height: 225,
|
|
||||||
width: 225,
|
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
key: value.key,
|
key: value.key,
|
||||||
extension: value.extension,
|
extension: value.extension,
|
||||||
|
|
||||||
id: value.id,
|
id: value.id,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
size: value.size,
|
size: value.size,
|
||||||
@@ -63,7 +62,7 @@ function JobsDocumentsComponent({
|
|||||||
const fileName = value.key.split("/").pop();
|
const fileName = value.key.split("/").pop();
|
||||||
acc.other.push({
|
acc.other.push({
|
||||||
source: GenerateSrcUrl(value),
|
source: GenerateSrcUrl(value),
|
||||||
src: thumb,
|
src: "",
|
||||||
thumbnail: thumb,
|
thumbnail: thumb,
|
||||||
tags: [
|
tags: [
|
||||||
{
|
{
|
||||||
@@ -86,9 +85,10 @@ function JobsDocumentsComponent({
|
|||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
height: 225,
|
thumbnailHeight: 225,
|
||||||
width: 225,
|
thumbnailWidth: 225,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
|
|
||||||
extension: value.extension,
|
extension: value.extension,
|
||||||
key: value.key,
|
key: value.key,
|
||||||
id: value.id,
|
id: value.id,
|
||||||
@@ -148,15 +148,35 @@ function JobsDocumentsComponent({
|
|||||||
<Card title={t("jobs.labels.documents-images")}>
|
<Card title={t("jobs.labels.documents-images")}>
|
||||||
<Gallery
|
<Gallery
|
||||||
images={galleryImages.images}
|
images={galleryImages.images}
|
||||||
onClick={(index, item) => {
|
backdropClosesModal={true}
|
||||||
setModalState({ open: true, index: index });
|
currentImageWillChange={onCurrentImageChange}
|
||||||
// window.open(
|
customControls={[
|
||||||
// item.fullsize,
|
<Button
|
||||||
// "_blank",
|
key="edit-button"
|
||||||
// "toolbar=0,location=0,menubar=0"
|
style={{
|
||||||
// );
|
float: "right",
|
||||||
|
zIndex: "5",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
const newWindow = window.open(
|
||||||
|
`${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`,
|
||||||
|
"_blank",
|
||||||
|
"noopener,noreferrer"
|
||||||
|
);
|
||||||
|
if (newWindow) newWindow.opener = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditFilled />
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
onClickImage={(props) => {
|
||||||
|
window.open(
|
||||||
|
props.target.src,
|
||||||
|
"_blank",
|
||||||
|
"toolbar=0,location=0,menubar=0"
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
onSelect={(index, image) => {
|
onSelectImage={(index, image) => {
|
||||||
setgalleryImages({
|
setgalleryImages({
|
||||||
...galleryImages,
|
...galleryImages,
|
||||||
images: galleryImages.images.map((g, idx) =>
|
images: galleryImages.images.map((g, idx) =>
|
||||||
@@ -171,6 +191,8 @@ function JobsDocumentsComponent({
|
|||||||
<Card title={t("jobs.labels.documents-other")}>
|
<Card title={t("jobs.labels.documents-other")}>
|
||||||
<Gallery
|
<Gallery
|
||||||
images={galleryImages.other}
|
images={galleryImages.other}
|
||||||
|
backdropClosesModal={true}
|
||||||
|
enableLightbox={false}
|
||||||
thumbnailStyle={() => {
|
thumbnailStyle={() => {
|
||||||
return {
|
return {
|
||||||
backgroundImage: <FileExcelFilled />,
|
backgroundImage: <FileExcelFilled />,
|
||||||
@@ -179,14 +201,14 @@ function JobsDocumentsComponent({
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
onClick={(index) => {
|
onClickThumbnail={(index) => {
|
||||||
window.open(
|
window.open(
|
||||||
galleryImages.other[index].source,
|
galleryImages.other[index].source,
|
||||||
"_blank",
|
"_blank",
|
||||||
"toolbar=0,location=0,menubar=0"
|
"toolbar=0,location=0,menubar=0"
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onSelect={(index) => {
|
onSelectImage={(index) => {
|
||||||
setgalleryImages({
|
setgalleryImages({
|
||||||
...galleryImages,
|
...galleryImages,
|
||||||
other: galleryImages.other.map((g, idx) =>
|
other: galleryImages.other.map((g, idx) =>
|
||||||
@@ -197,53 +219,6 @@ function JobsDocumentsComponent({
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
{modalState.open && (
|
|
||||||
<Lightbox
|
|
||||||
toolbarButtons={[
|
|
||||||
<EditFilled
|
|
||||||
onClick={() => {
|
|
||||||
const newWindow = window.open(
|
|
||||||
`${window.location.protocol}//${
|
|
||||||
window.location.host
|
|
||||||
}/edit?documentId=${
|
|
||||||
galleryImages.images[modalState.index].id
|
|
||||||
}`,
|
|
||||||
"_blank",
|
|
||||||
"noopener,noreferrer"
|
|
||||||
);
|
|
||||||
if (newWindow) newWindow.opener = null;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
mainSrc={galleryImages.images[modalState.index].fullsize}
|
|
||||||
nextSrc={
|
|
||||||
galleryImages.images[
|
|
||||||
(modalState.index + 1) % galleryImages.images.length
|
|
||||||
].fullsize
|
|
||||||
}
|
|
||||||
prevSrc={
|
|
||||||
galleryImages.images[
|
|
||||||
(modalState.index + galleryImages.images.length - 1) %
|
|
||||||
galleryImages.images.length
|
|
||||||
].fullsize
|
|
||||||
}
|
|
||||||
onCloseRequest={() => setModalState({ open: false, index: 0 })}
|
|
||||||
onMovePrevRequest={() =>
|
|
||||||
setModalState({
|
|
||||||
...modalState,
|
|
||||||
index:
|
|
||||||
(modalState.index + galleryImages.images.length - 1) %
|
|
||||||
galleryImages.images.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMoveNextRequest={() =>
|
|
||||||
setModalState({
|
|
||||||
...modalState,
|
|
||||||
index: (modalState.index + 1) % galleryImages.images.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import Gallery from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateThumbUrl } from "./job-documents.utility";
|
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||||
|
|
||||||
function JobsDocumentGalleryExternal({
|
function JobsDocumentGalleryExternal({
|
||||||
data,
|
data,
|
||||||
@@ -15,8 +15,8 @@ function JobsDocumentGalleryExternal({
|
|||||||
let documents = data.reduce((acc, value) => {
|
let documents = data.reduce((acc, value) => {
|
||||||
if (value.type.startsWith("image")) {
|
if (value.type.startsWith("image")) {
|
||||||
acc.push({
|
acc.push({
|
||||||
//src: GenerateSrcUrl(value),
|
src: GenerateSrcUrl(value),
|
||||||
src: GenerateThumbUrl(value),
|
thumbnail: GenerateThumbUrl(value),
|
||||||
thumbnailHeight: 225,
|
thumbnailHeight: 225,
|
||||||
thumbnailWidth: 225,
|
thumbnailWidth: 225,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
@@ -39,7 +39,7 @@ function JobsDocumentGalleryExternal({
|
|||||||
<Gallery
|
<Gallery
|
||||||
images={galleryImages}
|
images={galleryImages}
|
||||||
backdropClosesModal={true}
|
backdropClosesModal={true}
|
||||||
onSelect={(index, image) => {
|
onSelectImage={(index, image) => {
|
||||||
setgalleryImages(
|
setgalleryImages(
|
||||||
galleryImages.map((g, idx) =>
|
galleryImages.map((g, idx) =>
|
||||||
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
|
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
|
||||||
import { Alert, Button, Card, Space } from "antd";
|
import { Alert, Button, Card, Space } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import Gallery from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -19,9 +19,6 @@ import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.downl
|
|||||||
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
|
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
|
||||||
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
|
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
|
||||||
|
|
||||||
import Lightbox from "react-image-lightbox";
|
|
||||||
import "react-image-lightbox/style.css";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
allMedia: selectAllMedia,
|
allMedia: selectAllMedia,
|
||||||
@@ -52,7 +49,6 @@ export function JobsDocumentsLocalGallery({
|
|||||||
vendorid,
|
vendorid,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalState, setModalState] = useState({ open: false, index: 0 });
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (job) {
|
if (job) {
|
||||||
if (invoice_number) {
|
if (invoice_number) {
|
||||||
@@ -74,20 +70,12 @@ export function JobsDocumentsLocalGallery({
|
|||||||
) {
|
) {
|
||||||
acc.images.push({
|
acc.images.push({
|
||||||
...val,
|
...val,
|
||||||
fullsize: val.src,
|
|
||||||
src: val.thumbnail,
|
|
||||||
height: val.thumbnailHeight,
|
|
||||||
width: val.thumbnailWidth,
|
|
||||||
...(val.optimized && { src: val.optimized, fullsize: val.src }),
|
...(val.optimized && { src: val.optimized, fullsize: val.src }),
|
||||||
});
|
});
|
||||||
if (val.optimized) optimized = true;
|
if (val.optimized) optimized = true;
|
||||||
} else {
|
} else {
|
||||||
acc.other.push({
|
acc.other.push({
|
||||||
...val,
|
...val,
|
||||||
fullsize: val.src,
|
|
||||||
src: val.thumbnail,
|
|
||||||
height: val.thumbnailHeight,
|
|
||||||
width: val.thumbnailWidth,
|
|
||||||
tags: [{ value: val.filename, title: val.filename }],
|
tags: [{ value: val.filename, title: val.filename }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -132,7 +120,8 @@ export function JobsDocumentsLocalGallery({
|
|||||||
<Card title={t("jobs.labels.documents-images")}>
|
<Card title={t("jobs.labels.documents-images")}>
|
||||||
<Gallery
|
<Gallery
|
||||||
images={jobMedia.images}
|
images={jobMedia.images}
|
||||||
onSelect={(index, image) => {
|
backdropClosesModal={true}
|
||||||
|
onSelectImage={(index, image) => {
|
||||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||||
}}
|
}}
|
||||||
{...(optimized && {
|
{...(optimized && {
|
||||||
@@ -144,23 +133,24 @@ export function JobsDocumentsLocalGallery({
|
|||||||
/>,
|
/>,
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
onClick={(index) => {
|
onClickImage={(props) => {
|
||||||
setModalState({ open: true, index: index });
|
const media = allMedia[job.id].find(
|
||||||
// const media = allMedia[job.id].find(
|
(m) => m.optimized === props.target.src
|
||||||
// (m) => m.optimized === item.src
|
);
|
||||||
// );
|
|
||||||
|
|
||||||
// window.open(
|
window.open(
|
||||||
// media ? media.fullsize : item.fullsize,
|
media ? media.src : props.target.src,
|
||||||
// "_blank",
|
"_blank",
|
||||||
// "toolbar=0,location=0,menubar=0"
|
"toolbar=0,location=0,menubar=0"
|
||||||
// );
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title={t("jobs.labels.documents-other")}>
|
<Card title={t("jobs.labels.documents-other")}>
|
||||||
<Gallery
|
<Gallery
|
||||||
images={jobMedia.other}
|
images={jobMedia.other}
|
||||||
|
backdropClosesModal={true}
|
||||||
|
enableLightbox={false}
|
||||||
thumbnailStyle={() => {
|
thumbnailStyle={() => {
|
||||||
return {
|
return {
|
||||||
backgroundImage: <FileExcelFilled />,
|
backgroundImage: <FileExcelFilled />,
|
||||||
@@ -169,48 +159,18 @@ export function JobsDocumentsLocalGallery({
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
onClick={(index) => {
|
onClickThumbnail={(index) => {
|
||||||
window.open(
|
window.open(
|
||||||
jobMedia.other[index].fullsize,
|
jobMedia.other[index].src,
|
||||||
"_blank",
|
"_blank",
|
||||||
"toolbar=0,location=0,menubar=0"
|
"toolbar=0,location=0,menubar=0"
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onSelect={(index, image) => {
|
onSelectImage={(index, image) => {
|
||||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
{modalState.open && (
|
|
||||||
<Lightbox
|
|
||||||
mainSrc={jobMedia.images[modalState.index].fullsize}
|
|
||||||
nextSrc={
|
|
||||||
jobMedia.images[(modalState.index + 1) % jobMedia.images.length]
|
|
||||||
.fullsize
|
|
||||||
}
|
|
||||||
prevSrc={
|
|
||||||
jobMedia.images[
|
|
||||||
(modalState.index + jobMedia.images.length - 1) %
|
|
||||||
jobMedia.images.length
|
|
||||||
].fullsize
|
|
||||||
}
|
|
||||||
onCloseRequest={() => setModalState({ open: false, index: 0 })}
|
|
||||||
onMovePrevRequest={() =>
|
|
||||||
setModalState({
|
|
||||||
...modalState,
|
|
||||||
index:
|
|
||||||
(modalState.index + jobMedia.images.length - 1) %
|
|
||||||
jobMedia.images.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMoveNextRequest={() =>
|
|
||||||
setModalState({
|
|
||||||
...modalState,
|
|
||||||
index: (modalState.index + 1) % jobMedia.images.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import Gallery from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -38,7 +38,7 @@ function JobDocumentsLocalGalleryExternal({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (jobId) {
|
if ( jobId) {
|
||||||
getJobMedia(jobId);
|
getJobMedia(jobId);
|
||||||
}
|
}
|
||||||
}, [jobId, getJobMedia]);
|
}, [jobId, getJobMedia]);
|
||||||
@@ -52,15 +52,11 @@ function JobDocumentsLocalGalleryExternal({
|
|||||||
val.type.mime &&
|
val.type.mime &&
|
||||||
val.type.mime.startsWith("image")
|
val.type.mime.startsWith("image")
|
||||||
) {
|
) {
|
||||||
acc.push({ ...val, src: val.thumbnail });
|
acc.push(val);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
: [];
|
: [];
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
|
|
||||||
documents
|
|
||||||
);
|
|
||||||
|
|
||||||
setgalleryImages(documents);
|
setgalleryImages(documents);
|
||||||
}, [allMedia, jobId, setgalleryImages, t]);
|
}, [allMedia, jobId, setgalleryImages, t]);
|
||||||
@@ -69,7 +65,8 @@ function JobDocumentsLocalGalleryExternal({
|
|||||||
<div className="clearfix">
|
<div className="clearfix">
|
||||||
<Gallery
|
<Gallery
|
||||||
images={galleryImages}
|
images={galleryImages}
|
||||||
onSelect={(index, image) => {
|
backdropClosesModal={true}
|
||||||
|
onSelectImage={(index, image) => {
|
||||||
setgalleryImages(
|
setgalleryImages(
|
||||||
galleryImages.map((g, idx) =>
|
galleryImages.map((g, idx) =>
|
||||||
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export function JobsExportAllButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||||
import axios from "axios";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
@@ -22,8 +21,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const [openSearchResults, setOpenSearchResults] = useState([]);
|
|
||||||
const [searchLoading, setSearchLoading] = useState(false);
|
|
||||||
const { page, sortcolumn, sortorder } = search;
|
const { page, sortcolumn, sortorder } = search;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -196,28 +193,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (search.search && search.search.trim() !== "") {
|
|
||||||
searchJobs();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function searchJobs(value) {
|
|
||||||
try {
|
|
||||||
setSearchLoading(true);
|
|
||||||
const searchData = await axios.post("/search", {
|
|
||||||
search: value || search.search,
|
|
||||||
index: "jobs",
|
|
||||||
});
|
|
||||||
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error while fetching search results", error);
|
|
||||||
} finally {
|
|
||||||
setSearchLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
extra={
|
extra={
|
||||||
@@ -230,7 +205,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
delete search.search;
|
delete search.search;
|
||||||
delete search.page;
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -246,32 +220,24 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
onSearch={(value) => {
|
onSearch={(value) => {
|
||||||
search.search = value;
|
search.search = value;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
searchJobs(value);
|
|
||||||
}}
|
}}
|
||||||
loading={loading || searchLoading}
|
|
||||||
enterButton
|
enterButton
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
loading={loading || searchLoading}
|
loading={loading}
|
||||||
pagination={
|
pagination={{
|
||||||
search?.search
|
position: "top",
|
||||||
? {
|
pageSize: 25,
|
||||||
pageSize: 25,
|
current: parseInt(page || 1),
|
||||||
showSizeChanger: false,
|
total: total,
|
||||||
}
|
showSizeChanger: false,
|
||||||
: {
|
}}
|
||||||
pageSize: 25,
|
|
||||||
current: parseInt(page || 1),
|
|
||||||
total: total,
|
|
||||||
showSizeChanger: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={search?.search ? openSearchResults : jobs}
|
dataSource={jobs}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -260,19 +260,6 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
filters:
|
|
||||||
(jobs &&
|
|
||||||
jobs
|
|
||||||
.map((j) => j.ins_co_nm)
|
|
||||||
.filter(onlyUnique)
|
|
||||||
.map((s) => {
|
|
||||||
return {
|
|
||||||
text: s,
|
|
||||||
value: [s],
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[],
|
|
||||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,27 +75,6 @@ export function JobNotesComponent({
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("notes.fields.type"),
|
|
||||||
dataIndex: "type",
|
|
||||||
key: "type",
|
|
||||||
width: 120,
|
|
||||||
filteredValue: filter?.type || null,
|
|
||||||
filters: [
|
|
||||||
{ value: "general", text: t("notes.fields.types.general") },
|
|
||||||
{ value: "customer", text: t("notes.fields.types.customer") },
|
|
||||||
{ value: "shop", text: t("notes.fields.types.shop") },
|
|
||||||
{ value: "office", text: t("notes.fields.types.office") },
|
|
||||||
{ value: "parts", text: t("notes.fields.types.parts") },
|
|
||||||
{ value: "paint", text: t("notes.fields.types.paint") },
|
|
||||||
{
|
|
||||||
value: "supplement",
|
|
||||||
text: t("notes.fields.types.supplement"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onFilter: (value, record) => value.includes(record.type),
|
|
||||||
render: (text, record) => t(`notes.fields.types.${record.type}`),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("notes.fields.text"),
|
title: t("notes.fields.text"),
|
||||||
dataIndex: "text",
|
dataIndex: "text",
|
||||||
@@ -127,7 +106,7 @@ export function JobNotesComponent({
|
|||||||
title: t("notes.actions.actions"),
|
title: t("notes.actions.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
width: 200,
|
width: 150,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -272,19 +272,6 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
filters:
|
|
||||||
(jobs &&
|
|
||||||
jobs
|
|
||||||
.map((j) => j.ins_co_nm)
|
|
||||||
.filter(onlyUnique)
|
|
||||||
.map((s) => {
|
|
||||||
return {
|
|
||||||
text: s,
|
|
||||||
value: [s],
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[],
|
|
||||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export function LaborAllocationsTable({
|
|||||||
<Card title={t("jobs.labels.laborallocations")}>
|
<Card title={t("jobs.labels.laborallocations")}>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey={(record) => `${record.cost_center} ${record.mod_lbr_ty}`}
|
rowKey="cost_center"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
dataSource={totals}
|
dataSource={totals}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ export const CalculateAllocationsTotals = (
|
|||||||
timetickets,
|
timetickets,
|
||||||
adjustments = []
|
adjustments = []
|
||||||
) => {
|
) => {
|
||||||
|
console.log(
|
||||||
|
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
|
||||||
|
adjustments
|
||||||
|
);
|
||||||
const responsibilitycenters = bodyshop.md_responsibility_centers;
|
const responsibilitycenters = bodyshop.md_responsibility_centers;
|
||||||
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
|
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
|
||||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
import {
|
import { Checkbox, Col, Form, Input, Row, Space, Switch, Tag } from "antd";
|
||||||
Checkbox,
|
|
||||||
Col,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Row,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Tag,
|
|
||||||
} from "antd";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -56,28 +46,6 @@ export function NoteUpsertModalComponent({ form, noteUpsertModal }) {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
|
||||||
<Form.Item
|
|
||||||
label={t("notes.fields.type")}
|
|
||||||
name="type"
|
|
||||||
initialValue="general"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={[
|
|
||||||
{ value: "general", label: t("notes.fields.types.general") },
|
|
||||||
{ value: "customer", label: t("notes.fields.types.customer") },
|
|
||||||
{ value: "shop", label: t("notes.fields.types.shop") },
|
|
||||||
{ value: "office", label: t("notes.fields.types.office") },
|
|
||||||
{ value: "parts", label: t("notes.fields.types.parts") },
|
|
||||||
{ value: "paint", label: t("notes.fields.types.paint") },
|
|
||||||
{
|
|
||||||
value: "supplement",
|
|
||||||
label: t("notes.fields.types.supplement"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<NotesPresetButton form={form} />
|
<NotesPresetButton form={form} />
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
|
|||||||
const [updateNote] = useMutation(UPDATE_NOTE);
|
const [updateNote] = useMutation(UPDATE_NOTE);
|
||||||
|
|
||||||
const { visible, context, actions } = noteUpsertModal;
|
const { visible, context, actions } = noteUpsertModal;
|
||||||
const { jobId, existingNote, text } = context;
|
const { jobId, existingNote } = context;
|
||||||
const { refetch } = actions;
|
const { refetch } = actions;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -45,12 +45,8 @@ export function NoteUpsertModalContainer({
|
|||||||
form.setFieldsValue(existingNote);
|
form.setFieldsValue(existingNote);
|
||||||
} else if (!existingNote && visible) {
|
} else if (!existingNote && visible) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
|
||||||
if (text) {
|
|
||||||
form.setFieldValue("text", text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [existingNote, form, visible, text]);
|
}, [existingNote, form, visible]);
|
||||||
|
|
||||||
const handleFinish = async (formValues) => {
|
const handleFinish = async (formValues) => {
|
||||||
const { relatedros, ...values } = formValues;
|
const { relatedros, ...values } = formValues;
|
||||||
@@ -86,7 +82,6 @@ export function NoteUpsertModalContainer({
|
|||||||
{ ...values, jobid: jobId, created_by: currentUser.email },
|
{ ...values, jobid: jobId, created_by: currentUser.email },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
refetchQueries: ["QUERY_NOTES_BY_JOB_PK"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (AdditionalNoteInserts.length > 0) {
|
if (AdditionalNoteInserts.length > 0) {
|
||||||
|
|||||||
@@ -1,40 +1,15 @@
|
|||||||
import { Button, Form, notification, PageHeader, Popconfirm } from "antd";
|
import { Button, Form, notification, PageHeader } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries";
|
import { UPDATE_OWNER } from "../../graphql/owners.queries";
|
||||||
import OwnerDetailFormComponent from "./owner-detail-form.component";
|
import OwnerDetailFormComponent from "./owner-detail-form.component";
|
||||||
|
|
||||||
function OwnerDetailFormContainer({ owner, refetch }) {
|
function OwnerDetailFormContainer({ owner, refetch }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const history = useHistory();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateOwner] = useMutation(UPDATE_OWNER);
|
const [updateOwner] = useMutation(UPDATE_OWNER);
|
||||||
const [deleteOwner] = useMutation(DELETE_OWNER);
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const result = await deleteOwner({
|
|
||||||
variables: { id: owner.id },
|
|
||||||
});
|
|
||||||
console.log(result);
|
|
||||||
if (result.errors) {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("owners.errors.deleting", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
} else {
|
|
||||||
notification["success"]({
|
|
||||||
message: t("owners.successes.delete"),
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
history.push(`/manage/owners`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -66,29 +41,15 @@ function OwnerDetailFormContainer({ owner, refetch }) {
|
|||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t("menus.header.owners")}
|
title={t("menus.header.owners")}
|
||||||
extra={[
|
extra={
|
||||||
<Popconfirm
|
|
||||||
trigger="click"
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
disabled={owner.jobs.length !== 0}
|
|
||||||
title={t("owners.labels.deleteconfirm")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="danger"
|
|
||||||
loading={loading}
|
|
||||||
disabled={owner.jobs.length !== 0}
|
|
||||||
>
|
|
||||||
{t("general.actions.delete")}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => form.submit()}
|
onClick={() => form.submit()}
|
||||||
>
|
>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>,
|
</Button>
|
||||||
]}
|
}
|
||||||
/>
|
/>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
|||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
record.vehicleid ? (
|
record.vehicleid ? (
|
||||||
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
||||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
{`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`}
|
||||||
record.v_model_desc || ""
|
|
||||||
}`.trim()}
|
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
t("jobs.errors.novehicle")
|
t("jobs.errors.novehicle")
|
||||||
|
|||||||
@@ -201,7 +201,6 @@ export function PartsOrderListTableComponent({
|
|||||||
subject: record.return
|
subject: record.return
|
||||||
? Templates.parts_return_slip.subject
|
? Templates.parts_return_slip.subject
|
||||||
: Templates.parts_order.subject,
|
: Templates.parts_order.subject,
|
||||||
to: record.vendor.email,
|
|
||||||
}}
|
}}
|
||||||
id={job.id}
|
id={job.id}
|
||||||
/>
|
/>
|
||||||
@@ -297,6 +296,7 @@ export function PartsOrderListTableComponent({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.act_price"),
|
title: t("parts_orders.fields.act_price"),
|
||||||
dataIndex: "act_price",
|
dataIndex: "act_price",
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import {
|
||||||
INSERT_NEW_PAYMENT,
|
INSERT_NEW_PAYMENT,
|
||||||
UPDATE_PAYMENT,
|
UPDATE_PAYMENT
|
||||||
} from "../../graphql/payments.queries";
|
} from "../../graphql/payments.queries";
|
||||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectPayment } from "../../redux/modals/modals.selectors";
|
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
@@ -52,7 +52,7 @@ function PaymentModalContainer({
|
|||||||
const { useStripe, sendby, ...paymentObj } = values;
|
const { useStripe, sendby, ...paymentObj } = values;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let updatedPayment; //Moved up from if statement for greater scope.
|
|
||||||
try {
|
try {
|
||||||
if (!context || (context && !context.id)) {
|
if (!context || (context && !context.id)) {
|
||||||
const newPayment = await insertPayment({
|
const newPayment = await insertPayment({
|
||||||
@@ -87,7 +87,7 @@ function PaymentModalContainer({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedPayment = await updatePayment({
|
const updatedPayment = await updatePayment({
|
||||||
variables: {
|
variables: {
|
||||||
paymentId: context.id,
|
paymentId: context.id,
|
||||||
payment: paymentObj,
|
payment: paymentObj,
|
||||||
@@ -101,11 +101,7 @@ function PaymentModalContainer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.refetch)
|
if (actions.refetch) actions.refetch();
|
||||||
actions.refetch(
|
|
||||||
updatedPayment && updatedPayment.data.update_payments.returning[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enterAgain) {
|
if (enterAgain) {
|
||||||
const prev = form.getFieldsValue(["date"]);
|
const prev = form.getFieldsValue(["date"]);
|
||||||
|
|
||||||
@@ -128,11 +124,7 @@ function PaymentModalContainer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) form.resetFields();
|
||||||
form.resetFields();
|
|
||||||
form.resetFields();
|
|
||||||
form.setFieldsValue(context);
|
|
||||||
}
|
|
||||||
}, [visible, form, context]);
|
}, [visible, form, context]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -147,7 +139,6 @@ function PaymentModalContainer({
|
|||||||
: t("payments.labels.edit")
|
: t("payments.labels.edit")
|
||||||
}
|
}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
destroyOnClose
|
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
width="50%"
|
width="50%"
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { useApolloClient } from "@apollo/client";
|
|
||||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||||
import axios from "axios";
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_PAYMENT_BY_ID } from "../../graphql/payments.queries";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -42,10 +39,7 @@ export function PaymentsListPaginated({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const [openSearchResults, setOpenSearchResults] = useState([]);
|
|
||||||
const [searchLoading, setSearchLoading] = useState(false);
|
|
||||||
const { page, sortcolumn, sortorder } = search;
|
const { page, sortcolumn, sortorder } = search;
|
||||||
const client = useApolloClient();
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
@@ -58,17 +52,13 @@ export function PaymentsListPaginated({
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||||
// sortOrder: sortcolumn === "ro_number" && sortorder,
|
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => (
|
||||||
return record.job ? (
|
<Link to={"/manage/jobs/" + record.job.id}>
|
||||||
<Link to={"/manage/jobs/" + record.job.id}>
|
{record.job.ro_number || t("general.labels.na")}
|
||||||
{record.job.ro_number || t("general.labels.na")}
|
</Link>
|
||||||
</Link>
|
),
|
||||||
) : (
|
|
||||||
<span>{t("general.labels.na")}</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("payments.fields.paymentnum"),
|
title: t("payments.fields.paymentnum"),
|
||||||
@@ -82,16 +72,16 @@ export function PaymentsListPaginated({
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
// sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
||||||
// sortOrder: sortcolumn === "owner" && sortorder,
|
sortOrder: sortcolumn === "owner" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.job?.owner ? (
|
return record.job.owner ? (
|
||||||
<Link to={"/manage/owners/" + record.job?.owner?.id}>
|
<Link to={"/manage/owners/" + record.job.owner.id}>
|
||||||
<OwnerNameDisplay ownerObject={record.job} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<OwnerNameDisplay ownerObject={record.job} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -157,33 +147,10 @@ export function PaymentsListPaginated({
|
|||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
disabled={record.exportedat}
|
disabled={record.exportedat}
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
let apolloResults;
|
|
||||||
if (search.search) {
|
|
||||||
const { data } = await client.query({
|
|
||||||
query: QUERY_PAYMENT_BY_ID,
|
|
||||||
variables: {
|
|
||||||
paymentId: record.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
apolloResults = data.payments_by_pk;
|
|
||||||
}
|
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {
|
actions: { refetch: refetch },
|
||||||
refetch: apolloResults
|
context: record,
|
||||||
? (updatedRecord) => {
|
|
||||||
setOpenSearchResults((results) =>
|
|
||||||
results.map((result) => {
|
|
||||||
if (result.id !== record.id) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return updatedRecord;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: refetch,
|
|
||||||
},
|
|
||||||
context: apolloResults ? apolloResults : record,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -210,28 +177,6 @@ export function PaymentsListPaginated({
|
|||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (search.search && search.search.trim() !== "") {
|
|
||||||
searchPayments();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function searchPayments(value) {
|
|
||||||
try {
|
|
||||||
setSearchLoading(true);
|
|
||||||
const searchData = await axios.post("/search", {
|
|
||||||
search: value || search.search,
|
|
||||||
index: "payments",
|
|
||||||
});
|
|
||||||
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error while fetching search results", error);
|
|
||||||
} finally {
|
|
||||||
setSearchLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
extra={
|
extra={
|
||||||
@@ -244,7 +189,6 @@ export function PaymentsListPaginated({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
delete search.search;
|
delete search.search;
|
||||||
delete search.page;
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -268,33 +212,24 @@ export function PaymentsListPaginated({
|
|||||||
onSearch={(value) => {
|
onSearch={(value) => {
|
||||||
search.search = value;
|
search.search = value;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
searchPayments(value);
|
|
||||||
}}
|
}}
|
||||||
loading={loading || searchLoading}
|
|
||||||
enterButton
|
enterButton
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
loading={loading || searchLoading}
|
loading={loading}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
pagination={
|
pagination={{
|
||||||
search?.search
|
position: "top",
|
||||||
? {
|
pageSize: 25,
|
||||||
pageSize: 25,
|
current: parseInt(page || 1),
|
||||||
showSizeChanger: false,
|
total: total,
|
||||||
}
|
}}
|
||||||
: {
|
|
||||||
pageSize: 25,
|
|
||||||
current: parseInt(page || 1),
|
|
||||||
total: total,
|
|
||||||
showSizeChanger: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={search?.search ? openSearchResults : payments}
|
dataSource={payments}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
|
|||||||
})
|
})
|
||||||
.filter(
|
.filter(
|
||||||
(temp) =>
|
(temp) =>
|
||||||
!temp.regions ||
|
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config])
|
||||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
|
||||||
(temp.regions &&
|
|
||||||
bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredJobsReportsList =
|
const filteredJobsReportsList =
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { Col, List, Space, Typography } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const CardColorLegend = ({ bodyshop }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const data = bodyshop.ssbuckets.map((bucket) => {
|
|
||||||
let color = { r: 255, g: 255, b: 255 };
|
|
||||||
|
|
||||||
if (bucket.color) {
|
|
||||||
color = bucket.color;
|
|
||||||
|
|
||||||
if (bucket.color.rgb) {
|
|
||||||
color = bucket.color.rgb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: bucket.label,
|
|
||||||
color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<Typography>{t("production.labels.legend")}</Typography>
|
|
||||||
<List
|
|
||||||
grid={{
|
|
||||||
gutter: 16,
|
|
||||||
}}
|
|
||||||
dataSource={data}
|
|
||||||
renderItem={(item) => (
|
|
||||||
<List.Item>
|
|
||||||
<Space>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: "1.5rem",
|
|
||||||
aspectRatio: "1/1",
|
|
||||||
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div>{item.label}</div>
|
|
||||||
</Space>
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CardColorLegend;
|
|
||||||
@@ -18,31 +18,6 @@ import moment from "moment";
|
|||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
|
|
||||||
const cardColor = (ssbuckets, totalHrs) => {
|
|
||||||
const bucket = ssbuckets.filter(
|
|
||||||
(bucket) =>
|
|
||||||
bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true)
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
let color = { r: 255, g: 255, b: 255 };
|
|
||||||
|
|
||||||
if (bucket && bucket.color) {
|
|
||||||
color = bucket.color;
|
|
||||||
|
|
||||||
if (bucket.color.rgb) {
|
|
||||||
color = bucket.color.rgb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return color;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getContrastYIQ(bgColor) {
|
|
||||||
const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000;
|
|
||||||
|
|
||||||
return yiq >= 128 ? "black" : "white";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProductionBoardCard(
|
export default function ProductionBoardCard(
|
||||||
technician,
|
technician,
|
||||||
card,
|
card,
|
||||||
@@ -79,22 +54,10 @@ export default function ProductionBoardCard(
|
|||||||
.isSame(moment(card.scheduled_completion), "day") &&
|
.isSame(moment(card.scheduled_completion), "day") &&
|
||||||
"production-completion-soon"));
|
"production-completion-soon"));
|
||||||
|
|
||||||
const totalHrs =
|
|
||||||
card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
|
|
||||||
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="react-kanban-card imex-kanban-card"
|
className="react-kanban-card imex-kanban-card"
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
|
||||||
backgroundColor:
|
|
||||||
cardSettings &&
|
|
||||||
cardSettings.cardcolor &&
|
|
||||||
`rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
|
||||||
color:
|
|
||||||
cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor),
|
|
||||||
}}
|
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<ProductionAlert record={card} key="alert" />
|
<ProductionAlert record={card} key="alert" />
|
||||||
|
|||||||
@@ -104,13 +104,6 @@ export default function ProductionBoardKanbanCardSettings({
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
valuePropName="checked"
|
|
||||||
label={t("production.labels.cardcolor")}
|
|
||||||
name="cardcolor"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -173,7 +166,7 @@ export default function ProductionBoardKanbanCardSettings({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover content={overlay} visible={visible} placement="topRight">
|
<Popover content={overlay} visible={visible}>
|
||||||
<Button loading={loading} onClick={() => setVisible(true)}>
|
<Button loading={loading} onClick={() => setVisible(true)}>
|
||||||
{t("production.labels.cardsettings")}
|
{t("production.labels.cardsettings")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se
|
|||||||
//import "@asseinfo/react-kanban/dist/styles.css";
|
//import "@asseinfo/react-kanban/dist/styles.css";
|
||||||
import "./production-board-kanban.styles.scss";
|
import "./production-board-kanban.styles.scss";
|
||||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
@@ -222,7 +221,6 @@ export function ProductionBoardKanbanComponent({
|
|||||||
employeeassignments: true,
|
employeeassignments: true,
|
||||||
scheduled_completion: true,
|
scheduled_completion: true,
|
||||||
stickyheader: false,
|
stickyheader: false,
|
||||||
cardcolor: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -258,11 +256,6 @@ export function ProductionBoardKanbanComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{cardSettings.cardcolor && (
|
|
||||||
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ProductionListDetailComponent jobs={data} />
|
<ProductionListDetailComponent jobs={data} />
|
||||||
<StickyContainer>
|
<StickyContainer>
|
||||||
<Board
|
<Board
|
||||||
|
|||||||
@@ -91,13 +91,11 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
b.v_make_desc + b.v_model_desc
|
b.v_make_desc + b.v_model_desc
|
||||||
),
|
),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={`/manage/vehicles/${record.vehicleid}`}>{`${
|
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||||
record.v_model_yr || ""
|
record.v_model_desc || ""
|
||||||
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
|
} ${record.v_color || ""} ${record.plate_no || ""}`}</span>
|
||||||
record.v_color || ""
|
|
||||||
} ${record.plate_no || ""}`}</Link>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Input, Popover, Space } from "antd";
|
import { Button, Input, Popover } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaRegStickyNote } from "react-icons/fa";
|
import { FaRegStickyNote } from "react-icons/fa";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
export default function ProductionListColumnProductionNote({ record }) {
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setNoteUpsertContext: (context) =>
|
|
||||||
dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
|
|
||||||
});
|
|
||||||
|
|
||||||
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [note, setNote] = useState(
|
const [note, setNote] = useState(
|
||||||
@@ -71,26 +60,12 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
|||||||
// onPressEnter={handleSaveNote}
|
// onPressEnter={handleSaveNote}
|
||||||
autoFocus
|
autoFocus
|
||||||
allowClear
|
allowClear
|
||||||
style={{ marginBottom: "1em" }}
|
|
||||||
/>
|
/>
|
||||||
<Space>
|
<div>
|
||||||
<Button onClick={handleSaveNote} type="primary">
|
<Button onClick={handleSaveNote}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
</div>
|
||||||
onClick={() => {
|
|
||||||
setVisible(false);
|
|
||||||
setNoteUpsertContext({
|
|
||||||
context: {
|
|
||||||
jobId: record.id,
|
|
||||||
text: note,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("notes.actions.savetojobnotes")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
@@ -110,8 +85,3 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ProductionListColumnProductionNote);
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export function ProductionListTable({
|
|||||||
const assoc = bodyshop.associations.find(
|
const assoc = bodyshop.associations.find(
|
||||||
(a) => a.useremail === currentUser.email
|
(a) => a.useremail === currentUser.email
|
||||||
);
|
);
|
||||||
|
|
||||||
if (assoc) {
|
if (assoc) {
|
||||||
await updateDefaultProdView({
|
await updateDefaultProdView({
|
||||||
variables: { assocId: assoc.id, view: value },
|
variables: { assocId: assoc.id, view: value },
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function ProductionListTable({
|
|||||||
state,
|
state,
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
}).find((e) => e.key === k.key),
|
}).find((e) => e.key === k.key),
|
||||||
width: k.width ?? 100,
|
width: k.width,
|
||||||
};
|
};
|
||||||
})) ||
|
})) ||
|
||||||
[]
|
[]
|
||||||
@@ -267,8 +267,6 @@ export function ProductionListTable({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
|
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
|
||||||
title: headerItem(c),
|
title: headerItem(c),
|
||||||
ellipsis: true,
|
|
||||||
width: c.width ?? 100,
|
|
||||||
onHeaderCell: (column) => ({
|
onHeaderCell: (column) => ({
|
||||||
width: column.width,
|
width: column.width,
|
||||||
onResize: handleResize(index),
|
onResize: handleResize(index),
|
||||||
@@ -278,12 +276,11 @@ export function ProductionListTable({
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
scroll={{ x: 1000 }}
|
// scroll={{ x: true }}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
</ReactDragListView.DragColumn>
|
</ReactDragListView.DragColumn>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(ProductionListTable);
|
export default connect(mapStateToProps, null)(ProductionListTable);
|
||||||
|
|||||||
@@ -3,26 +3,8 @@ import { Resizable } from "react-resizable";
|
|||||||
|
|
||||||
export default function ResizableComponent(props) {
|
export default function ResizableComponent(props) {
|
||||||
const { onResize, width, ...restProps } = props;
|
const { onResize, width, ...restProps } = props;
|
||||||
|
|
||||||
if (!width) {
|
|
||||||
return <th {...restProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Resizable
|
<Resizable width={width || 200} height={0} onResize={onResize}>
|
||||||
width={width || 200}
|
|
||||||
height={0}
|
|
||||||
onResize={onResize}
|
|
||||||
draggableOpts={{ enableUserSelectHack: false }}
|
|
||||||
handle={
|
|
||||||
<span
|
|
||||||
className="react-resizable-handle"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<th {...restProps} />
|
<th {...restProps} />
|
||||||
</Resizable>
|
</Resizable>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Card, Col, Form, Input, notification } from "antd";
|
import { Button, Form, Input, notification } from "antd";
|
||||||
import { LockOutlined } from "@ant-design/icons";
|
import { LockOutlined } from "@ant-design/icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -48,99 +48,81 @@ export default connect(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<Col span={24}>
|
<Form
|
||||||
<Form
|
onFinish={handleFinish}
|
||||||
onFinish={handleFinish}
|
autoComplete={"no"}
|
||||||
autoComplete={"no"}
|
initialValues={currentUser}
|
||||||
initialValues={currentUser}
|
layout="vertical"
|
||||||
layout="vertical"
|
>
|
||||||
>
|
<LayoutFormRow>
|
||||||
<Card
|
<Form.Item
|
||||||
title={t("user.labels.profileinfo")}
|
label={t("user.fields.displayname")}
|
||||||
extra={
|
rules={[
|
||||||
<Button type="primary" key="submit" htmlType="submit">
|
{
|
||||||
{t("user.actions.updateprofile")}
|
required: true,
|
||||||
</Button>
|
//message: t("general.validation.required"),
|
||||||
}
|
},
|
||||||
|
]}
|
||||||
|
name="displayName"
|
||||||
>
|
>
|
||||||
<LayoutFormRow noDivider>
|
<Input />
|
||||||
<Form.Item
|
</Form.Item>
|
||||||
label={t("user.fields.displayname")}
|
<Form.Item label={t("user.fields.photourl")} name="photoURL">
|
||||||
rules={[
|
<Input />
|
||||||
{
|
</Form.Item>
|
||||||
required: true,
|
</LayoutFormRow>
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
<Button type="primary" key="submit" htmlType="submit">
|
||||||
]}
|
{t("user.actions.updateprofile")}
|
||||||
name="displayName"
|
</Button>
|
||||||
>
|
</Form>
|
||||||
<Input />
|
<Form
|
||||||
</Form.Item>
|
onFinish={handleChangePassword}
|
||||||
<Form.Item label={t("user.fields.photourl")} name="photoURL">
|
autoComplete={"no"}
|
||||||
<Input />
|
initialValues={currentUser}
|
||||||
</Form.Item>
|
layout="vertical"
|
||||||
</LayoutFormRow>
|
>
|
||||||
</Card>
|
<LayoutFormRow>
|
||||||
</Form>
|
<Form.Item label={t("general.labels.newpassword")} name="password">
|
||||||
</Col>
|
<Input
|
||||||
<Col span={24}>
|
prefix={<LockOutlined />}
|
||||||
<Form
|
type="password"
|
||||||
onFinish={handleChangePassword}
|
placeholder={t("general.labels.password")}
|
||||||
autoComplete={"no"}
|
/>
|
||||||
initialValues={currentUser}
|
</Form.Item>
|
||||||
layout="vertical"
|
<Form.Item
|
||||||
>
|
label={t("general.labels.confirmpassword")}
|
||||||
<Card
|
name="password-confirm"
|
||||||
title={t("user.labels.changepassword")}
|
dependencies={["password"]}
|
||||||
extra={
|
rules={[
|
||||||
<Button type="primary" key="submit" htmlType="submit">
|
{
|
||||||
{t("user.actions.changepassword")}
|
required: true,
|
||||||
</Button>
|
//message: t("general.validation.required"),
|
||||||
}
|
},
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(rule, value) {
|
||||||
|
if (!value || getFieldValue("password") === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
t("general.labels.passwordsdonotmatch")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<LayoutFormRow>
|
<Input
|
||||||
<Form.Item
|
prefix={<LockOutlined />}
|
||||||
label={t("general.labels.newpassword")}
|
type="password"
|
||||||
name="password"
|
placeholder={t("general.labels.password")}
|
||||||
>
|
/>
|
||||||
<Input
|
</Form.Item>
|
||||||
prefix={<LockOutlined />}
|
</LayoutFormRow>
|
||||||
type="password"
|
<Button type="primary" key="submit" htmlType="submit">
|
||||||
placeholder={t("general.labels.password")}
|
{t("user.actions.changepassword")}
|
||||||
/>
|
</Button>
|
||||||
</Form.Item>
|
</Form>
|
||||||
<Form.Item
|
</div>
|
||||||
label={t("general.labels.confirmpassword")}
|
|
||||||
name="password-confirm"
|
|
||||||
dependencies={["password"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (!value || getFieldValue("password") === value) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return Promise.reject(
|
|
||||||
t("general.labels.passwordsdonotmatch")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<LockOutlined />}
|
|
||||||
type="password"
|
|
||||||
placeholder={t("general.labels.password")}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Card>
|
|
||||||
</Form>
|
|
||||||
</Col>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Button, Card, Col, Input, Table, Typography } from "antd";
|
import React from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Table, Button, Typography } from "antd";
|
||||||
export default function ProfileShopsComponent({
|
export default function ProfileShopsComponent({
|
||||||
loading,
|
loading,
|
||||||
data,
|
data,
|
||||||
updateActiveShop,
|
updateActiveShop,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("associations.fields.shopname"),
|
title: t("associations.fields.shopname"),
|
||||||
@@ -40,38 +40,17 @@ export default function ProfileShopsComponent({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredData =
|
|
||||||
search === ""
|
|
||||||
? data
|
|
||||||
: data.filter((d) =>
|
|
||||||
d.bodyshop.shopname.toLowerCase().includes(search.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Table
|
||||||
<Card
|
title={() => (
|
||||||
title={
|
<Typography.Title level={4}>
|
||||||
<Typography.Title level={4}>
|
{t("profile.labels.activeshop")}
|
||||||
{t("profile.labels.activeshop")}
|
</Typography.Title>
|
||||||
</Typography.Title>
|
)}
|
||||||
}
|
loading={loading}
|
||||||
extra={
|
columns={columns}
|
||||||
<Input.Search
|
rowKey="id"
|
||||||
value={search}
|
dataSource={data}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
/>
|
||||||
allowClear
|
|
||||||
placeholder={t("general.labels.search")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table
|
|
||||||
pagination={false}
|
|
||||||
loading={loading}
|
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={filteredData}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import {
|
import {
|
||||||
QUERY_ALL_ASSOCIATIONS,
|
QUERY_ALL_ASSOCIATIONS,
|
||||||
UPDATE_ACTIVE_ASSOCIATION,
|
UPDATE_ASSOCIATION,
|
||||||
} from "../../graphql/associations.queries";
|
} from "../../graphql/associations.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ProfileShopsComponent from "./profile-shops.component";
|
import ProfileShopsComponent from "./profile-shops.component";
|
||||||
@@ -13,13 +13,9 @@ import { getToken } from "firebase/messaging";
|
|||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
@@ -29,18 +25,14 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ProfileShopsContainer);
|
)(ProfileShopsContainer);
|
||||||
|
|
||||||
export function ProfileShopsContainer({ bodyshop, currentUser }) {
|
export function ProfileShopsContainer({ bodyshop }) {
|
||||||
const { loading, error, data } = useQuery(QUERY_ALL_ASSOCIATIONS, {
|
const { loading, error, data } = useQuery(QUERY_ALL_ASSOCIATIONS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
variables: {
|
|
||||||
email: currentUser.email,
|
|
||||||
},
|
|
||||||
skip: !currentUser,
|
|
||||||
});
|
});
|
||||||
const [updateActiveAssociation] = useMutation(UPDATE_ACTIVE_ASSOCIATION);
|
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION);
|
||||||
|
|
||||||
const updateActiveShop = async (newActiveAssocId) => {
|
const updateActiveShop = async (activeShopId) => {
|
||||||
logImEXEvent("profile_change_active_shop");
|
logImEXEvent("profile_change_active_shop");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -54,12 +46,16 @@ export function ProfileShopsContainer({ bodyshop, currentUser }) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("No FCM token. Skipping unsubscribe.");
|
console.log("No FCM token. Skipping unsubscribe.");
|
||||||
}
|
}
|
||||||
|
await Promise.all(
|
||||||
await updateActiveAssociation({
|
data.associations.map(async (record) => {
|
||||||
variables: {
|
await updateAssocation({
|
||||||
newActiveAssocId: newActiveAssocId,
|
variables: {
|
||||||
},
|
assocId: record.id,
|
||||||
});
|
assocActive: record.id === activeShopId ? true : false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
//Force window refresh.
|
//Force window refresh.
|
||||||
|
|
||||||
|
|||||||
@@ -92,11 +92,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
|||||||
to: values.to,
|
to: values.to,
|
||||||
subject: Templates[values.key]?.subject,
|
subject: Templates[values.key]?.subject,
|
||||||
},
|
},
|
||||||
values.sendbyexcel === "excel"
|
values.sendby === "email" ? "e" : "p",
|
||||||
? "x"
|
|
||||||
: values.sendby === "email"
|
|
||||||
? "e"
|
|
||||||
: "p",
|
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -254,38 +250,15 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
|||||||
ranges={DatePIckerRanges}
|
ranges={DatePIckerRanges}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
|
<Form.Item
|
||||||
{() => {
|
label={t("general.labels.sendby")}
|
||||||
const key = form.getFieldValue("key");
|
name="sendby"
|
||||||
//Kind of Id
|
initialValue="print"
|
||||||
const reporttype = Templates[key] && Templates[key].reporttype;
|
>
|
||||||
|
<Radio.Group>
|
||||||
if (reporttype === "excel")
|
<Radio value="email">{t("general.labels.email")}</Radio>
|
||||||
return (
|
<Radio value="print">{t("general.labels.print")}</Radio>
|
||||||
<Form.Item
|
</Radio.Group>
|
||||||
label={t("general.labels.sendby")}
|
|
||||||
name="sendbyexcel"
|
|
||||||
initialValue="excel"
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="excel">{t("general.labels.excel")}</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
if (reporttype !== "excel")
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
label={t("general.labels.sendby")}
|
|
||||||
name="sendby"
|
|
||||||
initialValue="print"
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="email">{t("general.labels.email")}</Radio>
|
|
||||||
<Radio value="print">{t("general.labels.print")}</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import React, { useMemo } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
Legend,
|
Legend, PolarAngleAxis,
|
||||||
PolarAngleAxis,
|
|
||||||
PolarGrid,
|
PolarGrid,
|
||||||
PolarRadiusAxis,
|
PolarRadiusAxis,
|
||||||
Radar,
|
Radar, RadarChart,
|
||||||
RadarChart,
|
Tooltip
|
||||||
Tooltip,
|
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
@@ -46,8 +44,6 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
|
|||||||
<Space>
|
<Space>
|
||||||
{t("appointments.labels.expectedprodhrs")}
|
{t("appointments.labels.expectedprodhrs")}
|
||||||
<strong>{loadData?.expectedHours?.toFixed(1)}</strong>
|
<strong>{loadData?.expectedHours?.toFixed(1)}</strong>
|
||||||
{t("appointments.labels.expectedjobs")}
|
|
||||||
<strong>{loadData?.expectedJobCount}</strong>
|
|
||||||
</Space>
|
</Space>
|
||||||
<RadarChart
|
<RadarChart
|
||||||
// cx={300}
|
// cx={300}
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{loadData && loadData.allJobsOut ? (
|
{loadData && loadData.jobsOut ? (
|
||||||
loadData.allJobsOut.map((j) => (
|
loadData.jobsOut.map((j) => (
|
||||||
<tr key={j.id}>
|
<tr key={j.id}>
|
||||||
<td>
|
<td>
|
||||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||||
@@ -102,12 +102,11 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{loadData && loadData.allJobsIn ? (
|
{loadData && loadData.jobsIn ? (
|
||||||
loadData.allJobsIn.map((j) => (
|
loadData.jobsIn.map((j) => (
|
||||||
<tr key={j.id}>
|
<tr key={j.id}>
|
||||||
<td>
|
<td>
|
||||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||||
{j.status}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<OwnerNameDisplay ownerObject={j} />
|
<OwnerNameDisplay ownerObject={j} />
|
||||||
@@ -143,7 +142,7 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
title={t("appointments.labels.arrivingjobs")}
|
title={t("appointments.labels.arrivingjobs")}
|
||||||
>
|
>
|
||||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
{(loadData.hoursIn || 0) && loadData.hoursIn.toFixed(2)}
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover
|
<Popover
|
||||||
placement={"bottom"}
|
placement={"bottom"}
|
||||||
@@ -152,7 +151,7 @@ export function ScheduleCalendarHeaderComponent({
|
|||||||
title={t("appointments.labels.completingjobs")}
|
title={t("appointments.labels.completingjobs")}
|
||||||
>
|
>
|
||||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
{(loadData.hoursOut || 0) && loadData.hoursOut.toFixed(2)}
|
||||||
</Popover>
|
</Popover>
|
||||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import HeaderComponent from "./schedule-calendar-header.component";
|
|||||||
import "./schedule-calendar.styles.scss";
|
import "./schedule-calendar.styles.scss";
|
||||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||||
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||||
import { Alert, Collapse } from "antd";
|
import { Alert } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -53,28 +53,7 @@ export function ScheduleCalendarWrapperComponent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<JobDetailCards />
|
<JobDetailCards />
|
||||||
{problemJobs && problemJobs.length > 2 ? (
|
{problemJobs &&
|
||||||
<Collapse>
|
|
||||||
<Collapse.Panel
|
|
||||||
header={
|
|
||||||
<span style={{ color: "tomato" }}>
|
|
||||||
{t("appointments.labels.severalerrorsfound")}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{problemJobs.map((problem) => (
|
|
||||||
<Alert
|
|
||||||
key={problem.id}
|
|
||||||
type="error"
|
|
||||||
message={t("appointments.labels.dataconsistency", {
|
|
||||||
ro_number: problem.ro_number,
|
|
||||||
code: problem.code,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Collapse.Panel>
|
|
||||||
</Collapse>
|
|
||||||
) : (
|
|
||||||
problemJobs.map((problem) => (
|
problemJobs.map((problem) => (
|
||||||
<Alert
|
<Alert
|
||||||
key={problem.id}
|
key={problem.id}
|
||||||
@@ -84,8 +63,7 @@ export function ScheduleCalendarWrapperComponent({
|
|||||||
code: problem.code,
|
code: problem.code,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
)}
|
|
||||||
|
|
||||||
<Calendar
|
<Calendar
|
||||||
events={data}
|
events={data}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import {
|
import { Button, Card, Checkbox, Col, PageHeader, Row, Space } from "antd";
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Checkbox,
|
|
||||||
Col,
|
|
||||||
PageHeader,
|
|
||||||
Row,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
} from "antd";
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
@@ -18,39 +9,22 @@ import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
|||||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
||||||
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ScheduleCalendarComponent);
|
|
||||||
|
|
||||||
export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||||
const [filter, setFilter] = useLocalStorage("filter_events", {
|
const [filter, setFilter] = useLocalStorage("filter_events", {
|
||||||
intake: true,
|
intake: true,
|
||||||
manual: true,
|
manual: true,
|
||||||
employeevacation: true,
|
employeevacation: true,
|
||||||
ins_co_nm: null,
|
|
||||||
});
|
});
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
return data.filter(
|
return data.filter(
|
||||||
(d) =>
|
(d) =>
|
||||||
(d.block ||
|
d.block ||
|
||||||
(filter.intake && d.isintake) ||
|
(filter.intake && d.isintake) ||
|
||||||
(filter.manual && !d.isintake && d.block === false) ||
|
(filter.manual && !d.isintake && d.block === false) ||
|
||||||
(d.__typename === "employee_vacation" &&
|
(d.__typename === "employee_vacation" &&
|
||||||
filter.employeevacation &&
|
filter.employeevacation &&
|
||||||
!!d.employee)) &&
|
!!d.employee)
|
||||||
(filter.ins_co_nm && filter.ins_co_nm.length > 0
|
|
||||||
? filter.ins_co_nm.includes(d.job?.ins_co_nm)
|
|
||||||
: true)
|
|
||||||
);
|
);
|
||||||
}, [data, filter]);
|
}, [data, filter]);
|
||||||
|
|
||||||
@@ -63,21 +37,6 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<ScheduleAtsSummary appointments={filteredData} />
|
<ScheduleAtsSummary appointments={filteredData} />
|
||||||
<Select
|
|
||||||
style={{ minWidth: "15rem" }}
|
|
||||||
mode="multiple"
|
|
||||||
placeholder={t("schedule.labels.ins_co_nm_filter")}
|
|
||||||
allowClear
|
|
||||||
onClear={() => setFilter({ ...filter, ins_co_nm: [] })}
|
|
||||||
value={filter?.ins_co_nm ? filter.ins_co_nm : []}
|
|
||||||
onChange={(e) => {
|
|
||||||
setFilter({ ...filter, ins_co_nm: e });
|
|
||||||
}}
|
|
||||||
options={bodyshop.md_ins_cos.map((i) => ({
|
|
||||||
label: i.name,
|
|
||||||
value: i.name,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={filter?.intake}
|
checked={filter?.intake}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { calculateScheduleLoad } from "../../redux/application/application.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
@@ -29,7 +28,6 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ScheduleJobModalComponent({
|
export function ScheduleJobModalComponent({
|
||||||
@@ -38,7 +36,6 @@ export function ScheduleJobModalComponent({
|
|||||||
existingAppointments,
|
existingAppointments,
|
||||||
lbrHrsData,
|
lbrHrsData,
|
||||||
jobId,
|
jobId,
|
||||||
calculateScheduleLoad,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -60,7 +57,6 @@ export function ScheduleJobModalComponent({
|
|||||||
|
|
||||||
const handleDateBlur = () => {
|
const handleDateBlur = () => {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
|
|
||||||
if (lbrHrsData) {
|
if (lbrHrsData) {
|
||||||
const totalHours =
|
const totalHours =
|
||||||
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs +
|
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
@@ -134,12 +130,7 @@ export function ScheduleJobModalComponent({
|
|||||||
className="imex-flex-row__margin"
|
className="imex-flex-row__margin"
|
||||||
key={idx}
|
key={idx}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const ssDate = moment(d);
|
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
|
||||||
if (ssDate.isBefore(moment())) {
|
|
||||||
form.setFieldsValue({ start: moment() });
|
|
||||||
} else {
|
|
||||||
form.setFieldsValue({ start: moment(d).add(8, "hours") });
|
|
||||||
}
|
|
||||||
handleDateBlur();
|
handleDateBlur();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -200,9 +191,6 @@ export function ScheduleJobModalComponent({
|
|||||||
<Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}>
|
<Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}>
|
||||||
{() => {
|
{() => {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
if (values.start) {
|
|
||||||
calculateScheduleLoad(moment(values.start).add(3, "days"));
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="schedule-job-modal">
|
<div className="schedule-job-modal">
|
||||||
<ScheduleDayViewContainer day={values.start} />
|
<ScheduleDayViewContainer day={values.start} />
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ export function ScheduleJobModalContainer({
|
|||||||
date_scheduled: new Date(),
|
date_scheduled: new Date(),
|
||||||
scheduled_in: values.start,
|
scheduled_in: values.start,
|
||||||
scheduled_completion: values.scheduled_completion,
|
scheduled_completion: values.scheduled_completion,
|
||||||
lost_sale_reason: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.schedule-job-modal {
|
.schedule-job-modal {
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow-y: auto;
|
|
||||||
.rbc-calendar {
|
.rbc-calendar {
|
||||||
.rbc-toolbar {
|
.rbc-toolbar {
|
||||||
.rbc-btn-group {
|
.rbc-btn-group {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import Dinero from "dinero.js";
|
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload, label }) => {
|
|
||||||
if (active && payload && payload.length) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor: "white",
|
|
||||||
border: "1px solid gray",
|
|
||||||
padding: "0.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p style={{ margin: "0" }}>{label}</p>
|
|
||||||
{payload.map((data, index) => {
|
|
||||||
if (data.dataKey === "sales" || data.dataKey === "accSales")
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
style={{ margin: "10px 0", color: data.color }}
|
|
||||||
key={index}
|
|
||||||
>{`${data.name} : ${Dinero({
|
|
||||||
amount: Math.round(data.value * 100),
|
|
||||||
}).toFormat()}`}</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
style={{ margin: "10px 0", color: data.color }}
|
|
||||||
key={index}
|
|
||||||
>{`${data.name} : ${data.value}`}</p>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomTooltip;
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import _ from "lodash";
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -19,7 +17,7 @@ import {
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||||
import CustomTooltip from "./chart-custom-tooltip";
|
import _ from "lodash";
|
||||||
|
|
||||||
const graphProps = {
|
const graphProps = {
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
@@ -46,19 +44,14 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
|||||||
return {
|
return {
|
||||||
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
|
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
|
||||||
painthrs: dayAcc.painthrs + dayVal.painthrs,
|
painthrs: dayAcc.painthrs + dayVal.painthrs,
|
||||||
sales:
|
|
||||||
dayAcc.painthrs +
|
|
||||||
dayVal.job.job_totals.totals.subtotal.amount / 100 +
|
|
||||||
2500,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ bodyhrs: 0, painthrs: 0, sales: 0 }
|
{ bodyhrs: 0, painthrs: 0 }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
dayhrs = {
|
dayhrs = {
|
||||||
bodyhrs: 0,
|
bodyhrs: 0,
|
||||||
painthrs: 0,
|
painthrs: 0,
|
||||||
sales: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +64,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
|||||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||||
val
|
val
|
||||||
) +
|
),
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
|
||||||
1
|
1
|
||||||
),
|
),
|
||||||
accHrs: _.round(
|
accHrs: _.round(
|
||||||
@@ -82,13 +73,6 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
|||||||
: dayhrs.painthrs + dayhrs.bodyhrs,
|
: dayhrs.painthrs + dayhrs.bodyhrs,
|
||||||
1
|
1
|
||||||
),
|
),
|
||||||
sales: _.round(dayhrs.sales, 2),
|
|
||||||
accSales: _.round(
|
|
||||||
acc.length > 0
|
|
||||||
? acc[acc.length - 1].accSales + dayhrs.sales
|
|
||||||
: dayhrs.sales,
|
|
||||||
2
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [...acc, theValue];
|
return [...acc, theValue];
|
||||||
@@ -103,27 +87,22 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<CartesianGrid stroke="#f5f5f5" />
|
<CartesianGrid stroke="#f5f5f5" />
|
||||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||||
<YAxis
|
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||||
strokeWidth={graphProps.strokeWidth}
|
<Tooltip />
|
||||||
// allowDataOverflow
|
|
||||||
dataKey="sales"
|
|
||||||
yAxisId="right"
|
|
||||||
tickFormatter={(value) =>
|
|
||||||
Dinero({ amount: Math.round(value * 100) }).toFormat()
|
|
||||||
}
|
|
||||||
orientation="right"
|
|
||||||
/>
|
|
||||||
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
|
|
||||||
<Tooltip content={<CustomTooltip />} />
|
|
||||||
<Legend />
|
<Legend />
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
name="Accumulated Hours"
|
||||||
|
dataKey="accHrs"
|
||||||
|
fill="lightgreen"
|
||||||
|
stroke="green"
|
||||||
|
/>
|
||||||
<Bar
|
<Bar
|
||||||
name="Body Hours"
|
name="Body Hours"
|
||||||
dataKey="bodyHrs"
|
dataKey="bodyHrs"
|
||||||
stackId="day"
|
stackId="day"
|
||||||
barSize={20}
|
barSize={20}
|
||||||
fill="darkblue"
|
fill="darkblue"
|
||||||
yAxisId="left"
|
|
||||||
/>
|
/>
|
||||||
<Bar
|
<Bar
|
||||||
name="Paint Hours"
|
name="Paint Hours"
|
||||||
@@ -131,42 +110,12 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
|||||||
stackId="day"
|
stackId="day"
|
||||||
barSize={20}
|
barSize={20}
|
||||||
fill="darkred"
|
fill="darkred"
|
||||||
yAxisId="left"
|
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
name="Target Hours"
|
name="Target Hours"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="accTargetHrs"
|
dataKey="accTargetHrs"
|
||||||
stroke="#ff7300"
|
stroke="#ff7300"
|
||||||
yAxisId="left"
|
|
||||||
strokeWidth={graphProps.strokeWidth}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Area
|
|
||||||
type="monotone"
|
|
||||||
name="MTD Hours"
|
|
||||||
dataKey="accHrs"
|
|
||||||
fill="lightblue"
|
|
||||||
stroke="blue"
|
|
||||||
yAxisId="left"
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
// <Area
|
|
||||||
// type="monotone"
|
|
||||||
// name="MTD Sales"
|
|
||||||
// dataKey="accSales"
|
|
||||||
// fill="lightgreen"
|
|
||||||
// stroke="green"
|
|
||||||
// yAxisId="right"
|
|
||||||
// />
|
|
||||||
}
|
|
||||||
<Bar
|
|
||||||
name="Sales"
|
|
||||||
dataKey="sales"
|
|
||||||
stackId="day"
|
|
||||||
barSize={20}
|
|
||||||
fill="darkgreen"
|
|
||||||
yAxisId="right"
|
|
||||||
strokeWidth={graphProps.strokeWidth}
|
strokeWidth={graphProps.strokeWidth}
|
||||||
/>
|
/>
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, Divider, Statistic } from "antd";
|
import { Card, Statistic } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -41,9 +41,6 @@ export function ScoreboardDayStats({ bodyshop, date, entries }) {
|
|||||||
label="P"
|
label="P"
|
||||||
value={paintHrs.toFixed(1)}
|
value={paintHrs.toFixed(1)}
|
||||||
/>
|
/>
|
||||||
<Divider style={{ margin: 0 }} />
|
|
||||||
|
|
||||||
<Statistic value={(bodyHrs + paintHrs).toFixed(1)} />
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CalendarOutlined } from "@ant-design/icons";
|
import { CalendarOutlined } from "@ant-design/icons";
|
||||||
import { Card, Col, Divider, Row, Statistic } from "antd";
|
import { Card, Col, Row, Statistic } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
@@ -177,9 +177,6 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
<Statistic value={values.toDatePaint.toFixed(1)} />
|
<Statistic value={values.toDatePaint.toFixed(1)} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
|
||||||
<Divider style={{ margin: 5 }} />
|
|
||||||
</Row>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col {...statSpans}></Col>
|
<Col {...statSpans}></Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
@@ -187,53 +184,14 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
value={(values.todayPaint + values.todayBody).toFixed(1)}
|
value={(values.todayPaint + values.todayBody).toFixed(1)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}></Col>
|
||||||
<Statistic
|
|
||||||
value={(
|
|
||||||
Util.WeeklyTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
|
||||||
bodyshop
|
|
||||||
) +
|
|
||||||
Util.WeeklyTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
|
||||||
bodyshop
|
|
||||||
)
|
|
||||||
).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
value={(values.weeklyPaint + values.weeklyBody).toFixed(1)}
|
value={(values.weeklyPaint + values.weeklyBody).toFixed(1)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}></Col>
|
||||||
<Statistic
|
<Col {...statSpans}></Col>
|
||||||
value={(
|
|
||||||
Util.MonthlyTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
|
||||||
bodyshop
|
|
||||||
) +
|
|
||||||
Util.MonthlyTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
|
||||||
bodyshop
|
|
||||||
)
|
|
||||||
).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col {...statSpans}>
|
|
||||||
<Statistic
|
|
||||||
value={(
|
|
||||||
Util.AsOfTodayTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
|
||||||
bodyshop
|
|
||||||
) +
|
|
||||||
Util.AsOfTodayTargetHrs(
|
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
|
||||||
bodyshop
|
|
||||||
)
|
|
||||||
).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col {...statSpans}>
|
<Col {...statSpans}>
|
||||||
<Statistic
|
<Statistic
|
||||||
value={(values.toDatePaint + values.toDateBody).toFixed(1)}
|
value={(values.toDatePaint + values.toDateBody).toFixed(1)}
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export default function ScoreboardTimeTickets() {
|
|||||||
totalLastMonth: 0,
|
totalLastMonth: 0,
|
||||||
totalOverPeriod: 0,
|
totalOverPeriod: 0,
|
||||||
actualTotalOverPeriod: 0,
|
actualTotalOverPeriod: 0,
|
||||||
totalEffieciencyOverPeriod: 0,
|
|
||||||
employees: {},
|
employees: {},
|
||||||
};
|
};
|
||||||
data.fixedperiod.forEach((ticket) => {
|
data.fixedperiod.forEach((ticket) => {
|
||||||
@@ -95,7 +94,6 @@ export default function ScoreboardTimeTickets() {
|
|||||||
totalLastMonth: 0,
|
totalLastMonth: 0,
|
||||||
totalOverPeriod: 0,
|
totalOverPeriod: 0,
|
||||||
actualTotalOverPeriod: 0,
|
actualTotalOverPeriod: 0,
|
||||||
totalEffieciencyOverPeriod: 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,30 +221,6 @@ export default function ScoreboardTimeTickets() {
|
|||||||
|
|
||||||
ret2.push(r);
|
ret2.push(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add total efficiency of employees
|
|
||||||
const totalActualAndProductive = Object.keys(ret.employees)
|
|
||||||
.map((key) => {
|
|
||||||
return { employee_number: key, ...ret.employees[key] };
|
|
||||||
})
|
|
||||||
.reduce(
|
|
||||||
(acc, e) => {
|
|
||||||
return {
|
|
||||||
totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod,
|
|
||||||
actualTotalOverPeriod:
|
|
||||||
acc.actualTotalOverPeriod + e.actualTotalOverPeriod,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{ totalOverPeriod: 0, actualTotalOverPeriod: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
ret.totalEffieciencyOverPeriod =
|
|
||||||
totalActualAndProductive.actualTotalOverPeriod
|
|
||||||
? (totalActualAndProductive.totalOverPeriod /
|
|
||||||
totalActualAndProductive.actualTotalOverPeriod) *
|
|
||||||
100
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
roundObject(ret);
|
roundObject(ret);
|
||||||
roundObject(totals);
|
roundObject(totals);
|
||||||
roundObject(ret2);
|
roundObject(ret2);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
key: "efficiencyoverperiod",
|
key: "efficiencyoverperiod",
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
`${(
|
`${(
|
||||||
(record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) *
|
(record.totalOverPeriod / (record.actualTotalOverPeriod || .1)) *
|
||||||
100
|
100
|
||||||
).toFixed(1)} %`,
|
).toFixed(1)} %`,
|
||||||
},
|
},
|
||||||
@@ -113,12 +113,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
value={data.totalOverPeriod}
|
value={data.totalOverPeriod}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
|
||||||
<Statistic
|
|
||||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
|
||||||
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
{t("scoreboard.labels.calendarperiod")}
|
{t("scoreboard.labels.calendarperiod")}
|
||||||
@@ -127,7 +121,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
<Col md={24} lg={20}>
|
<Col md={24} lg={20}>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="employee_number"
|
rowKey='employee_number'
|
||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
id="employee_number"
|
id="employee_number"
|
||||||
scroll={{ y: "300px" }}
|
scroll={{ y: "300px" }}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { Button, Card, Tabs } from "antd";
|
import { Button, Card, Tabs } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -9,7 +8,6 @@ import ShopInfoGeneral from "./shop-info.general.component";
|
|||||||
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
|
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
|
||||||
import ShopInfoLaborRates from "./shop-info.laborrates.component";
|
import ShopInfoLaborRates from "./shop-info.laborrates.component";
|
||||||
import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
|
import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
|
||||||
import ShopInfoPartsScan from "./shop-info.parts-scan";
|
|
||||||
import ShopInfoRbacComponent from "./shop-info.rbac.component";
|
import ShopInfoRbacComponent from "./shop-info.rbac.component";
|
||||||
import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
|
import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
|
||||||
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||||
@@ -25,11 +23,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
||||||
|
|
||||||
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||||
const { CriticalPartsScanning } = useTreatments(
|
|
||||||
["CriticalPartsScanning"],
|
|
||||||
{},
|
|
||||||
bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -78,11 +71,6 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
|||||||
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}>
|
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}>
|
||||||
<ShopInfoLaborRates form={form} />
|
<ShopInfoLaborRates form={form} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
{CriticalPartsScanning.treatment === "on" && (
|
|
||||||
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}>
|
|
||||||
<ShopInfoPartsScan form={form} />
|
|
||||||
</Tabs.TabPane>
|
|
||||||
)}
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import {
|
|||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
|
||||||
import PhoneFormItem, {
|
import PhoneFormItem, {
|
||||||
PhoneItemFormatterValidation,
|
PhoneItemFormatterValidation,
|
||||||
} from "../form-items-formatted/phone-form-item.component";
|
} from "../form-items-formatted/phone-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
|
|
||||||
import momentTZ from "moment-timezone";
|
import momentTZ from "moment-timezone";
|
||||||
const timeZonesList = momentTZ.tz.names();
|
const timeZonesList = momentTZ.tz.names();
|
||||||
@@ -473,13 +473,6 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
name={["enforce_conversion_category"]}
|
|
||||||
label={t("bodyshop.fields.enforce_conversion_category")}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["target_touchtime"]}
|
name={["target_touchtime"]}
|
||||||
label={t("bodyshop.fields.target_touchtime")}
|
label={t("bodyshop.fields.target_touchtime")}
|
||||||
@@ -551,13 +544,6 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
>
|
>
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
name={["use_paint_scale_data"]}
|
|
||||||
label={t("bodyshop.fields.use_paint_scale_data")}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["attach_pdf_to_email"]}
|
name={["attach_pdf_to_email"]}
|
||||||
label={t("bodyshop.fields.attach_pdf_to_email")}
|
label={t("bodyshop.fields.attach_pdf_to_email")}
|
||||||
@@ -596,13 +582,6 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
name={["tt_enforce_hours_for_tech_console"]}
|
|
||||||
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["bill_allow_post_to_closed"]}
|
name={["bill_allow_post_to_closed"]}
|
||||||
label={t("bodyshop.fields.bill_allow_post_to_closed")}
|
label={t("bodyshop.fields.bill_allow_post_to_closed")}
|
||||||
@@ -1365,14 +1344,7 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
>
|
>
|
||||||
<InputNumber precision={0} min={0} max={100} />
|
<InputNumber precision={0} min={0} max={100} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
label={t("joblines.fields.ah_detail_line")}
|
|
||||||
key={`${index}ah_detail_line`}
|
|
||||||
name={[field.name, "ah_detail_line"]}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
|
||||||
import { Button, Form, Input, Space } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|
||||||
|
|
||||||
export default function ShopInfoPartsScan({ form }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<LayoutFormRow header={t("bodyshop.labels.md_parts_scan")}>
|
|
||||||
<Form.List name={["md_parts_scan"]}>
|
|
||||||
{(fields, { add, remove, move }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<Form.Item key={field.key}>
|
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.md_parts_scan.expression")}
|
|
||||||
key={`${index}expression`}
|
|
||||||
name={[field.name, "expression"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.md_parts_scan.flags")}
|
|
||||||
key={`${index}flags`}
|
|
||||||
name={[field.name, "flags"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space wrap>
|
|
||||||
<DeleteFilled
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows
|
|
||||||
move={move}
|
|
||||||
index={index}
|
|
||||||
total={fields.length}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => {
|
|
||||||
add();
|
|
||||||
}}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{t("bodyshop.actions.addpartsrule")}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.List>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -217,9 +217,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
{t("jobs.fields.ponumber")}
|
{t("jobs.fields.ponumber")}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
<Select.Option value="account_number">
|
<Select.Option value="account_number">
|
||||||
{t(
|
{t("jobs.fields.dms.control_type.account_number")}
|
||||||
"jobs.fields.dms.control_type.account_number"
|
|
||||||
)}
|
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -425,15 +423,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{bodyshop.cdk_dealerid && (
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.dms.dms_control_override")}
|
|
||||||
key={`${index}dms_control_override`}
|
|
||||||
name={[field.name, "dms_control_override"]}
|
|
||||||
>
|
|
||||||
<Input onBlur={handleBlur} />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -557,15 +546,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{bodyshop.cdk_dealerid && (
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.dms.dms_control_override")}
|
|
||||||
key={`${index}dms_control_override`}
|
|
||||||
name={[field.name, "dms_control_override"]}
|
|
||||||
>
|
|
||||||
<Input onBlur={handleBlur} />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove(field.name);
|
remove(field.name);
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColorPicker = ({ value, onChange, style, ...restProps }) => {
|
const ColorPicker = ({ value, onChange, style, ...restProps }) => {
|
||||||
const handleChange = (color) => {
|
const handleChange = (color) => {
|
||||||
if (onChange) onChange(color.rgb);
|
if (onChange) onChange(color.rgb);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import { ColorPicker } from "./shop-info.rostatus.component";
|
|
||||||
|
|
||||||
export default function ShopInfoSchedulingComponent({ form }) {
|
export default function ShopInfoSchedulingComponent({ form }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -77,19 +76,6 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
|||||||
>
|
>
|
||||||
<InputNumber min={0} />
|
<InputNumber min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
name={["md_lost_sale_reasons"]}
|
|
||||||
label={t("bodyshop.fields.md_lost_sale_reasons")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
// required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
type: "array",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select mode="tags" />
|
|
||||||
</Form.Item>
|
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
|
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
|
||||||
<Space wrap size="large">
|
<Space wrap size="large">
|
||||||
@@ -278,50 +264,17 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
|||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Space wrap>
|
||||||
<Space direction="horizontal">
|
<DeleteFilled
|
||||||
<Form.Item
|
onClick={() => {
|
||||||
label={
|
remove(field.name);
|
||||||
<Space>
|
}}
|
||||||
{t("bodyshop.fields.ssbuckets.color")}
|
/>
|
||||||
<Button
|
<FormListMoveArrows
|
||||||
size="small"
|
move={move}
|
||||||
onClick={() => {
|
index={index}
|
||||||
form.setFieldValue([
|
total={fields.length}
|
||||||
"ssbuckets",
|
/>
|
||||||
field.name,
|
|
||||||
"color",
|
|
||||||
]);
|
|
||||||
|
|
||||||
form.setFields([
|
|
||||||
{
|
|
||||||
name: ["ssbuckets", field.name, "color"],
|
|
||||||
touched: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
key={`${index}color`}
|
|
||||||
name={[field.name, "color"]}
|
|
||||||
>
|
|
||||||
<ColorPicker />
|
|
||||||
</Form.Item>
|
|
||||||
<Space wrap>
|
|
||||||
<DeleteFilled
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows
|
|
||||||
move={move}
|
|
||||||
index={index}
|
|
||||||
total={fields.length}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export default function ShopUsersAuthEdit({ association }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!visible && (
|
{!visible && (
|
||||||
<div
|
<div style={{ cursor: "pointer" }} onClick={() => setVisible(true)}>
|
||||||
style={{ cursor: "pointer" }} //onClick={() => setVisible(true)}
|
|
||||||
>
|
|
||||||
{association.authlevel || t("general.labels.na")}
|
{association.authlevel || t("general.labels.na")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ export function TechClockInContainer({
|
|||||||
technician,
|
technician,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
}) {
|
}) {
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:",
|
|
||||||
technician
|
|
||||||
);
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
|
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
|
||||||
@@ -37,10 +33,6 @@ export function TechClockInContainer({
|
|||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const emps = bodyshop.employees.filter(
|
|
||||||
(e) => e.id === (technician && technician.id)
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const theTime = (await axios.post("/utils/time")).data;
|
const theTime = (await axios.post("/utils/time")).data;
|
||||||
@@ -95,12 +87,7 @@ export function TechClockInContainer({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: { timeticket: { employeeid: technician.id } },
|
||||||
timeticket: {
|
|
||||||
employeeid: technician.id,
|
|
||||||
flat_rate: emps.flat_rate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
@@ -21,8 +21,6 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
|
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
|
||||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -41,17 +39,7 @@ export function TechClockOffButton({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
|
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { queryLoading, data: lineTicketData } = useQuery(
|
|
||||||
GET_LINE_TICKET_BY_PK,
|
|
||||||
{
|
|
||||||
variables: {
|
|
||||||
id: jobId,
|
|
||||||
},
|
|
||||||
skip: !jobId,
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const emps = bodyshop.employees.filter(
|
const emps = bodyshop.employees.filter(
|
||||||
(e) => e.id === (technician && technician.id)
|
(e) => e.id === (technician && technician.id)
|
||||||
@@ -71,7 +59,6 @@ export function TechClockOffButton({
|
|||||||
emps &&
|
emps &&
|
||||||
emps.rates.filter((r) => r.cost_center === values.cost_center)[0]
|
emps.rates.filter((r) => r.cost_center === values.cost_center)[0]
|
||||||
?.rate,
|
?.rate,
|
||||||
flat_rate: emps && emps.flat_rate,
|
|
||||||
ciecacode:
|
ciecacode:
|
||||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||||
? values.cost_center
|
? values.cost_center
|
||||||
@@ -141,54 +128,6 @@ export function TechClockOffButton({
|
|||||||
required: true,
|
required: true,
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
console.log(
|
|
||||||
bodyshop.tt_enforce_hours_for_tech_console
|
|
||||||
);
|
|
||||||
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!value ||
|
|
||||||
getFieldValue("cost_center") === null ||
|
|
||||||
!lineTicketData
|
|
||||||
)
|
|
||||||
return Promise.resolve();
|
|
||||||
|
|
||||||
//Check the cost center,
|
|
||||||
const totals = CalculateAllocationsTotals(
|
|
||||||
bodyshop,
|
|
||||||
lineTicketData.joblines,
|
|
||||||
lineTicketData.timetickets,
|
|
||||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
|
||||||
);
|
|
||||||
|
|
||||||
const fieldTypeToCheck =
|
|
||||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
|
||||||
? "mod_lbr_ty"
|
|
||||||
: "cost_center";
|
|
||||||
|
|
||||||
const costCenterDiff =
|
|
||||||
Math.round(
|
|
||||||
totals.find(
|
|
||||||
(total) =>
|
|
||||||
total[fieldTypeToCheck] ===
|
|
||||||
getFieldValue("cost_center")
|
|
||||||
)?.difference * 10
|
|
||||||
) / 10;
|
|
||||||
|
|
||||||
if (value > costCenterDiff)
|
|
||||||
return Promise.reject(
|
|
||||||
t(
|
|
||||||
"timetickets.validation.hoursenteredmorethanavailable"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber min={0} precision={1} />
|
<InputNumber min={0} precision={1} />
|
||||||
@@ -238,11 +177,7 @@ export function TechClockOffButton({
|
|||||||
</Col>
|
</Col>
|
||||||
{!isShiftTicket && (
|
{!isShiftTicket && (
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<LaborAllocationContainer
|
<LaborAllocationContainer jobid={jobId} />
|
||||||
jobid={jobId || null}
|
|
||||||
loading={queryLoading}
|
|
||||||
lineTicketData={lineTicketData}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { Card, Col, Space, Statistic, Typography } from "antd";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries";
|
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
|
||||||
const { Title } = Typography;
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
technician: selectTechnician,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({});
|
|
||||||
|
|
||||||
const TechJobStatistics = ({ technician }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const startDate = moment().startOf("week");
|
|
||||||
const endDate = moment().endOf("week");
|
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(
|
|
||||||
QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE,
|
|
||||||
{
|
|
||||||
variables: {
|
|
||||||
start: startDate.format("YYYY-MM-DD"),
|
|
||||||
end: endDate.format("YYYY-MM-DD"),
|
|
||||||
fixedStart: moment().startOf("month").format("YYYY-MM-DD"),
|
|
||||||
fixedEnd: moment().endOf("month").format("YYYY-MM-DD"),
|
|
||||||
employeeid: technician.id,
|
|
||||||
},
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const totals = useMemo(() => {
|
|
||||||
if (data && data.timetickets && data.fixedperiod) {
|
|
||||||
const week = data.timetickets.reduce(
|
|
||||||
(acc, val) => {
|
|
||||||
acc.productivehrs = acc.productivehrs + val.productivehrs;
|
|
||||||
acc.actualhrs = acc.actualhrs + val.actualhrs;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ productivehrs: 0, actualhrs: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const month = data.fixedperiod.reduce(
|
|
||||||
(acc, val) => {
|
|
||||||
acc.productivehrs = acc.productivehrs + val.productivehrs;
|
|
||||||
acc.actualhrs = acc.actualhrs + val.actualhrs;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ productivehrs: 0, actualhrs: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
week,
|
|
||||||
month,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
week: { productivehrs: 0, actualhrs: 0 },
|
|
||||||
month: { productivehrs: 0, actualhrs: 0 },
|
|
||||||
};
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card title={t("scoreboard.labels.productivestatistics")}>
|
|
||||||
<Space size={100}>
|
|
||||||
<Col>
|
|
||||||
<Title level={5}>{t("scoreboard.labels.thisweek")}</Title>
|
|
||||||
<Space size={20}>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.fields.productivehrs")}
|
|
||||||
value={totals.week.productivehrs.toFixed(2)}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.fields.actualhrs")}
|
|
||||||
value={totals.week.actualhrs.toFixed(2)}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.labels.efficiency")}
|
|
||||||
value={
|
|
||||||
totals.week.actualhrs
|
|
||||||
? `${(
|
|
||||||
(totals.week.productivehrs / totals.week.actualhrs) *
|
|
||||||
100
|
|
||||||
).toFixed(2)}%`
|
|
||||||
: "0%"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Title level={5}>{t("scoreboard.labels.thismonth")}</Title>
|
|
||||||
<Space size={20}>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.fields.productivehrs")}
|
|
||||||
value={totals.month.productivehrs.toFixed(2)}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.fields.actualhrs")}
|
|
||||||
value={totals.month.actualhrs.toFixed(2)}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("timetickets.labels.efficiency")}
|
|
||||||
value={
|
|
||||||
totals.month.actualhrs
|
|
||||||
? `${(
|
|
||||||
(totals.month.productivehrs / totals.month.actualhrs) *
|
|
||||||
100
|
|
||||||
).toFixed(2)}%`
|
|
||||||
: "0%"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics);
|
|
||||||
@@ -29,17 +29,7 @@ export function TechSider({ technician, techLogout }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sider
|
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse}>
|
||||||
style={{
|
|
||||||
height: "100vh",
|
|
||||||
position: "sticky",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
}}
|
|
||||||
collapsible
|
|
||||||
collapsed={collapsed}
|
|
||||||
onCollapse={onCollapse}
|
|
||||||
>
|
|
||||||
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="1"
|
key="1"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function TimeTicketList({
|
|||||||
text: (() => {
|
text: (() => {
|
||||||
const emp = bodyshop.employees.find((e) => e.id === s);
|
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||||
|
|
||||||
return `${emp?.first_name} ${emp?.last_name}`;
|
return `${emp.first_name} ${emp.last_name}`;
|
||||||
})(), //
|
})(), //
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -14,7 +14,6 @@ import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
@@ -39,11 +38,7 @@ export function TimeTicketModalComponent({
|
|||||||
employeeSelectDisabled,
|
employeeSelectDisabled,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loadLineTicketData, { called, loading, data: lineTicketData }] =
|
|
||||||
useLazyQuery(GET_LINE_TICKET_BY_PK, {
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
});
|
|
||||||
const CostCenterSelect = ({ emps, value, ...props }) => {
|
const CostCenterSelect = ({ emps, value, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
@@ -87,10 +82,9 @@ export function TimeTicketModalComponent({
|
|||||||
label={t("timetickets.fields.ro_number")}
|
label={t("timetickets.fields.ro_number")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: !(
|
required:
|
||||||
form.getFieldValue("cost_center") ===
|
!form.getFieldValue("cost_center") ===
|
||||||
"timetickets.labels.shift"
|
"timetickets.labels.shift",
|
||||||
),
|
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -181,51 +175,6 @@ export function TimeTicketModalComponent({
|
|||||||
label={t("timetickets.fields.productivehrs")}
|
label={t("timetickets.fields.productivehrs")}
|
||||||
name="productivehrs"
|
name="productivehrs"
|
||||||
rules={[
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!value ||
|
|
||||||
getFieldValue("cost_center") === null ||
|
|
||||||
!lineTicketData
|
|
||||||
)
|
|
||||||
return Promise.resolve();
|
|
||||||
|
|
||||||
//Check the cost center,
|
|
||||||
const totals = CalculateAllocationsTotals(
|
|
||||||
bodyshop,
|
|
||||||
lineTicketData.joblines,
|
|
||||||
lineTicketData.timetickets,
|
|
||||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
|
||||||
);
|
|
||||||
|
|
||||||
const fieldTypeToCheck =
|
|
||||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
|
||||||
? "mod_lbr_ty"
|
|
||||||
: "cost_center";
|
|
||||||
|
|
||||||
const costCenterDiff =
|
|
||||||
Math.round(
|
|
||||||
totals.find(
|
|
||||||
(total) =>
|
|
||||||
total[fieldTypeToCheck] ===
|
|
||||||
getFieldValue("cost_center")
|
|
||||||
)?.difference * 10
|
|
||||||
) / 10;
|
|
||||||
|
|
||||||
if (value > costCenterDiff)
|
|
||||||
return Promise.reject(
|
|
||||||
t(
|
|
||||||
"timetickets.validation.hoursenteredmorethanavailable"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
required:
|
required:
|
||||||
form.getFieldValue("cost_center") !==
|
form.getFieldValue("cost_center") !==
|
||||||
@@ -341,28 +290,23 @@ export function TimeTicketModalComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Form.Item dependencies={["jobid"]}>
|
<Form.Item dependencies={["jobid"]}>
|
||||||
{() => {
|
{() => (
|
||||||
const jobid = form.getFieldValue("jobid");
|
<LaborAllocationContainer
|
||||||
if (
|
jobid={form.getFieldValue("jobid") || null}
|
||||||
(!called && jobid) ||
|
/>
|
||||||
(jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading)
|
)}
|
||||||
) {
|
|
||||||
loadLineTicketData({ variables: { id: jobid } });
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<LaborAllocationContainer
|
|
||||||
jobid={jobid || null}
|
|
||||||
loading={loading}
|
|
||||||
lineTicketData={lineTicketData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
|
export function LaborAllocationContainer({ jobid }) {
|
||||||
|
const { loading, data: lineTicketData } = useQuery(GET_LINE_TICKET_BY_PK, {
|
||||||
|
variables: { id: jobid },
|
||||||
|
skip: !jobid,
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
nextFetchPolicy: "network-only",
|
||||||
|
});
|
||||||
if (loading) return <LoadingSkeleton />;
|
if (loading) return <LoadingSkeleton />;
|
||||||
if (!lineTicketData) return null;
|
if (!lineTicketData) return null;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ const JobRelatedTicketsTable = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: `${item.jobKey}${costCenter}`,
|
id: `${item.jobKey}${costCenter}`,
|
||||||
costCenter,
|
|
||||||
item,
|
item,
|
||||||
actHrs: actHrs.toFixed(1),
|
actHrs: actHrs.toFixed(1),
|
||||||
prodHrs: prodHrs.toFixed(1),
|
prodHrs: prodHrs.toFixed(1),
|
||||||
@@ -152,9 +151,7 @@ const JobRelatedTicketsTable = ({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
`${record.item.employee.first_name} ${record.item.employee.last_name} ${
|
`${record.item.employee.first_name} ${record.item.employee.last_name}`,
|
||||||
record.costCenter ? `(${record.costCenter})` : ""
|
|
||||||
}`.trim(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("timetickets.fields.actualhrs"),
|
title: t("timetickets.fields.actualhrs"),
|
||||||
|
|||||||
@@ -1,41 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button, Form, notification, PageHeader, Popconfirm } from "antd";
|
import { Button, Form, notification, PageHeader } from "antd";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import VehicleDetailFormComponent from "./vehicle-detail-form.component";
|
import VehicleDetailFormComponent from "./vehicle-detail-form.component";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { DELETE_VEHICLE, UPDATE_VEHICLE } from "../../graphql/vehicles.queries";
|
import { UPDATE_VEHICLE } from "../../graphql/vehicles.queries";
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
|
|
||||||
function VehicleDetailFormContainer({ vehicle, refetch }) {
|
function VehicleDetailFormContainer({ vehicle, refetch }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateVehicle] = useMutation(UPDATE_VEHICLE);
|
const [updateVehicle] = useMutation(UPDATE_VEHICLE);
|
||||||
const [deleteVehicle] = useMutation(DELETE_VEHICLE);
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const result = await deleteVehicle({
|
|
||||||
variables: { id: vehicle.id },
|
|
||||||
});
|
|
||||||
console.log(result);
|
|
||||||
if (result.errors) {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("vehicles.errors.deleting", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
} else {
|
|
||||||
notification["success"]({
|
|
||||||
message: t("vehicles.successes.delete"),
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
history.push(`/manage/vehicles`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -65,29 +40,15 @@ function VehicleDetailFormContainer({ vehicle, refetch }) {
|
|||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t("menus.header.vehicles")}
|
title={t("menus.header.vehicles")}
|
||||||
extra={[
|
extra={
|
||||||
<Popconfirm
|
|
||||||
trigger="click"
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
disabled={vehicle.jobs.length !== 0}
|
|
||||||
title={t("vehicles.labels.deleteconfirm")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="danger"
|
|
||||||
loading={loading}
|
|
||||||
disabled={vehicle.jobs.length !== 0}
|
|
||||||
>
|
|
||||||
{t("general.actions.delete")}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => form.submit()}
|
onClick={() => form.submit()}
|
||||||
>
|
>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>,
|
</Button>
|
||||||
]}
|
}
|
||||||
/>
|
/>
|
||||||
<Form
|
<Form
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
|
|||||||
@@ -268,7 +268,6 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
|
|||||||
scheduled_in
|
scheduled_in
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
status
|
status
|
||||||
lost_sale_reason
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -295,12 +294,6 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
|||||||
where: { inproduction: { _eq: true }, suspended: { _eq: false } }
|
where: { inproduction: { _eq: true }, suspended: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
actual_in
|
|
||||||
scheduled_in
|
|
||||||
actual_completion
|
|
||||||
scheduled_completion
|
|
||||||
inproduction
|
|
||||||
ro_number
|
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(
|
||||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
@@ -334,15 +327,12 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
status
|
|
||||||
ro_number
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
actual_completion
|
actual_completion
|
||||||
scheduled_in
|
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
inproduction
|
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(
|
||||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
@@ -370,16 +360,11 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
|||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
scheduled_in
|
scheduled_in
|
||||||
actual_in
|
|
||||||
scheduled_completion
|
|
||||||
ro_number
|
ro_number
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
alt_transport
|
alt_transport
|
||||||
actual_completion
|
|
||||||
inproduction
|
|
||||||
status
|
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(
|
||||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { gql } from "@apollo/client";
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
export const QUERY_ALL_ASSOCIATIONS = gql`
|
export const QUERY_ALL_ASSOCIATIONS = gql`
|
||||||
query QUERY_ALL_ASSOCIATIONS($email: String) {
|
query QUERY_ALL_ASSOCIATIONS {
|
||||||
associations(
|
associations(order_by: { bodyshop: { shopname: asc } }) {
|
||||||
where: { useremail: { _eq: $email } }
|
|
||||||
order_by: { bodyshop: { shopname: asc } }
|
|
||||||
) {
|
|
||||||
id
|
id
|
||||||
active
|
active
|
||||||
bodyshop {
|
bodyshop {
|
||||||
@@ -30,30 +27,6 @@ export const UPDATE_ASSOCIATION = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const UPDATE_ACTIVE_ASSOCIATION = gql`
|
|
||||||
mutation UPDATE_ACTIVE_ASSOCIATION($newActiveAssocId: uuid) {
|
|
||||||
nweActive: update_associations(
|
|
||||||
where: { id: { _eq: $newActiveAssocId } }
|
|
||||||
_set: { active: true }
|
|
||||||
) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
shopid
|
|
||||||
active
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inactive: update_associations(
|
|
||||||
where: { id: { _neq: $newActiveAssocId } }
|
|
||||||
_set: { active: false }
|
|
||||||
) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
shopid
|
|
||||||
active
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const UPDATE_ACTIVE_PROD_LIST_VIEW = gql`
|
export const UPDATE_ACTIVE_PROD_LIST_VIEW = gql`
|
||||||
mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) {
|
mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) {
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ export const DELETE_BILL = gql`
|
|||||||
|
|
||||||
export const QUERY_ALL_BILLS_PAGINATED = gql`
|
export const QUERY_ALL_BILLS_PAGINATED = gql`
|
||||||
query QUERY_ALL_BILLS_PAGINATED(
|
query QUERY_ALL_BILLS_PAGINATED(
|
||||||
|
$search: String
|
||||||
$offset: Int
|
$offset: Int
|
||||||
$limit: Int
|
$limit: Int
|
||||||
$order: [bills_order_by!]!
|
$order: [bills_order_by!]!
|
||||||
) {
|
) {
|
||||||
bills(
|
search_bills(
|
||||||
|
args: { search: $search }
|
||||||
offset: $offset
|
offset: $offset
|
||||||
limit: $limit
|
limit: $limit
|
||||||
order_by: $order
|
order_by: $order
|
||||||
@@ -49,7 +51,7 @@ export const QUERY_ALL_BILLS_PAGINATED = gql`
|
|||||||
ro_number
|
ro_number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bills_aggregate {
|
search_bills_aggregate(args: { search: $search }) {
|
||||||
aggregate {
|
aggregate {
|
||||||
count(distinct: true)
|
count(distinct: true)
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,6 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
|||||||
vendor {
|
vendor {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
email
|
|
||||||
}
|
}
|
||||||
order_date
|
order_date
|
||||||
deliver_by
|
deliver_by
|
||||||
@@ -103,7 +104,6 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
|||||||
vendor {
|
vendor {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
email
|
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
invoice_number
|
invoice_number
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
query QUERY_BODYSHOP {
|
query QUERY_BODYSHOP {
|
||||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||||
associations {
|
associations {
|
||||||
id
|
|
||||||
authlevel
|
authlevel
|
||||||
useremail
|
useremail
|
||||||
default_prod_list_view
|
default_prod_list_view
|
||||||
@@ -113,11 +112,6 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
localmediaservernetwork
|
localmediaservernetwork
|
||||||
localmediatoken
|
localmediatoken
|
||||||
enforce_conversion_csr
|
enforce_conversion_csr
|
||||||
md_lost_sale_reasons
|
|
||||||
md_parts_scan
|
|
||||||
enforce_conversion_category
|
|
||||||
tt_enforce_hours_for_tech_console
|
|
||||||
use_paint_scale_data
|
|
||||||
employees {
|
employees {
|
||||||
user_email
|
user_email
|
||||||
id
|
id
|
||||||
@@ -127,7 +121,6 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
employee_number
|
employee_number
|
||||||
rates
|
rates
|
||||||
external_id
|
external_id
|
||||||
flat_rate
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,10 +222,6 @@ export const UPDATE_SHOP = gql`
|
|||||||
localmediaservernetwork
|
localmediaservernetwork
|
||||||
localmediatoken
|
localmediatoken
|
||||||
enforce_conversion_csr
|
enforce_conversion_csr
|
||||||
md_lost_sale_reasons
|
|
||||||
md_parts_scan
|
|
||||||
enforce_conversion_category
|
|
||||||
tt_enforce_hours_for_tech_console
|
|
||||||
employees {
|
employees {
|
||||||
id
|
id
|
||||||
first_name
|
first_name
|
||||||
|
|||||||
@@ -31,18 +31,6 @@ import { gql } from "@apollo/client";
|
|||||||
// }
|
// }
|
||||||
// `;
|
// `;
|
||||||
|
|
||||||
export const UNREAD_CONVERSATION_COUNT = gql`
|
|
||||||
query UNREAD_CONVERSATION_COUNT {
|
|
||||||
messages_aggregate(
|
|
||||||
where: { read: { _eq: false }, isoutbound: { _eq: false } }
|
|
||||||
) {
|
|
||||||
aggregate {
|
|
||||||
count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CONVERSATION_LIST_QUERY = gql`
|
export const CONVERSATION_LIST_QUERY = gql`
|
||||||
query CONVERSATION_LIST_QUERY {
|
query CONVERSATION_LIST_QUERY {
|
||||||
conversations(
|
conversations(
|
||||||
|
|||||||
@@ -284,7 +284,6 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
|
|||||||
clm_no
|
clm_no
|
||||||
v_make_desc
|
v_make_desc
|
||||||
v_color
|
v_color
|
||||||
vehicleid
|
|
||||||
plate_no
|
plate_no
|
||||||
actual_in
|
actual_in
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
@@ -537,7 +536,6 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
driveable
|
driveable
|
||||||
towin
|
towin
|
||||||
loss_of_use
|
loss_of_use
|
||||||
lost_sale_reason
|
|
||||||
vehicle {
|
vehicle {
|
||||||
id
|
id
|
||||||
plate_no
|
plate_no
|
||||||
@@ -722,8 +720,6 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
prt_dsmk_m
|
prt_dsmk_m
|
||||||
ioucreated
|
ioucreated
|
||||||
convertedtolbr
|
convertedtolbr
|
||||||
ah_detail_line
|
|
||||||
critical
|
|
||||||
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
||||||
id
|
id
|
||||||
quantity
|
quantity
|
||||||
@@ -1156,8 +1152,31 @@ export const UPDATE_JOBS = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const CONVERT_JOB_TO_RO = gql`
|
export const CONVERT_JOB_TO_RO = gql`
|
||||||
mutation CONVERT_JOB_TO_RO($jobId: uuid!, $job: jobs_set_input!) {
|
mutation CONVERT_JOB_TO_RO(
|
||||||
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
|
$jobId: uuid!
|
||||||
|
$class: String
|
||||||
|
$ins_co_nm: String!
|
||||||
|
$ca_gst_registrant: Boolean
|
||||||
|
$driveable: Boolean
|
||||||
|
$towin: Boolean
|
||||||
|
$referral_source: String
|
||||||
|
$referral_source_extra: String
|
||||||
|
$employee_csr: uuid
|
||||||
|
) {
|
||||||
|
update_jobs(
|
||||||
|
where: { id: { _eq: $jobId } }
|
||||||
|
_set: {
|
||||||
|
converted: true
|
||||||
|
ins_co_nm: $ins_co_nm
|
||||||
|
class: $class
|
||||||
|
ca_gst_registrant: $ca_gst_registrant
|
||||||
|
towin: $towin
|
||||||
|
driveable: $driveable
|
||||||
|
referral_source: $referral_source
|
||||||
|
referral_source_extra: $referral_source_extra
|
||||||
|
employee_csr: $employee_csr
|
||||||
|
}
|
||||||
|
) {
|
||||||
returning {
|
returning {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
@@ -1278,7 +1297,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
|||||||
|
|
||||||
export const SEARCH_FOR_JOBS = gql`
|
export const SEARCH_FOR_JOBS = gql`
|
||||||
query SEARCH_FOR_JOBS($search: String!) {
|
query SEARCH_FOR_JOBS($search: String!) {
|
||||||
search_jobs(args: { search: $search }, limit: 25) {
|
search_jobs(args: { search: $search }) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
ownr_fn
|
ownr_fn
|
||||||
@@ -1781,12 +1800,14 @@ export const QUERY_ALL_JOB_FIELDS = gql`
|
|||||||
|
|
||||||
export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
||||||
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
|
||||||
|
$search: String
|
||||||
$offset: Int
|
$offset: Int
|
||||||
$limit: Int
|
$limit: Int
|
||||||
$order: [jobs_order_by!]
|
$order: [jobs_order_by!]
|
||||||
$statusList: [String!]
|
$statusList: [String!]
|
||||||
) {
|
) {
|
||||||
jobs(
|
search_jobs(
|
||||||
|
args: { search: $search }
|
||||||
offset: $offset
|
offset: $offset
|
||||||
limit: $limit
|
limit: $limit
|
||||||
order_by: $order
|
order_by: $order
|
||||||
@@ -1817,7 +1838,10 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
|||||||
updated_at
|
updated_at
|
||||||
ded_amt
|
ded_amt
|
||||||
}
|
}
|
||||||
jobs_aggregate(where: { status: { _in: $statusList } }) {
|
search_jobs_aggregate(
|
||||||
|
args: { search: $search }
|
||||||
|
where: { status: { _in: $statusList } }
|
||||||
|
) {
|
||||||
aggregate {
|
aggregate {
|
||||||
count(distinct: true)
|
count(distinct: true)
|
||||||
}
|
}
|
||||||
@@ -2058,7 +2082,6 @@ export const QUERY_JOB_EXPORT_DMS = gql`
|
|||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
ins_co_nm
|
|
||||||
kmin
|
kmin
|
||||||
kmout
|
kmout
|
||||||
v_make_desc
|
v_make_desc
|
||||||
|
|||||||
@@ -4,16 +4,7 @@ export const INSERT_NEW_NOTE = gql`
|
|||||||
mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) {
|
mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) {
|
||||||
insert_notes(objects: $noteInput) {
|
insert_notes(objects: $noteInput) {
|
||||||
returning {
|
returning {
|
||||||
created_at
|
|
||||||
created_by
|
|
||||||
critical
|
|
||||||
id
|
id
|
||||||
jobid
|
|
||||||
private
|
|
||||||
text
|
|
||||||
updated_at
|
|
||||||
audit
|
|
||||||
type
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +15,8 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
|
|||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
vehicle {
|
vehicle{
|
||||||
jobs {
|
jobs{
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
status
|
status
|
||||||
@@ -42,7 +33,6 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
|
|||||||
text
|
text
|
||||||
updated_at
|
updated_at
|
||||||
audit
|
audit
|
||||||
type
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +52,6 @@ export const UPDATE_NOTE = gql`
|
|||||||
text
|
text
|
||||||
updated_at
|
updated_at
|
||||||
audit
|
audit
|
||||||
type
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,14 +94,6 @@ export const UPDATE_OWNER = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DELETE_OWNER = gql`
|
|
||||||
mutation DELETE_OWNER($id: uuid!) {
|
|
||||||
delete_owners_by_pk(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const QUERY_ALL_OWNERS = gql`
|
export const QUERY_ALL_OWNERS = gql`
|
||||||
query QUERY_ALL_OWNERS {
|
query QUERY_ALL_OWNERS {
|
||||||
owners {
|
owners {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user