diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs index 59014c1..40dc159 100644 --- a/client/.eslintrc.cjs +++ b/client/.eslintrc.cjs @@ -1,4 +1,5 @@ module.exports = { + $schema: "https://json.schemastore.org/eslintrc", root: true, env: { browser: true, es2020: true }, extends: [ @@ -12,5 +13,6 @@ module.exports = { rules: { "@typescript-eslint/no-explicit-any": "warn", "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "react/no-unescaped-entities": "off", }, }; diff --git a/client/src/App.tsx b/client/src/App.tsx index 091b9a6..2aa71da 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -6,9 +6,8 @@ import { Suspense } from "react"; import { ToastContainer } from "react-toastify"; import { Routes } from "./Routes"; import { LoadingOverlay, TailwindIndicator } from "./components"; -import { createAxiosDataProvider } from "./lib/axios"; -import { createAxiosAuthActions } from "./lib/axios/createAxiosAuthActions"; -import { DataSourceProvider } from "./lib/hooks/useDataSource/DataSourceContext"; +import { createAxiosAuthActions, createAxiosDataProvider } from "./lib/axios"; +import { DataSourceProvider } from "./lib/hooks"; function App() { const queryClient = new QueryClient({ @@ -28,6 +27,7 @@ function App() { }> + diff --git a/client/src/Routes.tsx b/client/src/Routes.tsx index 21f011e..eac1139 100644 --- a/client/src/Routes.tsx +++ b/client/src/Routes.tsx @@ -86,7 +86,7 @@ export const Routes = () => { element: , }, { - path: "edit", + path: "edit/:id", element: , }, ], @@ -109,11 +109,7 @@ export const Routes = () => { }, { path: "/logout", - element: ( - - - - ), + element: , }, ]; diff --git a/client/src/app/catalog/CatalogActions.ts b/client/src/app/catalog/CatalogActions.ts deleted file mode 100644 index d3d1746..0000000 --- a/client/src/app/catalog/CatalogActions.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { IDataSource } from '@/lib/hooks/useDataSource/DataSource'; - -export type SuccessNotificationResponse = { - message: string; - description?: string; -}; - -export type PermissionResponse = unknown; - -export type IdentityResponse = unknown; - -export type CatalogActionCheckResponse = { - authenticated: boolean; - redirectTo?: string; - logout?: boolean; - error?: Error; -}; - -export type CatalogActionOnErrorResponse = { - redirectTo?: string; - logout?: boolean; - error?: Error; -}; - -export type CatalogActionResponse = { - success: boolean; - redirectTo?: string; - error?: Error; - [key: string]: unknown; - successNotification?: SuccessNotificationResponse; -}; - -export interface ICatalogActions { - listCatalog: ( - dataSource: IDataSource, - pagination: { - pageIndex: number; - pageSize: number; - } = { - pageIndex: INITIAL_PAGE_INDEX, - pageSize: INITIAL_PAGE_SIZE, - }, - quickSearchTerm: string = "", - ): Promise> => { - return dataProvider.getList({ - resource: "invoices", - quickSearchTerm, - pagination, - }); - }; - - - } -} diff --git a/client/src/app/catalog/components/CatalogDataTable.tsx b/client/src/app/catalog/components/CatalogDataTable.tsx index 5925145..e7e32ab 100644 --- a/client/src/app/catalog/components/CatalogDataTable.tsx +++ b/client/src/app/catalog/components/CatalogDataTable.tsx @@ -24,7 +24,7 @@ export const CatalogDataTable = () => { searchTerm: globalFilter, }); - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { id: "id" as const, @@ -65,7 +65,7 @@ export const CatalogDataTable = () => { id: "retail_price" as const, accessorKey: "retail_price", header: () =>
{t("catalog.list.columns.retail_price")}
, - cell: ({ row }: { row: Row }) => { + cell: ({ row }: { row: Row }) => { const price = MoneyValue.create(row.original.retail_price).object; return
{price.toFormat()}
; }, @@ -113,10 +113,8 @@ export const CatalogDataTable = () => { } return ( - <> - - - - + + + ); }; diff --git a/client/src/app/dashboard/index.tsx b/client/src/app/dashboard/index.tsx index 70abb23..975f789 100644 --- a/client/src/app/dashboard/index.tsx +++ b/client/src/app/dashboard/index.tsx @@ -32,6 +32,7 @@ import { PaginationItem, Progress, Separator, + Skeleton, Table, TableBody, TableCell, @@ -44,20 +45,24 @@ import { TabsTrigger, } from "@/ui"; import { t } from "i18next"; +import { useNavigate } from "react-router-dom"; export const DashboardPage = () => { - const { data, status } = useGetIdentity(); + const navigate = useNavigate(); + const { data: userIdentity, status } = useGetIdentity(); return ( - {status === "success" && ( + {status === "success" ? (

{`${t("dashboard.welcome")}, ${ - data?.name + userIdentity?.name }`}

+ ) : ( + )}
@@ -72,7 +77,9 @@ export const DashboardPage = () => { - + diff --git a/client/src/app/quotes/components/CatalogPickerDataTable.tsx b/client/src/app/quotes/components/CatalogPickerDataTable.tsx new file mode 100644 index 0000000..2f9363d --- /dev/null +++ b/client/src/app/quotes/components/CatalogPickerDataTable.tsx @@ -0,0 +1,134 @@ +import { Card, CardContent } from "@/ui"; + +import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components"; + +import { useCatalogList } from "@/app/catalog/hooks"; +import { DataTable } from "@/components"; +import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar"; +import { useDataTable, useDataTableContext } from "@/lib/hooks"; +import { cn } from "@/lib/utils"; +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"; + +export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) => void }) => { + const navigate = useNavigate(); + const { pagination, globalFilter, isFiltered } = useDataTableContext(); + + const { data, isPending, isError, error } = useCatalogList({ + pagination: { + pageIndex: pagination.pageIndex, + pageSize: pagination.pageSize, + }, + searchTerm: globalFilter, + }); + + const columns = useMemo[]>(() => { + return [ + { + id: "description" as const, + accessorKey: "description", + enableResizing: false, + header: () => null, + cell: ({ + row, + renderValue, + }: { + row: Row; + renderValue: () => any; + }) => { + const price = MoneyValue.create(row.original.retail_price).object; + const points = row.original.points; + return ( + + ); + }, + }, + ]; + }, []); + + const { table } = useDataTable({ + data: data?.items ?? [], + columns: columns, + pageCount: data?.total_pages ?? -1, + }); + + if (isError) { + return ; + } + + if (isPending) { + return ( + + + + + + ); + } + + if (data?.total_items === 0 && !isFiltered) { + return ( + navigate("/catalog/add")} + /> + ); + } + + return ( + + + + ); +}; diff --git a/client/src/app/quotes/components/SortableDataTable.tsx b/client/src/app/quotes/components/SortableDataTable.tsx index 337e2d1..76b5ef6 100644 --- a/client/src/app/quotes/components/SortableDataTable.tsx +++ b/client/src/app/quotes/components/SortableDataTable.tsx @@ -125,6 +125,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP const [rowSelection, setRowSelection] = useState({}); const [activeId, setActiveId] = useState(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [columnVisibility, setColumnVisibility] = useState({}); const sorteableRowIds = useMemo(() => data.map((item) => item.id), [data]); @@ -145,8 +146,8 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getRowId: (originalRow: unknown) => originalRow?.id, - debugHeaders: true, - debugColumns: true, + debugHeaders: false, + debugColumns: false, meta: { insertItem: (rowIndex: number, data: object = {}) => { actions.insert(rowIndex, data, { shouldFocus: true }); @@ -263,16 +264,43 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP const hadleNewItem = useCallback(() => { actions.append([ { - description: "a", + quantity: { amount: "123" }, + description: "aaaa", + retail_price: { + amount: "10000", + precision: 4, + currency: "EUR", + }, + discount: { + amount: 35, + }, }, { - description: "b", + quantity: { + amount: "2", + }, + description: "bbbb", + discount: { + amount: 55, + }, }, { - description: "c", + quantity: { + amount: "3", + }, + description: "cccc", + discount: { + amount: 75, + }, }, { - description: "d", + quantity: { + amount: "4", + }, + description: "dddd", + discount: { + amount: 10, + }, }, ]); }, [actions]); diff --git a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx index 249592f..2b17ee2 100644 --- a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx @@ -1,20 +1,21 @@ import { - ButtonGroup, - CancelButton, - FormGroup, FormMoneyField, + FormPercentageField, + FormQuantityField, FormTextAreaField, - FormTextField, - SubmitButton, } from "@/components"; -import { Input } from "@/ui"; -import { t } from "i18next"; +import { DataTableProvider } from "@/lib/hooks"; +import { cn } from "@/lib/utils"; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui"; +import { Quantity } from "@shared/contexts"; +import { useCallback, useState } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; import { useDetailColumns } from "../../hooks"; +import { CatalogPickerDataTable } from "../CatalogPickerDataTable"; import { SortableDataTable } from "../SortableDataTable"; export const QuoteDetailsCardEditor = () => { - const { control, register, formState } = useFormContext(); + const { control, register, watch, getValues, setValue } = useFormContext(); const { fields, ...fieldActions } = useFieldArray({ control, @@ -39,27 +40,16 @@ export const QuoteDetailsCardEditor = () => { accessorKey: "quantity", header: "quantity", size: 5, - cell: ({ row: { index }, column: { id } }) => { - return ( - - ); + cell: ({ row: { index } }) => { + return ; }, }, { id: "description" as const, accessorKey: "description", - cell: ({ row: { index }, column: { id } }) => { - return ( - - ); + header: "description", + cell: ({ row: { index } }) => { + return ; }, }, @@ -69,7 +59,7 @@ export const QuoteDetailsCardEditor = () => { header: "retail_price", size: 10, cell: ({ row: { index }, column: { id } }) => { - return ; + return ; }, }, { @@ -78,7 +68,7 @@ export const QuoteDetailsCardEditor = () => { header: "price", size: 10, cell: ({ row: { index }, column: { id } }) => { - return ; + return ; }, }, { @@ -87,7 +77,7 @@ export const QuoteDetailsCardEditor = () => { header: "discount", size: 5, cell: ({ row: { index }, column: { id } }) => { - return ; + return ; }, }, { @@ -96,7 +86,7 @@ export const QuoteDetailsCardEditor = () => { header: "total", size: 10, cell: ({ row: { index }, column: { id } }) => { - return ; + return ; }, }, ], @@ -135,24 +125,54 @@ export const QuoteDetailsCardEditor = () => { } ); + const handleInsertArticle = useCallback( + (newArticle) => { + console.log(newArticle); + + fieldActions.append({ + ...newArticle, + quantity: { + amount: 1, + precision: Quantity.DEFAULT_PRECISION, + }, + }); + }, + [fieldActions] + ); + + const [isCollapsed, setIsCollapsed] = useState(false); + + const defaultLayout = [265, 440, 655]; + const navCollapsedSize = 4; + return ( - - null} size='sm' /> - - - } + -
+ { + setIsCollapsed(true); + }} + onExpand={() => { + setIsCollapsed(false); + }} + className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")} + > -
-
+ + + + + + + + ); }; diff --git a/client/src/app/quotes/create.tsx b/client/src/app/quotes/create.tsx index c2b912a..785364b 100644 --- a/client/src/app/quotes/create.tsx +++ b/client/src/app/quotes/create.tsx @@ -9,38 +9,21 @@ import { t } from "i18next"; import { ChevronLeft } from "lucide-react"; import { SubmitButton } from "@/components"; +import { useWarnAboutChange } from "@/lib/hooks"; import { Button, Form } from "@/ui"; +import { ICreateQuote_Request_DTO } from "@shared/contexts"; +import { useEffect } from "react"; import { FieldErrors, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; import { useQuotes } from "./hooks"; -type QuoteDataForm = { - id: string; - status: string; - date: string; - reference: string; - customer_information: string; - lang_code: string; - currency_code: string; - payment_method: string; - notes: string; - validity: string; - items: any[]; -}; - -/*type QuoteCreateProps = { - isOverModal?: boolean; -};*/ +interface QuoteDataForm extends ICreateQuote_Request_DTO {} export const QuoteCreate = () => { - //const [loading, setLoading] = useState(false); - - //const { data: userIdentity } = useGetIdentity(); - //console.log(userIdentity); - const navigate = useNavigate(); - const { useMutation } = useQuotes(); - const { mutate } = useMutation(); + const { setWarnWhen } = useWarnAboutChange(); + const { useCreate } = useQuotes(); + const { mutate } = useCreate(); const form = useForm({ defaultValues: { @@ -50,11 +33,23 @@ export const QuoteCreate = () => { }, }); + const { watch, handleSubmit } = form; + + useEffect(() => { + const subscription = watch((values: any, { type }: { type?: any }) => { + if (type === "change") { + // Hay cambios en el formulario + //setWarnWhen(true); + } + }); + return () => subscription.unsubscribe(); + }, [watch, setWarnWhen]); + const onSubmit: SubmitHandler = async (formData) => { - alert(JSON.stringify(formData)); + console.log(JSON.stringify(formData)); try { - //setLoading(true); + setWarnWhen(false); mutate(formData, { onSuccess: (data) => { navigate(`/quotes/edit/${data.id}`, { relative: "path", replace: true }); @@ -73,10 +68,15 @@ export const QuoteCreate = () => { return (
- +
- @@ -85,7 +85,7 @@ export const QuoteCreate = () => {
-
+
{ description={t("quotes.create.form_fields.customer_information.desc")} placeholder={t("quotes.create.form_fields.customer_information.placeholder")} /> -
-
- +
+ - + +
diff --git a/client/src/app/quotes/edit.tsx b/client/src/app/quotes/edit.tsx index 38668db..42550df 100644 --- a/client/src/app/quotes/edit.tsx +++ b/client/src/app/quotes/edit.tsx @@ -1,21 +1,23 @@ -import { ChevronLeft } from "lucide-react"; - -import { SubmitButton } from "@/components"; +import { FormMoneyField, LoadingOverlay, SubmitButton } from "@/components"; +import { calculateItemTotals } from "@/lib/calc"; import { useGetIdentity } from "@/lib/hooks"; +import { useUrlId } from "@/lib/hooks/useUrlId"; import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui"; +import { IUpdateQuote_Request_DTO, MoneyValue } from "@shared/contexts"; import { t } from "i18next"; -import { useState } from "react"; +import { ChevronLeftIcon } from "lucide-react"; +import { useEffect, useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; -import { - QuoteDetailsCardEditor, - QuoteDocumentsCardEditor, - QuoteGeneralCardEditor, -} from "./components/editors"; +import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors"; import { useQuotes } from "./hooks"; -type QuoteDataForm = { - id: string; - status: string; +// simple typesafe helperfunction +type EndsWith = T extends `${infer f}${b}` ? T : never; +const endsWith = (str: T, prefix: b): str is EndsWith => + str.endsWith(prefix); + +interface QuoteDataForm extends IUpdateQuote_Request_DTO { + /*status: string; date: string; reference: string; customer_information: string; @@ -24,23 +26,31 @@ type QuoteDataForm = { payment_method: string; notes: string; validity: string; - items: any[]; -}; + discount: IPercentage; -type QuoteCreateProps = { - isOverModal?: boolean; -}; + subtotal: IMoney; -export const QuoteEdit = ({ isOverModal }: QuoteCreateProps) => { + items: { + quantity: IQuantity; + description: string; + retail_price: IMoney; + price: IMoney; + discount: IPercentage; + total: IMoney; + }[];*/ +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const QuoteEdit = () => { const [loading, setLoading] = useState(false); + const quoteId = useUrlId(); const { data: userIdentity } = useGetIdentity(); - console.log(userIdentity); - const { useQuery, useMutation } = useQuotes(); + const { useOne, useUpdate } = useQuotes(); - const { data } = useQuery; - const { mutate } = useMutation; + const { data, status } = useOne(quoteId); + const { mutate } = useUpdate(quoteId); const form = useForm({ mode: "onBlur", @@ -54,53 +64,119 @@ export const QuoteEdit = ({ isOverModal }: QuoteCreateProps) => { payment_method: "", notes: "", validity: "", + subtotal: "", items: [], }, }); const onSubmit: SubmitHandler = async (data) => { - alert(JSON.stringify(data)); + console.debug(JSON.stringify(data)); try { setLoading(true); - data.currency_code = "EUR"; - data.lang_code = String(userIdentity?.language); - - mutate(data); + // Transformación del form -> typo de request + mutate(data, { + onError: (error) => { + alert(error); + }, + //onSettled: () => {}, + onSuccess: () => { + alert("guardado"); + }, + }); } finally { setLoading(false); } }; + const { watch, getValues, setValue } = form; + + useEffect(() => { + const { unsubscribe } = watch((_, { name, type }) => { + const value = getValues(); + + console.debug({ name, type }); + + if (name) { + if (name === "items") { + const { items } = value; + let quoteSubtotal = MoneyValue.create().object; + + // Recálculo líneas + items.map((item, index) => { + const itemTotals = calculateItemTotals(item); + quoteSubtotal = quoteSubtotal.add(itemTotals.total); + + setValue(`items.${index}.price`, itemTotals.price.toObject()); + setValue(`items.${index}.total`, itemTotals.total.toObject()); + }); + + console.log(quoteSubtotal.toFormat()); + + // Recálculo completo + setValue("subtotal", quoteSubtotal.toObject()); + } + + if ( + endsWith(name, "quantity") || + endsWith(name, "retail_price") || + endsWith(name, "discount") + ) { + const { items } = value; + const [, indexString, fieldName] = String(name).split("."); + const index = parseInt(indexString); + + const itemTotals = calculateItemTotals(items[index]); + + setValue(`items.${index}.price`, itemTotals.price.toObject()); + setValue(`items.${index}.total`, itemTotals.total.toObject()); + + // Recálculo completo + } + } + }); + return () => unsubscribe(); + }, [watch, getValues, setValue]); + + if (status !== "success") { + return ; + } + return (

- {t("quotes.create.title")} + {t("quotes.edit.title")}

- {t("quotes.status.draft")} + {data.status}
- {t("quotes.create.buttons.save_quote")} + {t("common.save")}
- + + + + {t("quotes.create.tabs.general")} {t("quotes.create.tabs.items")} - {t("quotes.create.tabs.documents")} {t("quotes.create.tabs.history")} @@ -110,9 +186,6 @@ export const QuoteEdit = ({ isOverModal }: QuoteCreateProps) => { - - -
diff --git a/client/src/app/quotes/hooks/useQuotes.tsx b/client/src/app/quotes/hooks/useQuotes.tsx index 4886062..0cac965 100644 --- a/client/src/app/quotes/hooks/useQuotes.tsx +++ b/client/src/app/quotes/hooks/useQuotes.tsx @@ -6,6 +6,8 @@ import { ICreateQuote_Request_DTO, ICreateQuote_Response_DTO, IGetQuote_Response_DTO, + IUpdateQuote_Request_DTO, + IUpdateQuote_Response_DTO, UniqueID, } from "@shared/contexts"; @@ -14,22 +16,23 @@ export type UseQuotesGetParamsType = { queryOptions?: Record; }; -export const useQuotes = (params?: UseQuotesGetParamsType) => { +export const useQuotes = () => { const dataSource = useDataSource(); const keys = useQueryKey(); return { - useQuery: () => + useOne: (id?: string, params?: UseQuotesGetParamsType) => useOne({ queryKey: keys().data().resource("quotes").action("one").id("").params().get(), queryFn: () => dataSource.getOne({ resource: "quotes", - id: "", + id: String(id), }), + enabled: !!id, ...params, }), - useMutation: () => + useCreate: () => useSave({ mutationKey: keys().data().resource("quotes").action("one").id("").params().get(), mutationFn: (data) => { @@ -50,5 +53,17 @@ export const useQuotes = (params?: UseQuotesGetParamsType) => { }); }, }), + + useUpdate: (id: string) => + useSave({ + mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(), + mutationFn: (data) => { + return dataSource.updateOne({ + resource: "quotes", + id, + data, + }); + }, + }), }; }; diff --git a/client/src/app/settings/SettingsActions.ts b/client/src/app/settings/SettingsActions.ts deleted file mode 100644 index d5f0f05..0000000 --- a/client/src/app/settings/SettingsActions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useOne } from '@/lib/hooks/useDataSource'; -import { IDataSource } from '@/lib/hooks/useDataSource/DataSource'; - -export type SuccessNotificationResponse = { - message: string; - description?: string; -}; - -export type PermissionResponse = unknown; - -export type IdentityResponse = unknown; - -export type CatalogActionCheckResponse = { - authenticated: boolean; - redirectTo?: string; - logout?: boolean; - error?: Error; -}; - -export type CatalogActionOnErrorResponse = { - redirectTo?: string; - logout?: boolean; - error?: Error; -}; - -export type CatalogActionResponse = { - success: boolean; - redirectTo?: string; - error?: Error; - [key: string]: unknown; - successNotification?: SuccessNotificationResponse; -}; - -export interface ISettingsActions { - getSettings: ( - dataSource: IDataSource, - ): Promise => { - - return useOne( - - ) - return dataProvider.getList({ - resource: "invoices", - quickSearchTerm, - pagination, - }); - }; - - - } -} diff --git a/client/src/components/DataTable/DataTable.tsx b/client/src/components/DataTable/DataTable.tsx index b604fcd..179fb2b 100644 --- a/client/src/components/DataTable/DataTable.tsx +++ b/client/src/components/DataTable/DataTable.tsx @@ -1,6 +1,14 @@ import { ColumnDef, Table as ReactTable, flexRender } from "@tanstack/react-table"; +import { PropsWithChildren, ReactNode } from "react"; import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + Separator, Table, TableBody, TableCaption, @@ -8,11 +16,9 @@ import { TableHead, TableHeader, TableRow, -} from "@/ui/table"; -import { PropsWithChildren, ReactNode } from "react"; +} from "@/ui"; import { cn } from "@/lib/utils"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader } from "@/ui"; import { DataTableColumnHeader } from "./DataTableColumnHeader"; import { DataTablePagination, DataTablePaginationProps } from "./DataTablePagination"; @@ -23,38 +29,58 @@ export type DataTablePaginationOptionsProps = Pick< "visible" >; +export type DataTableHeaderOptionsProps = { + visible: boolean; +}; + export type DataTableProps = PropsWithChildren<{ table: ReactTable; + title?: ReactNode; + description?: ReactNode; caption?: ReactNode; paginationOptions?: DataTablePaginationOptionsProps; + headerOptions?: DataTableHeaderOptionsProps; className?: string; + rowClassName?: string; + cellClassName?: string; }>; export function DataTable({ table, + title, + description, caption, paginationOptions, + headerOptions = { visible: true }, children, className, - ...props + rowClassName, + cellClassName, }: DataTableProps) { + const headerVisible = headerOptions?.visible; + return ( - <> - + + {(title || description) && ( - - {children} - + {title} + {description} - - - {typeof caption !== "undefined" && {caption}} + )} + + {children && ( + <> +
{children}
+ + + )} + +
+ {typeof caption !== "undefined" && {caption}} + {headerVisible && table.getHeaderGroups().length && ( {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( ({ ))} - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - ) : ( - - - No hay datos para mostrar - + )} + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} - )} - -
-
- - - -
- + )) + ) : ( + + + No hay datos para mostrar + + + )} + + + + + + +
); } diff --git a/client/src/components/DataTable/DataTableColumnHeader.tsx b/client/src/components/DataTable/DataTableColumnHeader.tsx index 321cda1..470d9e4 100644 --- a/client/src/components/DataTable/DataTableColumnHeader.tsx +++ b/client/src/components/DataTable/DataTableColumnHeader.tsx @@ -28,7 +28,7 @@ export function DataTableColumnHeader({ <>
diff --git a/client/src/components/DataTable/DataTablePagination.tsx b/client/src/components/DataTable/DataTablePagination.tsx index 0d2f69f..d22a880 100644 --- a/client/src/components/DataTable/DataTablePagination.tsx +++ b/client/src/components/DataTable/DataTablePagination.tsx @@ -68,6 +68,7 @@ export function DataTablePagination({