.
This commit is contained in:
parent
e6498b4104
commit
f39dbe95cc
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@ -10,6 +10,12 @@
|
||||
"webRoot": "${workspaceFolder}/client"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Launch Chrome localhost",
|
||||
"type": "pwa-chrome",
|
||||
"port": 9222
|
||||
},
|
||||
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
|
||||
@ -1,22 +1,59 @@
|
||||
import { Card, CardContent } from "@/ui";
|
||||
|
||||
import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
||||
|
||||
import { DataTable } from "@/components";
|
||||
import { useDataTable } from "@/lib/hooks";
|
||||
import { IListArticles_Response_DTO, IListResponse_DTO } from "@shared/contexts";
|
||||
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||
import { IListArticles_Response_DTO, MoneyValue } from "@shared/contexts";
|
||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||
import { t } from "i18next";
|
||||
import { useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCatalogList } from "../hooks";
|
||||
|
||||
type CatalogTableViewProps = {
|
||||
data: IListResponse_DTO<IListArticles_Response_DTO>;
|
||||
};
|
||||
export const CatalogDataTable = () => {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
||||
console.log("pagination PADRE => ", pagination);
|
||||
|
||||
export const CatalogDataTable = ({ data }: CatalogTableViewProps) => {
|
||||
const columns = useMemo(
|
||||
const { data, isPending, isError, error } = useCatalogList({
|
||||
pagination: {
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
},
|
||||
searchTerm: globalFilter,
|
||||
});
|
||||
|
||||
const columns = useMemo<ColumnDef<IListArticles_Response_DTO, any>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "description" as const,
|
||||
accessorKey: "description",
|
||||
size: 400,
|
||||
enableHiding: false,
|
||||
enableSorting: false,
|
||||
header: () => <>{t("catalog.list.columns.description")}</>,
|
||||
enableResizing: false,
|
||||
size: 300,
|
||||
},
|
||||
{
|
||||
id: "points" as const,
|
||||
accessorKey: "points",
|
||||
header: () => <div className='text-right'>{t("catalog.list.columns.points")}</div>,
|
||||
cell: ({ renderValue }: { renderValue: () => any }) => (
|
||||
<div className='text-right'>{renderValue()}</div>
|
||||
),
|
||||
enableResizing: false,
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
id: "retail_price" as const,
|
||||
accessorKey: "retail_price",
|
||||
header: () => <div className='text-right'>{t("catalog.list.columns.retail_price")}</div>,
|
||||
cell: ({ row }: { row: Row<any> }) => {
|
||||
const price = MoneyValue.create(row.original.retail_price).object;
|
||||
return <div className='text-right'>{price.toFormat()}</div>;
|
||||
},
|
||||
enableResizing: false,
|
||||
size: 20,
|
||||
},
|
||||
],
|
||||
[]
|
||||
@ -28,9 +65,41 @@ export const CatalogDataTable = ({ data }: CatalogTableViewProps) => {
|
||||
pageCount: data?.total_pages ?? -1,
|
||||
});
|
||||
|
||||
if (isError) {
|
||||
return <ErrorOverlay subtitle={(error as Error).message} />;
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<DataTableSkeleton
|
||||
columnCount={6}
|
||||
searchableColumnCount={1}
|
||||
filterableColumnCount={2}
|
||||
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
||||
shrinkZero
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (data?.total_items === 0 && !isFiltered) {
|
||||
return (
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza cargando los artículos del catálogo'
|
||||
buttonText=''
|
||||
onButtonClick={() => navigate("/catalog/add")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable table={table} paginationOptions={{ visible: true }} />
|
||||
<DataTable table={table} paginationOptions={{ visible: true }}>
|
||||
<DataTableToolbar table={table} />
|
||||
</DataTable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Layout, LayoutContent, LayoutHeader } from "@/components";
|
||||
import { DataTableProvider } from "@/lib/hooks";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { CatalogProvider } from "./CatalogContext";
|
||||
|
||||
export const CatalogLayout = ({ children }: PropsWithChildren) => {
|
||||
@ -9,8 +9,14 @@ export const CatalogLayout = ({ children }: PropsWithChildren) => {
|
||||
<Layout>
|
||||
<LayoutHeader />
|
||||
<LayoutContent>
|
||||
<DataTableProvider>{children}</DataTableProvider>
|
||||
<div className='flex items-center'>
|
||||
<h1 className='text-lg font-semibold md:text-2xl'>
|
||||
<Trans i18nKey='catalog.title' />
|
||||
</h1>
|
||||
</div>
|
||||
{children}
|
||||
</LayoutContent>
|
||||
1
|
||||
</Layout>
|
||||
</CatalogProvider>
|
||||
);
|
||||
|
||||
@ -28,108 +28,17 @@ import {
|
||||
|
||||
import { File, ListFilter, MoreHorizontal, PlusCircle } from "lucide-react";
|
||||
|
||||
import { DataTable, DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
||||
|
||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||
import { useMemo } from "react";
|
||||
import { DataTableProvider } from "@/lib/hooks";
|
||||
import { Trans } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CatalogDataTable } from "./components";
|
||||
import { useCatalogList } from "./hooks/useCatalogList";
|
||||
|
||||
export const CatalogList = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { pagination } = useDataTableContext();
|
||||
console.log("pagination PADRE => ", pagination);
|
||||
|
||||
const { data, isPending, isError, error, refetch } = useCatalogList({
|
||||
pagination: {
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
},
|
||||
});
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: "description" as const,
|
||||
accessorKey: "description",
|
||||
size: 400,
|
||||
enableHiding: false,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: data?.items ?? [],
|
||||
columns: columns,
|
||||
pageCount: data?.total_pages ?? -1,
|
||||
});
|
||||
|
||||
return <DataTable table={table} paginationOptions={{ visible: true }} />;
|
||||
|
||||
if (isError || isPending) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPagination({
|
||||
pageIndex: pagination.pageIndex + 1,
|
||||
});
|
||||
}}
|
||||
>
|
||||
+ Página
|
||||
</Button>
|
||||
{data.items.map((row) => (
|
||||
<p>{row.description}</p>
|
||||
))}
|
||||
<CatalogDataTable data={data} />;
|
||||
</>
|
||||
<DataTableProvider>
|
||||
<CatalogDataTable />
|
||||
</DataTableProvider>
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
return <ErrorOverlay subtitle={(error as Error).message} />;
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<Card x-chunk='dashboard-06-chunk-0'>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='catalog.title' />
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your products and view their sales performance.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<DataTableSkeleton
|
||||
columnCount={6}
|
||||
searchableColumnCount={1}
|
||||
filterableColumnCount={2}
|
||||
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
||||
shrinkZero
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (data?.total_items === 0) {
|
||||
return (
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza cargando los artículos del catálogo'
|
||||
buttonText=''
|
||||
onButtonClick={() => navigate("/catalog/add")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue='all'>
|
||||
|
||||
@ -9,12 +9,12 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/ui/table";
|
||||
import { ReactNode } from "react";
|
||||
import { PropsWithChildren, ReactNode } from "react";
|
||||
|
||||
import { Card, CardContent, CardFooter, CardHeader } from "@/ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader } from "@/ui";
|
||||
import { DataTableColumnHeader } from "./DataTableColumnHeader";
|
||||
import { DataTablePagination, DataTablePaginationProps } from "./DataTablePagination";
|
||||
import { DataTableToolbar } from "./DataTableToolbar";
|
||||
|
||||
export type DataTableColumnProps<TData, TValue> = ColumnDef<TData, TValue>;
|
||||
|
||||
@ -23,25 +23,31 @@ export type DataTablePaginationOptionsProps<TData> = Pick<
|
||||
"visible"
|
||||
>;
|
||||
|
||||
export type DataTableProps<TData> = {
|
||||
export type DataTableProps<TData> = PropsWithChildren<{
|
||||
table: ReactTable<TData>;
|
||||
caption?: ReactNode;
|
||||
className?: string;
|
||||
|
||||
paginationOptions?: DataTablePaginationOptionsProps<TData>;
|
||||
};
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
export function DataTable<TData>({ table, caption, paginationOptions }: DataTableProps<TData>) {
|
||||
export function DataTable<TData>({
|
||||
table,
|
||||
caption,
|
||||
paginationOptions,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: DataTableProps<TData>) {
|
||||
return (
|
||||
<>
|
||||
<DataTableToolbar table={table} />
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<DataTablePagination
|
||||
className='flex-1'
|
||||
visible={paginationOptions?.visible}
|
||||
table={table}
|
||||
/>
|
||||
<CardHeader className='pb-0'>
|
||||
<CardDescription
|
||||
className={cn("w-full space-y-2.5 overflow-auto mt-7", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='pt-6'>
|
||||
<Table>
|
||||
|
||||
@ -10,15 +10,10 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
Separator,
|
||||
} from "@/ui";
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowDownUpIcon,
|
||||
ArrowUpIcon,
|
||||
EyeOffIcon,
|
||||
} from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import { ArrowDownIcon, ArrowDownUpIcon, ArrowUpIcon, EyeOffIcon } from "lucide-react";
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
|
||||
table: Table<TData>;
|
||||
header: Header<TData, TValue>;
|
||||
}
|
||||
@ -34,20 +29,21 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
<div
|
||||
className={cn(
|
||||
"data-[state=open]:bg-accent font-semiboldw text-muted-foreground uppercase",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</div>
|
||||
|
||||
{header.column.getCanResize() && (
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
orientation='vertical'
|
||||
className={cn(
|
||||
"absolute top-0 h-full w-[5px] bg-black/10 cursor-col-resize",
|
||||
table.options.columnResizeDirection,
|
||||
header.column.getIsResizing() ? "bg-primary opacity-100" : "",
|
||||
header.column.getIsResizing() ? "bg-primary opacity-100" : ""
|
||||
)}
|
||||
{...{
|
||||
onDoubleClick: () => header.column.resetSize(),
|
||||
@ -55,12 +51,9 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
onTouchStart: header.getResizeHandler(),
|
||||
style: {
|
||||
transform:
|
||||
table.options.columnResizeMode === "onEnd" &&
|
||||
header.column.getIsResizing()
|
||||
table.options.columnResizeMode === "onEnd" && header.column.getIsResizing()
|
||||
? `translateX(${
|
||||
(table.options.columnResizeDirection === "rtl"
|
||||
? -1
|
||||
: 1) *
|
||||
(table.options.columnResizeDirection === "rtl" ? -1 : 1) *
|
||||
(table.getState().columnSizingInfo.deltaOffset ?? 0)
|
||||
}px)`
|
||||
: "",
|
||||
@ -79,41 +72,67 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
<Button
|
||||
aria-label={
|
||||
header.column.getIsSorted() === "desc"
|
||||
? "En orden descendente. Click para ordenar ascendentemente."
|
||||
? t("common.sort_desc_description")
|
||||
: header.column.getIsSorted() === "asc"
|
||||
? "En order ascendente. Click para ordenar descendentemente."
|
||||
: "Sin orden. Click para ordenar ascendentemente."
|
||||
? t("common.sort_asc_description")
|
||||
: t("sort_none_description")
|
||||
}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="-ml-3 h-8 data-[state=open]:bg-accent font-bold text-muted-foreground"
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
className='-ml-3 h-8 data-[state=open]:bg-accent font-bold text-muted-foreground'
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
|
||||
{header.column.getIsSorted() === "desc" ? (
|
||||
<ArrowDownIcon className="w-4 h-4 ml-2" />
|
||||
<ArrowDownIcon className='w-4 h-4 ml-2' aria-hidden='true' />
|
||||
) : header.column.getIsSorted() === "asc" ? (
|
||||
<ArrowUpIcon className="w-4 h-4 ml-2" />
|
||||
<ArrowUpIcon className='w-4 h-4 ml-2' aria-hidden='true' />
|
||||
) : (
|
||||
<ArrowDownUpIcon className="w-4 h-4 ml-2 text-muted-foreground/30" />
|
||||
<ArrowDownUpIcon
|
||||
className='w-4 h-4 ml-2 text-muted-foreground/30'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => header.column.toggleSorting(false)}>
|
||||
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Ascendente
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => header.column.toggleSorting(true)}>
|
||||
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Descendente
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuContent align='start'>
|
||||
{header.column.getCanSort() && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={() => header.column.toggleSorting(false)}
|
||||
aria-label={t("common.sort_asc")}
|
||||
>
|
||||
<ArrowUpIcon
|
||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{t("common.sort_asc")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => header.column.toggleSorting(true)}
|
||||
aria-label={t("common.sort_desc")}
|
||||
>
|
||||
<ArrowDownIcon
|
||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{t("common.sort_desc")}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{header.column.getCanSort() && header.column.getCanHide() && <DropdownMenuSeparator />}
|
||||
|
||||
{header.column.getCanHide() && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => header.column.toggleVisibility(false)}
|
||||
aria-label={t("Hide")}
|
||||
>
|
||||
<EyeOffIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Ocultar
|
||||
<EyeOffIcon
|
||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{t("Hide")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
|
||||
@ -2,6 +2,7 @@ import { DEFAULT_PAGE_SIZES, INITIAL_PAGE_INDEX } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui";
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
@ -31,12 +32,15 @@ export function DataTablePagination<TData>({
|
||||
return (
|
||||
<div className={cn("flex items-center justify-between px-2", className)}>
|
||||
<div className='flex-1 text-base text-muted-foreground'>
|
||||
{table.getFilteredSelectedRowModel().rows.length} de{" "}
|
||||
{table.getFilteredRowModel().rows.length} filas(s) seleccionadas.
|
||||
{t("common.rows_selected", {
|
||||
count: table.getFilteredSelectedRowModel().rows.length,
|
||||
total: table.getFilteredRowModel().rows.length,
|
||||
})}
|
||||
</div>
|
||||
<div className='flex items-center space-x-6 lg:space-x-8'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<p className='text-base font-medium'>Filas por página</p>
|
||||
<p className='text-base font-medium'>{t("common.rows_per_page")}</p>
|
||||
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
@ -56,7 +60,10 @@ export function DataTablePagination<TData>({
|
||||
</Select>
|
||||
</div>
|
||||
<div className='flex w-[100px] items-center justify-center text-base font-medium'>
|
||||
Página {table.getState().pagination.pageIndex + 1} de {table.getPageCount()}
|
||||
{t("common.num_page_of_total", {
|
||||
count: table.getState().pagination.pageIndex + 1,
|
||||
total: table.getPageCount(),
|
||||
})}
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button
|
||||
@ -65,7 +72,7 @@ export function DataTablePagination<TData>({
|
||||
onClick={() => table.setPageIndex(INITIAL_PAGE_INDEX)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className='sr-only'>Ir a la primera página</span>
|
||||
<span className='sr-only'>{t("common.go_to_first_page")}</span>
|
||||
<ChevronsLeftIcon className='w-4 h-4' />
|
||||
</Button>
|
||||
<Button
|
||||
@ -74,7 +81,7 @@ export function DataTablePagination<TData>({
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className='sr-only'>Ir a la página anterior</span>
|
||||
<span className='sr-only'>{t("common.go_to_prev_page")}</span>
|
||||
<ChevronLeftIcon className='w-4 h-4' />
|
||||
</Button>
|
||||
<Button
|
||||
@ -83,7 +90,7 @@ export function DataTablePagination<TData>({
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className='sr-only'>Ir a la página siguiente</span>
|
||||
<span className='sr-only'>{t("common.go_to_next_page")}</span>
|
||||
<ChevronRightIcon className='w-4 h-4' />
|
||||
</Button>
|
||||
<Button
|
||||
@ -92,7 +99,7 @@ export function DataTablePagination<TData>({
|
||||
onClick={() => table.setPageIndex(table.getPageCount() + 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className='sr-only'>Ir a la última página</span>
|
||||
<span className='sr-only'>{t("common.go_to_last_page")}</span>
|
||||
<ChevronsRightIcon className='w-4 h-4' />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
import {
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/ui";
|
||||
|
||||
interface DataTableSkeletonProps {
|
||||
/**
|
||||
@ -53,11 +46,20 @@ interface DataTableSkeletonProps {
|
||||
cellWidths?: string[];
|
||||
|
||||
/**
|
||||
* Flag to prevent the table from shrinking to fit the content.
|
||||
* Flag to show the pagination bar.
|
||||
* @default true
|
||||
* @type boolean | undefined
|
||||
*/
|
||||
withPagination?: boolean;
|
||||
|
||||
/**
|
||||
* Flag to prevent the table cells from shrinking.
|
||||
* @default false
|
||||
* @type boolean | undefined
|
||||
*/
|
||||
shrinkZero?: boolean;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function DataTableSkeleton({
|
||||
@ -67,32 +69,33 @@ export function DataTableSkeleton({
|
||||
filterableColumnCount = 0,
|
||||
showViewOptions = true,
|
||||
cellWidths = ["auto"],
|
||||
withPagination = true,
|
||||
shrinkZero = false,
|
||||
className,
|
||||
...skeletonProps
|
||||
}: DataTableSkeletonProps) {
|
||||
return (
|
||||
<div className="w-full space-y-3 overflow-auto">
|
||||
<div className="flex items-center justify-between w-full p-1 space-x-2 overflow-auto">
|
||||
<div className="flex items-center flex-1 space-x-2">
|
||||
<div className={cn("w-full space-y-2.5 overflow-auto", className)} {...skeletonProps}>
|
||||
<div className='flex items-center justify-between w-full p-1 space-x-2 overflow-auto'>
|
||||
<div className='flex items-center flex-1 space-x-2'>
|
||||
{searchableColumnCount > 0
|
||||
? Array.from({ length: searchableColumnCount }).map((_, i) => (
|
||||
<Skeleton key={i} className="w-40 h-7 lg:w-60" />
|
||||
<Skeleton key={i} className='w-40 h-7 lg:w-60' />
|
||||
))
|
||||
: null}
|
||||
{filterableColumnCount > 0
|
||||
? Array.from({ length: filterableColumnCount }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-7 w-[4.5rem] border-dashed" />
|
||||
<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}
|
||||
{showViewOptions ? <Skeleton className='ml-auto hidden h-7 w-[4.5rem] lg:flex' /> : null}
|
||||
</div>
|
||||
<div className="border rounded-md">
|
||||
<div className='border rounded-md'>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{Array.from({ length: 1 }).map((_, i) => (
|
||||
<TableRow key={i} className="hover:bg-transparent">
|
||||
<TableRow key={i} className='hover:bg-transparent'>
|
||||
{Array.from({ length: columnCount }).map((_, j) => (
|
||||
<TableHead
|
||||
key={j}
|
||||
@ -101,7 +104,7 @@ export function DataTableSkeleton({
|
||||
minWidth: shrinkZero ? cellWidths[j] : "auto",
|
||||
}}
|
||||
>
|
||||
<Skeleton className="w-full h-6" />
|
||||
<Skeleton className='w-full h-6' />
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
@ -109,7 +112,7 @@ export function DataTableSkeleton({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{Array.from({ length: rowCount }).map((_, i) => (
|
||||
<TableRow key={i} className="hover:bg-transparent">
|
||||
<TableRow key={i} className='hover:bg-transparent'>
|
||||
{Array.from({ length: columnCount }).map((_, j) => (
|
||||
<TableCell
|
||||
key={j}
|
||||
@ -118,7 +121,7 @@ export function DataTableSkeleton({
|
||||
minWidth: shrinkZero ? cellWidths[j] : "auto",
|
||||
}}
|
||||
>
|
||||
<Skeleton className="w-full h-6" />
|
||||
<Skeleton className='w-full h-6' />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
@ -126,24 +129,26 @@ export function DataTableSkeleton({
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex flex-col-reverse items-center justify-between w-full gap-4 px-2 py-1 overflow-auto sm:flex-row sm:gap-8">
|
||||
<Skeleton className="w-40 h-8" />
|
||||
<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="w-24 h-8" />
|
||||
<Skeleton className="h-8 w-[4.5rem]" />
|
||||
</div>
|
||||
<div className="flex items-center justify-center text-sm font-medium">
|
||||
<Skeleton className="w-20 h-8" />
|
||||
</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 items-center justify-between w-full gap-4 p-1 overflow-auto sm:gap-8'>
|
||||
<Skeleton className='w-40 h-7 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='w-24 h-7' />
|
||||
<Skeleton className='h-7 w-[4.5rem]' />
|
||||
</div>
|
||||
<div className='flex items-center justify-center text-sm font-medium'>
|
||||
<Skeleton className='w-20 h-7' />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,138 +1,54 @@
|
||||
import { Table } from "@tanstack/react-table";
|
||||
|
||||
//import { priorities, statuses } from './Data'
|
||||
import { DataTableFilterField } from "@/lib/types";
|
||||
import { DataTableFilterField, useDataTableContext } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button, Input } from "@/ui";
|
||||
import { CrossIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { DataTableFacetedFilter } from "../DataTableFacetedFilter";
|
||||
import { t } from "i18next";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { DataTableColumnOptions } from "./DataTableColumnOptions";
|
||||
|
||||
interface DataTableToolbarProps<TData>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface DataTableToolbarProps<TData> extends React.HTMLAttributes<HTMLDivElement> {
|
||||
table: Table<TData>;
|
||||
filterFields?: DataTableFilterField<TData>[];
|
||||
}
|
||||
|
||||
export function DataTableToolbar<TData>({
|
||||
table,
|
||||
filterFields = [],
|
||||
children,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: DataTableToolbarProps<TData>) {
|
||||
const isFiltered = table.getState().columnFilters.length > 0;
|
||||
|
||||
// Memoize computation of searchableColumns and filterableColumns
|
||||
const { searchableColumns, filterableColumns } = useMemo(() => {
|
||||
return {
|
||||
searchableColumns: filterFields.filter((field) => !field.options),
|
||||
filterableColumns: filterFields.filter((field) => field.options),
|
||||
};
|
||||
}, [filterFields]);
|
||||
const { globalFilter, isFiltered, setGlobalFilter, resetGlobalFilter } = useDataTableContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between space-x-2 overflow-auto p-1",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center flex-1 space-x-2">
|
||||
{searchableColumns.length > 0 &&
|
||||
searchableColumns.map(
|
||||
(column) =>
|
||||
table.getColumn(column.value ? String(column.value) : "") && (
|
||||
<Input
|
||||
key={String(column.value)}
|
||||
placeholder={column.placeholder}
|
||||
value={
|
||||
(table
|
||||
.getColumn(String(column.value))
|
||||
?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event) =>
|
||||
table
|
||||
.getColumn(String(column.value))
|
||||
?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="w-40 h-8 lg:w-64"
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{filterableColumns.length > 0 &&
|
||||
filterableColumns.map(
|
||||
(column) =>
|
||||
table.getColumn(column.value ? String(column.value) : "") && (
|
||||
<DataTableFacetedFilter
|
||||
key={String(column.value)}
|
||||
column={table.getColumn(
|
||||
column.value ? String(column.value) : "",
|
||||
)}
|
||||
title={column.label}
|
||||
options={column.options ?? []}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<div className='flex items-center flex-1 space-x-2'>
|
||||
<Input
|
||||
key='global-filter'
|
||||
placeholder={t("catalog.list.global_filter_placeholder")}
|
||||
value={globalFilter}
|
||||
onChange={(event) => setGlobalFilter(String(event.target.value))}
|
||||
className='w-3/12 h-8 lg:w-6/12'
|
||||
/>
|
||||
|
||||
{isFiltered && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => table.resetColumnFilters()}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset filters
|
||||
<CrossIcon className="w-4 h-4 ml-2" />
|
||||
<Button variant='ghost' onClick={() => resetGlobalFilter()} className='h-8 px-2 lg:px-3'>
|
||||
{t("common.reset_filter")}
|
||||
<XIcon className='w-4 h-4 ml-2' />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className='flex items-center gap-2'>
|
||||
{children}
|
||||
{table.options.enableHiding && <DataTableColumnOptions table={table} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/*
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center flex-1 space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
value={(table.getColumn("customer")?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) =>
|
||||
table.getColumn("customer")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="h-8 w-[150px] lg:w-[250px]"
|
||||
/>
|
||||
{table.getColumn("status") && (
|
||||
<DataTableFacetedFilter
|
||||
column={table.getColumn("status")}
|
||||
title="Status"
|
||||
options={statuses}
|
||||
/>
|
||||
)}
|
||||
{table.getColumn("priority") && (
|
||||
<DataTableFacetedFilter
|
||||
column={table.getColumn("priority")}
|
||||
title="Priority"
|
||||
options={priorities}
|
||||
/>
|
||||
)}
|
||||
{isFiltered && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => table.resetColumnFilters()}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset
|
||||
<CrossIcon className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DataTableViewOptions table={table} />
|
||||
</div>
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const Layout = ({ children }: PropsWithChildren) => {
|
||||
return <div className='flex flex-col w-full min-h-screen'>{children}</div>;
|
||||
};
|
||||
export const Layout = ({ children }: PropsWithChildren) => (
|
||||
<div className='flex flex-col w-full min-h-screen'>{children}</div>
|
||||
);
|
||||
|
||||
Layout.displayName = "Layout";
|
||||
|
||||
@ -1,18 +1,44 @@
|
||||
import { usePaginationParams } from "@/lib/hooks";
|
||||
import { PropsWithChildren, createContext } from "react";
|
||||
import { PaginationState, usePaginationParams } from "@/lib/hooks";
|
||||
import { SortingState } from "@tanstack/react-table";
|
||||
import { PropsWithChildren, createContext, useCallback, useMemo, useState } from "react";
|
||||
|
||||
export interface IDataTableContextState {}
|
||||
export interface IDataTableContextState {
|
||||
pagination: PaginationState;
|
||||
setPagination: (newPagination: PaginationState) => void;
|
||||
sorting: [];
|
||||
setSorting: () => void;
|
||||
globalFilter: string;
|
||||
setGlobalFilter: (newGlobalFilter: string) => void;
|
||||
resetGlobalFilter: () => void;
|
||||
isFiltered: boolean;
|
||||
}
|
||||
|
||||
export const DataTableContext = createContext<IDataTableContextState | null>(null);
|
||||
|
||||
export const DataTableProvider = ({ children }: PropsWithChildren) => {
|
||||
export const DataTableProvider = ({
|
||||
initialGlobalFilter = "",
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
initialGlobalFilter?: string;
|
||||
}>) => {
|
||||
const [pagination, setPagination] = usePaginationParams();
|
||||
const [globalFilter, setGlobalFilter] = useState<string>(initialGlobalFilter);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const isFiltered = useMemo(() => Boolean(globalFilter.length), [globalFilter]);
|
||||
const resetGlobalFilter = useCallback(() => setGlobalFilter(""), []);
|
||||
|
||||
return (
|
||||
<DataTableContext.Provider
|
||||
value={{
|
||||
pagination,
|
||||
setPagination,
|
||||
sorting,
|
||||
setSorting,
|
||||
globalFilter,
|
||||
setGlobalFilter,
|
||||
resetGlobalFilter,
|
||||
isFiltered,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./DataTableContext";
|
||||
export * from "./types";
|
||||
export * from "./useDataTable";
|
||||
export * from "./useDataTableColumns";
|
||||
export * from "./useDataTableContext";
|
||||
|
||||
@ -8,14 +8,12 @@ import {
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
type ColumnDef,
|
||||
type ColumnFiltersState,
|
||||
type SortingState,
|
||||
type VisibilityState,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import { getDataTableSelectionColumn } from "@/components";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import React, { useCallback } from "react";
|
||||
import { DataTableFilterField } from "./types";
|
||||
import { useDataTableContext } from "./useDataTableContext";
|
||||
|
||||
@ -63,6 +61,22 @@ interface UseDataTableProps<TData, TValue> {
|
||||
*/
|
||||
enableRowSelection?: boolean;
|
||||
|
||||
/**
|
||||
* The default number of rows per page.
|
||||
* @default 10
|
||||
* @type number | undefined
|
||||
* @example 20
|
||||
*/
|
||||
defaultPerPage?: number;
|
||||
|
||||
/**
|
||||
* The default sort order.
|
||||
* @default undefined
|
||||
* @type `${Extract<keyof TData, string | number>}.${"asc" | "desc"}` | undefined
|
||||
* @example "createdAt.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.
|
||||
@ -110,78 +124,13 @@ export function useDataTable<TData, TValue>({
|
||||
enableSorting = false,
|
||||
enableHiding = false,
|
||||
enableRowSelection = false,
|
||||
onPaginationChange,
|
||||
filterFields = [],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
enableAdvancedFilter = false,
|
||||
}: UseDataTableProps<TData, TValue>) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const { pagination, setPagination } = useDataTableContext();
|
||||
console.log("pagination TABLA =>", pagination);
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
// Memoize computation of searchableColumns and filterableColumns
|
||||
const { searchableColumns, filterableColumns } = useMemo(() => {
|
||||
return {
|
||||
searchableColumns: filterFields.filter((field) => !field.options),
|
||||
filterableColumns: filterFields.filter((field) => field.options),
|
||||
};
|
||||
}, [filterFields]);
|
||||
|
||||
// Create query string
|
||||
/*const createQueryString = 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 = useMemo(() => {
|
||||
return Array.from(searchParams.entries()).reduce<ColumnFiltersState>(
|
||||
(filters, [key, value]) => {
|
||||
const filterableColumn = filterableColumns.find((column) => column.value === key);
|
||||
const searchableColumn = searchableColumns.find((column) => column.value === key);
|
||||
|
||||
if (filterableColumn) {
|
||||
filters.push({
|
||||
id: key,
|
||||
value: value.split("."),
|
||||
});
|
||||
} else if (searchableColumn) {
|
||||
filters.push({
|
||||
id: key,
|
||||
value: [value],
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, [filterableColumns, searchableColumns, searchParams]);
|
||||
const { pagination, setPagination, sorting, setSorting } = useDataTableContext();
|
||||
|
||||
// Table states
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||
|
||||
const [columnFilters, setColumnFilters] =
|
||||
React.useState<ColumnFiltersState>(initialColumnFilters);
|
||||
|
||||
const paginationUpdater: OnChangeFn<PaginationState> = (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
const newPagination = updater(pagination);
|
||||
@ -191,84 +140,12 @@ export function useDataTable<TData, TValue>({
|
||||
|
||||
const sortingUpdater: OnChangeFn<SortingState> = (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
setSorting(updater(sorting));
|
||||
const newSorting = updater(sorting);
|
||||
console.log(newSorting);
|
||||
setSorting(newSorting);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle server-side filtering
|
||||
/*const debouncedSearchableColumnFilters = JSON.parse(
|
||||
useDebounce(
|
||||
JSON.stringify(
|
||||
columnFilters.filter((filter) => {
|
||||
return searchableColumns.find((column) => column.value === filter.id);
|
||||
})
|
||||
),
|
||||
500
|
||||
)
|
||||
) as ColumnFiltersState;*/
|
||||
|
||||
/*const filterableColumnFilters = columnFilters.filter((filter) => {
|
||||
return filterableColumns.find((column) => column.value === filter.id);
|
||||
});
|
||||
|
||||
const [mounted, setMounted] = useState(false);*/
|
||||
|
||||
/*useEffect(() => {
|
||||
// Opt out when advanced filter is enabled, because it contains additional params
|
||||
if (enableAdvancedFilter) return;
|
||||
|
||||
// Prevent resetting the page on initial render
|
||||
if (!mounted) {
|
||||
setMounted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize new params
|
||||
const newParamsObject = {
|
||||
page: 1,
|
||||
};
|
||||
|
||||
// Handle debounced searchable column filters
|
||||
for (const column of debouncedSearchableColumnFilters) {
|
||||
if (typeof column.value === "string") {
|
||||
Object.assign(newParamsObject, {
|
||||
[column.id]: typeof column.value === "string" ? column.value : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle filterable column filters
|
||||
for (const column of filterableColumnFilters) {
|
||||
if (typeof column.value === "object" && Array.isArray(column.value)) {
|
||||
Object.assign(newParamsObject, { [column.id]: column.value.join(".") });
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted values
|
||||
for (const key of searchParams.keys()) {
|
||||
if (
|
||||
(searchableColumns.find((column) => column.value === key) &&
|
||||
!debouncedSearchableColumnFilters.find(
|
||||
(column) => column.id === key
|
||||
)) ||
|
||||
(filterableColumns.find((column) => column.value === key) &&
|
||||
!filterableColumnFilters.find((column) => column.id === key))
|
||||
) {
|
||||
Object.assign(newParamsObject, { [key]: null });
|
||||
}
|
||||
}
|
||||
|
||||
// After cumulating all the changes, push new params
|
||||
navigate(`${location.pathname}?${createQueryString(newParamsObject)}`);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
//JSON.stringify(debouncedSearchableColumnFilters),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
JSON.stringify(filterableColumnFilters),
|
||||
]);*/
|
||||
|
||||
const getTableColumns = useCallback(() => {
|
||||
const _columns = columns;
|
||||
if (enableRowSelection) {
|
||||
@ -278,17 +155,16 @@ export function useDataTable<TData, TValue>({
|
||||
}, [columns, enableRowSelection]);
|
||||
|
||||
const table = useReactTable({
|
||||
columns: getTableColumns(),
|
||||
data,
|
||||
columns: getTableColumns(),
|
||||
pageCount: pageCount ?? -1,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
//getPaginationRowModel: getPaginationRowModel(),
|
||||
|
||||
state: {
|
||||
pagination,
|
||||
sorting,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
columnFilters,
|
||||
},
|
||||
|
||||
enableRowSelection,
|
||||
@ -303,16 +179,17 @@ export function useDataTable<TData, TValue>({
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
|
||||
manualPagination: true,
|
||||
pageCount: pageCount ?? -1,
|
||||
onPaginationChange: paginationUpdater,
|
||||
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
|
||||
manualFiltering: true,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
|
||||
debugTable: true,
|
||||
debugHeaders: true,
|
||||
debugColumns: true,
|
||||
});
|
||||
|
||||
return { table };
|
||||
|
||||
@ -40,22 +40,6 @@ export const usePaginationParams = (
|
||||
|
||||
const [pagination, setPagination] = usePagination(calculatedPageIndex, calculatedPageSize);
|
||||
|
||||
/*useEffect(() => {
|
||||
// Actualizar la URL cuando cambia la paginación
|
||||
const actualSearchParam = Object.fromEntries(new URLSearchParams(urlSearchParams));
|
||||
|
||||
if (
|
||||
String(pagination.pageIndex) !== actualSearchParam.page_index ||
|
||||
String(pagination.pageSize) !== actualSearchParam.page_size
|
||||
) {
|
||||
setUrlSearchParams({
|
||||
...actualSearchParam,
|
||||
page_index: String(pagination.pageIndex),
|
||||
page_size: String(pagination.pageSize),
|
||||
});
|
||||
}
|
||||
}, [pagination]);*/
|
||||
|
||||
const updatePagination = (newPagination: PaginationState) => {
|
||||
const _validatedPagination = setPagination(newPagination);
|
||||
|
||||
|
||||
@ -4,7 +4,21 @@
|
||||
"cancel": "Cancelar",
|
||||
"no": "No",
|
||||
"yes": "Sí",
|
||||
"Accept": "Aceptar"
|
||||
"accept": "Aceptar",
|
||||
"hide": "Ocultar",
|
||||
"sort_asc": "Asc",
|
||||
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
|
||||
"sort_desc": "Desc",
|
||||
"sort_desc_description": "En orden descendente. Click para ordenar ascendentemente.",
|
||||
"sort_none_description": "Sin orden. Click para ordenar ascendentemente.",
|
||||
"rows_selected": "{{count}} de {{total}} fila(s) seleccionadas.",
|
||||
"rows_per_page": "Filas por página",
|
||||
"num_page_of_total": "Página {{count}} de {{total}}",
|
||||
"go_to_first_page": "Ir a la primera página",
|
||||
"go_to_prev_page": "Ir a la página anterior",
|
||||
"go_to_next_page": "Ir a la página siguiente",
|
||||
"go_to_last_page": "Ir a la última página",
|
||||
"reset_filter": "Quitar el filtro"
|
||||
},
|
||||
"main_menu": {
|
||||
"home": "Inicio",
|
||||
@ -39,7 +53,15 @@
|
||||
"welcome": "Bienvenido"
|
||||
},
|
||||
"catalog": {
|
||||
"title": "Catálogo de artículos"
|
||||
"title": "Catálogo de artículos",
|
||||
"list": {
|
||||
"global_filter_placeholder": "Escribe aquí para filtrar los artículos...",
|
||||
"columns": {
|
||||
"description": "Descripción",
|
||||
"points": "Puntos",
|
||||
"retail_price": "PVP"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,18 +2,13 @@ import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className='relative w-full overflow-auto'>
|
||||
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
@ -28,11 +23,7 @@ const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
@ -42,28 +33,24 @@ const TableFooter = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
@ -87,7 +74,7 @@ const TableCell = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
className={cn("py-2 px-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@ -97,21 +84,8 @@ const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
};
|
||||
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user