From 0a34eb062633aa10ee5c602b9deb60f7f0e4cf88 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Fri, 13 Oct 2023 00:27:09 +0530 Subject: [PATCH 01/12] resolve empty string search param; virtual list WIP --- apps/devfractal-demo/package.json | 1 + apps/devfractal-demo/src/main.tsx | 4 +- .../{DataTable.tsx => DataGridApp.tsx} | 113 +++++++++++++++--- .../src/server-side/components/Header.tsx | 2 - .../src/server-side/components/Pagination.tsx | 9 ++ 5 files changed, 108 insertions(+), 21 deletions(-) rename apps/devfractal-demo/src/server-side/{DataTable.tsx => DataGridApp.tsx} (57%) diff --git a/apps/devfractal-demo/package.json b/apps/devfractal-demo/package.json index bd113ec2..2f3aa09a 100644 --- a/apps/devfractal-demo/package.json +++ b/apps/devfractal-demo/package.json @@ -71,6 +71,7 @@ "react-dom": "18.2.0", "react-error-boundary": "4.0.11", "react-router-dom": "6.16.0", + "react-virtuoso": "4.6.1", "sort-by": "1.2.0", "tailwind-merge": "1.14.0", "tailwindcss-animate": "1.0.7", diff --git a/apps/devfractal-demo/src/main.tsx b/apps/devfractal-demo/src/main.tsx index 71cb63d0..a42fc70d 100644 --- a/apps/devfractal-demo/src/main.tsx +++ b/apps/devfractal-demo/src/main.tsx @@ -3,7 +3,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom' import { queryClient } from '@/queryClient' -import { DataTable } from './server-side/DataTable' +import { DataGridApp } from './server-side/DataGridApp' import './global.css' if (process.env.NODE_ENV === 'development') { @@ -14,7 +14,7 @@ if (process.env.NODE_ENV === 'development') { const router = createBrowserRouter([ { path: '*', - element: , + element: , }, ]) diff --git a/apps/devfractal-demo/src/server-side/DataTable.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx similarity index 57% rename from apps/devfractal-demo/src/server-side/DataTable.tsx rename to apps/devfractal-demo/src/server-side/DataGridApp.tsx index 90ec1495..2888193b 100644 --- a/apps/devfractal-demo/src/server-side/DataTable.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -9,8 +9,14 @@ import { remove$, insert$, fromSearchParams, + isEmptyString, + omit$, + TableBody, + TableRow, + TableCell, } from 'devfractal' import { useSearchParams } from 'react-router-dom' +import { TableVirtuoso } from 'react-virtuoso' import { DataBody } from './components/DataBody' import { DataHeader } from './components/DataHeaders' @@ -21,8 +27,9 @@ import { fetchProducts } from './query' // @TODO: Row operations: https://ui.shadcn.com/docs/components/combobox#dropdown-menu // https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state +// @TODO: Too many rerenders? -export function DataTable(): JSX.Element { +export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( toSearch({ page: 1, @@ -93,17 +100,17 @@ export function DataTable(): JSX.Element { } function handleSearch(value: { searchBy: string; search: string }) { - setState(toSearch({ ...queryParams, ...value })) + const nextSearchState = isEmptyString(value.search) + ? omit$(queryParams, ['searchBy', 'search']) + : { ...queryParams, ...value } + + setState(toSearch(nextSearchState)) } - // @TODO: Improve - add generics may be function handleColumns(header: string) { const index = headers.indexOf(header) const column = queryParams.column.includes(header) - ? (remove$( - queryParams.column, - queryParams.column.indexOf(header), - ) as string[]) + ? remove$(queryParams.column, queryParams.column.indexOf(header)) : insert$(queryParams.column, index, header) setState(toSearch({ ...queryParams, column })) @@ -121,20 +128,19 @@ export function DataTable(): JSX.Element { onSelect={handleColumns} columns={data.columns} /> - - - -
- - {/* @TODO: CLean up prop passing */} + + + ) } + +function DataTable< + T extends { id: number; [k: string]: string | number }, +>(props: { + data: T[] + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + +
+ ) +} + +export function VirtualDataTable< + T extends { id: number; [k: string]: string | number }, +>(props: { + data: T[] + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + + {props.data.length > 0 ? ( + { + return ( + + {Object.keys(data) + .filter(key => key !== 'id') + .map(item => { + return {data[item]} + })} + + ) + }} + /> + ) : ( + + No data! + + )} + +
+ ) +} + +// { +// data.length > 0 ? ( +// data.map(product => { +// return ( +// +// {Object.keys(product) +// .filter(key => key !== 'id') +// .map(item => { +// return {product[item]} +// })} +// +// ) +// }) +// ) : ( +// +// No data! +// +// ) +// } diff --git a/apps/devfractal-demo/src/server-side/components/Header.tsx b/apps/devfractal-demo/src/server-side/components/Header.tsx index af3d8f86..6c52f9f8 100644 --- a/apps/devfractal-demo/src/server-side/components/Header.tsx +++ b/apps/devfractal-demo/src/server-side/components/Header.tsx @@ -28,8 +28,6 @@ export function Header({ return ( - {/* @TODO: shad-cn does not seem to support multiple selections */} - diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index 5bb066b9..ec6ecda1 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -1,6 +1,7 @@ import { Button, HStack, + Input, Select, SelectContent, SelectGroup, @@ -14,6 +15,7 @@ import { export function Pagination({ currentPage, totalPages, + totalItems, limit, onSetLimit, onNext, @@ -24,6 +26,7 @@ export function Pagination({ currentPage: number totalPages: number limit: number + totalItems: number onSetLimit: (value: string) => void onNext: () => void onPrev: () => void @@ -46,7 +49,13 @@ export function Pagination({ + per page + + + + Show all {totalItems} + From 3d494a9c9224c73e615ae667d333cb0103549a9a Mon Sep 17 00:00:00 2001 From: Shibashis Date: Fri, 13 Oct 2023 16:22:29 +0530 Subject: [PATCH 02/12] add virtual data table; modify pagination --- apps/devfractal-demo/package.json | 2 +- .../src/mocks/data-table/dataHandlers.ts | 34 ++++-- .../src/mocks/data-table/operations.ts | 33 ++++-- .../src/server-side/DataGridApp.tsx | 106 ++++++------------ .../server-side/components/DataHeaders.tsx | 2 +- .../src/server-side/components/Pagination.tsx | 82 ++++++++------ .../components/VirtualDataTable.tsx | 66 +++++++++++ apps/devfractal-demo/src/server-side/query.ts | 5 +- 8 files changed, 200 insertions(+), 130 deletions(-) create mode 100644 apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx diff --git a/apps/devfractal-demo/package.json b/apps/devfractal-demo/package.json index 2f3aa09a..a70bc6e0 100644 --- a/apps/devfractal-demo/package.json +++ b/apps/devfractal-demo/package.json @@ -6,7 +6,7 @@ "build": "vite build", "build-storybook": "storybook build", "clean": "rimraf .turbo node_modules dist", - "dev": "vite --host 0.0.0.0 --port 3000 --clearScreen false", + "dev": "vite --force --host 0.0.0.0 --port 3000 --clearScreen false", "db:dev": "json-server --port 8080 --watch db.json", "coverage": "vitest run --coverage", "deploy": "vercel deploy dist --team=turborepo --confirm", diff --git a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts index 190e7f31..b9afa1e2 100644 --- a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts +++ b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts @@ -1,8 +1,11 @@ import { fromSearchParams, isArray, isNotNullish, toInt } from 'devfractal' import { rest } from 'msw' +import { data } from '@/server-side/products' + import { getSearchedProducts, + getSelectedColumns, getSlicedProducts, getSortedProducts, } from './operations' @@ -18,6 +21,7 @@ export const dataHandlers = [ return res( ctx.json( getSearchedProducts( + queryParams.show, toInt(queryParams.page), toInt(queryParams.limit), isArray(queryParams.column) @@ -36,6 +40,7 @@ export const dataHandlers = [ return res( ctx.json( getSortedProducts( + queryParams.show, toInt(queryParams.page), toInt(queryParams.limit), isArray(queryParams.column) @@ -48,16 +53,25 @@ export const dataHandlers = [ ) } - return res( - ctx.json( - getSlicedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], + if ( + queryParams.show === 'paged' && + isNotNullish(queryParams.page) && + isNotNullish(queryParams.limit) + ) { + return res( + ctx.json( + getSlicedProducts( + toInt(queryParams.page), + toInt(queryParams.limit), + isArray(queryParams.column) + ? queryParams.column + : [queryParams.column], + ), ), - ), - ) + ) + } + + // @TODO: Correct -> modify function definition + return res(ctx.json(getSelectedColumns(data, queryParams.column))) }), ] diff --git a/apps/devfractal-demo/src/mocks/data-table/operations.ts b/apps/devfractal-demo/src/mocks/data-table/operations.ts index c7a69f7b..905a9e0d 100644 --- a/apps/devfractal-demo/src/mocks/data-table/operations.ts +++ b/apps/devfractal-demo/src/mocks/data-table/operations.ts @@ -21,6 +21,20 @@ const intializeProducts = (): void => { intializeProducts() // Operations +export const getSelectedColumns = ( + products: Products, + columns: string[], +): ProductsResponse => { + return { + products: data.map(product => + pick$(product, ['id', ...(columns as (keyof Product)[])]), + ), + columns, + currentPage: 0, + totalItems: products.length, + totalPages: 0, + } +} export const getSlicedProducts = ( page: number, @@ -34,11 +48,7 @@ export const getSlicedProducts = ( const totalPages = Math.ceil(products.length / limit) return { - products: products - .slice(startIndex, endIndex) - .map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ), + products: products.slice(startIndex, endIndex), currentPage: page, totalPages, totalItems: products.length, @@ -47,13 +57,17 @@ export const getSlicedProducts = ( } export const getSortedProducts = ( + show: 'all' | 'paged', page: number, limit: number, columns: string[], sortKey: keyof Product, order: 'asc' | 'desc', ): ProductsResponse => { - const slicedResult = getSlicedProducts(page, limit, columns) + const slicedResult = + show === 'all' + ? getSelectedColumns(products, columns) + : getSlicedProducts(page, limit, columns) return { ...slicedResult, @@ -64,6 +78,7 @@ export const getSortedProducts = ( // @TODO: Function overloading? export const getSearchedProducts = ( + show: 'all' | 'paged', page: number, limit: number, columns: string[], @@ -74,10 +89,10 @@ export const getSearchedProducts = ( ): ProductsResponse => { const result = sortKey && order - ? getSortedProducts(page, limit, columns, sortKey, order) + ? getSortedProducts(show, page, limit, columns, sortKey, order) : getSlicedProducts(page, limit, columns) - const products = + const searchResult = searchBy !== 'all' ? result.products.filter(product => product[searchBy] @@ -95,7 +110,7 @@ export const getSearchedProducts = ( return { ...result, - products, + products: searchResult, totalPages, } } diff --git a/apps/devfractal-demo/src/server-side/DataGridApp.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx index 2888193b..79cff9eb 100644 --- a/apps/devfractal-demo/src/server-side/DataGridApp.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -11,17 +11,14 @@ import { fromSearchParams, isEmptyString, omit$, - TableBody, - TableRow, - TableCell, } from 'devfractal' import { useSearchParams } from 'react-router-dom' -import { TableVirtuoso } from 'react-virtuoso' import { DataBody } from './components/DataBody' import { DataHeader } from './components/DataHeaders' import { Header } from './components/Header' import { Pagination } from './components/Pagination' +import { VirtualDataTable } from './components/VirtualDataTable' import { headers } from './products' import { fetchProducts } from './query' @@ -32,6 +29,7 @@ import { fetchProducts } from './query' export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( toSearch({ + show: 'paged', page: 1, limit: 10, column: ['title', 'price', 'brand', 'category'], @@ -44,6 +42,7 @@ export function DataGridApp(): JSX.Element { const { isLoading, isSuccess, data } = useQuery({ queryKey: [ 'products', + queryParams.show, queryParams.page, queryParams.limit, queryParams.column, @@ -54,8 +53,9 @@ export function DataGridApp(): JSX.Element { ], queryFn: () => fetchProducts({ - page: toInt(queryParams.page), - limit: toInt(queryParams.limit), + show: queryParams.show, + page: queryParams.page ? toInt(queryParams.page) : undefined, + limit: queryParams.limit ? toInt(queryParams.limit) : undefined, column: queryParams.column, sortBy: queryParams.sortBy, order: queryParams.order, @@ -107,11 +107,19 @@ export function DataGridApp(): JSX.Element { setState(toSearch(nextSearchState)) } + function handleInfinite() { + const show = queryParams.show === 'all' ? 'paged' : 'all' + + setState(toSearch({ ...queryParams, show })) + } + + // @TODO: Fix spreading of string when the column is not an array function handleColumns(header: string) { const index = headers.indexOf(header) - const column = queryParams.column.includes(header) - ? remove$(queryParams.column, queryParams.column.indexOf(header)) - : insert$(queryParams.column, index, header) + const column = + queryParams.column.indexOf(header) !== -1 + ? remove$(queryParams.column, queryParams.column.indexOf(header)) + : insert$(queryParams.column, index, header.toString()) setState(toSearch({ ...queryParams, column })) } @@ -129,14 +137,24 @@ export function DataGridApp(): JSX.Element { columns={data.columns} /> - + {queryParams.show === 'paged' ? ( + + ) : ( + + )} ) : ( @@ -171,60 +190,3 @@ function DataTable< ) } - -export function VirtualDataTable< - T extends { id: number; [k: string]: string | number }, ->(props: { - data: T[] - headers: string[] - onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void - onSearch: (value: { searchBy: string; search: string }) => void -}): JSX.Element { - return ( - - - - {props.data.length > 0 ? ( - { - return ( - - {Object.keys(data) - .filter(key => key !== 'id') - .map(item => { - return {data[item]} - })} - - ) - }} - /> - ) : ( - - No data! - - )} - -
- ) -} - -// { -// data.length > 0 ? ( -// data.map(product => { -// return ( -// -// {Object.keys(product) -// .filter(key => key !== 'id') -// .map(item => { -// return {product[item]} -// })} -// -// ) -// }) -// ) : ( -// -// No data! -// -// ) -// } diff --git a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx b/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx index d83aece3..dc4ba5fa 100644 --- a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx +++ b/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx @@ -50,7 +50,7 @@ export function DataHeader({ ) } -function HeaderWrapper({ +export function HeaderWrapper({ header, onOrder, onSearch, diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index ec6ecda1..605a41f0 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -13,6 +13,7 @@ import { // @TODO: Add prop types export function Pagination({ + show, currentPage, totalPages, totalItems, @@ -22,7 +23,9 @@ export function Pagination({ onPrev, onFirst, onLast, + onCheck, }: { + show: 'all' | 'paged' currentPage: number totalPages: number limit: number @@ -32,53 +35,62 @@ export function Pagination({ onPrev: () => void onFirst: () => void onLast: (last: number) => void + onCheck: () => void }): JSX.Element { return ( - + {show === 'paged' && ( + + )} - per page + {show === 'paged' && ( + per page + )} - + Show all {totalItems} - - {currentPage} of {totalPages} - + {show === 'paged' && ( + + {currentPage} of {totalPages} + + )} - - - - - - + {show === 'paged' && ( + + + + + + + )} ) } diff --git a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx new file mode 100644 index 00000000..b5937c71 --- /dev/null +++ b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx @@ -0,0 +1,66 @@ +import { Box, isArray } from 'devfractal' +import { TableVirtuoso } from 'react-virtuoso' + +import { HeaderWrapper } from './DataHeaders' + +// @TODO: Type mismatch with shadcn and div-table as components + +export function VirtualDataTable< + T extends { id: number; [k: string]: number | string }, +>({ + data, + headers, + onOrder, + onSearch, +}: { + headers: string[] + data: readonly T[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + ( + + {isArray(headers) ? ( + headers.map(header => { + return ( + + + + ) + }) + ) : ( + + + + )} + + )} + itemContent={(_index, product) => ( + <> + {Object.keys(product) + .filter(key => key !== 'id') + .map(item => { + return ( + + {product[item]} + + ) + })} + + )} + /> + + ) +} diff --git a/apps/devfractal-demo/src/server-side/query.ts b/apps/devfractal-demo/src/server-side/query.ts index b36e06b4..55d4bc61 100644 --- a/apps/devfractal-demo/src/server-side/query.ts +++ b/apps/devfractal-demo/src/server-side/query.ts @@ -3,8 +3,9 @@ import { isDefined, keys, pick$, urlcat } from 'devfractal' import type { ProductsResponse } from '@/mocks/data-table/operations' export async function fetchProducts(params: { - page: number - limit: number + show: 'all' | 'paged' + page?: number + limit?: number column: string[] sortBy?: string order?: 'asc' | 'desc' From 12f5d40ade094813b77fd7e62ee3791057920b03 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Fri, 13 Oct 2023 00:27:09 +0530 Subject: [PATCH 03/12] resolve empty string search param; virtual list WIP --- apps/devfractal-demo/package.json | 1 + apps/devfractal-demo/src/main.tsx | 4 +- .../{DataTable.tsx => DataGridApp.tsx} | 113 +++++++++++++++--- .../src/server-side/components/Header.tsx | 2 - .../src/server-side/components/Pagination.tsx | 9 ++ 5 files changed, 108 insertions(+), 21 deletions(-) rename apps/devfractal-demo/src/server-side/{DataTable.tsx => DataGridApp.tsx} (57%) diff --git a/apps/devfractal-demo/package.json b/apps/devfractal-demo/package.json index 672bfba7..a70bc6e0 100644 --- a/apps/devfractal-demo/package.json +++ b/apps/devfractal-demo/package.json @@ -71,6 +71,7 @@ "react-dom": "18.2.0", "react-error-boundary": "4.0.11", "react-router-dom": "6.16.0", + "react-virtuoso": "4.6.1", "sort-by": "1.2.0", "tailwind-merge": "1.14.0", "tailwindcss-animate": "1.0.7", diff --git a/apps/devfractal-demo/src/main.tsx b/apps/devfractal-demo/src/main.tsx index 71cb63d0..a42fc70d 100644 --- a/apps/devfractal-demo/src/main.tsx +++ b/apps/devfractal-demo/src/main.tsx @@ -3,7 +3,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom' import { queryClient } from '@/queryClient' -import { DataTable } from './server-side/DataTable' +import { DataGridApp } from './server-side/DataGridApp' import './global.css' if (process.env.NODE_ENV === 'development') { @@ -14,7 +14,7 @@ if (process.env.NODE_ENV === 'development') { const router = createBrowserRouter([ { path: '*', - element: , + element: , }, ]) diff --git a/apps/devfractal-demo/src/server-side/DataTable.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx similarity index 57% rename from apps/devfractal-demo/src/server-side/DataTable.tsx rename to apps/devfractal-demo/src/server-side/DataGridApp.tsx index 90ec1495..2888193b 100644 --- a/apps/devfractal-demo/src/server-side/DataTable.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -9,8 +9,14 @@ import { remove$, insert$, fromSearchParams, + isEmptyString, + omit$, + TableBody, + TableRow, + TableCell, } from 'devfractal' import { useSearchParams } from 'react-router-dom' +import { TableVirtuoso } from 'react-virtuoso' import { DataBody } from './components/DataBody' import { DataHeader } from './components/DataHeaders' @@ -21,8 +27,9 @@ import { fetchProducts } from './query' // @TODO: Row operations: https://ui.shadcn.com/docs/components/combobox#dropdown-menu // https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state +// @TODO: Too many rerenders? -export function DataTable(): JSX.Element { +export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( toSearch({ page: 1, @@ -93,17 +100,17 @@ export function DataTable(): JSX.Element { } function handleSearch(value: { searchBy: string; search: string }) { - setState(toSearch({ ...queryParams, ...value })) + const nextSearchState = isEmptyString(value.search) + ? omit$(queryParams, ['searchBy', 'search']) + : { ...queryParams, ...value } + + setState(toSearch(nextSearchState)) } - // @TODO: Improve - add generics may be function handleColumns(header: string) { const index = headers.indexOf(header) const column = queryParams.column.includes(header) - ? (remove$( - queryParams.column, - queryParams.column.indexOf(header), - ) as string[]) + ? remove$(queryParams.column, queryParams.column.indexOf(header)) : insert$(queryParams.column, index, header) setState(toSearch({ ...queryParams, column })) @@ -121,20 +128,19 @@ export function DataTable(): JSX.Element { onSelect={handleColumns} columns={data.columns} /> - - - -
- - {/* @TODO: CLean up prop passing */} + + + ) } + +function DataTable< + T extends { id: number; [k: string]: string | number }, +>(props: { + data: T[] + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + +
+ ) +} + +export function VirtualDataTable< + T extends { id: number; [k: string]: string | number }, +>(props: { + data: T[] + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + + {props.data.length > 0 ? ( + { + return ( + + {Object.keys(data) + .filter(key => key !== 'id') + .map(item => { + return {data[item]} + })} + + ) + }} + /> + ) : ( + + No data! + + )} + +
+ ) +} + +// { +// data.length > 0 ? ( +// data.map(product => { +// return ( +// +// {Object.keys(product) +// .filter(key => key !== 'id') +// .map(item => { +// return {product[item]} +// })} +// +// ) +// }) +// ) : ( +// +// No data! +// +// ) +// } diff --git a/apps/devfractal-demo/src/server-side/components/Header.tsx b/apps/devfractal-demo/src/server-side/components/Header.tsx index af3d8f86..6c52f9f8 100644 --- a/apps/devfractal-demo/src/server-side/components/Header.tsx +++ b/apps/devfractal-demo/src/server-side/components/Header.tsx @@ -28,8 +28,6 @@ export function Header({ return ( - {/* @TODO: shad-cn does not seem to support multiple selections */} - diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index 5bb066b9..ec6ecda1 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -1,6 +1,7 @@ import { Button, HStack, + Input, Select, SelectContent, SelectGroup, @@ -14,6 +15,7 @@ import { export function Pagination({ currentPage, totalPages, + totalItems, limit, onSetLimit, onNext, @@ -24,6 +26,7 @@ export function Pagination({ currentPage: number totalPages: number limit: number + totalItems: number onSetLimit: (value: string) => void onNext: () => void onPrev: () => void @@ -46,7 +49,13 @@ export function Pagination({ + per page + + + + Show all {totalItems} + From e72521b900e18ae78082ab3cb5d3d8fdb92ae23d Mon Sep 17 00:00:00 2001 From: Shibashis Date: Fri, 13 Oct 2023 16:22:29 +0530 Subject: [PATCH 04/12] add virtual data table; modify pagination --- .../src/mocks/data-table/dataHandlers.ts | 34 ++++-- .../src/mocks/data-table/operations.ts | 33 ++++-- .../src/server-side/DataGridApp.tsx | 106 ++++++------------ .../server-side/components/DataHeaders.tsx | 2 +- .../src/server-side/components/Pagination.tsx | 82 ++++++++------ .../components/VirtualDataTable.tsx | 66 +++++++++++ apps/devfractal-demo/src/server-side/query.ts | 5 +- 7 files changed, 199 insertions(+), 129 deletions(-) create mode 100644 apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx diff --git a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts index 190e7f31..b9afa1e2 100644 --- a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts +++ b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts @@ -1,8 +1,11 @@ import { fromSearchParams, isArray, isNotNullish, toInt } from 'devfractal' import { rest } from 'msw' +import { data } from '@/server-side/products' + import { getSearchedProducts, + getSelectedColumns, getSlicedProducts, getSortedProducts, } from './operations' @@ -18,6 +21,7 @@ export const dataHandlers = [ return res( ctx.json( getSearchedProducts( + queryParams.show, toInt(queryParams.page), toInt(queryParams.limit), isArray(queryParams.column) @@ -36,6 +40,7 @@ export const dataHandlers = [ return res( ctx.json( getSortedProducts( + queryParams.show, toInt(queryParams.page), toInt(queryParams.limit), isArray(queryParams.column) @@ -48,16 +53,25 @@ export const dataHandlers = [ ) } - return res( - ctx.json( - getSlicedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], + if ( + queryParams.show === 'paged' && + isNotNullish(queryParams.page) && + isNotNullish(queryParams.limit) + ) { + return res( + ctx.json( + getSlicedProducts( + toInt(queryParams.page), + toInt(queryParams.limit), + isArray(queryParams.column) + ? queryParams.column + : [queryParams.column], + ), ), - ), - ) + ) + } + + // @TODO: Correct -> modify function definition + return res(ctx.json(getSelectedColumns(data, queryParams.column))) }), ] diff --git a/apps/devfractal-demo/src/mocks/data-table/operations.ts b/apps/devfractal-demo/src/mocks/data-table/operations.ts index c7a69f7b..905a9e0d 100644 --- a/apps/devfractal-demo/src/mocks/data-table/operations.ts +++ b/apps/devfractal-demo/src/mocks/data-table/operations.ts @@ -21,6 +21,20 @@ const intializeProducts = (): void => { intializeProducts() // Operations +export const getSelectedColumns = ( + products: Products, + columns: string[], +): ProductsResponse => { + return { + products: data.map(product => + pick$(product, ['id', ...(columns as (keyof Product)[])]), + ), + columns, + currentPage: 0, + totalItems: products.length, + totalPages: 0, + } +} export const getSlicedProducts = ( page: number, @@ -34,11 +48,7 @@ export const getSlicedProducts = ( const totalPages = Math.ceil(products.length / limit) return { - products: products - .slice(startIndex, endIndex) - .map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ), + products: products.slice(startIndex, endIndex), currentPage: page, totalPages, totalItems: products.length, @@ -47,13 +57,17 @@ export const getSlicedProducts = ( } export const getSortedProducts = ( + show: 'all' | 'paged', page: number, limit: number, columns: string[], sortKey: keyof Product, order: 'asc' | 'desc', ): ProductsResponse => { - const slicedResult = getSlicedProducts(page, limit, columns) + const slicedResult = + show === 'all' + ? getSelectedColumns(products, columns) + : getSlicedProducts(page, limit, columns) return { ...slicedResult, @@ -64,6 +78,7 @@ export const getSortedProducts = ( // @TODO: Function overloading? export const getSearchedProducts = ( + show: 'all' | 'paged', page: number, limit: number, columns: string[], @@ -74,10 +89,10 @@ export const getSearchedProducts = ( ): ProductsResponse => { const result = sortKey && order - ? getSortedProducts(page, limit, columns, sortKey, order) + ? getSortedProducts(show, page, limit, columns, sortKey, order) : getSlicedProducts(page, limit, columns) - const products = + const searchResult = searchBy !== 'all' ? result.products.filter(product => product[searchBy] @@ -95,7 +110,7 @@ export const getSearchedProducts = ( return { ...result, - products, + products: searchResult, totalPages, } } diff --git a/apps/devfractal-demo/src/server-side/DataGridApp.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx index 2888193b..79cff9eb 100644 --- a/apps/devfractal-demo/src/server-side/DataGridApp.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -11,17 +11,14 @@ import { fromSearchParams, isEmptyString, omit$, - TableBody, - TableRow, - TableCell, } from 'devfractal' import { useSearchParams } from 'react-router-dom' -import { TableVirtuoso } from 'react-virtuoso' import { DataBody } from './components/DataBody' import { DataHeader } from './components/DataHeaders' import { Header } from './components/Header' import { Pagination } from './components/Pagination' +import { VirtualDataTable } from './components/VirtualDataTable' import { headers } from './products' import { fetchProducts } from './query' @@ -32,6 +29,7 @@ import { fetchProducts } from './query' export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( toSearch({ + show: 'paged', page: 1, limit: 10, column: ['title', 'price', 'brand', 'category'], @@ -44,6 +42,7 @@ export function DataGridApp(): JSX.Element { const { isLoading, isSuccess, data } = useQuery({ queryKey: [ 'products', + queryParams.show, queryParams.page, queryParams.limit, queryParams.column, @@ -54,8 +53,9 @@ export function DataGridApp(): JSX.Element { ], queryFn: () => fetchProducts({ - page: toInt(queryParams.page), - limit: toInt(queryParams.limit), + show: queryParams.show, + page: queryParams.page ? toInt(queryParams.page) : undefined, + limit: queryParams.limit ? toInt(queryParams.limit) : undefined, column: queryParams.column, sortBy: queryParams.sortBy, order: queryParams.order, @@ -107,11 +107,19 @@ export function DataGridApp(): JSX.Element { setState(toSearch(nextSearchState)) } + function handleInfinite() { + const show = queryParams.show === 'all' ? 'paged' : 'all' + + setState(toSearch({ ...queryParams, show })) + } + + // @TODO: Fix spreading of string when the column is not an array function handleColumns(header: string) { const index = headers.indexOf(header) - const column = queryParams.column.includes(header) - ? remove$(queryParams.column, queryParams.column.indexOf(header)) - : insert$(queryParams.column, index, header) + const column = + queryParams.column.indexOf(header) !== -1 + ? remove$(queryParams.column, queryParams.column.indexOf(header)) + : insert$(queryParams.column, index, header.toString()) setState(toSearch({ ...queryParams, column })) } @@ -129,14 +137,24 @@ export function DataGridApp(): JSX.Element { columns={data.columns} /> - + {queryParams.show === 'paged' ? ( + + ) : ( + + )} ) : ( @@ -171,60 +190,3 @@ function DataTable< ) } - -export function VirtualDataTable< - T extends { id: number; [k: string]: string | number }, ->(props: { - data: T[] - headers: string[] - onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void - onSearch: (value: { searchBy: string; search: string }) => void -}): JSX.Element { - return ( - - - - {props.data.length > 0 ? ( - { - return ( - - {Object.keys(data) - .filter(key => key !== 'id') - .map(item => { - return {data[item]} - })} - - ) - }} - /> - ) : ( - - No data! - - )} - -
- ) -} - -// { -// data.length > 0 ? ( -// data.map(product => { -// return ( -// -// {Object.keys(product) -// .filter(key => key !== 'id') -// .map(item => { -// return {product[item]} -// })} -// -// ) -// }) -// ) : ( -// -// No data! -// -// ) -// } diff --git a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx b/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx index d83aece3..dc4ba5fa 100644 --- a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx +++ b/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx @@ -50,7 +50,7 @@ export function DataHeader({ ) } -function HeaderWrapper({ +export function HeaderWrapper({ header, onOrder, onSearch, diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index ec6ecda1..605a41f0 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -13,6 +13,7 @@ import { // @TODO: Add prop types export function Pagination({ + show, currentPage, totalPages, totalItems, @@ -22,7 +23,9 @@ export function Pagination({ onPrev, onFirst, onLast, + onCheck, }: { + show: 'all' | 'paged' currentPage: number totalPages: number limit: number @@ -32,53 +35,62 @@ export function Pagination({ onPrev: () => void onFirst: () => void onLast: (last: number) => void + onCheck: () => void }): JSX.Element { return ( - + {show === 'paged' && ( + + )} - per page + {show === 'paged' && ( + per page + )} - + Show all {totalItems} - - {currentPage} of {totalPages} - + {show === 'paged' && ( + + {currentPage} of {totalPages} + + )} - - - - - - + {show === 'paged' && ( + + + + + + + )} ) } diff --git a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx new file mode 100644 index 00000000..b5937c71 --- /dev/null +++ b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx @@ -0,0 +1,66 @@ +import { Box, isArray } from 'devfractal' +import { TableVirtuoso } from 'react-virtuoso' + +import { HeaderWrapper } from './DataHeaders' + +// @TODO: Type mismatch with shadcn and div-table as components + +export function VirtualDataTable< + T extends { id: number; [k: string]: number | string }, +>({ + data, + headers, + onOrder, + onSearch, +}: { + headers: string[] + data: readonly T[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + ( + + {isArray(headers) ? ( + headers.map(header => { + return ( + + + + ) + }) + ) : ( + + + + )} + + )} + itemContent={(_index, product) => ( + <> + {Object.keys(product) + .filter(key => key !== 'id') + .map(item => { + return ( + + {product[item]} + + ) + })} + + )} + /> + + ) +} diff --git a/apps/devfractal-demo/src/server-side/query.ts b/apps/devfractal-demo/src/server-side/query.ts index b36e06b4..55d4bc61 100644 --- a/apps/devfractal-demo/src/server-side/query.ts +++ b/apps/devfractal-demo/src/server-side/query.ts @@ -3,8 +3,9 @@ import { isDefined, keys, pick$, urlcat } from 'devfractal' import type { ProductsResponse } from '@/mocks/data-table/operations' export async function fetchProducts(params: { - page: number - limit: number + show: 'all' | 'paged' + page?: number + limit?: number column: string[] sortBy?: string order?: 'asc' | 'desc' From 0a789db2985826d1f0afd00fee25e73c5aa2636c Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 08:34:57 +0530 Subject: [PATCH 05/12] improve msw handler operations; add utils --- .../src/mocks/data-table/dataHandlers.ts | 86 ++----- .../src/mocks/data-table/database.ts | 154 ++++++++++++ .../src/mocks/data-table/operations.ts | 232 +++++++++--------- .../src/mocks/data-table/utils.ts | 52 ++++ .../src/server-side/DataGridApp.tsx | 10 +- .../src/server-side/components/DataBody.tsx | 11 +- .../src/server-side/components/Pagination.tsx | 12 +- .../components/VirtualDataTable.tsx | 10 +- .../src/server-side/components/index.ts | 0 apps/devfractal-demo/src/server-side/index.ts | 2 + .../src/server-side/products.ts | 4 + apps/devfractal-demo/src/server-side/query.ts | 2 +- apps/devfractal-demo/src/server-side/utils.ts | 35 --- 13 files changed, 369 insertions(+), 241 deletions(-) create mode 100644 apps/devfractal-demo/src/mocks/data-table/database.ts create mode 100644 apps/devfractal-demo/src/mocks/data-table/utils.ts create mode 100644 apps/devfractal-demo/src/server-side/components/index.ts create mode 100644 apps/devfractal-demo/src/server-side/index.ts delete mode 100644 apps/devfractal-demo/src/server-side/utils.ts diff --git a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts index b9afa1e2..08b6b9ee 100644 --- a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts +++ b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts @@ -1,77 +1,27 @@ -import { fromSearchParams, isArray, isNotNullish, toInt } from 'devfractal' +import { fromSearchParams } from 'devfractal' import { rest } from 'msw' -import { data } from '@/server-side/products' - -import { - getSearchedProducts, - getSelectedColumns, - getSlicedProducts, - getSortedProducts, -} from './operations' +import { getDataFromTable } from './database' export const dataHandlers = [ rest.get('/api/data/products', (req, res, ctx) => { const queryParams = fromSearchParams(req.url.searchParams) + const { show, column, page, limit, sortBy, order, searchBy, search } = + queryParams - if ( - isNotNullish(queryParams.searchBy) && - isNotNullish(queryParams.search) - ) { - return res( - ctx.json( - getSearchedProducts( - queryParams.show, - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - queryParams.searchBy, - queryParams.search, - queryParams.sortBy, - queryParams.order, - ), - ), - ) - } - - if (isNotNullish(queryParams.sortBy) && isNotNullish(queryParams.order)) { - return res( - ctx.json( - getSortedProducts( - queryParams.show, - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - queryParams.sortBy, - queryParams.order, - ), - ), - ) - } - - if ( - queryParams.show === 'paged' && - isNotNullish(queryParams.page) && - isNotNullish(queryParams.limit) - ) { - return res( - ctx.json( - getSlicedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - ), - ), - ) - } - - // @TODO: Correct -> modify function definition - return res(ctx.json(getSelectedColumns(data, queryParams.column))) + return res( + ctx.json( + getDataFromTable({ + show, + column, + page, + limit, + sortBy, + order, + searchBy, + search, + }), + ), + ) }), ] diff --git a/apps/devfractal-demo/src/mocks/data-table/database.ts b/apps/devfractal-demo/src/mocks/data-table/database.ts new file mode 100644 index 00000000..0acc3f93 --- /dev/null +++ b/apps/devfractal-demo/src/mocks/data-table/database.ts @@ -0,0 +1,154 @@ +import { + any, + chain, + filter, + isArray, + isDefined, + isUndefined, + omit$, + paged, + pick$, + pipe, +} from 'devfractal' + +import { data, type Product } from '@/server-side' + +import { orderBy, picked } from './utils' + +export type ProductsResponse = { + products: Array + currentPage: number + totalPages: number + totalItems: number + columns: string[] +} + +export function getDataFromTable(params: { + show: 'all' | 'paged' + column: string | string[] + page?: number + limit?: number + sortBy?: string + order?: 'asc' | 'desc' + searchBy?: keyof Product | 'all' + search?: string +}): ProductsResponse { + if ( + params.show === 'all' && + isUndefined(params.page) && + isUndefined(params.limit) + ) { + const selected = isArray(params.column) ? params.column : [params.column] + + return { + products: data.map(product => + pick$(product, [...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + return getPaginatedProducts({ ...omit$(params, ['show']) }) +} + +function getPaginatedProducts(params: { + column: string | string[] + page?: number + limit?: number + sortBy?: string + order?: 'asc' | 'desc' + searchBy?: keyof Product | 'all' + search?: string +}): ProductsResponse { + const selected = isArray(params.column) ? params.column : [params.column] + + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.sortBy) && + isDefined(params.order) && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + paged(params.page, params.limit), + orderBy(params.sortBy as keyof Product, params.order), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.sortBy) && + isDefined(params.order) + ) { + return { + products: chain( + data, + paged(params.page, params.limit), + orderBy(params.sortBy as keyof Product, params.order), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + if (isDefined(params.page) && isDefined(params.limit)) { + return { + products: chain( + data, + paged(params.page, params.limit), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + return { + products: chain( + data, + paged(1, 10), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 1, + totalItems: data.length, + totalPages: Math.ceil(data.length / 10), + } +} + +function hasSearchString, K extends keyof T>( + obj: T, + key: K | 'all', + text: string, +): boolean { + if (key !== 'all') { + return obj[key].toString().toLowerCase().includes(text.toLowerCase()) + } + + return pipe( + Object.values(obj), + any(value => value.toString().toLowerCase().includes(text.toLowerCase())), + ) +} diff --git a/apps/devfractal-demo/src/mocks/data-table/operations.ts b/apps/devfractal-demo/src/mocks/data-table/operations.ts index 905a9e0d..e1eccf49 100644 --- a/apps/devfractal-demo/src/mocks/data-table/operations.ts +++ b/apps/devfractal-demo/src/mocks/data-table/operations.ts @@ -1,116 +1,116 @@ -import { pick$ } from 'devfractal' - -import { data, type Product, type Products } from '@/server-side/products' -import { iorderBy } from '@/server-side/utils' - -export type ProductsResponse = { - products: Products - currentPage: number - totalPages: number - totalItems: number - columns: string[] -} - -const products: Products = [] - -// Load data -const intializeProducts = (): void => { - products.push(...data) -} - -intializeProducts() - -// Operations -export const getSelectedColumns = ( - products: Products, - columns: string[], -): ProductsResponse => { - return { - products: data.map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ), - columns, - currentPage: 0, - totalItems: products.length, - totalPages: 0, - } -} - -export const getSlicedProducts = ( - page: number, - limit: number, - columns: string[], -): ProductsResponse => { - // @TODO: use paged from array methods - const endIndex = page * limit - const startIndex = endIndex - limit - - const totalPages = Math.ceil(products.length / limit) - - return { - products: products.slice(startIndex, endIndex), - currentPage: page, - totalPages, - totalItems: products.length, - columns, - } -} - -export const getSortedProducts = ( - show: 'all' | 'paged', - page: number, - limit: number, - columns: string[], - sortKey: keyof Product, - order: 'asc' | 'desc', -): ProductsResponse => { - const slicedResult = - show === 'all' - ? getSelectedColumns(products, columns) - : getSlicedProducts(page, limit, columns) - - return { - ...slicedResult, - products: iorderBy(slicedResult.products, sortKey, order), - } -} - -// @TODO: Function overloading? - -export const getSearchedProducts = ( - show: 'all' | 'paged', - page: number, - limit: number, - columns: string[], - searchBy: keyof Product | 'all', - search: string, - sortKey?: keyof Product, - order?: 'asc' | 'desc', -): ProductsResponse => { - const result = - sortKey && order - ? getSortedProducts(show, page, limit, columns, sortKey, order) - : getSlicedProducts(page, limit, columns) - - const searchResult = - searchBy !== 'all' - ? result.products.filter(product => - product[searchBy] - .toString() - .toLowerCase() - .includes(search.toLowerCase()), - ) - : result.products.filter(product => - Object.values(product).some(value => - value.toString().toLowerCase().includes(search.toLowerCase()), - ), - ) - - const totalPages = Math.ceil(products.length / limit) - - return { - ...result, - products: searchResult, - totalPages, - } -} +// import { pick$ } from 'devfractal' + +// import { iorderBy } from '@/mocks/data-table/utils' +// import { data, type Product, type Products } from '@/server-side/products' + +// export type ProductsResponse = { +// products: Array +// currentPage: number +// totalPages: number +// totalItems: number +// columns: string[] +// } + +// const products: Products = [] + +// // Load data +// const intializeProducts = (): void => { +// products.push(...data) +// } + +// intializeProducts() + +// // Operations +// export const getSelectedColumns = ( +// products: Products, +// columns: string[], +// ): ProductsResponse => { +// return { +// products: data.map(product => +// pick$(product, ['id', ...(columns as (keyof Product)[])]), +// ), +// columns, +// currentPage: 0, +// totalItems: products.length, +// totalPages: 0, +// } +// } + +// export const getSlicedProducts = ( +// page: number, +// limit: number, +// columns: string[], +// ): ProductsResponse => { +// // @TODO: use paged from array methods +// const endIndex = page * limit +// const startIndex = endIndex - limit + +// const totalPages = Math.ceil(products.length / limit) + +// return { +// products: products.slice(startIndex, endIndex), +// currentPage: page, +// totalPages, +// totalItems: products.length, +// columns, +// } +// } + +// export const getSortedProducts = ( +// show: 'all' | 'paged', +// page: number, +// limit: number, +// columns: string[], +// sortKey: keyof Product, +// order: 'asc' | 'desc', +// ): ProductsResponse => { +// const slicedResult = +// show === 'all' +// ? getSelectedColumns(products, columns) +// : getSlicedProducts(page, limit, columns) + +// return { +// ...slicedResult, +// products: iorderBy(slicedResult.products, sortKey, order), +// } +// } + +// // @TODO: Function overloading? + +// export const getSearchedProducts = ( +// show: 'all' | 'paged', +// page: number, +// limit: number, +// columns: string[], +// searchBy: keyof Product | 'all', +// search: string, +// sortKey?: keyof Product, +// order?: 'asc' | 'desc', +// ): ProductsResponse => { +// const result = +// sortKey && order +// ? getSortedProducts(show, page, limit, columns, sortKey, order) +// : getSlicedProducts(page, limit, columns) + +// const searchResult = +// searchBy !== 'all' +// ? result.products.filter(product => +// product[searchBy] +// .toString() +// .toLowerCase() +// .includes(search.toLowerCase()), +// ) +// : result.products.filter(product => +// Object.values(product).some(value => +// value.toString().toLowerCase().includes(search.toLowerCase()), +// ), +// ) + +// const totalPages = Math.ceil(products.length / limit) + +// return { +// ...result, +// products: searchResult, +// totalPages, +// } +// } diff --git a/apps/devfractal-demo/src/mocks/data-table/utils.ts b/apps/devfractal-demo/src/mocks/data-table/utils.ts new file mode 100644 index 00000000..99acf63c --- /dev/null +++ b/apps/devfractal-demo/src/mocks/data-table/utils.ts @@ -0,0 +1,52 @@ +import { all, chain, isDefined, map, pick$, pipe } from 'devfractal' + +type Order = 'asc' | 'desc' + +function cmp(key: K, order: Order) { + return (a: T, b: T): number => { + const x = a[key] + const y = b[key] + + if (x < y) { + return order === 'asc' ? -1 : 1 + } + if (x > y) { + return order === 'asc' ? 1 : -1 + } + return 0 + } +} + +export function iorderBy( + arr: Iterable, + key: K, + order: Order = 'asc', +): T[] { + return [...arr].sort(cmp(key, order)) +} + +export function orderBy( + key: K, + order: Order = 'asc', +) { + return (arr: Iterable) => { + return [...arr].sort(cmp(key, order)) + } +} + +// @TODO: Possible to convert to type guard? +export function areDefined(arr: unknown[]): boolean { + return pipe( + arr, + all(el => isDefined(el)), + ) +} + +export function picked(keys: K[]) { + return (arr: Iterable): Iterable> => { + return chain( + arr, + map(e => pick$(e, [...keys])), + ) + } +} diff --git a/apps/devfractal-demo/src/server-side/DataGridApp.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx index 79cff9eb..1a7905c9 100644 --- a/apps/devfractal-demo/src/server-side/DataGridApp.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -109,8 +109,10 @@ export function DataGridApp(): JSX.Element { function handleInfinite() { const show = queryParams.show === 'all' ? 'paged' : 'all' + const params = + show === 'all' ? omit$(queryParams, ['page', 'limit']) : queryParams - setState(toSearch({ ...queryParams, show })) + setState(toSearch({ ...params, show })) } // @TODO: Fix spreading of string when the column is not an array @@ -175,10 +177,8 @@ export function DataGridApp(): JSX.Element { ) } -function DataTable< - T extends { id: number; [k: string]: string | number }, ->(props: { - data: T[] +function DataTable(props: { + data: Array headers: string[] onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void onSearch: (value: { searchBy: string; search: string }) => void diff --git a/apps/devfractal-demo/src/server-side/components/DataBody.tsx b/apps/devfractal-demo/src/server-side/components/DataBody.tsx index df450bde..bcda421b 100644 --- a/apps/devfractal-demo/src/server-side/components/DataBody.tsx +++ b/apps/devfractal-demo/src/server-side/components/DataBody.tsx @@ -1,16 +1,15 @@ -import { TableBody, TableCell, TableRow } from 'devfractal' +import { faker } from '@faker-js/faker' +import { TableBody, TableCell, TableRow, keys } from 'devfractal' // @TODO: Improve generics -export function DataBody< - T extends { id: number; [k: string]: number | string }, ->({ data }: { data: readonly T[] }): JSX.Element { +export function DataBody({ data }: { data: Array }): JSX.Element { return ( {data.length > 0 ? ( data.map(product => { return ( - - {Object.keys(product) + + {keys(product) .filter(key => key !== 'id') .map(item => { return {product[item]} diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index 605a41f0..a155c19f 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -9,6 +9,7 @@ import { SelectTrigger, SelectValue, Text, + toInt, } from 'devfractal' // @TODO: Add prop types @@ -74,18 +75,21 @@ export function Pagination({ {show === 'paged' && ( - - - diff --git a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx index b5937c71..4300078d 100644 --- a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx +++ b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx @@ -1,20 +1,18 @@ -import { Box, isArray } from 'devfractal' +import { Box, isArray, keys } from 'devfractal' import { TableVirtuoso } from 'react-virtuoso' import { HeaderWrapper } from './DataHeaders' // @TODO: Type mismatch with shadcn and div-table as components -export function VirtualDataTable< - T extends { id: number; [k: string]: number | string }, ->({ +export function VirtualDataTable({ data, headers, onOrder, onSearch, }: { headers: string[] - data: readonly T[] + data: Array onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void onSearch: (value: { searchBy: string; search: string }) => void }): JSX.Element { @@ -49,7 +47,7 @@ export function VirtualDataTable< )} itemContent={(_index, product) => ( <> - {Object.keys(product) + {keys(product) .filter(key => key !== 'id') .map(item => { return ( diff --git a/apps/devfractal-demo/src/server-side/components/index.ts b/apps/devfractal-demo/src/server-side/components/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/devfractal-demo/src/server-side/index.ts b/apps/devfractal-demo/src/server-side/index.ts new file mode 100644 index 00000000..ad2af726 --- /dev/null +++ b/apps/devfractal-demo/src/server-side/index.ts @@ -0,0 +1,2 @@ +export * from './DataGridApp' +export * from './products' diff --git a/apps/devfractal-demo/src/server-side/products.ts b/apps/devfractal-demo/src/server-side/products.ts index ad2697a8..58520c3b 100644 --- a/apps/devfractal-demo/src/server-side/products.ts +++ b/apps/devfractal-demo/src/server-side/products.ts @@ -2,6 +2,10 @@ export type Products = typeof data export type Product = (typeof data)[number] +export type ProductWithoutTitle = Omit +export type ProductWithoutPrice = Omit +export type ProductWithoutBrand = Omit +export type ProductWithoutCategory = Omit export const headers = ['title', 'price', 'brand', 'category'] diff --git a/apps/devfractal-demo/src/server-side/query.ts b/apps/devfractal-demo/src/server-side/query.ts index 55d4bc61..55517ce6 100644 --- a/apps/devfractal-demo/src/server-side/query.ts +++ b/apps/devfractal-demo/src/server-side/query.ts @@ -1,6 +1,6 @@ import { isDefined, keys, pick$, urlcat } from 'devfractal' -import type { ProductsResponse } from '@/mocks/data-table/operations' +import type { ProductsResponse } from '@/mocks/data-table/database' export async function fetchProducts(params: { show: 'all' | 'paged' diff --git a/apps/devfractal-demo/src/server-side/utils.ts b/apps/devfractal-demo/src/server-side/utils.ts deleted file mode 100644 index e182529f..00000000 --- a/apps/devfractal-demo/src/server-side/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { pick$ } from 'devfractal' - -import { type Product, type Products } from './products' - -type Order = 'asc' | 'desc' - -export function cmp(key: K, order: Order) { - return (a: T, b: T): number => { - const x = a[key] - const y = b[key] - - if (x < y) { - return order === 'asc' ? -1 : 1 - } - if (x > y) { - return order === 'asc' ? 1 : -1 - } - return 0 - } -} - -export function iorderBy( - arr: Iterable, - key: K, - order: Order = 'asc', -): T[] { - return [...arr].sort(cmp(key, order)) -} - -// @TODO: Type safety and server side version -export const getProductsByColumns = (products: Products, columns: string[]) => { - return products.map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ) -} From f6a1ef285836f5a82d50dc72cd121d0a298d1249 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 08:40:09 +0530 Subject: [PATCH 06/12] resolve lint error --- examples/ui-examples/src/main.tsx | 1 - libs/ui-core/src/cui/ErrorFallback.tsx | 1 + libs/ui-core/src/cui/Loading.tsx | 1 + libs/ui-core/src/cui/ZodErrorView.tsx | 1 + libs/ui/src/pagination/usePagination.test.ts | 1 + 5 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/ui-examples/src/main.tsx b/examples/ui-examples/src/main.tsx index d060717b..6e19ef3e 100644 --- a/examples/ui-examples/src/main.tsx +++ b/examples/ui-examples/src/main.tsx @@ -1,5 +1,4 @@ import { createRoot } from '@srtp/react' - import './global.css' import { H1 } from '@srtp/ui' diff --git a/libs/ui-core/src/cui/ErrorFallback.tsx b/libs/ui-core/src/cui/ErrorFallback.tsx index 6050400b..6e68d8fe 100644 --- a/libs/ui-core/src/cui/ErrorFallback.tsx +++ b/libs/ui-core/src/cui/ErrorFallback.tsx @@ -4,6 +4,7 @@ import { ZodError } from 'zod' import { H2, Paragraph } from '../ui' import { cn } from '../utils' + import { Container } from './Container' import { ZodErrorView } from './ZodErrorView' diff --git a/libs/ui-core/src/cui/Loading.tsx b/libs/ui-core/src/cui/Loading.tsx index 4be6142e..c56c0122 100644 --- a/libs/ui-core/src/cui/Loading.tsx +++ b/libs/ui-core/src/cui/Loading.tsx @@ -1,4 +1,5 @@ import { Loader2 } from 'lucide-react' + import { cn } from '../utils' export type LoadingProps = Readonly<{ diff --git a/libs/ui-core/src/cui/ZodErrorView.tsx b/libs/ui-core/src/cui/ZodErrorView.tsx index cff5492b..c7da1f5a 100644 --- a/libs/ui-core/src/cui/ZodErrorView.tsx +++ b/libs/ui-core/src/cui/ZodErrorView.tsx @@ -2,6 +2,7 @@ import { CastError } from '@srtp/core' import type { ZodError } from 'zod' import { H1, Ul } from '../ui' + import { Container } from './Container' export type ZodErrorProps = { diff --git a/libs/ui/src/pagination/usePagination.test.ts b/libs/ui/src/pagination/usePagination.test.ts index 97ff9bc6..12c6d159 100644 --- a/libs/ui/src/pagination/usePagination.test.ts +++ b/libs/ui/src/pagination/usePagination.test.ts @@ -1,5 +1,6 @@ import { renderHook, act } from '@testing-library/react' import { expect, expectTypeOf, test, describe } from 'vitest' + import { usePagination, type PaginationResult, From 261ee825c70d65f353d0b04011183acfbfdf8bf9 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 09:18:08 +0530 Subject: [PATCH 07/12] refactor pagination --- .../src/server-side/components/Pagination.tsx | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index 946d5992..a155c19f 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -61,41 +61,12 @@ export function Pagination({ per page )} - - - Show all {totalItems} - - {show === 'paged' && ( - - )} - - {show === 'paged' && ( - per page - )} - Show all {totalItems} - {show === 'paged' && ( - - {currentPage} of {totalPages} - - )} {show === 'paged' && ( {currentPage} of {totalPages} From 11f6a54fb9625d53cee9cf30b8fae33cb900df90 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 22:44:59 +0530 Subject: [PATCH 08/12] resolve error in column selection; fix handler operations --- .../src/mocks/data-table/database.ts | 92 +++++++++++++++++-- .../src/mocks/data-table/utils.ts | 6 ++ .../src/server-side/DataGridApp.tsx | 40 +++----- .../src/server-side/components/DataBody.tsx | 27 ------ .../src/server-side/components/DataTable.tsx | 90 ++++++++++++++++++ .../{DataHeaders.tsx => HeaderWrapper.tsx} | 44 +-------- .../src/server-side/components/Pagination.tsx | 6 +- .../components/VirtualDataTable.tsx | 7 +- .../src/server-side/components/index.ts | 0 9 files changed, 205 insertions(+), 107 deletions(-) delete mode 100644 apps/devfractal-demo/src/server-side/components/DataBody.tsx create mode 100644 apps/devfractal-demo/src/server-side/components/DataTable.tsx rename apps/devfractal-demo/src/server-side/components/{DataHeaders.tsx => HeaderWrapper.tsx} (60%) delete mode 100644 apps/devfractal-demo/src/server-side/components/index.ts diff --git a/apps/devfractal-demo/src/mocks/data-table/database.ts b/apps/devfractal-demo/src/mocks/data-table/database.ts index 0acc3f93..e9f27db7 100644 --- a/apps/devfractal-demo/src/mocks/data-table/database.ts +++ b/apps/devfractal-demo/src/mocks/data-table/database.ts @@ -4,10 +4,8 @@ import { filter, isArray, isDefined, - isUndefined, omit$, paged, - pick$, pipe, } from 'devfractal' @@ -33,16 +31,42 @@ export function getDataFromTable(params: { searchBy?: keyof Product | 'all' search?: string }): ProductsResponse { + const selected = isArray(params.column) ? params.column : [params.column] + if ( params.show === 'all' && - isUndefined(params.page) && - isUndefined(params.limit) + isDefined(params.sortBy) && + isDefined(params.order) && + isDefined(params.searchBy) && + isDefined(params.search) ) { - const selected = isArray(params.column) ? params.column : [params.column] + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + orderBy(params.sortBy as keyof Product, params.order), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + if ( + params.show === 'all' && + isDefined(params.sortBy) && + isDefined(params.order) + ) { return { - products: data.map(product => - pick$(product, [...selected] as (keyof Product)[]), + products: chain( + data, + orderBy(params.sortBy as keyof Product, params.order), + picked([...selected] as (keyof Product)[]), ), columns: selected, currentPage: 0, @@ -51,6 +75,37 @@ export function getDataFromTable(params: { } } + if ( + params.show === 'all' && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + if (params.show === 'all') { + return { + products: chain(data, picked([...selected] as (keyof Product)[])), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + return getPaginatedProducts({ ...omit$(params, ['show']) }) } @@ -111,6 +166,29 @@ function getPaginatedProducts(params: { } } + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + paged(params.page, params.limit), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + if (isDefined(params.page) && isDefined(params.limit)) { return { products: chain( diff --git a/apps/devfractal-demo/src/mocks/data-table/utils.ts b/apps/devfractal-demo/src/mocks/data-table/utils.ts index 99acf63c..0e4f7c0d 100644 --- a/apps/devfractal-demo/src/mocks/data-table/utils.ts +++ b/apps/devfractal-demo/src/mocks/data-table/utils.ts @@ -50,3 +50,9 @@ export function picked(keys: K[]) { ) } } + +export function insertAt(src: readonly T[], index: number, value: T) { + const ans = [...src.slice(0, index), value, ...src.slice(index)] + console.log(ans) + return ans +} diff --git a/apps/devfractal-demo/src/server-side/DataGridApp.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx index f688e7aa..2a56453c 100644 --- a/apps/devfractal-demo/src/server-side/DataGridApp.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -1,21 +1,21 @@ import { useQuery } from '@tanstack/react-query' import { - Table, VStack, Text, HStack, toInt, toSearch, remove$, - insert$, fromSearchParams, isEmptyString, omit$, + isArray, } from 'devfractal' import { useSearchParams } from 'react-router-dom' -import { DataBody } from './components/DataBody' -import { DataHeader } from './components/DataHeaders' +import { insertAt } from '@/mocks/data-table/utils' + +import { DataTable } from './components/DataTable' import { Header } from './components/Header' import { Pagination } from './components/Pagination' import { VirtualDataTable } from './components/VirtualDataTable' @@ -24,8 +24,6 @@ import { fetchProducts } from './query' // @TODO: Row operations: https://ui.shadcn.com/docs/components/combobox#dropdown-menu // https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state -// @TODO: Too many rerenders? -// @TODO: Too many rerenders? export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( @@ -117,14 +115,18 @@ export function DataGridApp(): JSX.Element { setState(toSearch({ ...params, show })) } - // @TODO: Fix spreading of string when the column is not an array - // @TODO: Fix spreading of string when the column is not an array function handleColumns(header: string) { + const selected = isArray(queryParams.column) + ? queryParams.column + : [queryParams.column] + const index = headers.indexOf(header) - const column = - queryParams.column.indexOf(header) !== -1 - ? remove$(queryParams.column, queryParams.column.indexOf(header)) - : insert$(queryParams.column, index, header.toString()) + + const column = selected.includes(header) + ? selected.length > 1 + ? remove$(selected, selected.indexOf(header)) + : selected + : insertAt(selected, index, header) setState(toSearch({ ...queryParams, column })) } @@ -179,17 +181,3 @@ export function DataGridApp(): JSX.Element { ) } - -function DataTable(props: { - data: Array - headers: string[] - onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void - onSearch: (value: { searchBy: string; search: string }) => void -}): JSX.Element { - return ( - - - -
- ) -} diff --git a/apps/devfractal-demo/src/server-side/components/DataBody.tsx b/apps/devfractal-demo/src/server-side/components/DataBody.tsx deleted file mode 100644 index bcda421b..00000000 --- a/apps/devfractal-demo/src/server-side/components/DataBody.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { faker } from '@faker-js/faker' -import { TableBody, TableCell, TableRow, keys } from 'devfractal' - -// @TODO: Improve generics -export function DataBody({ data }: { data: Array }): JSX.Element { - return ( - - {data.length > 0 ? ( - data.map(product => { - return ( - - {keys(product) - .filter(key => key !== 'id') - .map(item => { - return {product[item]} - })} - - ) - }) - ) : ( - - No data! - - )} - - ) -} diff --git a/apps/devfractal-demo/src/server-side/components/DataTable.tsx b/apps/devfractal-demo/src/server-side/components/DataTable.tsx new file mode 100644 index 00000000..519836dd --- /dev/null +++ b/apps/devfractal-demo/src/server-side/components/DataTable.tsx @@ -0,0 +1,90 @@ +import { faker } from '@faker-js/faker' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + isArray, + keys, +} from 'devfractal' + +import { HeaderWrapper } from './HeaderWrapper' + +export function DataTable(props: { + data: Array + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + +
+ ) +} + +export function DataHeader({ + headers, + onOrder, + onSearch, +}: { + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + {isArray(headers) ? ( + headers.map(header => { + return ( + + + + ) + }) + ) : ( + + + + )} + + + ) +} + +// @TODO: Improve generics +export function DataBody({ data }: { data: Array }): JSX.Element { + return ( + + {data.length > 0 ? ( + data.map(product => { + return ( + + {keys(product) + .filter(key => key !== 'id') + .map(item => { + return {product[item]} + })} + + ) + }) + ) : ( + + No data! + + )} + + ) +} diff --git a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx b/apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx similarity index 60% rename from apps/devfractal-demo/src/server-side/components/DataHeaders.tsx rename to apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx index dc4ba5fa..a821277d 100644 --- a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx +++ b/apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx @@ -1,55 +1,13 @@ -import { PopoverContent } from '@radix-ui/react-popover' import { Button, Input, Popover, + PopoverContent, PopoverTrigger, - TableHead, - TableHeader, - TableRow, VStack, - isArray, } from 'devfractal' import React from 'react' -export function DataHeader({ - headers, - onOrder, - onSearch, -}: { - headers: string[] - onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void - onSearch: (value: { searchBy: string; search: string }) => void -}): JSX.Element { - return ( - - - {isArray(headers) ? ( - headers.map(header => { - return ( - - - - ) - }) - ) : ( - - - - )} - - - ) -} - export function HeaderWrapper({ header, onOrder, diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index a155c19f..79766c86 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -62,7 +62,11 @@ export function Pagination({ )} - + Show all {totalItems} diff --git a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx index 4300078d..12112df1 100644 --- a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx +++ b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx @@ -1,10 +1,11 @@ import { Box, isArray, keys } from 'devfractal' import { TableVirtuoso } from 'react-virtuoso' -import { HeaderWrapper } from './DataHeaders' - -// @TODO: Type mismatch with shadcn and div-table as components +import { HeaderWrapper } from './HeaderWrapper' +// @TODO: Type mismatch with shad-cn and div-table as components -> Cannot be merged due to inconsistent html semantics +// @TODO: Need to check if only the table body can use virtual table +// @TODO: Fix styling for virtual table export function VirtualDataTable({ data, headers, diff --git a/apps/devfractal-demo/src/server-side/components/index.ts b/apps/devfractal-demo/src/server-side/components/index.ts deleted file mode 100644 index e69de29b..00000000 From 8a20bbdd291b836c07eaa86b87a962e55f3ec20a Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 22:48:02 +0530 Subject: [PATCH 09/12] resolve lint error --- apps/devfractal-demo/src/mocks/data-table/utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/devfractal-demo/src/mocks/data-table/utils.ts b/apps/devfractal-demo/src/mocks/data-table/utils.ts index 0e4f7c0d..0d5af526 100644 --- a/apps/devfractal-demo/src/mocks/data-table/utils.ts +++ b/apps/devfractal-demo/src/mocks/data-table/utils.ts @@ -52,7 +52,5 @@ export function picked(keys: K[]) { } export function insertAt(src: readonly T[], index: number, value: T) { - const ans = [...src.slice(0, index), value, ...src.slice(index)] - console.log(ans) - return ans + return [...src.slice(0, index), value, ...src.slice(index)] } From 5637b1efc6b46271945425f086cbab536dc7d30f Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 23:10:57 +0530 Subject: [PATCH 10/12] add virtuoso package --- pnpm-lock.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14e8a7dc..9c43dd4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -459,6 +459,9 @@ importers: react-router-dom: specifier: 6.16.0 version: 6.16.0(react-dom@18.2.0)(react@18.2.0) + react-virtuoso: + specifier: 4.6.1 + version: 4.6.1(react-dom@18.2.0)(react@18.2.0) sort-by: specifier: 1.2.0 version: 1.2.0 @@ -24636,6 +24639,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-virtuoso@4.6.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-dQq0yOdRjdWIYaiTvUbudqmTodGSdWGt5lVYz3mM07TTEV91yv7eL5Fn3FPEJOA36whScxPWg0GiYBIOZYEIEA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16 || >=17 || >= 18' + react-dom: '>=16 || >=17 || >= 18' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-window-infinite-loader@1.0.9(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw==} engines: {node: '>8.0.0'} From 0b3b92bcf45069202fb5d4fdb3663e65b85f0735 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Sun, 15 Oct 2023 23:10:57 +0530 Subject: [PATCH 11/12] add virtuoso package --- pnpm-lock.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14e8a7dc..9c43dd4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -459,6 +459,9 @@ importers: react-router-dom: specifier: 6.16.0 version: 6.16.0(react-dom@18.2.0)(react@18.2.0) + react-virtuoso: + specifier: 4.6.1 + version: 4.6.1(react-dom@18.2.0)(react@18.2.0) sort-by: specifier: 1.2.0 version: 1.2.0 @@ -24636,6 +24639,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-virtuoso@4.6.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-dQq0yOdRjdWIYaiTvUbudqmTodGSdWGt5lVYz3mM07TTEV91yv7eL5Fn3FPEJOA36whScxPWg0GiYBIOZYEIEA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16 || >=17 || >= 18' + react-dom: '>=16 || >=17 || >= 18' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-window-infinite-loader@1.0.9(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw==} engines: {node: '>8.0.0'} From f7255d8f4f0e9dd6b62a0b0936ced7bf56bdbfb2 Mon Sep 17 00:00:00 2001 From: Shibashis Date: Tue, 24 Oct 2023 18:26:06 +0530 Subject: [PATCH 12/12] minor changes --- .../src/mocks/data-table/operations.ts | 116 ------------------ .../src/server-side/products.ts | 4 - 2 files changed, 120 deletions(-) delete mode 100644 apps/devfractal-demo/src/mocks/data-table/operations.ts diff --git a/apps/devfractal-demo/src/mocks/data-table/operations.ts b/apps/devfractal-demo/src/mocks/data-table/operations.ts deleted file mode 100644 index e1eccf49..00000000 --- a/apps/devfractal-demo/src/mocks/data-table/operations.ts +++ /dev/null @@ -1,116 +0,0 @@ -// import { pick$ } from 'devfractal' - -// import { iorderBy } from '@/mocks/data-table/utils' -// import { data, type Product, type Products } from '@/server-side/products' - -// export type ProductsResponse = { -// products: Array -// currentPage: number -// totalPages: number -// totalItems: number -// columns: string[] -// } - -// const products: Products = [] - -// // Load data -// const intializeProducts = (): void => { -// products.push(...data) -// } - -// intializeProducts() - -// // Operations -// export const getSelectedColumns = ( -// products: Products, -// columns: string[], -// ): ProductsResponse => { -// return { -// products: data.map(product => -// pick$(product, ['id', ...(columns as (keyof Product)[])]), -// ), -// columns, -// currentPage: 0, -// totalItems: products.length, -// totalPages: 0, -// } -// } - -// export const getSlicedProducts = ( -// page: number, -// limit: number, -// columns: string[], -// ): ProductsResponse => { -// // @TODO: use paged from array methods -// const endIndex = page * limit -// const startIndex = endIndex - limit - -// const totalPages = Math.ceil(products.length / limit) - -// return { -// products: products.slice(startIndex, endIndex), -// currentPage: page, -// totalPages, -// totalItems: products.length, -// columns, -// } -// } - -// export const getSortedProducts = ( -// show: 'all' | 'paged', -// page: number, -// limit: number, -// columns: string[], -// sortKey: keyof Product, -// order: 'asc' | 'desc', -// ): ProductsResponse => { -// const slicedResult = -// show === 'all' -// ? getSelectedColumns(products, columns) -// : getSlicedProducts(page, limit, columns) - -// return { -// ...slicedResult, -// products: iorderBy(slicedResult.products, sortKey, order), -// } -// } - -// // @TODO: Function overloading? - -// export const getSearchedProducts = ( -// show: 'all' | 'paged', -// page: number, -// limit: number, -// columns: string[], -// searchBy: keyof Product | 'all', -// search: string, -// sortKey?: keyof Product, -// order?: 'asc' | 'desc', -// ): ProductsResponse => { -// const result = -// sortKey && order -// ? getSortedProducts(show, page, limit, columns, sortKey, order) -// : getSlicedProducts(page, limit, columns) - -// const searchResult = -// searchBy !== 'all' -// ? result.products.filter(product => -// product[searchBy] -// .toString() -// .toLowerCase() -// .includes(search.toLowerCase()), -// ) -// : result.products.filter(product => -// Object.values(product).some(value => -// value.toString().toLowerCase().includes(search.toLowerCase()), -// ), -// ) - -// const totalPages = Math.ceil(products.length / limit) - -// return { -// ...result, -// products: searchResult, -// totalPages, -// } -// } diff --git a/apps/devfractal-demo/src/server-side/products.ts b/apps/devfractal-demo/src/server-side/products.ts index 58520c3b..ad2697a8 100644 --- a/apps/devfractal-demo/src/server-side/products.ts +++ b/apps/devfractal-demo/src/server-side/products.ts @@ -2,10 +2,6 @@ export type Products = typeof data export type Product = (typeof data)[number] -export type ProductWithoutTitle = Omit -export type ProductWithoutPrice = Omit -export type ProductWithoutBrand = Omit -export type ProductWithoutCategory = Omit export const headers = ['title', 'price', 'brand', 'category']