From 8670f386dcc32c6f40b3bbf0c6765f4dc7b51bd8 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 22 Nov 2023 17:14:52 -0500 Subject: [PATCH 01/17] refactors --- libs/awsUtils.js | 56 +++++++++++++++++++++++++ os-loader.js | 74 +++++++-------------------------- server/opensearch/os-handler.js | 62 +++++++-------------------- 3 files changed, 86 insertions(+), 106 deletions(-) create mode 100644 libs/awsUtils.js diff --git a/libs/awsUtils.js b/libs/awsUtils.js new file mode 100644 index 000000000..4f7ee9aa4 --- /dev/null +++ b/libs/awsUtils.js @@ -0,0 +1,56 @@ +require("dotenv").config({ + path: require("path").resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); +const {isNil} = require('lodash'); +const aws4 = require("aws4"); +const {Connection, Client} = require("@opensearch-project/opensearch"); +const {defaultProvider} = require("@aws-sdk/credential-provider-node"); + +const createAwsConnector = (credentials, region) => { + class AmazonConnection extends Connection { + buildRequestObject(params) { + const request = super.buildRequestObject(params); + request.service = "es"; + request.region = region; + request.headers = request.headers || {}; + request.headers["host"] = request.hostname; + + return aws4.sign(request, credentials); + } + } + return { + Connection: AmazonConnection, + }; +}; + +const getClient = async () => { + + // We have manual configuration for OpenSearch, + // Return a client using these custom credentials + if ( + !isNil(process.env.OPEN_SEARCH_PASSWORD) && + !isNil(process.env.OPEN_SEARCH_USER) && + !isNil(process.env.OPEN_SEARCH_HOST) && + !isNil(process.env.OPEN_SEARCH_PROTOCOL) + ) { + // The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it. + const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, ''); + const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`; + + return new Client({ + node, + }); + } + + // Default to the AWS Credentials Provider. + const credentials = await defaultProvider()(); + return new Client({ + ...createAwsConnector(credentials, "ca-central-1"), + node: process.env.OPEN_SEARCH_HOST, + }); +}; + +module.exports = { getClient }; \ No newline at end of file diff --git a/os-loader.js b/os-loader.js index a7876187a..bdabb87ac 100644 --- a/os-loader.js +++ b/os-loader.js @@ -1,59 +1,17 @@ -const Dinero = require("dinero.js"); - -//const client = require("../graphql-client/graphql-client").client; -const _ = require("lodash"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("./server/utils/logger"); - -const path = require("path"); -const client = require("./server/graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` + path: require("path").resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` ), }); -const { Client, Connection } = require("@opensearch-project/opensearch"); -const { defaultProvider } = require("@aws-sdk/credential-provider-node"); -const aws4 = require("aws4"); -const { gql } = require("graphql-request"); -const gqlclient = require("./server/graphql-client/graphql-client").client; -// const osClient = new Client({ -// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`, -// }); -var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com -const createAwsConnector = (credentials, region) => { - class AmazonConnection extends Connection { - buildRequestObject(params) { - const request = super.buildRequestObject(params); - request.service = "es"; - request.region = region; - request.headers = request.headers || {}; - request.headers["host"] = request.hostname; - - return aws4.sign(request, credentials); - } - } - return { - Connection: AmazonConnection, - }; -}; - -const getClient = async () => { - const credentials = await defaultProvider()(); - return new Client({ - ...createAwsConnector(credentials, "ca-central-1"), - node: host, - }); -}; +const {omit} = require("lodash"); +const gqlClient = require("./server/graphql-client/graphql-client").client; +const getClient = require('./libs/awsUtils'); async function OpenSearchUpdateHandler(req, res) { try { - var osClient = await getClient(); - // const osClient = new Client({ - // node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`, - // }); + const osClient = await getClient(); //Clear out all current documents // const deleteResult = await osClient.deleteByQuery({ @@ -67,11 +25,11 @@ async function OpenSearchUpdateHandler(req, res) { // return; - var batchSize = 1000; - var promiseQueue = []; + const batchSize = 1000; + const promiseQueue = []; //Jobs Load. - const jobsData = await gqlclient.request(`query{jobs{ + const jobsData = await gqlClient.request(`query{jobs{ id bodyshopid:shopid clm_no @@ -105,7 +63,7 @@ async function OpenSearchUpdateHandler(req, res) { } //Owner Load - const ownersData = await gqlclient.request(`{ + const ownersData = await gqlClient.request(`{ owners { id bodyshopid: shopid @@ -131,7 +89,7 @@ async function OpenSearchUpdateHandler(req, res) { } //Vehicles - const vehiclesData = await gqlclient.request(`{ + const vehiclesData = await gqlClient.request(`{ vehicles { id bodyshopid: shopid @@ -158,7 +116,7 @@ async function OpenSearchUpdateHandler(req, res) { } //payments - const paymentsData = await gqlclient.request(`{ + const paymentsData = await gqlClient.request(`{ payments { id amount @@ -198,7 +156,7 @@ async function OpenSearchUpdateHandler(req, res) { slicedArray.forEach((payment) => { bulkOperation.push({ index: { _index: "payments", _id: payment.id } }); bulkOperation.push({ - ..._.omit(payment, ["job"]), + ...omit(payment, ["job"]), bodyshopid: payment.job.bodyshopid, }); }); @@ -206,7 +164,7 @@ async function OpenSearchUpdateHandler(req, res) { } //bills - const billsData = await gqlclient.request(`{ + const billsData = await gqlClient.request(`{ bills { id date @@ -235,7 +193,7 @@ async function OpenSearchUpdateHandler(req, res) { slicedArray.forEach((bill) => { bulkOperation.push({ index: { _index: "bills", _id: bill.id } }); bulkOperation.push({ - ..._.omit(bill, ["job"]), + ...omit(bill, ["job"]), bodyshopid: bill.job.bodyshopid, }); }); diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index b6e5fc490..7cb544400 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -1,49 +1,18 @@ -const queries = require("../graphql-client/queries"); -const {pick} = require("lodash"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("../utils/logger"); -//const client = require("../graphql-client/graphql-client").client; - -const path = require("path"); -const client = require("../graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( + path: require("path").resolve( process.cwd(), `.env.${process.env.NODE_ENV || "development"}` ), }); -const {Client, Connection} = require("@opensearch-project/opensearch"); -const {defaultProvider} = require("@aws-sdk/credential-provider-node"); -const aws4 = require("aws4"); -const {gql} = require("graphql-request"); -var host = process.env.OPEN_SEARCH_HOST; +const GraphQLClient = require("graphql-request").GraphQLClient; +//const client = require("../graphql-client/graphql-client").client; +const logger = require("../utils/logger"); +const queries = require("../graphql-client/queries"); +const client = require("../graphql-client/graphql-client").client; +const {pick, isNil} = require("lodash"); +const {getClient} = require('../../libs/awsUtils'); -const createAwsConnector = (credentials, region) => { - class AmazonConnection extends Connection { - buildRequestObject(params) { - const request = super.buildRequestObject(params); - request.service = "es"; - request.region = region; - request.headers = request.headers || {}; - request.headers["host"] = request.hostname; - - return aws4.sign(request, credentials); - } - } - - return { - Connection: AmazonConnection, - }; -}; - -const getClient = async () => { - const credentials = await defaultProvider()(); - return new Client({ - ...createAwsConnector(credentials, "ca-central-1"), - node: host, - }); -}; async function OpenSearchUpdateHandler(req, res) { if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { @@ -51,10 +20,8 @@ async function OpenSearchUpdateHandler(req, res) { return; } try { - var osClient = await getClient(); - // const osClient = new Client({ - // node: `https://imex:@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`, - // }); + + const osClient = await getClient(); if (req.body.event.op === "DELETE") { let response; @@ -197,14 +164,12 @@ async function OpenSearchUpdateHandler(req, res) { body: document, }; - let response; - response = await osClient.index(payload); + const response = await osClient.index(payload); console.log(response.body); res.status(200).json(response.body); } } catch (error) { res.status(400).json(JSON.stringify(error)); - } finally { } } @@ -240,6 +205,8 @@ async function OpenSearchSearchHandler(req, res) { const osClient = await getClient(); + const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE) ? assocs.associations[0].shopid : process.env.BODY_SHOP_ID_MATCH_OVERRIDE + const {body} = await osClient.search({ ...(index ? {index} : {}), body: { @@ -249,7 +216,7 @@ async function OpenSearchSearchHandler(req, res) { must: [ { match: { - bodyshopid: assocs.associations[0].shopid, + bodyshopid: bodyShopIdMatchOverride, }, }, { @@ -318,7 +285,6 @@ async function OpenSearchSearchHandler(req, res) { error: JSON.stringify(error), }); res.status(400).json(error); - } finally { } } From 65f960db0045109438f2777ee7e2eff28504db36 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 23 Nov 2023 16:58:56 -0500 Subject: [PATCH 02/17] Add Margin around the Messaging Icon --- client/src/components/chat-popup/chat-popup.component.jsx | 2 +- client/src/components/chat-popup/chat-popup.styles.scss | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 6d582bdab..57ef96e4c 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -132,7 +132,7 @@ export function ChatPopupComponent({ onClick={() => toggleChatVisible()} style={{ cursor: "pointer" }} > - + {t("messaging.labels.messaging")} )} diff --git a/client/src/components/chat-popup/chat-popup.styles.scss b/client/src/components/chat-popup/chat-popup.styles.scss index 398762700..be9a5c0a7 100644 --- a/client/src/components/chat-popup/chat-popup.styles.scss +++ b/client/src/components/chat-popup/chat-popup.styles.scss @@ -13,6 +13,9 @@ height: 100%; } } +.chat-popup-info-icon { + margin-right: 5px; +} @media only screen and (min-width: 992px) { .chat-popup { From c5bed4f36df50afcbd5eadd2e9588ea3aab6e8be Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:32:59 -0500 Subject: [PATCH 03/17] Progress --- .../jobs-list/jobs-list.component.jsx | 737 +++++++++--------- client/src/graphql/jobs.queries.js | 60 ++ .../src/pages/jobs-all/jobs-all.container.jsx | 2 +- 3 files changed, 426 insertions(+), 373 deletions(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index e28e21901..072bcbd61 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,395 +1,388 @@ -import { - SyncOutlined, - ExclamationCircleFilled, - PauseCircleOutlined, - BranchesOutlined, -} from "@ant-design/icons"; -import { useQuery } from "@apollo/client"; -import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; +import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; +import {useQuery} from "@apollo/client"; +import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd"; import queryString from "query-string"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { Link, useHistory, useLocation } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; -import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import { onlyUnique } from "../../utils/arrayHelper"; +import React, {useState} from "react"; +import {useTranslation} from "react-i18next"; +import {connect} from "react-redux"; +import {Link, useHistory, useLocation} from "react-router-dom"; +import {createStructuredSelector} from "reselect"; +import {QUERY_ALL_ACTIVE_JOBS_PAGINATED} from "../../graphql/jobs.queries"; +import {selectBodyshop} from "../../redux/user/user.selectors"; +import {onlyUnique} from "../../utils/arrayHelper"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import { alphaSort } from "../../utils/sorters"; +import {alphaSort} from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; +import _ from "lodash"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop, }); -export function JobsList({ bodyshop }) { - const searchParams = queryString.parse(useLocation().search); - const { selected } = searchParams; - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; - const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); +export function JobsList({bodyshop,}) { + const pageLimit = 10; + const searchParams = queryString.parse(useLocation().search); + const {page, selected, sortorder, sortcolumn} = searchParams; + console.dir({page, selected, sortorder, sortcolumn}); + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: { text: "" }, - }); + const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { - const { t } = useTranslation(); - const history = useHistory(); - const [searchText, setSearchText] = useState(""); + variables: { + offset: page ? (page - 1) * pageLimit : 0, + limit: 10, + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], + order: [ + { + [sortcolumn || "ro_number"]: + sortorder && sortorder !== "false" + ? sortorder === "descend" + ? "desc" + : "asc" + : "desc", + }, + ], + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + }); - if (error) return ; + const total = data?.jobs_aggregate?.aggregate?.count || 0; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.comments || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; + const [state, setState] = useState({ + filteredInfo: {text: ""}, + }); - const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); - }; + const {t} = useTranslation(); + const history = useHistory(); + const [searchText, setSearchText] = useState(""); - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history.push({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; + if (error) return ; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => - parseInt((a.ro_number || "0").replace(/\D/g, "")) - - parseInt((b.ro_number || "0").replace(/\D/g, "")), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + const jobs = data?.jobs || []; - render: (text, record) => ( - e.stopPropagation()} - > - - {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, + const handleTableChange = (pagination, filters, sorter) => { + setState({...state, filteredInfo: filters}); - responsive: ["md"], - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.ownerid ? ( - e.stopPropagation()} - > - - - ) : ( - - + searchParams.page = pagination.current; + searchParams.sortcolumn = sorter.column && sorter.column.key; + searchParams.sortorder = sorter.order; + + if (filters.status) { + searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + } else { + delete searchParams.statusFilters; + } + + history.push({search: queryString.stringify(searchParams)}); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history.push({ + search: queryString.stringify({ + ...searchParams, + selected: record.id, + }), + }); + } + } + }; + + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: true, + sortOrder: + sortcolumn === "ro_number" && sortorder, + + render: (text, record) => ( + e.stopPropagation()} + > + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && ( + + )} + {record.iouparent && ( + + + + )} + + + ), + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + responsive: ["md"], + sorter: false, + sortOrder: sortcolumn === "owner" && sortorder, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()} + > + + + ) : ( + + - ); - }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, - - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), - }, - - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - ellipsis: true, - - responsive: ["md"], - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => - `${record.clm_no || ""}${ - record.po_number ? ` (PO: ${record.po_number})` : "" - }`, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - 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"], - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - responsive: ["md"], - ellipsis: true, - - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_total} - ), - }, - { - title: t("jobs.labels.estimator"), - dataIndex: "jobs.labels.estimator", - key: "jobs.labels.estimator", - ellipsis: true, - responsive: ["xl"], - filterSearch: true, - filters: - (jobs && - jobs - .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "N/A", - value: [s], - }; - })) || - [], - onFilter: (value, record) => - value.includes( - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() - ), - render: (text, record) => - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - responsive: ["md"], - }, - // { - // title: t("jobs.fields.owner_owing"), - // dataIndex: "owner_owing", - // key: "owner_owing", - // responsive: ["md"], - // render: (text, record) => ( - // {record.owner_owing} - // ), - // }, - ]; - - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%", - }; - - return ( - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - - } - > - { - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); + ); }, - }; - }} - /> - - ); + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: true, + sortOrder: + sortcolumn === "status" && sortorder, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s], + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status), + }, + + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()} + > + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + ); + }, + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + ellipsis: true, + responsive: ["md"], + sorter: true, + sortOrder: + sortcolumn === "plate_no" && sortorder, + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + responsive: ["md"], + sorter: true, + sortOrder: + sortcolumn === "clm_no" && sortorder, + render: (text, record) => + `${record.clm_no || ""}${ + record.po_number ? ` (PO: ${record.po_number})` : "" + }`, + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + 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"], + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + responsive: ["md"], + ellipsis: true, + sorter: true, + sortOrder: + sortcolumn === "clm_total" && sortorder, + render: (text, record) => ( + {record.clm_total} + ), + }, + { + title: t("jobs.labels.estimator"), + dataIndex: "jobs.labels.estimator", + key: "jobs.labels.estimator", + ellipsis: true, + responsive: ["xl"], + filterSearch: true, + filters: + (jobs && + jobs + .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "N/A", + value: [s], + }; + })) || + [], + onFilter: (value, record) => + value.includes( + `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() + ), + render: (text, record) => + `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true, + responsive: ["md"], + }, + // { + // title: t("jobs.fields.owner_owing"), + // dataIndex: "owner_owing", + // key: "owner_owing", + // responsive: ["md"], + // render: (text, record) => ( + // {record.owner_owing} + // ), + // }, + ]; + + const scrollMapper = { + xs: true, + sm: true, + md: true, + lg: "100%", + xl: "100%", + xxl: "100%", + }; + + return ( + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > +
{ + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio", + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + }, + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(JobsList); diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 21e82350b..1b87bace7 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -1,5 +1,65 @@ import { gql } from "@apollo/client"; +export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` + query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( + $offset: Int + $limit: Int + $order: [jobs_order_by!] + $statuses: [String!]!, + $isConverted: Boolean + ) { + jobs( + offset: $offset + limit: $limit + where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } + order_by: { created_at: desc } + ) { + iouparent + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + ownr_ea + ownerid + comment + plate_no + plate_st + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + actual_completion + actual_delivery + actual_in + production_vars + id + ins_co_nm + clm_no + po_number + clm_total + owner_owing + ro_number + scheduled_completion + scheduled_in + scheduled_delivery + status + updated_at + ded_amt + suspended + est_ct_fn + est_ct_ln + } + jobs_aggregate(where: { status: { _in: $statuses } }) { + aggregate { + count(distinct: true) + } + } + } +`; + export const QUERY_ALL_ACTIVE_JOBS = gql` query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) { jobs( diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index 2518d6c16..a678ecab9 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -33,7 +33,7 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, + offset: page ? (page - 1) * 10 : 0, limit: 25, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ From 432aa9f1e15c0dea320383118fa3125b6172e1e1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:34:17 -0500 Subject: [PATCH 04/17] Progress --- client/src/components/jobs-list/jobs-list.component.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index 072bcbd61..b05c32437 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -11,7 +11,6 @@ import {QUERY_ALL_ACTIVE_JOBS_PAGINATED} from "../../graphql/jobs.queries"; import {selectBodyshop} from "../../redux/user/user.selectors"; import {onlyUnique} from "../../utils/arrayHelper"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import {alphaSort} from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; From f96fefbfdccb4921f948f8c5914cf76338be2f68 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 27 Nov 2023 10:10:02 -0800 Subject: [PATCH 05/17] IO-2480 Unqueue Parts Label instead of Remove Remove is the uncorrect work as it doesn't actually remove just unqueue --- client/src/translations/en_us/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 27f804663..528a62f32 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2202,7 +2202,7 @@ "parts_orders": "Parts Orders", "print": "Show Printed Form", "receive": "Receive Parts Order", - "removefrompartsqueue": "Remove from Parts Queue?", + "removefrompartsqueue": "Unqueue from Parts Queue?", "returnpartsorder": "Return Parts Order", "sublet_order": "Sublet Order" }, From 742d2b5ff26da6f5e44618728cfb3f70128296cc Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 27 Nov 2023 10:13:43 -0800 Subject: [PATCH 06/17] IO-2481 Parts Queue Query Adjust query to only show converted Jobs --- client/src/graphql/jobs.queries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index b14e0e03b..8569e5d39 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -60,7 +60,7 @@ export const QUERY_PARTS_QUEUE = gql` } } jobs( - where: { _and: [{ status: { _in: $statuses } }] } + where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true} }] } offset: $offset limit: $limit order_by: $order From 8ebf7baa71cf54b11e4803e1d903c01438ba9ed7 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 27 Nov 2023 10:16:10 -0800 Subject: [PATCH 07/17] IO-2481 Parts Queue Query prettyier --- client/src/graphql/jobs.queries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 8569e5d39..7c6888d68 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -60,7 +60,7 @@ export const QUERY_PARTS_QUEUE = gql` } } jobs( - where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true} }] } + where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] } offset: $offset limit: $limit order_by: $order From bbcfc420d2d383bbe21420e086ba5d01225d62f9 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 27 Nov 2023 14:34:36 -0800 Subject: [PATCH 08/17] IO-2465 Restrict Update of Vehicle on Supplement to Override Header --- .../jobs-available-supplement.headerfields.js | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js index 3085f16c4..3cb02bbab 100644 --- a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js @@ -114,7 +114,7 @@ const headerFields = [ "ins_ct_ph", "ins_ct_phx", "loss_cat", - //ad2 + //AD2 "clmt_ln", "clmt_fn", "clmt_title", @@ -219,7 +219,37 @@ const headerFields = [ "loc_title", "loc_ph", "loc_phx", - "loc_ea" + "loc_ea", + //VEH + "impact_1", + "impact_2", + "dmg_memo", + "db_v_code", + "plate_no", + "plate_st", + "v_vin", + "v_cond", + "v_prod_dt", + "v_model_yr", + "v_makecode", + "v_makedesc", + "v_model", + "v_type", + "v_bstyle", + "v_trimcode", + "trim_color", + "v_mldgcode", + "v_engine", + "v_mileage", + "v_options", + "v_color", + "v_tone", + "v_stage", + "paint_cd1", + "paint_cd2", + "paint_cd3", + "v_memo", + ]; export default headerFields; From 5cb2b3940ef83c7a13da9cddd3f497faa1479b9c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 28 Nov 2023 10:08:33 -0800 Subject: [PATCH 09/17] IO-2483 Update Transations --- client/src/translations/en_us/common.json | 114 +++++++++++----------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 27f804663..0632ca98b 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -56,13 +56,13 @@ "history": "History", "inproduction": "Jobs In Production", "manualevent": "Add Manual Appointment", - "noarrivingjobs": "No jobs are arriving.", - "nocompletingjobs": "No jobs scheduled for completion.", + "noarrivingjobs": "No Jobs are arriving.", + "nocompletingjobs": "No Jobs scheduled for completion.", "nodateselected": "No date has been selected.", "priorappointments": "Previous Appointments", "reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ", "scheduledfor": "Scheduled appointment for: ", - "severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.", + "severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.", "smartscheduling": "Smart Scheduling", "suggesteddates": "Suggested Dates" }, @@ -117,11 +117,11 @@ "jobinproductionchange": "Job production status set to {{inproduction}}", "jobioucreated": "IOU Created.", "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.", - "jobnoteadded": "Note added to job.", - "jobnotedeleted": "Note deleted from job.", - "jobnoteupdated": "Note updated on job.", - "jobspartsorder": "Parts order {{order_number}} added to job.", - "jobspartsreturn": "Parts return {{order_number}} added to job.", + "jobnoteadded": "Note added to Job.", + "jobnotedeleted": "Note deleted from Job.", + "jobnoteupdated": "Note updated on Job.", + "jobspartsorder": "Parts order {{order_number}} added to Job.", + "jobspartsreturn": "Parts return {{order_number}} added to Job.", "jobstatuschange": "Job status changed to {{status}}.", "jobsupplement": "Job supplement imported." } @@ -210,7 +210,7 @@ "markforreexport": "Mark for Re-export", "new": "New Bill", "noneselected": "No bill selected.", - "onlycmforinvoiced": "Only credit memos can be entered for any job that has been invoiced, exported, or voided.", + "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.", "retailtotal": "Bills Retail Total", "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", "state_tax": "Provincial/State Tax", @@ -658,7 +658,7 @@ "printall": "Print All Documents" }, "errors": { - "complete": "Error during job checklist completion. {{error}}", + "complete": "Error during Job checklist completion. {{error}}", "nochecklist": "No checklist has been configured for your shop. " }, "labels": { @@ -666,7 +666,7 @@ "allow_text_message": "Permission to Text?", "checklist": "Checklist", "printpack": "Job Intake Print Pack", - "removefromproduction": "Remove job from production?" + "removefromproduction": "Remove Job from Production?" }, "successes": { "completed": "Job checklist completed." @@ -682,9 +682,9 @@ "senddltoform": "Insert Driver's License Information" }, "errors": { - "fetchingjobinfo": "Error fetching job info. {{error}}.", - "returning": "Error returning courtesy car. {{error}}", - "saving": "Error saving contract. {{error}}", + "fetchingjobinfo": "Error fetching Job Info. {{error}}.", + "returning": "Error returning Courtesy Car. {{error}}", + "saving": "Error saving Contract. {{error}}", "selectjobandcar": "Please ensure both a car and job are selected." }, "fields": { @@ -741,7 +741,7 @@ "driverinformation": "Driver's Information", "findcontract": "Find Contract", "findermodal": "Contract Finder", - "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", + "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", "populatefromjob": "Populate from Job", "rates": "Contract Rates", "time": "Time", @@ -763,7 +763,7 @@ "return": "Return Car" }, "errors": { - "saving": "Error saving courtesy card. {{error}}" + "saving": "Error saving Courtesy Car. {{error}}" }, "fields": { "color": "Color", @@ -914,7 +914,7 @@ "upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ", "upload_limitexceeded_title": "Unable to upload document(s)", "uploading": "Uploading...", - "usage": "of job storage used. ({{used}} / {{total}})" + "usage": "of Job storage used. ({{used}} / {{total}})" }, "successes": { "delete": "Document(s) deleted successfully.", @@ -1375,28 +1375,28 @@ }, "errors": { "addingtoproduction": "Error adding to production. {{error}}", - "cannotintake": "Intake cannot be completed for this job. It has either already been completed or the job is already here.", - "closing": "Error closing job. {{error}}", - "creating": "Error encountered while creating job. {{error}}", - "deleted": "Error deleting job. {{error}}", - "exporting": "Error exporting job. {{error}}", + "cannotintake": "Intake cannot be completed for this Job. It has either already been completed or the Job is already here.", + "closing": "Error closing Job. {{error}}", + "creating": "Error encountered while creating Job. {{error}}", + "deleted": "Error deleting Job. {{error}}", + "exporting": "Error exporting Job. {{error}}", "exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.", - "invoicing": "Error invoicing job. {{error}}", - "noaccess": "This job does not exist or you do not have access to it.", + "invoicing": "Error invoicing Job. {{error}}", + "noaccess": "This Job does not exist or you do not have access to it.", "nodamage": "No damage points on estimate.", - "nodates": "No dates specified for this job.", + "nodates": "No dates specified for this Job.", "nofinancial": "No financial data has been calculated yet for this job. Please save it again.", - "nojobselected": "No job is selected.", + "nojobselected": "No Job is selected.", "noowner": "No owner associated.", "novehicle": "No vehicle associated.", "partspricechange": "", "saving": "Error encountered while saving record.", - "scanimport": "Error importing job. {{message}}", - "totalscalc": "Error while calculating new job totals.", - "updating": "Error while updating job(s). {{error}}", + "scanimport": "Error importing Job. {{message}}", + "totalscalc": "Error while calculating new Job totals.", + "updating": "Error while updating Job(s). {{error}}", "validation": "Please ensure all fields are entered correctly.", "validationtitle": "Validation Error", - "voiding": "Error voiding job. {{error}}" + "voiding": "Error voiding Job. {{error}}" }, "fields": { "actual_completion": "Actual Completion", @@ -1673,9 +1673,9 @@ "adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.", "allocations": "Allocations", "alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.", - "alreadyclosed": "This job has already been closed.", + "alreadyclosed": "This Job has already been closed.", "appointmentconfirmation": "Send confirmation to customer?", - "associationwarning": "Any changes to associations will require updating the data from the new parent record to the job.", + "associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.", "audit": "Audit Trail", "available": "Available", "availablejobs": "Available Jobs", @@ -1683,7 +1683,7 @@ "days": "Days", "rate": "PVRT Rate" }, - "ca_gst_all_if_null": "If the job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ", + "ca_gst_all_if_null": "If the Job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ", "calc_repair_days": "Calculated Repair Days", "calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).", "cards": { @@ -1699,7 +1699,7 @@ "totals": "Totals", "vehicle": "Vehicle" }, - "changeclass": "Changing the job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?", + "changeclass": "Changing the Job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?", "checklistcompletedby": "Checklist completed by {{by}} at {{at}}", "checklistdocuments": "Checklist Documents", "checklists": "Checklists", @@ -1723,12 +1723,12 @@ "vehicleinfo": "Vehicle Info" }, "createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.", - "creating_new_job": "Creating new job...", + "creating_new_job": "Creating new Job...", "deductible": { "stands": "Stands", "waived": "Waived" }, - "deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ", + "deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ", "deletedelivery": "Delete Delivery Checklist", "deleteintake": "Delete Intake Checklist", "deliverchecklist": "Deliver Checklist", @@ -1749,7 +1749,7 @@ "documents": "Documents", "documents-images": "Images", "documents-other": "Other Documents", - "duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.", + "duplicateconfirm": "Are you sure you want to duplicate this Job? Some elements of this Job will not be duplicated.", "emailaudit": "Email Audit Trail", "employeeassignments": "Employee Assignments", "estimatelines": "Estimate Lines", @@ -1760,7 +1760,7 @@ "gppercent": "% G.P.", "hrs_claimed": "Hours Claimed", "hrs_total": "Hours Total", - "importnote": "The job was initially imported.", + "importnote": "The Job was initially imported.", "inproduction": "In Production", "intakechecklist": "Intake Checklist", "iou": "IOU", @@ -1793,9 +1793,9 @@ "calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the retail total of returns created. This does not take into account whether the credit was marked as received. You can find more information here.", "creditmemos": "The total retail amount of all returns created. This amount does not reflect credit memos that have been posted.", "creditsnotreceived": "This total reflects the total retail of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here here. ", - "discrep1": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.
  • \n
  • You do not have the latest supplement imported, or, a supplement must be submitted and then imported.
  • \n
  • You have posted a bill line to labor.
  • \n
\n
\nThere may be additional issues not listed above that prevent this job from reconciling.", - "discrep2": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Used an incorrect rate when deducting from labor.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this job from reconciling.", - "discrep3": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • A parts order return has not been created.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this job from reconciling.", + "discrep1": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.
  • \n
  • You do not have the latest supplement imported, or, a supplement must be submitted and then imported.
  • \n
  • You have posted a bill line to labor.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", + "discrep2": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Used an incorrect rate when deducting from labor.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", + "discrep3": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • A parts order return has not been created.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", "laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.", "partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).
\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.", "totalreturns": "The total retail amount of returns created for this job." @@ -1826,13 +1826,13 @@ "sale_parts": "Sales - Parts", "sale_sublet": "Sales - Sublet", "sales": "Sales", - "savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ", - "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ", + "savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ", + "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ", "specialcoveragepolicy": "Special Coverage Policy Applies", "state_tax_amt": "Provincial/State Taxes", "subletstotal": "Sublets Total", "subtotal": "Subtotal", - "supplementnote": "The job had a supplement imported.", + "supplementnote": "The Job had a supplement imported.", "suspended": "SUSPENDED", "suspense": "Suspense", "threshhold": "Max Threshold: ${{amount}}", @@ -1841,16 +1841,16 @@ "total_repairs": "Total Repairs", "total_sales": "Total Sales", "totals": "Totals", - "unvoidnote": "This job was unvoided.", + "unvoidnote": "This Job was unvoided.", "vehicle_info": "Vehicle", "vehicleassociation": "Vehicle Association", "viewallocations": "View Allocations", - "voidjob": "Are you sure you want to void this job? This cannot be easily undone. ", - "voidnote": "This job was voided." + "voidjob": "Are you sure you want to void this Job? This cannot be easily undone. ", + "voidnote": "This Job was voided." }, "successes": { "addedtoproduction": "Job added to production board.", - "all_deleted": "{{count}} jobs deleted successfully.", + "all_deleted": "{{count}} Jobs deleted successfully.", "closed": "Job closed successfully.", "converted": "Job converted successfully.", "created": "Job created successfully. Click to view.", @@ -2026,7 +2026,7 @@ }, "errors": { "invalidphone": "The phone number is invalid. Unable to open conversation. ", - "noattachedjobs": "No jobs have been associated to this conversation. ", + "noattachedjobs": "No Jobs have been associated to this conversation. ", "updatinglabel": "Error updating label. {{error}}" }, "labels": { @@ -2035,7 +2035,7 @@ "maxtenimages": "You can only select up to a maximum of 10 images at a time.", "messaging": "Messaging", "noallowtxt": "This customer has not indicated their permission to be messaged.", - "nojobs": "Not associated to any job.", + "nojobs": "Not associated to any Job.", "nopush": "Polling Mode Enabled", "phonenumber": "Phone #", "presets": "Presets", @@ -2455,15 +2455,15 @@ "unsuspend": "Unsuspend" }, "errors": { - "boardupdate": "Error encountered updating job. {{message}}", + "boardupdate": "Error encountered updating Job. {{message}}", "removing": "Error removing from production board. {{error}}", "settings": "Error saving board settings: {{error}}" }, "labels": { "actual_in": "Actual In", "alert": "Alert", - "alertoff": "Remove alert from job", - "alerton": "Add alert to job", + "alertoff": "Remove alert from Job", + "alerton": "Add alert to Job", "ats": "Alternative Transportation", "bodyhours": "B", "bodypriority": "B/P", @@ -2674,9 +2674,9 @@ "edit": "Edit" }, "errors": { - "adding": "Error adding job to scoreboard. {{message}}", - "removing": "Error removing job from scoreboard. {{message}}", - "updating": "Error updating scoreboard. {{message}}" + "adding": "Error adding Job to Scoreboard. {{message}}", + "removing": "Error removing Job from Scoreboard. {{message}}", + "updating": "Error updating Scoreboard. {{message}}" }, "fields": { "bodyhrs": "Body Hours", @@ -2774,7 +2774,7 @@ "ro_number": "Job to Post Against" }, "labels": { - "alreadyclockedon": "You are already clocked in to the following job(s):", + "alreadyclockedon": "You are already clocked in to the following Job(s):", "ambreak": "AM Break", "amshift": "AM Shift", "clockhours": "Shift Clock Hours Summary", From d7f52d864a3bc7032736c28fcb626da715350fd6 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 28 Nov 2023 16:05:23 -0500 Subject: [PATCH 10/17] Add Global search to Active Jobs. --- .../jobs-list/jobs-list.component.jsx | 101 +++++++++++++----- client/src/utils/config.js | 1 + 2 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 client/src/utils/config.js diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index b05c32437..ac5eb34aa 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,8 +1,8 @@ import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; import {useQuery} from "@apollo/client"; -import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd"; +import {Button, Card, Grid, Input, Space, Table, Tooltip, Typography} from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; import {Link, useHistory, useLocation} from "react-router-dom"; @@ -15,22 +15,27 @@ import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; +import { pageLimit } from '../../utils/config'; +import axios from "axios"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); +const mapDispatchToProps = () => ({ +}); + export function JobsList({bodyshop,}) { - const pageLimit = 10; - const searchParams = queryString.parse(useLocation().search); - const {page, selected, sortorder, sortcolumn} = searchParams; - console.dir({page, selected, sortorder, sortcolumn}); + const search = queryString.parse(useLocation().search); + const [openSearchResults, setOpenSearchResults] = useState([]); + const [searchLoading, setSearchLoading] = useState(false); + const {page, selected, sortorder, sortcolumn} = search; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { - variables: { offset: page ? (page - 1) * pageLimit : 0, limit: 10, @@ -58,34 +63,56 @@ export function JobsList({bodyshop,}) { const {t} = useTranslation(); const history = useHistory(); - const [searchText, setSearchText] = useState(""); - - if (error) return ; const jobs = data?.jobs || []; const handleTableChange = (pagination, filters, sorter) => { + // TODO: Is this needed? setState({...state, filteredInfo: filters}); - searchParams.page = pagination.current; - searchParams.sortcolumn = sorter.column && sorter.column.key; - searchParams.sortorder = sorter.order; + search.page = pagination.current; + search.sortcolumn = sorter.column && sorter.column.key; + search.sortorder = sorter.order; if (filters.status) { - searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + search.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); } else { - delete searchParams.statusFilters; + delete search.statusFilters; } - history.push({search: queryString.stringify(searchParams)}); + history.push({search: queryString.stringify(search)}); }; + useEffect(() => { + if (search.search && search.search.trim() !== "") { + searchJobs(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (error) return ; + + 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); + } + } + const handleOnRowClick = (record) => { if (record) { if (record.id) { history.push({ search: queryString.stringify({ - ...searchParams, + ...search, selected: record.id, }), }); @@ -328,24 +355,42 @@ export function JobsList({bodyshop,}) { title={t("titles.bc.jobs-active")} extra={ + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} { - setSearchText(e.target.value); + placeholder={search.search || t("general.labels.search")} + onSearch={(value) => { + search.search = value; + history.push({ search: queryString.stringify(search) }); + searchJobs(value); }} - value={searchText} + loading={loading || searchLoading} enterButton /> } >
{ + onRow={(record) => { return { - onClick: (event) => { + onClick: () => { handleOnRowClick(record); }, }; @@ -384,4 +429,4 @@ export function JobsList({bodyshop,}) { ); } -export default connect(mapStateToProps, null)(JobsList); +export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/utils/config.js b/client/src/utils/config.js new file mode 100644 index 000000000..784074f44 --- /dev/null +++ b/client/src/utils/config.js @@ -0,0 +1 @@ +export const pageLimit = 10; From ec2519eae4aaefe1d5482636a59142f84d49b809 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 29 Nov 2023 13:37:02 -0500 Subject: [PATCH 11/17] Move PageSize (PageLimit) to an external configuration file. --- .../accounting-payables-table.component.jsx | 3 ++- .../accounting-payments-table.component.jsx | 3 ++- .../audit-trail-list/audit-trail-list.component.jsx | 3 ++- .../email-audit-trail-list.component.jsx | 3 ++- .../components/contract-jobs/contract-jobs.component.jsx | 3 ++- .../contracts-list/contracts-list.component.jsx | 3 ++- .../courtesy-car-contract-list.component.jsx | 3 ++- .../csi-response-list-paginated.component.jsx | 3 ++- .../monthly-job-costing.component.jsx | 3 ++- .../scheduled-in-today/scheduled-in-today.component.jsx | 3 ++- .../scheduled-out-today.component.jsx | 3 ++- .../dms-allocations-summary-ap.component.jsx | 3 ++- .../dms-allocations-summary.component.jsx | 3 ++- .../inventory-list/inventory-list.component.jsx | 3 ++- .../job-costing-parts-table.component.jsx | 3 ++- .../jobs-list-paginated.component.jsx | 8 +++----- client/src/components/jobs-list/jobs-list.component.jsx | 9 +++++---- .../jobs-ready-list/jobs-ready-list.component.jsx | 3 ++- .../src/components/owners-list/owners-list.component.jsx | 3 ++- .../payment-list-paginated.component.jsx | 5 +++-- .../scoreboard-jobs-list.component.jsx | 5 +++-- .../components/vehicles-list/vehicles-list.component.jsx | 3 ++- client/src/pages/bills/bills.page.component.jsx | 5 +++-- .../src/pages/export-logs/export-logs.page.component.jsx | 3 ++- .../src/pages/parts-queue/parts-queue.page.component.jsx | 3 ++- client/src/pages/phonebook/phonebook.page.component.jsx | 3 ++- client/src/utils/config.js | 5 ++++- 27 files changed, 63 insertions(+), 37 deletions(-) diff --git a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx index fcdcca0b6..260589c2f 100644 --- a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx +++ b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx @@ -15,6 +15,7 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -210,7 +211,7 @@ export function AccountingPayablesTableComponent({
`${record.InvoiceNumber}${record.Account}`} dataSource={allocationsSummary} diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx index d244a341c..7c9944149 100644 --- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx @@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import Dinero from "dinero.js"; import { SyncOutlined } from "@ant-design/icons"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -94,7 +95,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { )}
), }, - { title: t("jobs.fields.owner"), dataIndex: "ownr_ln", @@ -125,7 +125,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { title: t("vehicles.fields.plate_no"), dataIndex: "plate_no", key: "plate_no", - ellipsis: true, sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), sortOrder: sortcolumn === "plate_no" && sortorder, @@ -137,7 +136,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { title: t("jobs.fields.clm_no"), dataIndex: "clm_no", key: "clm_no", - ellipsis: true, sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), sortOrder: sortcolumn === "clm_no" && sortorder, @@ -259,11 +257,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { pagination={ search?.search ? { - pageSize: 25, + pageSize: pageLimit, showSizeChanger: false, } : { - pageSize: 25, + pageSize: pageLimit, current: parseInt(page || 1), total: total, showSizeChanger: false, diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index ac5eb34aa..f025f1401 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -14,7 +14,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import _ from "lodash"; +import {flattenDeep} from "lodash"; import { pageLimit } from '../../utils/config'; import axios from "axios"; @@ -75,7 +75,7 @@ export function JobsList({bodyshop,}) { search.sortorder = sorter.order; if (filters.status) { - search.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + search.statusFilters = JSON.stringify(flattenDeep(filters.status)); } else { delete search.statusFilters; } @@ -85,7 +85,9 @@ export function JobsList({bodyshop,}) { useEffect(() => { if (search.search && search.search.trim() !== "") { - searchJobs(); + searchJobs().catch(e => { + console.error('Something went wrong searching for jobs in the job-list component', e); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -217,7 +219,6 @@ export function JobsList({bodyshop,}) { [], onFilter: (value, record) => value.includes(record.status), }, - { title: t("jobs.fields.vehicle"), dataIndex: "vehicle", diff --git a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx index 678c5e228..fee461df2 100644 --- a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx +++ b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx @@ -20,6 +20,7 @@ import { alphaSort } from "../../utils/sorters"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -377,7 +378,7 @@ export function JobsReadyList({ bodyshop }) { >
({ setPartsOrderContext: (context) => @@ -295,11 +296,11 @@ export function BillsListPage({ pagination={ search?.search ? { - pageSize: 25, + pageSize: pageLimit, showSizeChanger: false, } : { - pageSize: 25, + pageSize: pageLimit, current: parseInt(page || 1), total: total, showSizeChanger: false, diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 0b4dcd634..0c6517a58 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -12,6 +12,7 @@ import AlertComponent from "../../components/alert/alert.component"; import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateTimeFormatter } from "../../utils/DateFormatter"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -178,7 +179,7 @@ export function ExportLogsPageComponent({ bodyshop }) { loading={loading} pagination={{ position: "top", - pageSize: 25, + pageSize: pageLimit, current: parseInt(page || 1), total: data && data.search_exportlog_aggregate.aggregate.count, }} diff --git a/client/src/pages/parts-queue/parts-queue.page.component.jsx b/client/src/pages/parts-queue/parts-queue.page.component.jsx index 5d6226bdb..62970d788 100644 --- a/client/src/pages/parts-queue/parts-queue.page.component.jsx +++ b/client/src/pages/parts-queue/parts-queue.page.component.jsx @@ -18,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter"; import { alphaSort, dateSort } from "../../utils/sorters"; import useLocalStorage from "../../utils/useLocalStorage"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -296,7 +297,7 @@ export function PartsQueuePageComponent({ bodyshop }) { loading={loading} pagination={{ position: "top", - pageSize: 50, + pageSize: pageLimit, // current: parseInt(page || 1), // total: data && data.jobs_aggregate.aggregate.count, }} diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index 9a7a13777..baf4e18ec 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -17,6 +17,7 @@ import { import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component"; import { alphaSort } from "../../utils/sorters"; import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -189,7 +190,7 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { loading={loading} pagination={{ position: "top", - pageSize: 25, + pageSize: pageLimit, current: parseInt(page || 1), total: data && data.search_phonebook_aggregate.aggregate.count, }} diff --git a/client/src/utils/config.js b/client/src/utils/config.js index 784074f44..2a968907c 100644 --- a/client/src/utils/config.js +++ b/client/src/utils/config.js @@ -1 +1,4 @@ -export const pageLimit = 10; + +// Sometimes referred to as PageSize, this variable controls the amount of records +// to show on one page during pagination. +export const pageLimit = 50; From 2252091b5317624d33dddd880662590013abf70c Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 29 Nov 2023 16:09:20 -0500 Subject: [PATCH 12/17] Fix order issue on all jobs --- .../jobs-list-paginated/jobs-list-paginated.component.jsx | 2 -- client/src/graphql/jobs.queries.js | 2 +- client/src/pages/jobs-all/jobs-all.container.jsx | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index f1455d2a7..b5048d0ef 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -34,10 +34,8 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { title: t("jobs.fields.ro_number"), dataIndex: "ro_number", key: "ro_number", - sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), sortOrder: sortcolumn === "ro_number" && sortorder, - render: (text, record) => ( {record.ro_number || t("general.labels.na")} diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index a583b7808..b18673de6 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -12,7 +12,7 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` offset: $offset limit: $limit where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } - order_by: { created_at: desc } + order_by: $order ) { iouparent ownr_fn diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index a678ecab9..dd78369a0 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -40,9 +40,9 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { { [sortcolumn || "ro_number"]: sortorder && sortorder !== "false" - ? sortorder === "descend" + ? (sortorder === "descend" ? "desc" - : "asc" + : "asc") : "desc", }, ], From 0d70545b98fdf4ab5b1c023a8ebce4e8f463e54d Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 29 Nov 2023 16:10:30 -0500 Subject: [PATCH 13/17] Fix order issue on all jobs --- client/src/components/jobs-list/jobs-list.component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index f025f1401..885b53634 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -44,9 +44,9 @@ export function JobsList({bodyshop,}) { { [sortcolumn || "ro_number"]: sortorder && sortorder !== "false" - ? sortorder === "descend" + ? (sortorder === "descend" ? "desc" - : "asc" + : "asc") : "desc", }, ], From 4b289388bfe620f4932e4ff016ed968c9380a1d2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 29 Nov 2023 17:27:08 -0500 Subject: [PATCH 14/17] Fix issues with limits. --- .../components/inventory-list/inventory-list.container.jsx | 5 +++-- client/src/components/jobs-list/jobs-list.component.jsx | 2 +- client/src/components/owners-list/owners-list.container.jsx | 5 +++-- .../src/components/vehicles-list/vehicles-list.container.jsx | 5 +++-- client/src/pages/bills/bills.page.container.jsx | 5 +++-- client/src/pages/contracts/contracts.page.container.jsx | 5 +++-- .../courtesy-car-detail.page.container.jsx | 5 +++-- client/src/pages/export-logs/export-logs.page.component.jsx | 4 ++-- client/src/pages/jobs-all/jobs-all.container.jsx | 5 +++-- .../src/pages/payments-all/payments-all.container.page.jsx | 5 +++-- client/src/pages/phonebook/phonebook.page.component.jsx | 4 ++-- client/src/pages/shop-csi/shop-csi.container.page.jsx | 5 +++-- 12 files changed, 32 insertions(+), 23 deletions(-) diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index bb265060f..e98266464 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -11,6 +11,7 @@ import { } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, consumedIsNull: showall === "true" ? null : true, order: [ { diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index 885b53634..b7bd3141d 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -38,7 +38,7 @@ export function JobsList({bodyshop,}) { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { offset: page ? (page - 1) * pageLimit : 0, - limit: 10, + limit: pageLimit, statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], order: [ { diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 78db6bf2b..7303c4222 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function OwnersListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -16,8 +17,8 @@ export default function OwnersListContainer() { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 34fc4940b..2e85236f8 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function VehiclesListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -15,8 +16,8 @@ export default function VehiclesListContainer() { { variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 22a8b61b9..40e662899 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -13,6 +13,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -38,8 +39,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index c4f4fef7b..466b468e0 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -12,6 +12,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -29,8 +30,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index e41e80f40..93bffe97f 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -19,6 +19,7 @@ import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -41,8 +42,8 @@ export function CourtesyCarDetailPageContainer({ const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { variables: { id: ccId, - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 0c6517a58..7ae8977cf 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -30,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index dd78369a0..b9fa9a52a 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -13,6 +13,7 @@ import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -33,8 +34,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 10 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ { diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 07547bc2e..3d3cb6287 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -14,6 +14,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -34,8 +35,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index baf4e18ec..5ce863444 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -36,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "lastname"]: sortorder diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index 132ee7999..f0c295685 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -16,6 +16,7 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -42,8 +43,8 @@ export function ShopCsiContainer({ nextFetchPolicy: "network-only", variables: { //search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "completedon"]: sortorder From 9f1f58a9c7d65a820eeb5812d393d65011377134 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 29 Nov 2023 17:14:39 -0800 Subject: [PATCH 15/17] IO-2465 Adjust Headerfile override and comment out line --- ...jobs-available-supplement.estlines.util.js | 4 +-- .../jobs-available-supplement.headerfields.js | 25 ++----------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js index aa67589db..46af12638 100644 --- a/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js @@ -1,6 +1,6 @@ -import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries"; import { gql } from "@apollo/client"; import _ from "lodash"; +import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries"; export const GetSupplementDelta = async (client, jobId, newLines) => { const { @@ -50,7 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => { .reduce((acc, value, idx) => { return acc + generateRemoveQuery(value, idx); }, ""); - console.log(insertQueries, updateQueries, removeQueries); + //console.log(insertQueries, updateQueries, removeQueries); if ((insertQueries + updateQueries + removeQueries).trim() === "") { return new Promise((resolve, reject) => { diff --git a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js index 3cb02bbab..07e91f8ea 100644 --- a/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js @@ -221,35 +221,14 @@ const headerFields = [ "loc_phx", "loc_ea", //VEH - "impact_1", - "impact_2", - "dmg_memo", - "db_v_code", "plate_no", "plate_st", "v_vin", - "v_cond", - "v_prod_dt", "v_model_yr", - "v_makecode", - "v_makedesc", - "v_model", - "v_type", - "v_bstyle", - "v_trimcode", - "trim_color", - "v_mldgcode", - "v_engine", - "v_mileage", + "v_make_desc", + "v_model_desc", "v_options", "v_color", - "v_tone", - "v_stage", - "paint_cd1", - "paint_cd2", - "paint_cd3", - "v_memo", - ]; export default headerFields; From dfd8845864a25cb796a6eba977d4eadac30f2267 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 29 Nov 2023 17:32:09 -0800 Subject: [PATCH 16/17] IO-2484 Next Service KMs Allow null and only display warning if not null and current milage is greater than service KMs --- .../courtesy-car-form/courtesy-car-form.component.jsx | 3 +-- .../down.sql | 2 ++ .../up.sql | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/down.sql create mode 100644 hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/up.sql diff --git a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx index a6da8ddc3..d5315ed3a 100644 --- a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx +++ b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx @@ -228,8 +228,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {() => { const nextservicekm = form.getFieldValue("nextservicekm"); const mileageOver = - nextservicekm <= form.getFieldValue("mileage"); - + nextservicekm && nextservicekm <= form.getFieldValue("mileage"); if (mileageOver) return ( diff --git a/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/down.sql b/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/down.sql new file mode 100644 index 000000000..efc416b2e --- /dev/null +++ b/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/down.sql @@ -0,0 +1,2 @@ +alter table "public"."courtesycars" alter column "nextservicekm" set not null; +alter table "public"."courtesycars" alter column "nextservicekm" set default '0'; diff --git a/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/up.sql b/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/up.sql new file mode 100644 index 000000000..3a6640343 --- /dev/null +++ b/hasura/migrations/1701307239375_alter_table_public_courtesycars_alter_column_nextservicekm/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE "public"."courtesycars" ALTER COLUMN "nextservicekm" drop default; +alter table "public"."courtesycars" alter column "nextservicekm" drop not null; From 806daebd3ff6fac067950cef171a4f5c2f7ef58a Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 29 Nov 2023 19:51:03 -0800 Subject: [PATCH 17/17] IO-2485 Correct onlyFuture on typed values --- .../form-date-picker.component.jsx | 13 +++++++++-- .../form-date-time-picker.component.jsx | 23 ++++++++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/client/src/components/form-date-picker/form-date-picker.component.jsx b/client/src/components/form-date-picker/form-date-picker.component.jsx index e9ace8082..05a894e8b 100644 --- a/client/src/components/form-date-picker/form-date-picker.component.jsx +++ b/client/src/components/form-date-picker/form-date-picker.component.jsx @@ -65,8 +65,17 @@ export function FormDatePicker({ }); } - if (_a.isValid() && onChange) - onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); + if (_a.isValid() && onChange) { + if (onlyFuture) { + if (moment().subtract(1, "day").isBefore(_a)) { + onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); + } else { + onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment()); + } + } else { + onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); + } + } }; return ( diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index 79b01c529..a086c253e 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,9 +1,9 @@ import React, { forwardRef } from "react"; //import DatePicker from "react-datepicker"; //import "react-datepicker/src/stylesheets/datepicker.scss"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import { TimePicker } from "antd"; import moment from "moment"; +import FormDatePicker from "../form-date-picker/form-date-picker.component"; //To be used as a form element only. const DateTimePicker = ( @@ -26,20 +26,21 @@ const DateTimePicker = ( value={value} onBlur={onBlur} onChange={onChange} + onlyFuture={onlyFuture} isDateOnly={false} /> moment().isAfter(d), - })} - onChange={onChange} - showSecond={false} - minuteStep={15} - onBlur={onBlur} - format="hh:mm a" - {...restProps} + value={value ? moment(value) : null} + {...(onlyFuture && { + disabledDate: (d) => moment().isAfter(d), + })} + onChange={onChange} + showSecond={false} + minuteStep={15} + onBlur={onBlur} + format="hh:mm a" + {...restProps} /> );