From 55842faedde30ec301a5060592d60d5f788bb9ef Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 21 Nov 2023 14:01:33 -0800 Subject: [PATCH 01/27] IO-2468 CC Mileage and Service KMs --- .../courtesy-car-form.component.jsx | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) 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 29edf5243..a6da8ddc3 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 @@ -34,7 +34,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {/* */} - - + - - - +
+ + + + + p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm + } + > + {() => { + const nextservicekm = form.getFieldValue("nextservicekm"); + const mileageOver = + nextservicekm <= form.getFieldValue("mileage"); + + if (mileageOver) + return ( + + + + {t("contracts.labels.cardueforservice")} + + {`${nextservicekm} km`} + + ); + + return <>; + }} + +
- p.mileage !== c.mileage || - p.nextservicedate !== c.nextservicedate || - p.nextservicekm !== c.nextservicekm - } + shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate} > {() => { const nextservicedate = form.getFieldValue("nextservicedate"); - const nextservicekm = form.getFieldValue("nextservicekm"); - - const mileageOver = - nextservicekm <= form.getFieldValue("mileage"); - const dueForService = - nextservicedate && moment(nextservicedate).isBefore(moment()); + nextservicedate && + moment(nextservicedate).endOf("day").isSameOrBefore(moment()); - if (mileageOver || dueForService) + if (dueForService) return ( {t("contracts.labels.cardueforservice")} - {`${nextservicekm} km`} {nextservicedate} @@ -282,7 +299,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {() => { const expires = form.getFieldValue("registrationexpires"); - const dateover = expires && moment(expires).isBefore(moment()); + const dateover = + expires && moment(expires).endOf("day").isBefore(moment()); if (dateover) return ( @@ -317,7 +335,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {() => { const expires = form.getFieldValue("insuranceexpires"); - const dateover = expires && moment(expires).isBefore(moment()); + const dateover = + expires && moment(expires).endOf("day").isBefore(moment()); if (dateover) return ( From 14ebb280a3e8245672e1b72b8f4bcce956721fcf Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 21 Nov 2023 17:46:49 -0500 Subject: [PATCH 02/27] Add toggles to two modals to allow for auto parts queue toggle --- .../jobs-available-table.container.jsx | 94 +++++++++++-------- .../jobs-find-modal.component.jsx | 8 ++ .../jobs-find-modal.container.jsx | 4 + .../owner-find-modal.component.jsx | 9 +- .../owner-find-modal.container.jsx | 4 + .../shop-info/shop-info.general.component.jsx | 8 ++ client/src/graphql/bodyshop.queries.js | 2 + client/src/translations/en_us/common.json | 3 + client/src/translations/es/common.json | 3 + client/src/translations/fr/common.json | 3 + hasura/metadata/tables.yaml | 4 + .../down.sql | 4 + .../up.sql | 2 + 13 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 hasura/migrations/1700599672091_alter_table_public_bodyshops_add_column_md_functionality_toggles/down.sql create mode 100644 hasura/migrations/1700599672091_alter_table_public_bodyshops_add_column_md_functionality_toggles/up.sql diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 0e74276b2..12d10baf9 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -73,6 +73,8 @@ export function JobsAvailableContainer({ const [selectedJob, setSelectedJob] = useState(null); const [selectedOwner, setSelectedOwner] = useState(null); + const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle); + const [insertLoading, setInsertLoading] = useState(false); const [insertNote] = useMutation(INSERT_NEW_NOTE); @@ -94,6 +96,7 @@ export function JobsAvailableContainer({ logImEXEvent("job_import_new"); setOwnerModalVisible(false); + setInsertLoading(true); const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk); @@ -120,7 +123,7 @@ export function JobsAvailableContainer({ let existingVehicles; if (estData.est_data.v_vin) { - //There's vehicle data, need to double check the VIN. + //There's vehicle data, need to double-check the VIN. existingVehicles = await client.query({ query: SEARCH_VEHICLE_BY_VIN, variables: { @@ -143,7 +146,7 @@ export function JobsAvailableContainer({ text: t("jobs.labels.importnote"), }, }, - queued_for_parts: true, + queued_for_parts: partsQueueToggle, ...(existingVehicles && existingVehicles.data.vehicles.length > 0 ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } : {}), @@ -157,46 +160,51 @@ export function JobsAvailableContainer({ delete newJob.vehicle; } - insertNewJob({ - variables: { - job: newJob, - }, - }) - .then((r) => { - if (CriticalPartsScanning.treatment === "on") { - CriticalPartsScan(r.data.insert_jobs.returning[0].id); - } - notification["success"]({ - message: t("jobs.successes.created"), - onClick: () => { - history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); - }, - }); - //Job has been inserted. Clean up the available jobs record. + try { + const r = await insertNewJob({ + variables: { + job: newJob, + }, + }); - insertAuditTrail({ - jobid: r.data.insert_jobs.returning[0].id, - operation: AuditTrailMapping.jobimported(), - }); + if (CriticalPartsScanning.treatment === "on") { + CriticalPartsScan(r.data.insert_jobs.returning[0].id); + } - deleteJob({ - variables: { id: estData.id }, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); - }) - .catch((r) => { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", { error: r.message }), - }); + notification["success"]({ + message: t("jobs.successes.created"), + onClick: () => { + history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); + }, + }); + //Job has been inserted. Clean up the available jobs record. + + insertAuditTrail({ + jobid: r.data.insert_jobs.returning[0].id, + operation: AuditTrailMapping.jobimported(), + }); + + deleteJob({ + variables: { id: estData.id }, + }).then((r) => { refetch(); setInsertLoading(false); }); + + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } catch (err) { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { error: err.message }), + }); + refetch().catch(e => {console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)}); + setInsertLoading(false); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } + }; - //Suplement scenario + //Supplement scenario const onJobFindModalOk = async () => { logImEXEvent("job_import_supplement"); @@ -248,11 +256,14 @@ export function JobsAvailableContainer({ // "0.00" // ), // job_totals: newTotals, - // queued_for_parts: true, + queued_for_parts: partsQueueToggle, }, }, }); - if (CriticalPartsScanning.treatment === "on") { + + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + + if (CriticalPartsScanning.treatment === "on") { CriticalPartsScan(updateResult.data.update_jobs.returning[0].id); } if (updateResult.errors) { @@ -327,12 +338,14 @@ export function JobsAvailableContainer({ const onOwnerModalCancel = () => { setOwnerModalVisible(false); setSelectedOwner(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); }; const onJobModalCancel = () => { setJobModalVisible(false); modalSearchState[1](""); setSelectedJob(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); }; const addJobAsNew = (record) => { @@ -353,6 +366,8 @@ export function JobsAvailableContainer({ }, [addJobAsSupp, availableJobId, clm_no]); if (error) return ; + + return ( diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx index 330c0e5db..37a88e1ef 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx @@ -14,6 +14,8 @@ export default function JobsFindModalComponent({ importOptionsState, modalSearchState, jobsListRefetch, + partsQueueToggle, + setPartsQueueToggle, }) { const { t } = useTranslation(); const [modalSearch, setModalSearch] = modalSearchState; @@ -199,6 +201,12 @@ export default function JobsFindModalComponent({ > {t("jobs.labels.override_header")} + setPartsQueueToggle(e.target.checked)} + > + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} +
); } diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx index 7bb3591cb..64dc8be9a 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx @@ -24,6 +24,8 @@ export default connect( setSelectedJob, importOptionsState, modalSearchState, + partsQueueToggle, + setPartsQueueToggle, ...modalProps }) { const { t } = useTranslation(); @@ -91,6 +93,8 @@ export default connect( jobsListRefetch={jobsList.refetch} jobsList={jobsData} modalSearchState={modalSearchState} + partsQueueToggle={partsQueueToggle} + setPartsQueueToggle={setPartsQueueToggle} /> ) : null} diff --git a/client/src/components/owner-find-modal/owner-find-modal.component.jsx b/client/src/components/owner-find-modal/owner-find-modal.component.jsx index 177b4326e..3a5f313cd 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.component.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.component.jsx @@ -8,10 +8,11 @@ export default function OwnerFindModalComponent({ setSelectedOwner, ownersListLoading, ownersList, + partsQueueToggle, + setPartsQueueToggle, }) { //setSelectedOwner is used to set the record id of the owner to use for adding the job. const { t } = useTranslation(); - const columns = [ { title: t("owners.fields.ownr_ln"), @@ -109,6 +110,12 @@ export default function OwnerFindModalComponent({ > {t("owners.labels.create_new")} + setPartsQueueToggle(e.target.checked)} + > + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} + ); } diff --git a/client/src/components/owner-find-modal/owner-find-modal.container.jsx b/client/src/components/owner-find-modal/owner-find-modal.container.jsx index 9f1397ba1..f6c9453d7 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.container.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.container.jsx @@ -14,6 +14,8 @@ export default function OwnerFindModalContainer({ owner, selectedOwner, setSelectedOwner, + partsQueueToggle, + setPartsQueueToggle, ...modalProps }) { //use owner object to run query and find what possible owners there are. @@ -59,6 +61,8 @@ export default function OwnerFindModalContainer({ selectedOwner={selectedOwner} setSelectedOwner={setSelectedOwner} ownersListLoading={ownersList.loading} + partsQueueToggle={partsQueueToggle} + setPartsQueueToggle={setPartsQueueToggle} ownersList={ ownersList.data && ownersList.data.search_owners ? ownersList.data.search_owners diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index ebf2accaf..73861abc4 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -42,12 +42,20 @@ export function ShopInfoGeneral({ form, bodyshop }) { bodyshop && bodyshop.imexshopid ); + return (
+ + + Date: Tue, 21 Nov 2023 17:00:33 -0800 Subject: [PATCH 03/27] IO-2435 Total LAB & LAR in Production Board Header --- .../production-list-table.component.jsx | 20 +++++++++++++++++++ client/src/translations/en_us/common.json | 6 ++++-- client/src/translations/es/common.json | 2 ++ client/src/translations/fr/common.json | 2 ++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 1585c6480..3ea3391d3 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -184,6 +184,18 @@ export function ProductionListTable({ 0 ) .toFixed(1); + const totalLAB = data + .reduce( + (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAR = data + .reduce( + (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); return (
+ + Date: Tue, 21 Nov 2023 17:11:27 -0800 Subject: [PATCH 04/27] IO-2435 Production Board Visual Breakout --- .../production-board-kanban.component.jsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index 6d9750396..b1061f973 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -20,9 +20,9 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component"; import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component"; //import "@asseinfo/react-kanban/dist/styles.css"; +import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component"; import "./production-board-kanban.styles.scss"; 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({ bodyshop: selectBodyshop, technician: selectTechnician, @@ -153,6 +153,18 @@ export function ProductionBoardKanbanComponent({ 0 ) .toFixed(1); + const totalLAB = data + .reduce( + (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAR = data + .reduce( + (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; @@ -236,6 +248,14 @@ export function ProductionBoardKanbanComponent({ title={t("dashboard.titles.productionhours")} value={totalHrs} /> + + Date: Wed, 22 Nov 2023 17:14:52 -0500 Subject: [PATCH 05/27] 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 7e99a51495fe5296e509bb889055e6d579844b8d Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 22 Nov 2023 17:32:35 -0800 Subject: [PATCH 06/27] IO-2214 Lost Sale Date Add date_lost_sale to track date sale was lost, add in Audit Trail for both cancel and insertion of schedule, standardize date format on output. --- .../job-at-change/schedule-event.container.jsx | 14 +++++++++++++- .../jobs-admin-dates.component.jsx | 9 ++++++++- .../jobs-detail-dates.component.jsx | 7 +++++++ .../jobs-detail-header-actions.component.jsx | 11 +++++++++++ .../schedule-job-modal.container.jsx | 16 ++++++++++++++++ client/src/graphql/appointments.queries.js | 1 + client/src/graphql/jobs.queries.js | 2 ++ .../jobs-detail/jobs-detail.page.component.jsx | 17 +++++++++-------- client/src/translations/en_us/common.json | 3 +++ client/src/translations/es/common.json | 3 +++ client/src/translations/fr/common.json | 3 +++ client/src/utils/AuditTrailMappings.js | 4 ++++ client/src/utils/DateFormatter.jsx | 4 ++++ hasura/metadata/tables.yaml | 6 ++++++ .../down.sql | 4 ++++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 1 + 18 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql create mode 100644 hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql create mode 100644 hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql create mode 100644 hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql diff --git a/client/src/components/job-at-change/schedule-event.container.jsx b/client/src/components/job-at-change/schedule-event.container.jsx index 89065f0f7..60abf9c61 100644 --- a/client/src/components/job-at-change/schedule-event.container.jsx +++ b/client/src/components/job-at-change/schedule-event.container.jsx @@ -2,12 +2,16 @@ import { useMutation } from "@apollo/client"; import { notification } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; import ScheduleEventComponent from "./schedule-event.component"; export default function ScheduleEventContainer({ bodyshop, event, refetch }) { + const dispatch = useDispatch(); const { t } = useTranslation(); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [updateJob] = useMutation(UPDATE_JOB); @@ -34,16 +38,24 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) { const jobUpdate = await updateJob({ variables: { jobId: event.job.id, - job: { date_scheduled: null, scheduled_in: null, scheduled_completion: null, lost_sale_reason, + date_lost_sale: new Date(), status: bodyshop.md_ro_statuses.default_imported, }, }, }); + if (!jobUpdate.errors) { + dispatch( + insertAuditTrail({ + jobid: event.job.id, + operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), + }) + ); + } if (!!jobUpdate.errors) { notification["error"]({ message: t("jobs.errors.updating", { diff --git a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx index bfddbfefc..5ac31da74 100644 --- a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx +++ b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx @@ -13,6 +13,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { insertAuditTrail } from "../../redux/application/application.actions"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -53,7 +54,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { operation: AuditTrailMapping.admin_jobfieldchange( key, changedAuditFields[key] instanceof moment - ? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a") + ? DateTimeFormat(changedAuditFields[key]) : changedAuditFields[key] ), }); @@ -179,6 +180,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { + + + diff --git a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx index 05cd1b289..dcd6fd941 100644 --- a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx +++ b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx @@ -145,6 +145,13 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { + + + +
); diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 8531ddfe0..ae3ba9a7b 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -18,12 +18,14 @@ import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; @@ -50,6 +52,8 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setModalContext({ context: context, modal: "timeTicket" })), setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), }); export function JobsDetailHeaderActions({ @@ -64,6 +68,7 @@ export function JobsDetailHeaderActions({ jobRO, setTimeTicketContext, setCardPaymentContext, + insertAuditTrail, }) { const { t } = useTranslation(); const client = useApolloClient(); @@ -158,6 +163,7 @@ export function JobsDetailHeaderActions({ scheduled_in: null, scheduled_completion: null, lost_sale_reason, + date_lost_sale: new Date(), status: bodyshop.md_ro_statuses.default_imported, }, }, @@ -166,6 +172,11 @@ export function JobsDetailHeaderActions({ notification["success"]({ message: t("appointments.successes.canceled"), }); + insertAuditTrail({ + jobid: job.id, + operation: + AuditTrailMapping.appointmentcancel(lost_sale_reason), + }); return; } }} diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index c8b85be38..19c360a21 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -13,6 +13,7 @@ import { QUERY_APPOINTMENTS_BY_JOBID, } from "../../graphql/appointments.queries"; import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { setEmailOptions } from "../../redux/email/email.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectSchedule } from "../../redux/modals/modals.selectors"; @@ -20,6 +21,8 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import { DateTimeFormat } from "../../utils/DateFormatter"; import { TemplateList } from "../../utils/TemplateConstants"; import ScheduleJobModalComponent from "./schedule-job-modal.component"; @@ -31,6 +34,8 @@ const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({ toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), setEmailOptions: (e) => dispatch(setEmailOptions(e)), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), }); export function ScheduleJobModalContainer({ @@ -39,6 +44,7 @@ export function ScheduleJobModalContainer({ toggleModalVisible, setEmailOptions, currentUser, + insertAuditTrail, }) { const { visible, context, actions } = scheduleModal; const { jobId, job, previousEvent } = context; @@ -134,6 +140,15 @@ export function ScheduleJobModalContainer({ }, }); + if (!appt.errors) { + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.appointmentinsert( + DateTimeFormat(values.start) + ), + }); + } + if (!!appt.errors) { notification["error"]({ message: t("appointments.errors.saving", { @@ -155,6 +170,7 @@ export function ScheduleJobModalContainer({ scheduled_in: values.start, scheduled_completion: values.scheduled_completion, lost_sale_reason: null, + date_lost_sale: null, }, }, }); diff --git a/client/src/graphql/appointments.queries.js b/client/src/graphql/appointments.queries.js index fa2a3d7a0..9bef78daa 100644 --- a/client/src/graphql/appointments.queries.js +++ b/client/src/graphql/appointments.queries.js @@ -271,6 +271,7 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql` scheduled_completion status lost_sale_reason + date_lost_sale } } `; diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 21e82350b..b14e0e03b 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -675,6 +675,7 @@ export const GET_JOB_BY_PK = gql` date_scheduled date_invoiced date_last_contacted + date_lost_sale date_next_contact date_towin date_rentalresp @@ -1077,6 +1078,7 @@ export const UPDATE_JOB = gql` actual_in date_repairstarted date_void + date_lost_sale } } } diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index c98e8b1a9..001a18166 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -3,19 +3,19 @@ import Icon, { CalendarFilled, DollarCircleOutlined, FileImageFilled, - PrinterFilled, - ToolFilled, HistoryOutlined, + PrinterFilled, SyncOutlined, + ToolFilled, } from "@ant-design/icons"; import { Button, Divider, Form, - notification, PageHeader, Space, Tabs, + notification, } from "antd"; import Axios from "axios"; import moment from "moment"; @@ -27,6 +27,7 @@ import { connect } from "react-redux"; import { useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; +import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"; import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container"; @@ -42,17 +43,17 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component"; import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component"; import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; +import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; +import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import { insertAuditTrail } from "../../redux/application/application.actions"; -import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import UndefinedToNull from "../../utils/undefinedtonull"; -import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -172,7 +173,7 @@ export function JobsDetailPage({ operation: AuditTrailMapping.jobfieldchange( key, changedAuditFields[key] instanceof moment - ? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a") + ? DateTimeFormat(changedAuditFields[key]) : changedAuditFields[key] ), }); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 0695dce7b..6071d2695 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", "admin_jobunvoid": "ADMIN: Job has been unvoided.", + "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", + "appointmentinsert": "Appointment created. Appointment Date: {{start}}.", "billposted": "Bill with invoice number {{invoice_number}} posted.", "billupdated": "Bill with invoice number {{invoice_number}} updated.", "failedpayment": "Failed payment", @@ -1442,6 +1444,7 @@ "date_exported": "Exported", "date_invoiced": "Invoiced", "date_last_contacted": "Last Contacted Date", + "date_lost_sale": "Lost Sale Date", "date_next_contact": "Next Contact Date", "date_open": "Open", "date_rentalresp": "Shop Rental Responsibility Start", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index d04cb960e..1fa919eb4 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", "admin_jobunvoid": "", + "appointmentcancel": "", + "appointmentinsert": "", "billposted": "", "billupdated": "", "failedpayment": "", @@ -1442,6 +1444,7 @@ "date_exported": "Exportado", "date_invoiced": "Facturado", "date_last_contacted": "", + "date_lost_sale": "", "date_next_contact": "", "date_open": "Abierto", "date_rentalresp": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4931aed2b..340d64d04 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", "admin_jobunvoid": "", + "appointmentcancel": "", + "appointmentinsert": "", "billposted": "", "billupdated": "", "failedpayment": "", @@ -1442,6 +1444,7 @@ "date_exported": "Exportés", "date_invoiced": "Facturé", "date_last_contacted": "", + "date_lost_sale": "", "date_next_contact": "", "date_open": "Ouvrir", "date_rentalresp": "", diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index 47fdc4535..afa0de1e5 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -1,6 +1,10 @@ import i18n from "i18next"; const AuditTrailMapping = { + appointmentcancel: (lost_sale_reason) => + i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), + appointmentinsert: (start) => + i18n.t("audit_trail.messages.appointmentinsert", { start }), jobstatuschange: (status) => i18n.t("audit_trail.messages.jobstatuschange", { status }), admin_jobstatuschange: (status) => diff --git a/client/src/utils/DateFormatter.jsx b/client/src/utils/DateFormatter.jsx index 134095c78..d034266e3 100644 --- a/client/src/utils/DateFormatter.jsx +++ b/client/src/utils/DateFormatter.jsx @@ -31,3 +31,7 @@ export function TimeAgoFormatter(props) { ) : null; } + +export function DateTimeFormat(value) { + return moment(value).format("MM/DD/YYYY hh:mm A"); +} diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 773f384af..d94a97af0 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -890,11 +890,13 @@ - appt_colors - appt_length - attach_pdf_to_email + - autohouseid - bill_allow_post_to_closed - bill_tax_rates - cdk_configuration - cdk_dealerid - city + - claimscorpid - country - created_at - default_adjustment_rate @@ -928,6 +930,7 @@ - md_estimators - md_filehandlers - md_from_emails + - md_functionality_toggles - md_hour_split - md_ins_cos - md_jobline_presets @@ -1026,6 +1029,7 @@ - md_estimators - md_filehandlers - md_from_emails + - md_functionality_toggles - md_hour_split - md_ins_cos - md_jobline_presets @@ -3583,6 +3587,7 @@ - date_exported - date_invoiced - date_last_contacted + - date_lost_sale - date_next_contact - date_open - date_rentalresp @@ -3863,6 +3868,7 @@ - date_exported - date_invoiced - date_last_contacted + - date_lost_sale - date_next_contact - date_open - date_rentalresp diff --git a/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql new file mode 100644 index 000000000..1313da0c3 --- /dev/null +++ b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "date_lost_sale" timestamp with time zone +-- null; diff --git a/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql new file mode 100644 index 000000000..933f0acba --- /dev/null +++ b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "date_lost_sale" timestamp with time zone + null; diff --git a/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql new file mode 100644 index 000000000..32c37a9fe --- /dev/null +++ b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone; diff --git a/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql new file mode 100644 index 000000000..32c37a9fe --- /dev/null +++ b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone; From 74da3ec1ca5e1e1f0a2e2e9a9101eb276664623c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 22 Nov 2023 17:51:06 -0800 Subject: [PATCH 07/27] IO-2214 Add new Lost Sales report to Report Center --- client/src/translations/en_us/common.json | 3 ++- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 12 ++++++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 6071d2695..03362c86c 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1444,7 +1444,7 @@ "date_exported": "Exported", "date_invoiced": "Invoiced", "date_last_contacted": "Last Contacted Date", - "date_lost_sale": "Lost Sale Date", + "date_lost_sale": "Lost Sale", "date_next_contact": "Next Contact Date", "date_open": "Open", "date_rentalresp": "Shop Rental Responsibility Start", @@ -2599,6 +2599,7 @@ "jobs_reconcile": "Parts/Sublet/Labor Reconciliation", "jobs_scheduled_completion": "Jobs Scheduled Completion", "lag_time": "Lag Time", + "lost_sales": "Lost Sales", "open_orders": "Open Orders by Date", "open_orders_csr": "Open Orders by CSR", "open_orders_estimator": "Open Orders by Estimator", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 1fa919eb4..02fd16938 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2599,6 +2599,7 @@ "jobs_reconcile": "", "jobs_scheduled_completion": "", "lag_time": "", + "lost_sales": "", "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 340d64d04..3fbb38bfa 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2599,6 +2599,7 @@ "jobs_reconcile": "", "jobs_scheduled_completion": "", "lag_time": "", + "lost_sales": "", "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 79d9aa353..c5e10d712 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2014,6 +2014,18 @@ export const TemplateList = (type, context) => { }, group: "jobs", }, + lost_sales: { + title: i18n.t("reportcenter.templates.lost_sales"), + subject: i18n.t("reportcenter.templates.lost_sales"), + key: "lost_sales", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_lost_sale"), + }, + group: "customers", + }, } : {}), ...(!type || type === "courtesycarcontract" From 65f960db0045109438f2777ee7e2eff28504db36 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 23 Nov 2023 16:58:56 -0500 Subject: [PATCH 08/27] 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 7dcdd64a176c5b956051b4d7d8f43d34a7992834 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 23 Nov 2023 19:11:09 -0800 Subject: [PATCH 09/27] IO-2438 Adjust Start & End dates for timetickets --- .../scoreboard-timetickets.component.jsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx index d2ced5334..c0ad5d1fc 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -31,12 +31,8 @@ export default connect( export function ScoreboardTimeTicketsStats({ bodyshop }) { const { t } = useTranslation(); - const searchParams = queryString.parse(useLocation().search); - const { start, end } = searchParams; - const startDate = start - ? moment(start) - : moment().startOf("week").subtract(7, "days"); - const endDate = end ? moment(end) : moment().endOf("week"); + const startDate = moment().startOf("month") + const endDate = moment().endOf("month"); const fixedPeriods = useMemo(() => { const endOfThisMonth = moment().endOf("month"); From 07119e4e7e7f6df617df27ae0aaada54984930df Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 23 Nov 2023 19:14:00 -0800 Subject: [PATCH 10/27] IO-2438 Remove unneeded imports --- .../scoreboard-timetickets.component.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx index c0ad5d1fc..0117279d5 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -2,11 +2,9 @@ import { useQuery } from "@apollo/client"; import { Col, Row } from "antd"; import _ from "lodash"; import moment from "moment"; -import queryString from "query-string"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; -import { useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; From c5bed4f36df50afcbd5eadd2e9588ea3aab6e8be Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:32:59 -0500 Subject: [PATCH 11/27] 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 12/27] 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 59075ee61001425f22874ae78629a32c82d9fac9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:43:55 -0500 Subject: [PATCH 13/27] Update translations, move configuration toggle for autopartsqueue --- .../shop-info/shop-info.general.component.jsx | 14 +++++++------- client/src/translations/en_us/common.json | 2 +- client/src/translations/es/common.json | 2 +- client/src/translations/fr/common.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 73861abc4..5f7d31c03 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -49,13 +49,6 @@ export function ShopInfoGeneral({ form, bodyshop }) { header={t("bodyshop.labels.businessinformation")} id="businessinformation" > - - -
{ + 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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} /> ); From 34d93c4de0476242762708d8a8104894ed16b3d9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 4 Dec 2023 11:39:20 -0500 Subject: [PATCH 27/27] Reversion --- .../jobs-list/jobs-list.component.jsx | 248 ++++++++---------- 1 file changed, 105 insertions(+), 143 deletions(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index b7bd3141d..fb2e1daa6 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,120 +1,105 @@ -import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; -import {useQuery} from "@apollo/client"; -import {Button, Card, Grid, Input, Space, Table, Tooltip, Typography} from "antd"; +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 queryString from "query-string"; -import React, {useEffect, 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 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 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"; -import {flattenDeep} from "lodash"; -import { pageLimit } from '../../utils/config'; -import axios from "axios"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); -const mapDispatchToProps = () => ({ -}); - -export function JobsList({bodyshop,}) { - const search = queryString.parse(useLocation().search); - const [openSearchResults, setOpenSearchResults] = useState([]); - const [searchLoading, setSearchLoading] = useState(false); - const {page, selected, sortorder, sortcolumn} = search; - +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_PAGINATED, { + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, 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", }); - const total = data?.jobs_aggregate?.aggregate?.count || 0; - const [state, setState] = useState({ - filteredInfo: {text: ""}, + sortedInfo: {}, + filteredInfo: { text: "" }, }); - const {t} = useTranslation(); + const { t } = useTranslation(); const history = useHistory(); + const [searchText, setSearchText] = useState(""); - const jobs = data?.jobs || []; + if (error) return ; + + 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 handleTableChange = (pagination, filters, sorter) => { - // TODO: Is this needed? - setState({...state, filteredInfo: filters}); - - search.page = pagination.current; - search.sortcolumn = sorter.column && sorter.column.key; - search.sortorder = sorter.order; - - if (filters.status) { - search.statusFilters = JSON.stringify(flattenDeep(filters.status)); - } else { - delete search.statusFilters; - } - - history.push({search: queryString.stringify(search)}); + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); }; - useEffect(() => { - if (search.search && search.search.trim() !== "") { - 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 - }, []); - - 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({ - ...search, + ...searchParams, selected: record.id, }), }); @@ -127,9 +112,11 @@ export function JobsList({bodyshop,}) { title: t("jobs.fields.ro_number"), dataIndex: "ro_number", key: "ro_number", - sorter: true, + sorter: (a, b) => + parseInt((a.ro_number || "0").replace(/\D/g, "")) - + parseInt((b.ro_number || "0").replace(/\D/g, "")), sortOrder: - sortcolumn === "ro_number" && sortorder, + state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => ( {record.ro_number || t("general.labels.na")} {record.production_vars && record.production_vars.alert ? ( - + ) : null} {record.suspended && ( - + )} {record.iouparent && ( - + )} @@ -158,20 +145,22 @@ export function JobsList({bodyshop,}) { dataIndex: "owner", key: "owner", ellipsis: true, + responsive: ["md"], - sorter: false, - sortOrder: sortcolumn === "owner" && sortorder, + 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()} > - + ) : ( - + ); }, @@ -183,7 +172,7 @@ export function JobsList({bodyshop,}) { ellipsis: true, responsive: ["md"], render: (text, record) => ( - + ), }, { @@ -193,7 +182,7 @@ export function JobsList({bodyshop,}) { ellipsis: true, responsive: ["md"], render: (text, record) => ( - + ), }, @@ -202,9 +191,10 @@ export function JobsList({bodyshop,}) { dataIndex: "status", key: "status", ellipsis: true, - sorter: true, + + sorter: (a, b) => alphaSort(a.status, b.status), sortOrder: - sortcolumn === "status" && sortorder, + state.sortedInfo.columnKey === "status" && state.sortedInfo.order, filters: (jobs && jobs @@ -219,6 +209,7 @@ export function JobsList({bodyshop,}) { [], onFilter: (value, record) => value.includes(record.status), }, + { title: t("jobs.fields.vehicle"), dataIndex: "vehicle", @@ -246,10 +237,11 @@ export function JobsList({bodyshop,}) { dataIndex: "plate_no", key: "plate_no", ellipsis: true, + responsive: ["md"], - sorter: true, + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), sortOrder: - sortcolumn === "plate_no" && sortorder, + state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, }, { title: t("jobs.fields.clm_no"), @@ -257,9 +249,9 @@ export function JobsList({bodyshop,}) { key: "clm_no", ellipsis: true, responsive: ["md"], - sorter: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), sortOrder: - sortcolumn === "clm_no" && sortorder, + state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, render: (text, record) => `${record.clm_no || ""}${ record.po_number ? ` (PO: ${record.po_number})` : "" @@ -291,9 +283,10 @@ export function JobsList({bodyshop,}) { key: "clm_total", responsive: ["md"], ellipsis: true, - sorter: true, + + sorter: (a, b) => a.clm_total - b.clm_total, sortOrder: - sortcolumn === "clm_total" && sortorder, + state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, render: (text, record) => ( {record.clm_total} ), @@ -356,57 +349,26 @@ export function JobsList({bodyshop,}) { title={t("titles.bc.jobs-active")} extra={ - {search.search && ( - <> - - {t("general.labels.searchresults", { search: search.search })} - - - - )} { - search.search = value; - history.push({ search: queryString.stringify(search) }); - searchJobs(value); + placeholder={t("general.labels.search")} + onChange={(e) => { + setSearchText(e.target.value); }} - loading={loading || searchLoading} + value={searchText} enterButton /> } >
{ + onRow={(record, rowIndex) => { return { - onClick: () => { + onClick: (event) => { handleOnRowClick(record); }, }; @@ -430,4 +392,4 @@ export function JobsList({bodyshop,}) { ); } -export default connect(mapStateToProps, mapDispatchToProps)(JobsList); +export default connect(mapStateToProps, null)(JobsList);