Skip to content

Commit

Permalink
fix: fix minor bugs and update skeleton loading ui
Browse files Browse the repository at this point in the history
  • Loading branch information
nainglinnkhant committed Jul 3, 2024
1 parent 262b187 commit e04720e
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 109 deletions.
2 changes: 2 additions & 0 deletions src/app/_lib/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const searchParamsSchema = z.object({
operator: z.enum(["and", "or"]).optional(),
})

export type SearchParams = z.infer<typeof searchParamsSchema>

export const getTasksSchema = searchParamsSchema

export type GetTasksSchema = z.infer<typeof getTasksSchema>
Expand Down
25 changes: 10 additions & 15 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -21,29 +20,25 @@ export default async function IndexPage({ searchParams }: IndexPageProps) {

return (
<Shell className="gap-2">
{/**
* 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.
*/}
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
<DateRangePicker
triggerSize="sm"
triggerClassName="ml-auto w-56 sm:w-60"
align="end"
/>
</React.Suspense>
<React.Suspense
fallback={
<DataTableSkeleton
columnCount={5}
searchableColumnCount={1}
filterableColumnCount={2}
cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
shrinkZero
/>
}
>
{/**
* 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.
*/}
<DateRangePicker
triggerSize="sm"
triggerClassName="ml-auto w-56 sm:w-60"
align="end"
/>
{/**
* Passing promises and consuming them using React.use for triggering the suspense fallback.
* @see https://react.dev/reference/react/use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function DataTableFilterItem<TData>({
<Input
placeholder="Type here..."
className="h-8"
value={column?.getFilterValue() as string}
value={(column?.getFilterValue() as string) || ""}
onChange={(event) => {
setValue(event.target.value)
column?.setFilterValue(event.target.value)
Expand Down
38 changes: 15 additions & 23 deletions src/components/data-table/advanced/data-table-multi-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -52,9 +53,15 @@ export function DataTableMultiFilter<TData>({
setSelectedOptions,
defaultOpen,
}: DataTableMultiFilterProps<TData>) {
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 (
Expand Down Expand Up @@ -166,31 +173,16 @@ export function MultiFilterRow<TData>({
}
}, [selectedOption?.options.length])

// Create query string
const createQueryString = React.useCallback(
(params: Record<string, string | number | null>) => {
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,
}
Expand Down Expand Up @@ -301,7 +293,7 @@ export function MultiFilterRow<TData>({
<Input
placeholder="Type here..."
className="h-8"
value={column?.getFilterValue() as string}
value={(column?.getFilterValue() as string) || ""}
onChange={(event) => {
column?.setFilterValue(event.target.value)
}}
Expand Down
46 changes: 6 additions & 40 deletions src/components/data-table/data-table-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,6 @@ interface DataTableSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
*/
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.
Expand Down Expand Up @@ -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,
Expand All @@ -87,22 +63,12 @@ export function DataTableSkeleton(props: DataTableSkeletonProps) {
className={cn("w-full space-y-2.5 overflow-auto", className)}
{...skeletonProps}
>
<div className="flex w-full items-center justify-between space-x-2 overflow-auto p-1">
<div className="flex flex-1 items-center space-x-2">
{searchableColumnCount > 0
? Array.from({ length: searchableColumnCount }).map((_, i) => (
<Skeleton key={i} className="h-7 w-40 lg:w-60" />
))
: null}
{filterableColumnCount > 0
? Array.from({ length: filterableColumnCount }).map((_, i) => (
<Skeleton key={i} className="h-7 w-[4.5rem] border-dashed" />
))
: null}
</div>
{showViewOptions ? (
<Skeleton className="ml-auto hidden h-7 w-[4.5rem] lg:flex" />
) : null}
<Skeleton className="ml-auto h-7 w-56 sm:w-60" />
<div className="flex w-full items-center justify-end space-x-2 overflow-auto p-1">
<Skeleton className="h-7 w-[6.5rem]" />
<Skeleton className="h-7 w-[5.5rem]" />
<Skeleton className="h-7 w-20" />
<Skeleton className="hidden h-7 w-[6.25rem] lg:flex" />
</div>
<div className="rounded-md border">
<Table>
Expand Down
2 changes: 1 addition & 1 deletion src/config/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
49 changes: 20 additions & 29 deletions src/hooks/use-data-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TData, TValue> {
Expand Down Expand Up @@ -123,24 +124,6 @@ export function useDataTable<TData, TValue>({
}
}, [filterFields])

// Create query string
const createQueryString = React.useCallback(
(params: Record<string, string | number | null>) => {
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<ColumnFiltersState>(
Expand Down Expand Up @@ -194,10 +177,13 @@ export function useDataTable<TData, TValue>({

React.useEffect(() => {
router.push(
`${pathname}?${createQueryString({
page: pageIndex + 1,
per_page: pageSize,
})}`,
`${pathname}?${createQueryString(
{
page: pageIndex + 1,
per_page: pageSize,
},
searchParams
)}`,
{
scroll: false,
}
Expand All @@ -216,12 +202,15 @@ export function useDataTable<TData, TValue>({

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])
Expand Down Expand Up @@ -287,7 +276,9 @@ export function useDataTable<TData, TValue>({
}

// After cumulating all the changes, push new params
router.push(`${pathname}?${createQueryString(newParamsObject)}`)
router.push(
`${pathname}?${createQueryString(newParamsObject, searchParams)}`
)

table.setPageIndex(0)

Expand Down
20 changes: 20 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -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))
}
Expand Down Expand Up @@ -37,3 +40,20 @@ export function composeEventHandlers<E>(
}
}
}

export function createQueryString(
params: Partial<SearchParams>,
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()
}

0 comments on commit e04720e

Please sign in to comment.