Skip to content

Commit

Permalink
Merge pull request #132 from sadmann7/update-table
Browse files Browse the repository at this point in the history
feat: update data-table
  • Loading branch information
sadmann7 authored Apr 21, 2024
2 parents faeaaaa + 073bfce commit ff777a7
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 49 deletions.
6 changes: 6 additions & 0 deletions src/components/data-table/data-table-faceted-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export function DataTableFacetedFilter<TData, TValue>({
/>
)}
<span>{option.label}</span>
{option.withCount &&
column?.getFacetedUniqueValues()?.get(option.value) && (
<span className="ml-auto flex size-4 items-center justify-center font-mono text-xs">
{column?.getFacetedUniqueValues().get(option.value)}
</span>
)}
</CommandItem>
)
})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/data-table/data-table-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function DataTablePagination<TData>({
pageSizeOptions = [10, 20, 30, 40, 50],
}: DataTablePaginationProps<TData>) {
return (
<div className="flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto px-2 py-1 sm:flex-row sm:gap-8">
<div className="flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8">
<div className="flex-1 whitespace-nowrap text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
Expand Down
72 changes: 45 additions & 27 deletions src/components/data-table/data-table-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cn } from "@/lib/utils"
import { Skeleton } from "@/components/ui/skeleton"
import {
Table,
Expand All @@ -8,7 +9,7 @@ import {
TableRow,
} from "@/components/ui/table"

interface DataTableSkeletonProps {
interface DataTableSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* The number of columns in the table.
* @type number
Expand Down Expand Up @@ -52,6 +53,13 @@ interface DataTableSkeletonProps {
*/
cellWidths?: string[]

/**
* Flag to show the pagination bar.
* @default true
* @type boolean | undefined
*/
withPagination?: boolean

/**
* Flag to prevent the table from shrinking to fit the content.
* @default false
Expand All @@ -60,17 +68,25 @@ interface DataTableSkeletonProps {
shrinkZero?: boolean
}

export function DataTableSkeleton({
columnCount,
rowCount = 10,
searchableColumnCount = 0,
filterableColumnCount = 0,
showViewOptions = true,
cellWidths = ["auto"],
shrinkZero = false,
}: DataTableSkeletonProps) {
export function DataTableSkeleton(props: DataTableSkeletonProps) {
const {
columnCount,
rowCount = 10,
searchableColumnCount = 0,
filterableColumnCount = 0,
showViewOptions = true,
cellWidths = ["auto"],
withPagination = true,
shrinkZero = false,
className,
...skeletonProps
} = props

return (
<div className="w-full space-y-3 overflow-auto">
<div
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
Expand Down Expand Up @@ -126,24 +142,26 @@ export function DataTableSkeleton({
</TableBody>
</Table>
</div>
<div className="flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto px-2 py-1 sm:flex-row sm:gap-8">
<Skeleton className="h-8 w-40" />
<div className="flex flex-col-reverse items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
<div className="flex items-center space-x-2">
<Skeleton className="h-8 w-24" />
<Skeleton className="h-8 w-[4.5rem]" />
</div>
<div className="flex items-center justify-center text-sm font-medium">
<Skeleton className="h-8 w-20" />
</div>
<div className="flex items-center space-x-2">
<Skeleton className="hidden size-8 lg:block" />
<Skeleton className="size-8" />
<Skeleton className="size-8" />
<Skeleton className="hidden size-8 lg:block" />
{withPagination ? (
<div className="flex w-full items-center justify-between gap-4 overflow-auto p-1 sm:gap-8">
<Skeleton className="h-7 w-40 shrink-0" />
<div className="flex items-center gap-4 sm:gap-6 lg:gap-8">
<div className="flex items-center space-x-2">
<Skeleton className="h-7 w-24" />
<Skeleton className="h-7 w-[4.5rem]" />
</div>
<div className="flex items-center justify-center text-sm font-medium">
<Skeleton className="h-7 w-20" />
</div>
<div className="flex items-center space-x-2">
<Skeleton className="hidden size-7 lg:block" />
<Skeleton className="size-7" />
<Skeleton className="size-7" />
<Skeleton className="hidden size-7 lg:block" />
</div>
</div>
</div>
</div>
) : null}
</div>
)
}
6 changes: 3 additions & 3 deletions src/components/kbd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const kbdVariants = cva(
"select-none rounded border px-1.5 py-0.5 font-mono text-[0.7rem] font-normal shadow-sm disabled:opacity-50",
"select-none rounded border px-1.5 py-px font-mono text-[0.7rem] font-normal shadow-sm disabled:opacity-50",
{
variants: {
variant: {
default: "bg-accent",
outline: "bg-background",
default: "bg-accent text-accent-foreground",
outline: "bg-background text-foreground",
},
},
defaultVariants: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/products-combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function ProductsCombobox() {
<span className="sr-only">Search products</span>
<Kbd
title={isMacOs() ? "Command" : "Control"}
className="pointer-events-none absolute right-1.5 top-2 hidden xl:block"
className="pointer-events-none absolute right-1.5 top-1.5 hidden xl:block"
>
{isMacOs() ? "⌘" : "Ctrl"} K
</Kbd>
Expand Down
33 changes: 27 additions & 6 deletions src/hooks/use-data-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ interface UseDataTableProps<TData, TValue> {
*/
pageCount: number

/**
* The default number of rows per page.
* @default 10
* @type number | undefined
* @example 20
*/
defaultPerPage?: number

/**
* The default sort order.
* @default "createdAt.desc"
* @type `${Extract<keyof TData, string | number>}.${"asc" | "desc"}` | undefined
* @example "updatedAt.desc"
*/
defaultSort?: `${Extract<keyof TData, string | number>}.${"asc" | "desc"}`

/**
* Defines filter fields for the table. Supports both dynamic faceted filters and search filters.
* - Faceted filters are rendered when `options` are provided for a filter field.
Expand Down Expand Up @@ -84,14 +100,16 @@ interface UseDataTableProps<TData, TValue> {

const schema = z.object({
page: z.coerce.number().default(1),
per_page: z.coerce.number().default(10),
sort: z.string().optional().default("createdAt.desc"),
per_page: z.coerce.number().optional(),
sort: z.string().optional(),
})

export function useDataTable<TData, TValue>({
data,
columns,
pageCount,
defaultPerPage = 10,
defaultSort = "createdAt.desc" as `${Extract<keyof TData, string | number>}.${"asc" | "desc"}`,
filterFields = [],
enableAdvancedFilter = false,
}: UseDataTableProps<TData, TValue>) {
Expand All @@ -100,9 +118,10 @@ export function useDataTable<TData, TValue>({
const searchParams = useSearchParams()

// Search params
const { page, per_page, sort } = schema.parse(
Object.fromEntries(searchParams)
)
const search = schema.parse(Object.fromEntries(searchParams))
const page = search.page
const perPage = search.per_page ?? defaultPerPage
const sort = search.sort ?? defaultSort
const [column, order] = sort?.split(".") ?? []

// Memoize computation of searchableColumns and filterableColumns
Expand Down Expand Up @@ -171,7 +190,7 @@ export function useDataTable<TData, TValue>({
const [{ pageIndex, pageSize }, setPagination] =
React.useState<PaginationState>({
pageIndex: page - 1,
pageSize: per_page,
pageSize: perPage,
})

const pagination = React.useMemo(
Expand Down Expand Up @@ -283,6 +302,8 @@ export function useDataTable<TData, TValue>({
// After cumulating all the changes, push new params
router.push(`${pathname}?${createQueryString(newParamsObject)}`)

table.setPageIndex(0)

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
60 changes: 60 additions & 0 deletions src/lib/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type Table } from "@tanstack/react-table"

export function exportTableToCSV<TData>(
/**
* The table to export.
* @type Table<TData>
*/
table: Table<TData>,
opts: {
/**
* The filename for the CSV file.
* @default "table"
* @example "tasks"
*/
filename?: string
/**
* The columns to exclude from the CSV file.
* @default []
* @example ["select", "actions"]
*/
excludeColumns?: (keyof TData | "select" | "actions")[]
} = {}
): void {
const { filename = "table", excludeColumns = [] } = opts

// Retrieve headers (column names)
const headers = table
.getAllLeafColumns()
.map((column) => column.id)
.filter((id) => !excludeColumns.includes(id))

// Build CSV content
const csvContent = [
headers.join(","),
...table.getRowModel().rows.map((row) =>
headers
.map((header) => {
const cellValue = row.getValue(header)
// Handle values that might contain commas or newlines
return typeof cellValue === "string"
? `"${cellValue.replace(/"/g, '""')}"`
: cellValue
})
.join(",")
),
].join("\n")

// Create a Blob with CSV content
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" })

// Create a link and trigger the download
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.setAttribute("href", url)
link.setAttribute("download", `${filename}.csv`)
link.style.visibility = "hidden"
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
18 changes: 7 additions & 11 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type SQL } from "drizzle-orm"
import { type FileWithPath } from "react-dropzone"
import type Stripe from "stripe"
import { type ClientUploadedFileData } from "uploadthing/types"

Expand Down Expand Up @@ -40,16 +39,6 @@ export interface SearchParams {
[key: string]: string | string[] | undefined
}

export interface Option {
label: string
value: string
icon?: React.ComponentType<{ className?: string }>
}

export type FileWithPreview = FileWithPath & {
preview: string
}

export interface UploadedFile<T = unknown> extends ClientUploadedFileData<T> {}

export interface StoredFile {
Expand All @@ -58,6 +47,13 @@ export interface StoredFile {
url: string
}

export interface Option {
label: string
value: string
icon?: React.ComponentType<{ className?: string }>
withCount?: boolean
}

export interface DataTableFilterField<TData> {
label: string
value: keyof TData
Expand Down

0 comments on commit ff777a7

Please sign in to comment.