From 84e31cc939705e25ca372e3177829b87ddca8bcd Mon Sep 17 00:00:00 2001 From: Christiano Higuto Date: Thu, 7 Dec 2023 10:54:07 -0300 Subject: [PATCH 1/2] feat: add query builder to use table --- packages/react-material-ui/package.json | 1 + .../Table/TableBody/TableBodyRows.tsx | 4 +- .../Table/TableHeader/TableHeaderCell.tsx | 4 +- .../src/components/Table/TablePagination.tsx | 4 +- .../Table/TablePaginationNumbers.tsx | 4 +- .../src/components/Table/TableRoot.tsx | 40 ++- .../src/components/Table/TableRowSkeleton.tsx | 4 +- .../Table/hooks/useTableQueryState.ts | 51 ---- .../components/Table/hooks/useTableRoot.tsx | 4 +- .../components/Table/hooks/useTableState.ts | 47 +++ .../src/components/Table/types.ts | 6 +- .../src/components/Table/useTable.ts | 287 +++++++++++------- yarn.lock | 28 +- 13 files changed, 278 insertions(+), 206 deletions(-) delete mode 100644 packages/react-material-ui/src/components/Table/hooks/useTableQueryState.ts create mode 100644 packages/react-material-ui/src/components/Table/hooks/useTableState.ts diff --git a/packages/react-material-ui/package.json b/packages/react-material-ui/package.json index f25a0254..4b30bc58 100644 --- a/packages/react-material-ui/package.json +++ b/packages/react-material-ui/package.json @@ -30,6 +30,7 @@ "@mui/icons-material": "^5.10.6", "@mui/material": "^5.10.6", "@mui/utils": "5.9.3", + "@nestjsx/crud-request": "^5.0.0-alpha.3", "@rjsf/core": "^5.0.0-beta.13", "@rjsf/mui": "^5.0.0-beta.13", "@rjsf/utils": "^5.0.0-beta.13", diff --git a/packages/react-material-ui/src/components/Table/TableBody/TableBodyRows.tsx b/packages/react-material-ui/src/components/Table/TableBody/TableBodyRows.tsx index 57378111..74b6737f 100644 --- a/packages/react-material-ui/src/components/Table/TableBody/TableBodyRows.tsx +++ b/packages/react-material-ui/src/components/Table/TableBody/TableBodyRows.tsx @@ -76,8 +76,8 @@ export const TableBodyRows = ({ renderRow, isLoading = false, }: TableBodyRowProps) => { - const { rows, tableQuery, isControlled } = useTableRoot(); - const { page, rowsPerPage, order, orderBy } = tableQuery; + const { rows, tableState, isControlled } = useTableRoot(); + const { page, rowsPerPage, order, orderBy } = tableState; if (isLoading) { return ; diff --git a/packages/react-material-ui/src/components/Table/TableHeader/TableHeaderCell.tsx b/packages/react-material-ui/src/components/Table/TableHeader/TableHeaderCell.tsx index bc8faae7..4766af96 100644 --- a/packages/react-material-ui/src/components/Table/TableHeader/TableHeaderCell.tsx +++ b/packages/react-material-ui/src/components/Table/TableHeader/TableHeaderCell.tsx @@ -21,8 +21,8 @@ type TableHeaderCellProps = { * @returns A React JSX element representing the header cell. */ export const TableHeaderCell = ({ cell, ...rest }: TableHeaderCellProps) => { - const { tableQuery, handleSort } = useTableRoot(); - const { order, orderBy } = tableQuery; + const { tableState, handleSort } = useTableRoot(); + const { order, orderBy } = tableState; /** * Creates a sort handler function for a specific table column. diff --git a/packages/react-material-ui/src/components/Table/TablePagination.tsx b/packages/react-material-ui/src/components/Table/TablePagination.tsx index ba2c48ea..b356d3f9 100644 --- a/packages/react-material-ui/src/components/Table/TablePagination.tsx +++ b/packages/react-material-ui/src/components/Table/TablePagination.tsx @@ -27,10 +27,10 @@ export const TablePagination = ({ ...rest }: TablePaginationProps) => { const theme = useTheme(); - const { rows, tableQuery, total, handleChangePage, handleChangeRowsPerPage } = + const { rows, tableState, total, handleChangePage, handleChangeRowsPerPage } = useTableRoot(); - const { rowsPerPage, page } = tableQuery; + const { rowsPerPage, page } = tableState; return ( { - const { rows, pageCount, handleChangePage, tableQuery } = useTableRoot(); + const { rows, pageCount, handleChangePage, tableState } = useTableRoot(); - const { page } = tableQuery; + const { page } = tableState; return ( diff --git a/packages/react-material-ui/src/components/Table/TableRoot.tsx b/packages/react-material-ui/src/components/Table/TableRoot.tsx index 0b7c0033..46f4afc0 100644 --- a/packages/react-material-ui/src/components/Table/TableRoot.tsx +++ b/packages/react-material-ui/src/components/Table/TableRoot.tsx @@ -3,8 +3,8 @@ import React, { PropsWithChildren, useState } from 'react'; import { Box, BoxProps } from '@mui/material'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { HeaderProps, Order, RowProps, TableQueryStateProps } from './types'; -import { useTableQueryState } from './hooks/useTableQueryState'; +import { HeaderProps, Order, RowProps, TableStateProps } from './types'; +import { useTableState } from './hooks/useTableState'; import { TableContext } from './hooks/useTableRoot'; import { getSearchParams } from '../../utils/http'; @@ -14,18 +14,16 @@ type TableRootProps = headers: HeaderProps[]; total?: number; pageCount?: never; - tableQueryState?: never; - updateTableQueryState?: never; + tableState?: never; + updateTableState?: never; } | { rows: RowProps[]; headers: HeaderProps[]; total: number; pageCount: number; - tableQueryState: TableQueryStateProps; - updateTableQueryState: React.Dispatch< - React.SetStateAction - >; + tableState: TableStateProps; + updateTableState: React.Dispatch>; }; /** @@ -40,25 +38,23 @@ export const TableRoot = ({ headers = [], total, pageCount, - tableQueryState: controlledTableQueryState, - updateTableQueryState: controlledUpdateTableQueryState, + tableState: controlledTableState, + updateTableState: controlledUpdateTableState, ...rest }: PropsWithChildren) => { const searchParams = useSearchParams(); const pathname = usePathname(); const router = useRouter(); - const { tableQueryState, setTableQueryState } = useTableQueryState(); + const { tableState, setTableState } = useTableState(); const [selected, setSelected] = useState([]); - const isControlled = !!controlledTableQueryState; - const handleUpdateTableQuery = isControlled - ? controlledUpdateTableQueryState - : setTableQueryState; + const isControlled = !!controlledTableState; + const handleUpdateTableState = isControlled + ? controlledUpdateTableState + : setTableState; - const { order, orderBy } = isControlled - ? controlledTableQueryState - : tableQueryState; + const { order, orderBy } = isControlled ? controlledTableState : tableState; /** * Handles the change of the number of rows displayed per page in the table. @@ -73,7 +69,7 @@ export const TableRoot = ({ page: 1, }; - handleUpdateTableQuery((prevState) => ({ + handleUpdateTableState((prevState) => ({ ...prevState, ...newRowsPerPageProperties, })); @@ -152,7 +148,7 @@ export const TableRoot = ({ page: newPage, }; - handleUpdateTableQuery((prevState) => ({ + handleUpdateTableState((prevState) => ({ ...prevState, ...newPageProperty, })); @@ -178,7 +174,7 @@ export const TableRoot = ({ orderBy: property, }; - handleUpdateTableQuery((prevState) => ({ + handleUpdateTableState((prevState) => ({ ...prevState, ...newOrderProperties, })); @@ -199,7 +195,7 @@ export const TableRoot = ({ total, pageCount, isControlled, - tableQuery: isControlled ? controlledTableQueryState : tableQueryState, + tableState: isControlled ? controlledTableState : tableState, selected, isSelected, handleChangePage, diff --git a/packages/react-material-ui/src/components/Table/TableRowSkeleton.tsx b/packages/react-material-ui/src/components/Table/TableRowSkeleton.tsx index f2cb0c41..b6c50e5d 100644 --- a/packages/react-material-ui/src/components/Table/TableRowSkeleton.tsx +++ b/packages/react-material-ui/src/components/Table/TableRowSkeleton.tsx @@ -12,10 +12,10 @@ import { useTableRoot } from './hooks/useTableRoot'; * @returns A React element representing the table row skeleton. */ export const TableRowSkeleton = () => { - const { tableQuery } = useTableRoot(); + const { tableState } = useTableRoot(); const rowsToRender = Array.from( - { length: tableQuery.rowsPerPage }, + { length: tableState.rowsPerPage }, (_, index) => index + 1, ); diff --git a/packages/react-material-ui/src/components/Table/hooks/useTableQueryState.ts b/packages/react-material-ui/src/components/Table/hooks/useTableQueryState.ts deleted file mode 100644 index 432bd5ce..00000000 --- a/packages/react-material-ui/src/components/Table/hooks/useTableQueryState.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ReadonlyURLSearchParams, useSearchParams } from 'next/navigation'; -import { Order, TableQueryStateProps } from '../types'; -import { useState } from 'react'; - -export const TABLE_QUERY_STATE_DEFAULT_VALUE: TableQueryStateProps = { - order: Order.Asc, - orderBy: 'id', - rowsPerPage: 5, - page: 1, -}; - -export const getTableQueryState = ( - tableQuery: TableQueryStateProps, - searchParams?: ReadonlyURLSearchParams, -) => ({ - order: - (searchParams?.get('order') as Order) || - tableQuery?.order || - TABLE_QUERY_STATE_DEFAULT_VALUE.order, - orderBy: - searchParams?.get('orderBy') || - tableQuery?.orderBy || - TABLE_QUERY_STATE_DEFAULT_VALUE.orderBy, - rowsPerPage: - Number(searchParams?.get('rowsPerPage')) || - tableQuery?.rowsPerPage || - TABLE_QUERY_STATE_DEFAULT_VALUE.rowsPerPage, - page: - Number(searchParams?.get('page')) || - tableQuery?.page || - TABLE_QUERY_STATE_DEFAULT_VALUE.page, - simpleFilter: - (searchParams?.get('simpleFilter') && - JSON.parse(searchParams.get('simpleFilter'))) || - tableQuery?.simpleFilter || - undefined, - search: searchParams?.get('search') || tableQuery?.search || undefined, -}); - -export const useTableQueryState = (tableQuery?: TableQueryStateProps) => { - const searchParams = useSearchParams(); - - const [tableQueryState, setTableQueryState] = useState( - getTableQueryState(tableQuery, searchParams), - ); - - return { - tableQueryState, - setTableQueryState, - }; -}; diff --git a/packages/react-material-ui/src/components/Table/hooks/useTableRoot.tsx b/packages/react-material-ui/src/components/Table/hooks/useTableRoot.tsx index f0f4ed4f..35fced75 100644 --- a/packages/react-material-ui/src/components/Table/hooks/useTableRoot.tsx +++ b/packages/react-material-ui/src/components/Table/hooks/useTableRoot.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { createContext, useContext } from 'react'; -import { HeaderProps, RowProps, TableQueryStateProps } from '../types'; +import { HeaderProps, RowProps, TableStateProps } from '../types'; type TableContextProps = { rows: RowProps[]; @@ -9,7 +9,7 @@ type TableContextProps = { total: number; isControlled: boolean; pageCount: number; - tableQuery: TableQueryStateProps; + tableState: TableStateProps; selected: RowProps[]; isSelected: (id: string) => boolean; handleChangePage: (event: unknown, newPage: number) => void; diff --git a/packages/react-material-ui/src/components/Table/hooks/useTableState.ts b/packages/react-material-ui/src/components/Table/hooks/useTableState.ts new file mode 100644 index 00000000..66c11478 --- /dev/null +++ b/packages/react-material-ui/src/components/Table/hooks/useTableState.ts @@ -0,0 +1,47 @@ +import { ReadonlyURLSearchParams, useSearchParams } from 'next/navigation'; +import { Order, TableStateProps } from '../types'; +import { useState } from 'react'; + +export const TABLE_STATE_DEFAULT_VALUE: TableStateProps = { + order: Order.Asc, + orderBy: 'id', + rowsPerPage: 5, + page: 1, +}; + +export const getTableState = ( + initialState: TableStateProps, + searchParams?: ReadonlyURLSearchParams, +) => ({ + order: + (searchParams?.get('order') as Order) || + initialState?.order || + TABLE_STATE_DEFAULT_VALUE.order, + orderBy: + searchParams?.get('orderBy') || + initialState?.orderBy || + TABLE_STATE_DEFAULT_VALUE.orderBy, + rowsPerPage: + Number(searchParams?.get('rowsPerPage')) || + initialState?.rowsPerPage || + TABLE_STATE_DEFAULT_VALUE.rowsPerPage, + page: + Number(searchParams?.get('page')) || + initialState?.page || + TABLE_STATE_DEFAULT_VALUE.page, +}); + +export const useTableState = (initialState?: TableStateProps) => { + const searchParams = useSearchParams(); + + // In this state, we don't want the strings + // We want the values in the interface that nestjs provides + const [tableState, setTableState] = useState( + getTableState(initialState, searchParams), + ); + + return { + tableState, + setTableState, + }; +}; diff --git a/packages/react-material-ui/src/components/Table/types.ts b/packages/react-material-ui/src/components/Table/types.ts index 61d5da4f..f84f56f8 100644 --- a/packages/react-material-ui/src/components/Table/types.ts +++ b/packages/react-material-ui/src/components/Table/types.ts @@ -2,8 +2,6 @@ import { ReactNode } from 'react'; export type BasicType = string | number | boolean; -export type SimpleFilter = Record; - export type HeaderProps = { disablePadding?: boolean; id: string; @@ -53,13 +51,11 @@ export enum Order { Desc = 'desc', } -export type TableQueryStateProps = { +export type TableStateProps = { order?: Order; orderBy?: string; rowsPerPage?: number; page?: number; - simpleFilter?: SimpleFilter; - search?: string; }; export type RenderRowFunction = (row: RowProps, labelId: string) => ReactNode; diff --git a/packages/react-material-ui/src/components/Table/useTable.ts b/packages/react-material-ui/src/components/Table/useTable.ts index b197730d..d7fcaf0a 100644 --- a/packages/react-material-ui/src/components/Table/useTable.ts +++ b/packages/react-material-ui/src/components/Table/useTable.ts @@ -1,26 +1,81 @@ 'use client'; import { useRouter, usePathname, useSearchParams } from 'next/navigation'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import useDataProvider, { useQuery } from '@concepta/react-data-provider'; -import { Order, SimpleFilter, TableQueryStateProps } from './types'; +import { Order, TableStateProps } from './types'; import { - TABLE_QUERY_STATE_DEFAULT_VALUE, - useTableQueryState, -} from './hooks/useTableQueryState'; -import { getSearchParams } from '../../utils/http'; + TABLE_STATE_DEFAULT_VALUE, + useTableState, +} from './hooks/useTableState'; import { DataProviderRequestOptions } from '@concepta/react-data-provider/dist/interfaces'; +import { CreateQueryParams, RequestQueryBuilder } from '@nestjsx/crud-request'; +import { keyBy } from 'lodash'; -interface UseTableOptions { +const setFilters = ( + filters: CreateQueryParams['filter'][] | undefined, + query: RequestQueryBuilder, +) => { + if (!filters) return; + + filters.forEach((filter) => query.setFilter(filter)); +}; + +const getQueryParams = ( + searchParams: URLSearchParams, + initialState: QueryParams, +) => { + const params = new URLSearchParams(searchParams); + const filters = []; + + params.forEach((value, key) => { + if (key.startsWith('filter')) { + filters.push(value); + } + }); + + const formattedFilter = filters.map((filter) => { + const [field, operator, value] = filter.split('||'); + + return { + field, + operator, + value, + }; + }); + + const filtersByKey = keyBy(formattedFilter, 'field'); + + const searchInitialState = ( + searchParams?.get('search') && + RequestQueryBuilder.create().search(JSON.parse(searchParams?.get('search'))) + )?.queryObject.search; + + return { + filter: (filters && filtersByKey) || initialState?.filter || undefined, + search: searchInitialState || initialState?.search || undefined, + }; +}; + +type QueryParams = { + filter: { + [key: string]: CreateQueryParams['filter']; + }; + search: CreateQueryParams['search']; +}; + +type UseTableOptions = { rowsPerPage?: number; page?: number; orderBy?: string; order?: Order; - simpleFilter?: SimpleFilter; - search?: string; + filter: { + [key: string]: CreateQueryParams['filter']; + }; + search?: CreateQueryParams['search']; callbacks?: DataProviderRequestOptions; noPagination?: boolean; -} +}; export type UseTableProps = ( resource: string, @@ -31,19 +86,17 @@ export type UseTableProps = ( error: unknown; total: number; pageCount: number; + filter: { + [key: string]: CreateQueryParams['filter']; + }; + search: CreateQueryParams['search']; + tableState: TableStateProps; execute: () => void; refresh: () => void; - updateSimpleFilter: ( - simpleFilter: SimpleFilter | null, - resetPage?: boolean, - ) => void; - updateSearch: (search: string | null, resetPage?: boolean) => void; - simpleFilter: SimpleFilter; - search: string; - tableQueryState: TableQueryStateProps; - setTableQueryState: React.Dispatch< - React.SetStateAction - >; + setSearch: (search: CreateQueryParams['search'], resetPage?: boolean) => void; + setTableState: React.Dispatch>; + setFilter: (filter: CreateQueryParams['filter'], resetPage?: boolean) => void; + removeFilter: (filterName: string) => void; }; /** @@ -59,54 +112,35 @@ const useTable: UseTableProps = (resource, options) => { const router = useRouter(); const { get } = useDataProvider(); - const { tableQueryState, setTableQueryState } = useTableQueryState(options); - - useEffect(() => { - const newSearchParam = getSearchParams(searchParams, { - simpleFilter: JSON.stringify(tableQueryState?.simpleFilter), - }); - - router.replace(`${pathname}?${newSearchParam ?? ''}`); - }, [JSON.stringify(tableQueryState.simpleFilter)]); - - useEffect(() => { - const newSearchParam = getSearchParams(searchParams, { - search: tableQueryState?.search, - }); + const { tableState, setTableState } = useTableState(options); + const [queryParams, setQueryParams] = useState( + getQueryParams(searchParams, { + filter: options.filter, + search: options.search, + }), + ); - router.replace(`${pathname}?${newSearchParam ?? ''}`); - }, [tableQueryState.search]); + const getResource = () => { + let uri = resource; - const simpleFilterQuery = () => { - if (!tableQueryState.simpleFilter) return; + if (queryParams?.filter) { + const query = RequestQueryBuilder.create(); + setFilters(Object.values(queryParams?.filter), query); - const queryArr = []; - for (const [key, value] of Object.entries(tableQueryState.simpleFilter)) { - queryArr.push(`${key}${value}`); + uri = `${uri}?${query.query()}`; } - return queryArr; - }; - - useEffect(() => { - execute(); - }, [JSON.stringify(tableQueryState)]); - const getResource = () => { return get({ - uri: resource, + uri, queryParams: { - ...(tableQueryState?.rowsPerPage && + ...(tableState?.rowsPerPage && !options?.noPagination && { - limit: tableQueryState.rowsPerPage, + limit: tableState.rowsPerPage, }), - page: tableQueryState.page, - ...(tableQueryState?.orderBy && { - sort: `${ - tableQueryState?.orderBy - },${tableQueryState?.order.toUpperCase()}`, + page: tableState.page, + ...(tableState?.orderBy && { + sort: `${tableState?.orderBy},${tableState?.order.toUpperCase()}`, }), - ...(tableQueryState?.simpleFilter && { filter: simpleFilterQuery() }), - ...(tableQueryState?.search && { s: tableQueryState?.search }), }, }); }; @@ -117,57 +151,50 @@ const useTable: UseTableProps = (resource, options) => { options?.callbacks, ); - const updateSimpleFilter = ( - simpleFilter: SimpleFilter | null, - resetPage = true, - ) => { - setTableQueryState((prevState) => { - // Removed current simpleFilter from state + const setFilter = (filter: CreateQueryParams['filter'], resetPage = true) => { + setQueryParams((prevState) => { const updatedState = { ...prevState }; - for (const entries of Object.entries(simpleFilter)) { - const [key, value] = entries; - - if (!value && !updatedState?.simpleFilter?.[key]) continue; - - if (!value) { - delete updatedState.simpleFilter[key]; - } else { - if (typeof updatedState.simpleFilter === 'undefined') { - updatedState.simpleFilter = { - [key]: value, - }; - } else { - updatedState.simpleFilter[key] = value; - } - } + if (Array.isArray(filter)) { + filter.forEach((filterItem) => { + updatedState.filter = { + ...(updatedState.filter || {}), + [filterItem.field]: filterItem, + }; + }); + } else { + updatedState.filter = { + ...(updatedState.filter || {}), + [filter.field]: filter, + }; } if (!resetPage) { return updatedState; } - const updatedSimpleFilter = - updatedState?.simpleFilter && - Object.keys(updatedState.simpleFilter).length > 0 - ? updatedState.simpleFilter - : undefined; - - const res = { - ...(updatedState && { - ...updatedState, - simpleFilter: updatedSimpleFilter, - page: TABLE_QUERY_STATE_DEFAULT_VALUE.page, - }), - }; + setTableState((prevState) => ({ + ...prevState, + page: TABLE_STATE_DEFAULT_VALUE.page, + })); - return res; + return updatedState; }); }; - const updateSearch = (search: string | null, resetPage = true) => { - setTableQueryState((prevState) => { - // Removed current search from state + const removeFilter = (filterName: string) => { + setQueryParams((prevState) => { + // Removed current filter from state + const updatedState = { ...prevState }; + + delete updatedState.filter[filterName]; + + return updatedState; + }); + }; + + const setSearch = (search: CreateQueryParams['search'], resetPage = true) => { + setQueryParams((prevState) => { const updatedState = { ...prevState }; updatedState.search = search; @@ -176,34 +203,68 @@ const useTable: UseTableProps = (resource, options) => { return updatedState; } - const updatedSearch = updatedState?.search ?? undefined; + setTableState((prevState) => ({ + ...prevState, + page: TABLE_STATE_DEFAULT_VALUE.page, + })); + return updatedState; + }); + }; - const res = { - ...(updatedState && { - ...updatedState, - search: updatedSearch, - page: TABLE_QUERY_STATE_DEFAULT_VALUE.page, - }), - }; + const removeSearch = () => { + setQueryParams((prevState) => { + const updatedState = { ...prevState }; - return res; + delete updatedState.search; + return updatedState; }); }; + useEffect(() => { + const page = searchParams.get('page'); + const rowsPerPage = searchParams.get('rowsPerPage'); + const order = searchParams.get('order'); + const orderBy = searchParams.get('orderBy'); + + const tableStateSearchParams = [ + page && `page=${page}`, + rowsPerPage && `rowsPerPage=${rowsPerPage}`, + order && `order=${order}`, + orderBy && `orderBy=${orderBy}`, + ] + .filter(Boolean) + .join('&'); + + const query = RequestQueryBuilder.create(); + setFilters( + queryParams?.filter ? Object.values(queryParams.filter) : undefined, + query, + ); + query.search(queryParams.search); + + router.replace(`${pathname}?${query.query()}&${tableStateSearchParams}`); + }, [JSON.stringify(queryParams.filter)]); + + useEffect(() => { + execute(); + }, [JSON.stringify(queryParams), JSON.stringify(tableState)]); + return { data: data?.data, isPending, error, execute, refresh, - updateSimpleFilter, - simpleFilter: tableQueryState?.simpleFilter, - updateSearch, - search: tableQueryState?.search, + filter: queryParams?.filter, + search: queryParams?.search, total: data?.total, pageCount: data?.pageCount, - tableQueryState, - setTableQueryState, + tableState, + setSearch, + removeSearch, + setTableState, + setFilter, + removeFilter, }; }; diff --git a/yarn.lock b/yarn.lock index ac88e229..324561be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,6 +1762,19 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@nestjsx/crud-request@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/crud-request/-/crud-request-5.0.0-alpha.3.tgz#ee62f5ff454daef4973990faf9d1568e2a4b9bc8" + integrity sha512-EFeGgh/sSHhD3px9aj4hyjvOqBepMcRFAi5lrYuSUDmnj/eQOa4dg4orehU8gKrSAmgKNe9r0dx0kel7Zgivzg== + dependencies: + "@nestjsx/util" "^5.0.0-alpha.3" + qs "^6.8.0" + +"@nestjsx/util@^5.0.0-alpha.3": + version "5.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/@nestjsx/util/-/util-5.0.0-alpha.3.tgz#d1182280e9254f0d335efa369da17fa25d260135" + integrity sha512-UTYIxtyx80M42gOZ0VIInSiOLn78P6k1BCziiN8gE3FglzivQ1RGvPpmsRwioYEWG27gOgnMwOQnXv8Zbq8dzQ== + "@next/env@13.4.19": version "13.4.19" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.19.tgz#46905b4e6f62da825b040343cbc233144e9578d3" @@ -2172,7 +2185,7 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== -"@types/react-dom@18.2.0", "@types/react-dom@^18.2.0": +"@types/react-dom@^18.2.0": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.0.tgz#374f28074bb117f56f58c4f3f71753bebb545156" integrity sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA== @@ -2193,7 +2206,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.2.0", "@types/react@^17", "@types/react@^18.2.0": +"@types/react@*", "@types/react@^18.2.0": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== @@ -2202,6 +2215,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^17": + version "17.0.71" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.71.tgz#3673d446ad482b1564e44bf853b3ab5bcbc942c4" + integrity sha512-lfqOu9mp16nmaGRrS8deS2Taqhd5Ih0o92Te5Ws6I1py4ytHBcXLqh0YIqVsViqwVI5f+haiFM6hju814BzcmA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/scheduler@*": version "0.16.3" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -7815,7 +7837,7 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@^6.9.4: +qs@^6.8.0, qs@^6.9.4: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== From 14800d5832e51ea7660134c3bff69ebeee7b31d0 Mon Sep 17 00:00:00 2001 From: Christiano Higuto Date: Thu, 14 Dec 2023 10:51:12 -0300 Subject: [PATCH 2/2] feat: add RequestQueryParser --- .../src/components/Table/useTable.ts | 56 ++++++++----------- yarn.lock | 13 +---- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/packages/react-material-ui/src/components/Table/useTable.ts b/packages/react-material-ui/src/components/Table/useTable.ts index d7fcaf0a..5be3315b 100644 --- a/packages/react-material-ui/src/components/Table/useTable.ts +++ b/packages/react-material-ui/src/components/Table/useTable.ts @@ -9,7 +9,11 @@ import { useTableState, } from './hooks/useTableState'; import { DataProviderRequestOptions } from '@concepta/react-data-provider/dist/interfaces'; -import { CreateQueryParams, RequestQueryBuilder } from '@nestjsx/crud-request'; +import { + CreateQueryParams, + RequestQueryBuilder, + RequestQueryParser, +} from '@nestjsx/crud-request'; import { keyBy } from 'lodash'; const setFilters = ( @@ -25,34 +29,28 @@ const getQueryParams = ( searchParams: URLSearchParams, initialState: QueryParams, ) => { + // Get the searchParams const params = new URLSearchParams(searchParams); - const filters = []; - - params.forEach((value, key) => { - if (key.startsWith('filter')) { - filters.push(value); - } - }); - const formattedFilter = filters.map((filter) => { - const [field, operator, value] = filter.split('||'); + // Create an object + const searchParamsObject = Object.fromEntries(params); - return { - field, - operator, - value, - }; - }); + // Value parsed from query params to query builder + const queryBuilder = + RequestQueryParser.create().parseQuery(searchParamsObject); - const filtersByKey = keyBy(formattedFilter, 'field'); + // Get by key so we can remove if needed + const filtersByKey = keyBy(queryBuilder.filter, 'field'); - const searchInitialState = ( - searchParams?.get('search') && - RequestQueryBuilder.create().search(JSON.parse(searchParams?.get('search'))) - )?.queryObject.search; + // Get the search param from the URL + // And parse it to create a QueryBuilder + const searchInitialState = queryBuilder.search; return { - filter: (filters && filtersByKey) || initialState?.filter || undefined, + filter: + (Object.keys(filtersByKey)?.length && filtersByKey) || + initialState?.filter || + undefined, search: searchInitialState || initialState?.search || undefined, }; }; @@ -121,14 +119,7 @@ const useTable: UseTableProps = (resource, options) => { ); const getResource = () => { - let uri = resource; - - if (queryParams?.filter) { - const query = RequestQueryBuilder.create(); - setFilters(Object.values(queryParams?.filter), query); - - uri = `${uri}?${query.query()}`; - } + const uri = resource; return get({ uri, @@ -141,6 +132,8 @@ const useTable: UseTableProps = (resource, options) => { ...(tableState?.orderBy && { sort: `${tableState?.orderBy},${tableState?.order.toUpperCase()}`, }), + s: queryParams?.search, + filter: queryParams?.filter, }, }); }; @@ -188,7 +181,6 @@ const useTable: UseTableProps = (resource, options) => { const updatedState = { ...prevState }; delete updatedState.filter[filterName]; - return updatedState; }); }; @@ -243,7 +235,7 @@ const useTable: UseTableProps = (resource, options) => { query.search(queryParams.search); router.replace(`${pathname}?${query.query()}&${tableStateSearchParams}`); - }, [JSON.stringify(queryParams.filter)]); + }, [JSON.stringify(queryParams)]); useEffect(() => { execute(); diff --git a/yarn.lock b/yarn.lock index 324561be..88bab98b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2185,7 +2185,7 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== -"@types/react-dom@^18.2.0": +"@types/react-dom@18.2.0", "@types/react-dom@^18.2.0": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.0.tgz#374f28074bb117f56f58c4f3f71753bebb545156" integrity sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA== @@ -2206,7 +2206,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.0": +"@types/react@*", "@types/react@18.2.0", "@types/react@^17", "@types/react@^18.2.0": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== @@ -2215,15 +2215,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^17": - version "17.0.71" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.71.tgz#3673d446ad482b1564e44bf853b3ab5bcbc942c4" - integrity sha512-lfqOu9mp16nmaGRrS8deS2Taqhd5Ih0o92Te5Ws6I1py4ytHBcXLqh0YIqVsViqwVI5f+haiFM6hju814BzcmA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/scheduler@*": version "0.16.3" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"