From ee8cbe33c45adf9d6e6b03a0851be8043c627389 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 4 Dec 2023 14:28:44 -0500 Subject: [PATCH] Pagination Fix --- .../jobs-list/jobs-list.component.jsx | 327 ++++++++++-------- client/src/graphql/jobs.queries.js | 6 +- client/src/utils/config.js | 2 +- 3 files changed, 183 insertions(+), 152 deletions(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index fb2e1daa6..63e00ecb2 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,105 +1,117 @@ -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, Typography} 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, {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 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, }); -export function JobsList({ bodyshop }) { - const searchParams = queryString.parse(useLocation().search); - const { selected } = searchParams; +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,statusFilters} = search; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; - const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + + const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + statusFilters: statusFilters ? JSON.parse(statusFilters) : 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({ - sortedInfo: {}, - filteredInfo: { text: "" }, + filteredInfo: {text: ""}, }); - const { t } = useTranslation(); + const {t} = useTranslation(); const history = useHistory(); - const [searchText, setSearchText] = useState(""); - 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 jobs = data?.jobs || []; const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + 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)}); }; + 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({ - ...searchParams, + ...search, selected: record.id, }), }); @@ -112,11 +124,9 @@ export function JobsList({ bodyshop }) { 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, "")), + sorter: true, sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + sortcolumn === "ro_number" && sortorder, render: (text, record) => ( {record.ro_number || t("general.labels.na")} {record.production_vars && record.production_vars.alert ? ( - + ) : null} {record.suspended && ( - + )} {record.iouparent && ( - + )} @@ -145,22 +155,20 @@ export function JobsList({ bodyshop }) { dataIndex: "owner", key: "owner", ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + sorter: false, + sortOrder: sortcolumn === "owner" && sortorder, render: (text, record) => { return record.ownerid ? ( e.stopPropagation()} > - + ) : ( - + ); }, @@ -172,7 +180,7 @@ export function JobsList({ bodyshop }) { ellipsis: true, responsive: ["md"], render: (text, record) => ( - + ), }, { @@ -182,7 +190,7 @@ export function JobsList({ bodyshop }) { ellipsis: true, responsive: ["md"], render: (text, record) => ( - + ), }, @@ -191,25 +199,17 @@ export function JobsList({ bodyshop }) { dataIndex: "status", key: "status", ellipsis: true, - - sorter: (a, b) => alphaSort(a.status, b.status), + sorter: true, 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], - }; - })) || - [], + sortcolumn === "status" && sortorder, + filters:bodyshop.md_ro_statuses.statuses.map((s) => { + return { text: s, value: [s] }; + }), onFilter: (value, record) => value.includes(record.status), + render: (text, record) => { + return record.status || t("general.labels.na"); + }, }, - { title: t("jobs.fields.vehicle"), dataIndex: "vehicle", @@ -237,11 +237,10 @@ export function JobsList({ bodyshop }) { dataIndex: "plate_no", key: "plate_no", ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sorter: true, sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + sortcolumn === "plate_no" && sortorder, }, { title: t("jobs.fields.clm_no"), @@ -249,9 +248,9 @@ export function JobsList({ bodyshop }) { key: "clm_no", ellipsis: true, responsive: ["md"], - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sorter: true, sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + sortcolumn === "clm_no" && sortorder, render: (text, record) => `${record.clm_no || ""}${ record.po_number ? ` (PO: ${record.po_number})` : "" @@ -262,19 +261,20 @@ export function JobsList({ bodyshop }) { 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), + // TODO: Restore Filters? + // 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"], }, { @@ -283,10 +283,9 @@ export function JobsList({ bodyshop }) { key: "clm_total", responsive: ["md"], ellipsis: true, - - sorter: (a, b) => a.clm_total - b.clm_total, + sorter: true, sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + sortcolumn === "clm_total" && sortorder, render: (text, record) => ( {record.clm_total} ), @@ -297,23 +296,24 @@ export function JobsList({ bodyshop }) { 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() - ), + // TODO Restore Filters? + // 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(), }, @@ -349,26 +349,57 @@ 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); }, }; @@ -392,4 +423,4 @@ export function JobsList({ bodyshop }) { ); } -export default connect(mapStateToProps, null)(JobsList); +export default connect(mapStateToProps, mapDispatchToProps)(JobsList); diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index b18673de6..2bef6cdb5 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -5,13 +5,13 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` $offset: Int $limit: Int $order: [jobs_order_by!] - $statuses: [String!]!, + $statusFilters: [String!]!, $isConverted: Boolean ) { jobs( offset: $offset limit: $limit - where: { status: { _in: $statuses }, converted: { _eq: $isConverted } } + where: { status: { _in: $statusFilters }, converted: { _eq: $isConverted } } order_by: $order ) { iouparent @@ -52,7 +52,7 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` est_ct_fn est_ct_ln } - jobs_aggregate(where: { status: { _in: $statuses } }) { + jobs_aggregate(where: { status: { _in: $statusFilters } }) { aggregate { count(distinct: true) } diff --git a/client/src/utils/config.js b/client/src/utils/config.js index 2a968907c..dc9550980 100644 --- a/client/src/utils/config.js +++ b/client/src/utils/config.js @@ -1,4 +1,4 @@ // Sometimes referred to as PageSize, this variable controls the amount of records // to show on one page during pagination. -export const pageLimit = 50; +export const pageLimit = 10;