From e04720e687f45ec7b5313ab612d424b40312f707 Mon Sep 17 00:00:00 2001 From: nainglinnkhant Date: Wed, 3 Jul 2024 14:24:49 +0700 Subject: [PATCH] fix: fix minor bugs and update skeleton loading ui --- src/app/_lib/validations.ts | 2 + src/app/page.tsx | 25 ++++------ .../advanced/data-table-filter-item.tsx | 2 +- .../advanced/data-table-multi-filter.tsx | 38 ++++++-------- .../data-table/data-table-skeleton.tsx | 46 +++-------------- src/config/site.ts | 2 +- src/hooks/use-data-table.ts | 49 ++++++++----------- src/lib/utils.ts | 20 ++++++++ 8 files changed, 75 insertions(+), 109 deletions(-) diff --git a/src/app/_lib/validations.ts b/src/app/_lib/validations.ts index 57bd4d0..bd9059c 100644 --- a/src/app/_lib/validations.ts +++ b/src/app/_lib/validations.ts @@ -13,6 +13,8 @@ export const searchParamsSchema = z.object({ operator: z.enum(["and", "or"]).optional(), }) +export type SearchParams = z.infer + export const getTasksSchema = searchParamsSchema export type GetTasksSchema = z.infer diff --git a/src/app/page.tsx b/src/app/page.tsx index 13d32ad..e07911c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,6 @@ import * as React from "react" import type { SearchParams } from "@/types" -import { Skeleton } from "@/components/ui/skeleton" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" import { DateRangePicker } from "@/components/date-range-picker" import { Shell } from "@/components/shell" @@ -21,29 +20,25 @@ export default async function IndexPage({ searchParams }: IndexPageProps) { return ( - {/** - * The `DateRangePicker` component is used to render the date range picker UI. - * It is used to filter the tasks based on the selected date range it was created at. - * The business logic for filtering the tasks based on the selected date range is handled inside the component. - */} - }> - - } > + {/** + * The `DateRangePicker` component is used to render the date range picker UI. + * It is used to filter the tasks based on the selected date range it was created at. + * The business logic for filtering the tasks based on the selected date range is handled inside the component. + */} + {/** * Passing promises and consuming them using React.use for triggering the suspense fallback. * @see https://react.dev/reference/react/use diff --git a/src/components/data-table/advanced/data-table-filter-item.tsx b/src/components/data-table/advanced/data-table-filter-item.tsx index a0ad1af..835a008 100644 --- a/src/components/data-table/advanced/data-table-filter-item.tsx +++ b/src/components/data-table/advanced/data-table-filter-item.tsx @@ -157,7 +157,7 @@ export function DataTableFilterItem({ { setValue(event.target.value) column?.setFilterValue(event.target.value) diff --git a/src/components/data-table/advanced/data-table-multi-filter.tsx b/src/components/data-table/advanced/data-table-multi-filter.tsx index 7c7c08d..de4a75d 100644 --- a/src/components/data-table/advanced/data-table-multi-filter.tsx +++ b/src/components/data-table/advanced/data-table-multi-filter.tsx @@ -10,6 +10,7 @@ import { import type { Table } from "@tanstack/react-table" import { dataTableConfig, type DataTableConfig } from "@/config/data-table" +import { createQueryString } from "@/lib/utils" import { Button } from "@/components/ui/button" import { DropdownMenu, @@ -52,9 +53,15 @@ export function DataTableMultiFilter({ setSelectedOptions, defaultOpen, }: DataTableMultiFilterProps) { + const searchParams = useSearchParams() + + const currentOperator = dataTableConfig.logicalOperators.find( + (operator) => searchParams.get("operator") === operator.value + ) + const [open, setOpen] = React.useState(defaultOpen) const [operator, setOperator] = React.useState( - dataTableConfig.logicalOperators[0] + currentOperator || dataTableConfig.logicalOperators[0] ) return ( @@ -166,31 +173,16 @@ export function MultiFilterRow({ } }, [selectedOption?.options.length]) - // Create query string - const createQueryString = React.useCallback( - (params: Record) => { - const newSearchParams = new URLSearchParams(searchParams?.toString()) - - for (const [key, value] of Object.entries(params)) { - if (value === null) { - newSearchParams.delete(key) - } else { - newSearchParams.set(key, String(value)) - } - } - - return newSearchParams.toString() - }, - [searchParams] - ) - // Update operator query string React.useEffect(() => { if (operator?.value) { router.push( - `${pathname}?${createQueryString({ - operator: operator.value, - })}`, + `${pathname}?${createQueryString( + { + operator: operator.value, + }, + searchParams + )}`, { scroll: false, } @@ -301,7 +293,7 @@ export function MultiFilterRow({ { column?.setFilterValue(event.target.value) }} diff --git a/src/components/data-table/data-table-skeleton.tsx b/src/components/data-table/data-table-skeleton.tsx index b752a1e..d75a134 100644 --- a/src/components/data-table/data-table-skeleton.tsx +++ b/src/components/data-table/data-table-skeleton.tsx @@ -23,27 +23,6 @@ interface DataTableSkeletonProps extends React.HTMLAttributes { */ rowCount?: number - /** - * The number of searchable columns in the table. - * @default 0 - * @type number | undefined - */ - searchableColumnCount?: number - - /** - * The number of filterable columns in the table. - * @default 0 - * @type number | undefined - */ - filterableColumnCount?: number - - /** - * Flag to show the table view options. - * @default undefined - * @type boolean | undefined - */ - showViewOptions?: boolean - /** * The width of each cell in the table. * The length of the array should be equal to the columnCount. @@ -72,9 +51,6 @@ export function DataTableSkeleton(props: DataTableSkeletonProps) { const { columnCount, rowCount = 10, - searchableColumnCount = 0, - filterableColumnCount = 0, - showViewOptions = true, cellWidths = ["auto"], withPagination = true, shrinkZero = false, @@ -87,22 +63,12 @@ export function DataTableSkeleton(props: DataTableSkeletonProps) { className={cn("w-full space-y-2.5 overflow-auto", className)} {...skeletonProps} > -
-
- {searchableColumnCount > 0 - ? Array.from({ length: searchableColumnCount }).map((_, i) => ( - - )) - : null} - {filterableColumnCount > 0 - ? Array.from({ length: filterableColumnCount }).map((_, i) => ( - - )) - : null} -
- {showViewOptions ? ( - - ) : null} + +
+ + + +
diff --git a/src/config/site.ts b/src/config/site.ts index b3fbe8b..c00e39e 100644 --- a/src/config/site.ts +++ b/src/config/site.ts @@ -3,7 +3,7 @@ import { env } from "@/env" export type SiteConfig = typeof siteConfig export const siteConfig = { - name: "Table", + name: "Shadcn View Table", description: "Shadcn table component with server side sorting, pagination, filtering, and custom views.", url: diff --git a/src/hooks/use-data-table.ts b/src/hooks/use-data-table.ts index 89483cd..f22c939 100644 --- a/src/hooks/use-data-table.ts +++ b/src/hooks/use-data-table.ts @@ -19,6 +19,7 @@ import { } from "@tanstack/react-table" import { z } from "zod" +import { createQueryString } from "@/lib/utils" import { useDebounce } from "@/hooks/use-debounce" interface UseDataTableProps { @@ -123,24 +124,6 @@ export function useDataTable({ } }, [filterFields]) - // Create query string - const createQueryString = React.useCallback( - (params: Record) => { - const newSearchParams = new URLSearchParams(searchParams?.toString()) - - for (const [key, value] of Object.entries(params)) { - if (value === null) { - newSearchParams.delete(key) - } else { - newSearchParams.set(key, String(value)) - } - } - - return newSearchParams.toString() - }, - [searchParams] - ) - // Initial column filters const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { return Array.from(searchParams.entries()).reduce( @@ -194,10 +177,13 @@ export function useDataTable({ React.useEffect(() => { router.push( - `${pathname}?${createQueryString({ - page: pageIndex + 1, - per_page: pageSize, - })}`, + `${pathname}?${createQueryString( + { + page: pageIndex + 1, + per_page: pageSize, + }, + searchParams + )}`, { scroll: false, } @@ -216,12 +202,15 @@ export function useDataTable({ React.useEffect(() => { router.push( - `${pathname}?${createQueryString({ - page, - sort: sorting[0]?.id - ? `${sorting[0]?.id}.${sorting[0]?.desc ? "desc" : "asc"}` - : null, - })}` + `${pathname}?${createQueryString( + { + page, + sort: sorting[0]?.id + ? `${sorting[0]?.id}.${sorting[0]?.desc ? "desc" : "asc"}` + : undefined, + }, + searchParams + )}` ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [sorting]) @@ -287,7 +276,9 @@ export function useDataTable({ } // After cumulating all the changes, push new params - router.push(`${pathname}?${createQueryString(newParamsObject)}`) + router.push( + `${pathname}?${createQueryString(newParamsObject, searchParams)}` + ) table.setPageIndex(0) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1d5f09b..9ad8afb 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,9 @@ +import type { ReadonlyURLSearchParams } from "next/navigation" import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" +import type { SearchParams } from "@/app/_lib/validations" + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } @@ -37,3 +40,20 @@ export function composeEventHandlers( } } } + +export function createQueryString( + params: Partial, + searchParams: ReadonlyURLSearchParams +) { + const newSearchParams = new URLSearchParams(searchParams?.toString()) + + for (const [key, value] of Object.entries(params)) { + if (value === null || value === undefined) { + newSearchParams.delete(key) + } else { + newSearchParams.set(key, String(value)) + } + } + + return newSearchParams.toString() +}