From 8670f386dcc32c6f40b3bbf0c6765f4dc7b51bd8 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 22 Nov 2023 17:14:52 -0500 Subject: [PATCH 1/6] refactors --- libs/awsUtils.js | 56 +++++++++++++++++++++++++ os-loader.js | 74 +++++++-------------------------- server/opensearch/os-handler.js | 62 +++++++-------------------- 3 files changed, 86 insertions(+), 106 deletions(-) create mode 100644 libs/awsUtils.js diff --git a/libs/awsUtils.js b/libs/awsUtils.js new file mode 100644 index 000000000..4f7ee9aa4 --- /dev/null +++ b/libs/awsUtils.js @@ -0,0 +1,56 @@ +require("dotenv").config({ + path: require("path").resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); +const {isNil} = require('lodash'); +const aws4 = require("aws4"); +const {Connection, Client} = require("@opensearch-project/opensearch"); +const {defaultProvider} = require("@aws-sdk/credential-provider-node"); + +const createAwsConnector = (credentials, region) => { + class AmazonConnection extends Connection { + buildRequestObject(params) { + const request = super.buildRequestObject(params); + request.service = "es"; + request.region = region; + request.headers = request.headers || {}; + request.headers["host"] = request.hostname; + + return aws4.sign(request, credentials); + } + } + return { + Connection: AmazonConnection, + }; +}; + +const getClient = async () => { + + // We have manual configuration for OpenSearch, + // Return a client using these custom credentials + if ( + !isNil(process.env.OPEN_SEARCH_PASSWORD) && + !isNil(process.env.OPEN_SEARCH_USER) && + !isNil(process.env.OPEN_SEARCH_HOST) && + !isNil(process.env.OPEN_SEARCH_PROTOCOL) + ) { + // The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it. + const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, ''); + const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`; + + return new Client({ + node, + }); + } + + // Default to the AWS Credentials Provider. + const credentials = await defaultProvider()(); + return new Client({ + ...createAwsConnector(credentials, "ca-central-1"), + node: process.env.OPEN_SEARCH_HOST, + }); +}; + +module.exports = { getClient }; \ No newline at end of file diff --git a/os-loader.js b/os-loader.js index a7876187a..bdabb87ac 100644 --- a/os-loader.js +++ b/os-loader.js @@ -1,59 +1,17 @@ -const Dinero = require("dinero.js"); - -//const client = require("../graphql-client/graphql-client").client; -const _ = require("lodash"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("./server/utils/logger"); - -const path = require("path"); -const client = require("./server/graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` + path: require("path").resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` ), }); -const { Client, Connection } = require("@opensearch-project/opensearch"); -const { defaultProvider } = require("@aws-sdk/credential-provider-node"); -const aws4 = require("aws4"); -const { gql } = require("graphql-request"); -const gqlclient = require("./server/graphql-client/graphql-client").client; -// const osClient = new Client({ -// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`, -// }); -var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com -const createAwsConnector = (credentials, region) => { - class AmazonConnection extends Connection { - buildRequestObject(params) { - const request = super.buildRequestObject(params); - request.service = "es"; - request.region = region; - request.headers = request.headers || {}; - request.headers["host"] = request.hostname; - - return aws4.sign(request, credentials); - } - } - return { - Connection: AmazonConnection, - }; -}; - -const getClient = async () => { - const credentials = await defaultProvider()(); - return new Client({ - ...createAwsConnector(credentials, "ca-central-1"), - node: host, - }); -}; +const {omit} = require("lodash"); +const gqlClient = require("./server/graphql-client/graphql-client").client; +const getClient = require('./libs/awsUtils'); async function OpenSearchUpdateHandler(req, res) { try { - var osClient = await getClient(); - // const osClient = new Client({ - // node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`, - // }); + const osClient = await getClient(); //Clear out all current documents // const deleteResult = await osClient.deleteByQuery({ @@ -67,11 +25,11 @@ async function OpenSearchUpdateHandler(req, res) { // return; - var batchSize = 1000; - var promiseQueue = []; + const batchSize = 1000; + const promiseQueue = []; //Jobs Load. - const jobsData = await gqlclient.request(`query{jobs{ + const jobsData = await gqlClient.request(`query{jobs{ id bodyshopid:shopid clm_no @@ -105,7 +63,7 @@ async function OpenSearchUpdateHandler(req, res) { } //Owner Load - const ownersData = await gqlclient.request(`{ + const ownersData = await gqlClient.request(`{ owners { id bodyshopid: shopid @@ -131,7 +89,7 @@ async function OpenSearchUpdateHandler(req, res) { } //Vehicles - const vehiclesData = await gqlclient.request(`{ + const vehiclesData = await gqlClient.request(`{ vehicles { id bodyshopid: shopid @@ -158,7 +116,7 @@ async function OpenSearchUpdateHandler(req, res) { } //payments - const paymentsData = await gqlclient.request(`{ + const paymentsData = await gqlClient.request(`{ payments { id amount @@ -198,7 +156,7 @@ async function OpenSearchUpdateHandler(req, res) { slicedArray.forEach((payment) => { bulkOperation.push({ index: { _index: "payments", _id: payment.id } }); bulkOperation.push({ - ..._.omit(payment, ["job"]), + ...omit(payment, ["job"]), bodyshopid: payment.job.bodyshopid, }); }); @@ -206,7 +164,7 @@ async function OpenSearchUpdateHandler(req, res) { } //bills - const billsData = await gqlclient.request(`{ + const billsData = await gqlClient.request(`{ bills { id date @@ -235,7 +193,7 @@ async function OpenSearchUpdateHandler(req, res) { slicedArray.forEach((bill) => { bulkOperation.push({ index: { _index: "bills", _id: bill.id } }); bulkOperation.push({ - ..._.omit(bill, ["job"]), + ...omit(bill, ["job"]), bodyshopid: bill.job.bodyshopid, }); }); diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index b6e5fc490..7cb544400 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -1,49 +1,18 @@ -const queries = require("../graphql-client/queries"); -const {pick} = require("lodash"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("../utils/logger"); -//const client = require("../graphql-client/graphql-client").client; - -const path = require("path"); -const client = require("../graphql-client/graphql-client").client; require("dotenv").config({ - path: path.resolve( + path: require("path").resolve( process.cwd(), `.env.${process.env.NODE_ENV || "development"}` ), }); -const {Client, Connection} = require("@opensearch-project/opensearch"); -const {defaultProvider} = require("@aws-sdk/credential-provider-node"); -const aws4 = require("aws4"); -const {gql} = require("graphql-request"); -var host = process.env.OPEN_SEARCH_HOST; +const GraphQLClient = require("graphql-request").GraphQLClient; +//const client = require("../graphql-client/graphql-client").client; +const logger = require("../utils/logger"); +const queries = require("../graphql-client/queries"); +const client = require("../graphql-client/graphql-client").client; +const {pick, isNil} = require("lodash"); +const {getClient} = require('../../libs/awsUtils'); -const createAwsConnector = (credentials, region) => { - class AmazonConnection extends Connection { - buildRequestObject(params) { - const request = super.buildRequestObject(params); - request.service = "es"; - request.region = region; - request.headers = request.headers || {}; - request.headers["host"] = request.hostname; - - return aws4.sign(request, credentials); - } - } - - return { - Connection: AmazonConnection, - }; -}; - -const getClient = async () => { - const credentials = await defaultProvider()(); - return new Client({ - ...createAwsConnector(credentials, "ca-central-1"), - node: host, - }); -}; async function OpenSearchUpdateHandler(req, res) { if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { @@ -51,10 +20,8 @@ async function OpenSearchUpdateHandler(req, res) { return; } try { - var osClient = await getClient(); - // const osClient = new Client({ - // node: `https://imex:@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`, - // }); + + const osClient = await getClient(); if (req.body.event.op === "DELETE") { let response; @@ -197,14 +164,12 @@ async function OpenSearchUpdateHandler(req, res) { body: document, }; - let response; - response = await osClient.index(payload); + const response = await osClient.index(payload); console.log(response.body); res.status(200).json(response.body); } } catch (error) { res.status(400).json(JSON.stringify(error)); - } finally { } } @@ -240,6 +205,8 @@ async function OpenSearchSearchHandler(req, res) { const osClient = await getClient(); + const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE) ? assocs.associations[0].shopid : process.env.BODY_SHOP_ID_MATCH_OVERRIDE + const {body} = await osClient.search({ ...(index ? {index} : {}), body: { @@ -249,7 +216,7 @@ async function OpenSearchSearchHandler(req, res) { must: [ { match: { - bodyshopid: assocs.associations[0].shopid, + bodyshopid: bodyShopIdMatchOverride, }, }, { @@ -318,7 +285,6 @@ async function OpenSearchSearchHandler(req, res) { error: JSON.stringify(error), }); res.status(400).json(error); - } finally { } } From 65f960db0045109438f2777ee7e2eff28504db36 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 23 Nov 2023 16:58:56 -0500 Subject: [PATCH 2/6] Add Margin around the Messaging Icon --- client/src/components/chat-popup/chat-popup.component.jsx | 2 +- client/src/components/chat-popup/chat-popup.styles.scss | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 6d582bdab..57ef96e4c 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -132,7 +132,7 @@ export function ChatPopupComponent({ onClick={() => toggleChatVisible()} style={{ cursor: "pointer" }} > - + {t("messaging.labels.messaging")} )} diff --git a/client/src/components/chat-popup/chat-popup.styles.scss b/client/src/components/chat-popup/chat-popup.styles.scss index 398762700..be9a5c0a7 100644 --- a/client/src/components/chat-popup/chat-popup.styles.scss +++ b/client/src/components/chat-popup/chat-popup.styles.scss @@ -13,6 +13,9 @@ height: 100%; } } +.chat-popup-info-icon { + margin-right: 5px; +} @media only screen and (min-width: 992px) { .chat-popup { From c5bed4f36df50afcbd5eadd2e9588ea3aab6e8be Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:32:59 -0500 Subject: [PATCH 3/6] 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 4/6] 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 d7f52d864a3bc7032736c28fcb626da715350fd6 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 28 Nov 2023 16:05:23 -0500 Subject: [PATCH 5/6] Add Global search to Active Jobs. --- .../jobs-list/jobs-list.component.jsx | 101 +++++++++++++----- client/src/utils/config.js | 1 + 2 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 client/src/utils/config.js diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index b05c32437..ac5eb34aa 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,8 +1,8 @@ import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons"; import {useQuery} from "@apollo/client"; -import {Button, Card, Grid, Input, Space, Table, Tooltip} from "antd"; +import {Button, Card, Grid, Input, Space, Table, Tooltip, Typography} from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; import {Link, useHistory, useLocation} from "react-router-dom"; @@ -15,22 +15,27 @@ import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; +import { pageLimit } from '../../utils/config'; +import axios from "axios"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); +const mapDispatchToProps = () => ({ +}); + export function JobsList({bodyshop,}) { - const pageLimit = 10; - const searchParams = queryString.parse(useLocation().search); - const {page, selected, sortorder, sortcolumn} = searchParams; - console.dir({page, selected, sortorder, sortcolumn}); + const search = queryString.parse(useLocation().search); + const [openSearchResults, setOpenSearchResults] = useState([]); + const [searchLoading, setSearchLoading] = useState(false); + const {page, selected, sortorder, sortcolumn} = search; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { - variables: { offset: page ? (page - 1) * pageLimit : 0, limit: 10, @@ -58,34 +63,56 @@ export function JobsList({bodyshop,}) { const {t} = useTranslation(); const history = useHistory(); - const [searchText, setSearchText] = useState(""); - - if (error) return ; const jobs = data?.jobs || []; const handleTableChange = (pagination, filters, sorter) => { + // TODO: Is this needed? setState({...state, filteredInfo: filters}); - searchParams.page = pagination.current; - searchParams.sortcolumn = sorter.column && sorter.column.key; - searchParams.sortorder = sorter.order; + search.page = pagination.current; + search.sortcolumn = sorter.column && sorter.column.key; + search.sortorder = sorter.order; if (filters.status) { - searchParams.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); + search.statusFilters = JSON.stringify(_.flattenDeep(filters.status)); } else { - delete searchParams.statusFilters; + delete search.statusFilters; } - history.push({search: queryString.stringify(searchParams)}); + history.push({search: queryString.stringify(search)}); }; + useEffect(() => { + if (search.search && search.search.trim() !== "") { + searchJobs(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (error) return ; + + async function searchJobs(value) { + try { + setSearchLoading(true); + const searchData = await axios.post("/search", { + search: value || search.search, + index: "jobs", + }); + setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); + } catch (error) { + console.log("Error while fetching search results", error); + } finally { + setSearchLoading(false); + } + } + const handleOnRowClick = (record) => { if (record) { if (record.id) { history.push({ search: queryString.stringify({ - ...searchParams, + ...search, selected: record.id, }), }); @@ -328,24 +355,42 @@ export function JobsList({bodyshop,}) { title={t("titles.bc.jobs-active")} extra={ + {search.search && ( + <> + + {t("general.labels.searchresults", { search: search.search })} + + + + )} { - setSearchText(e.target.value); + placeholder={search.search || t("general.labels.search")} + onSearch={(value) => { + search.search = value; + history.push({ search: queryString.stringify(search) }); + searchJobs(value); }} - value={searchText} + loading={loading || searchLoading} enterButton /> } >
{ + onRow={(record) => { return { - onClick: (event) => { + onClick: () => { handleOnRowClick(record); }, }; @@ -384,4 +429,4 @@ export function JobsList({bodyshop,}) { ); } -export default connect(mapStateToProps, null)(JobsList); +export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/utils/config.js b/client/src/utils/config.js new file mode 100644 index 000000000..784074f44 --- /dev/null +++ b/client/src/utils/config.js @@ -0,0 +1 @@ +export const pageLimit = 10; From ec2519eae4aaefe1d5482636a59142f84d49b809 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 29 Nov 2023 13:37:02 -0500 Subject: [PATCH 6/6] 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;