This commit is contained in:
David Arranz 2024-08-25 22:06:24 +02:00
parent 910d84aa76
commit ffd35bee3b
19 changed files with 359 additions and 407 deletions

View File

@ -29,27 +29,19 @@ export const CatalogDataTable = () => {
{
id: "id" as const,
accessorKey: "id",
enableResizing: false,
size: 10,
},
{
id: "article_id" as const,
accessorKey: "id_article",
enableResizing: false,
size: 10,
},
{
id: "catalog_name" as const,
accessorKey: "catalog_name",
enableResizing: false,
size: 10,
},
{
id: "description" as const,
accessorKey: "description",
header: () => <>{t("catalog.list.columns.description")}</>,
enableResizing: false,
size: 100,
},
{
id: "points" as const,
@ -58,8 +50,6 @@ export const CatalogDataTable = () => {
cell: ({ renderValue }: { renderValue: () => any }) => (
<div className='text-right'>{renderValue()}</div>
),
enableResizing: false,
size: 20,
},
{
id: "retail_price" as const,
@ -69,8 +59,6 @@ export const CatalogDataTable = () => {
const price = MoneyValue.create(row.original.retail_price).object;
return <div className='text-right'>{price.toFormat()}</div>;
},
enableResizing: false,
size: 20,
},
],
[]

View File

@ -1,4 +1,3 @@
import { cn } from "@/lib/utils";
import { Button, ButtonProps } from "@/ui";
import { t } from "i18next";
import { PackagePlusIcon } from "lucide-react";
@ -13,16 +12,8 @@ export const AppendCatalogArticleRowButton = ({
className,
...props
}: AppendCatalogArticleRowButtonProps): JSX.Element => (
<Button
type='button'
variant='outline'
size='icon'
className={cn(
"w-full gap-1 border-dashed text-muted-foreground border-muted-foreground/50",
className
)}
{...props}
>
<Button type='button' variant='outline' {...props}>
{" "}
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
{label && <>{label}</>}
</Button>

View File

@ -1,4 +1,3 @@
import { cn } from "@/lib/utils";
import { Button, ButtonProps } from "@/ui";
import { t } from "i18next";
import { PlusCircleIcon } from "lucide-react";
@ -13,16 +12,7 @@ export const AppendEmptyRowButton = ({
className,
...props
}: AppendEmptyRowButtonProps): JSX.Element => (
<Button
type='button'
variant='outline'
size='icon'
className={cn(
"w-full gap-1 border-dashed text-muted-foreground border-muted-foreground/50",
className
)}
{...props}
>
<Button type='button' variant='outline' {...props}>
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
{label && <>{label}</>}
</Button>

View File

@ -4,10 +4,11 @@ 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 { Button } from "@/ui";
import { IListArticles_Response_DTO, MoneyValue } from "@shared/contexts";
import { ColumnDef, Row } from "@tanstack/react-table";
import { t } from "i18next";
import { PackagePlusIcon } from "lucide-react";
import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
@ -28,58 +29,45 @@ export const CatalogPickerDataTable = ({ onSelect }: { onSelect: (data: unknown)
{
id: "description" as const,
accessorKey: "description",
enableResizing: false,
header: () => null,
cell: ({
row,
renderValue,
}: {
row: Row<IListArticles_Response_DTO>;
renderValue: () => any;
}) => {
header: () => <>{t("catalog.list.columns.description")}</>,
},
{
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>
),
},
{
id: "retail_price" as const,
accessorKey: "retail_price",
header: () => <div className='text-right'>{t("catalog.list.columns.retail_price")}</div>,
cell: ({ row }: { row: Row<IListArticles_Response_DTO> }) => {
const price = MoneyValue.create(row.original.retail_price).object;
const points = row.original.points;
return (
<button
key={row.id}
className={cn(
"rounded-lg border p-3 w-full transition delay-150 duration-200 active:scale-95 hover:bg-primary ",
""
)}
onClick={
(event) => {
event.preventDefault();
onSelect && onSelect(row.original);
}
/*setMail({
...mail,
selected: item.id,
})*/
}
>
<div className='flex flex-row justify-between w-full space-x-6'>
<div className='w-3/4 text-left grow line-clamp-2 text-muted-foreground hover:text-foreground'>
{renderValue()}
</div>
<div className='w-1/4 text-right'>
<dl className='flex flex-row justify-end space-x-1'>
<dt className='text-xs font-medium text-accent-foreground/75'>
{t("catalog.list.columns.points")}:
</dt>
<dd className='text-xs font-semibold'>{points}</dd>
</dl>
<dl className='flex flex-row justify-end space-x-1'>
<dt className='text-xs font-medium text-accent-foreground/75'>
{t("catalog.list.columns.retail_price")}:
</dt>
<dt className='text-xs font-semibold'>{price.toFormat()}</dt>
</dl>
</div>
</div>
</button>
);
return <div className='text-right'>{price.toFormat()}</div>;
},
},
{
id: "row-actions",
header: () => null,
cell: ({ row }: { row: Row<IListArticles_Response_DTO> }) => (
<Button
size='sm'
variant='outline'
className='h-8 gap-1'
onClick={(event) => {
event.preventDefault();
onSelect && onSelect(row.original);
}}
>
<PackagePlusIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
{t("common.add")}
</span>
</Button>
),
},
];
}, []);
@ -119,12 +107,8 @@ export const CatalogPickerDataTable = ({ onSelect }: { onSelect: (data: unknown)
<DataTable
className='bg-transparent border-0 shadow-none'
table={table}
headerOptions={{ visible: false }}
paginationOptions={{ visible: true, enablePageSizeSelector: false }}
contentClassName='p-0'
footerClassName='p-0'
rowClassName='border-b-0'
cellClassName='px-0'
footerClassName='px-10 pt-2 border-t'
>
<DataTableToolbar fullWidthFilter={true} table={table} />
</DataTable>

View File

@ -1,14 +1,6 @@
import { DataTableColumnHeader } from "@/components";
import { Badge } from "@/ui";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/ui/table";
import { ButtonGroup, DataTableColumnHeader } from "@/components";
import { Badge, Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/ui";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/ui/table";
import {
DndContext,
DragEndEvent,
@ -30,6 +22,7 @@ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities";
import {
ColumnDef,
InitialTableState,
Row,
RowData,
RowSelectionState,
@ -65,6 +58,7 @@ export type QuoteItemsSortableDataTableProps = {
columns: ColumnDef<unknown, unknown>[];
data: Record<"id", string>[];
defaultValues: Readonly<{ [x: string]: any }> | undefined;
initialState?: InitialTableState;
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & Record<string, unknown>;
};
@ -101,13 +95,17 @@ export function QuoteItemsSortableDataTable({
columns,
data,
defaultValues,
initialState,
actions,
}: QuoteItemsSortableDataTableProps) {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [activeId, setActiveId] = useState<UniqueIdentifier | null>();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
initialState?.columnVisibility || {}
);
const sorteableRowIds = useMemo(() => data.map((item) => item.id), [data]);
const table = useReactTable({
@ -116,6 +114,8 @@ export function QuoteItemsSortableDataTable({
enableColumnResizing: false,
columnResizeMode: "onChange",
//defaultColumn,
initialState,
state: {
rowSelection,
columnVisibility,
@ -124,16 +124,21 @@ export function QuoteItemsSortableDataTable({
enableMultiRowSelection: true,
enableSorting: false,
enableHiding: true,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getRowId: (originalRow: unknown) => originalRow?.id,
debugTable: false,
debugHeaders: false,
debugColumns: false,
defaultColumn: {
size: 8, //starting column size
minSize: 1, //enforced during column resizing
maxSize: 96, //enforced during column resizing
minSize: 0, //starting column size
size: Number.MAX_SAFE_INTEGER, //enforced during column resizing
maxSize: Number.MAX_SAFE_INTEGER, //enforced during column resizing
},
meta: {
insertItem: (rowIndex: number, data?: unknown) => {
actions.insert(rowIndex, data || defaultValues?.items[0], { shouldFocus: true });
@ -154,7 +159,7 @@ export function QuoteItemsSortableDataTable({
const data = table
.getSelectedRowModel()
.rows.map((row) => ({ ...row.original, id: undefined }));
.rows.map((row: Row<any>) => ({ ...row.original, id: undefined }));
if (table.getRowModel().rows.length < lastIndex + 1) {
actions.append(data);
@ -184,7 +189,7 @@ export function QuoteItemsSortableDataTable({
actions.remove();
}
},
updateItem: (rowIndex: number, rowData: unknown, fieldName: string, value: unknown) => {
updateItem: (rowIndex: number, rowData: any, fieldName: string, value: unknown) => {
// Skip page index reset until after next rerender
// skipAutoResetPageIndex();
actions.update(rowIndex, { ...rowData, [`${fieldName}`]: value });
@ -264,169 +269,189 @@ export function QuoteItemsSortableDataTable({
onDragCancel={handleDragCancel}
collisionDetection={closestCenter}
>
<QuoteItemsSortableDataTableToolbar table={table} />
<Table className='table-fixed'>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} className={`px-2 py-1 w-${header.getSize()}`}>
{header.isPlaceholder ? null : (
<DataTableColumnHeader table={table} header={header} />
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
<SortableContext
items={filterItems(sorteableRowIds)}
strategy={verticalListSortingStrategy}
>
{filterItems(table.getRowModel().rows).map((row) => (
<QuoteItemsSortableTableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className='px-2 py-1 align-top'>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
<Card>
<CardHeader className='sticky z-10 border-b top-16 bg-card'>
<CardTitle>
<QuoteItemsSortableDataTableToolbar table={table} />
</CardTitle>
</CardHeader>
<CardContent>
<Table className='table-fixed'>
<TableHeader className='sticky top-0 z-10 bg-background'>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
{headerGroup.headers.map((header) => {
return (
<TableHead
key={header.id}
className='px-2 py-1'
style={{
width:
header.getSize() === Number.MAX_SAFE_INTEGER
? "auto"
: header.getSize(),
}}
>
{header.isPlaceholder ? null : (
<DataTableColumnHeader table={table} header={header} />
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
<SortableContext
items={filterItems(sorteableRowIds)}
strategy={verticalListSortingStrategy}
>
{filterItems(table.getRowModel().rows).map((row) => (
<QuoteItemsSortableTableRow key={(row as Row<any>).id} id={(row as Row<any>).id}>
{(row as Row<any>).getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className='px-2 py-2 align-top'
style={{
width:
cell.column.getSize() === Number.MAX_SAFE_INTEGER
? "auto"
: cell.column.getSize(),
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</QuoteItemsSortableTableRow>
))}
</QuoteItemsSortableTableRow>
))}
</SortableContext>
</TableBody>
<TableFooter className='bg-default'>
<TableRow className='hover:bg-default'>
<TableCell colSpan={3} className='py-6'></TableCell>
<TableCell colSpan={5} className='py-6'>
<div className='grid grid-cols-2 gap-6'>
<AppendEmptyRowButton onClick={() => table.options.meta?.appendItem()} />
<AppendCatalogArticleRowButton
onClick={() => table.options.meta?.pickCatalogArticle()}
/>
</div>
</TableCell>
<TableCell className='py-6'></TableCell>
</TableRow>
</TableFooter>
</Table>
</SortableContext>
</TableBody>
</Table>
{createPortal(
<DragOverlay dropAnimation={dropAnimationConfig} className={"z-40 opacity-100"}>
{activeId && (
<div className='relative flex flex-wrap'>
{table.getSelectedRowModel().rows.length ? (
<Badge
variant='destructive'
className='absolute z-50 flex items-center justify-center w-2 h-2 p-3 rounded-full top left -left-2 -top-2'
>
{table.getSelectedRowModel().rows.length}
</Badge>
) : null}
<div className='absolute z-40 bg-white border rounded shadow opacity-100 top left hover:bg-white border-muted-foreground/50'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
{createPortal(
<DragOverlay dropAnimation={dropAnimationConfig} className={"z-40 opacity-100"}>
{activeId && (
<div className='relative flex flex-wrap'>
{table.getSelectedRowModel().rows.length ? (
<Badge
variant='destructive'
className='absolute z-50 flex items-center justify-center w-2 h-2 p-3 rounded-full top left -left-2 -top-2'
>
{table.getSelectedRowModel().rows.length}
</Badge>
) : null}
<div className='absolute z-40 bg-white border rounded shadow opacity-100 top left hover:bg-white border-muted-foreground/50'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
{table.getSelectedRowModel().rows.length > 1 && (
<div className='absolute z-30 transform -translate-x-1 translate-y-1 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left rotate-1'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
{table.getSelectedRowModel().rows.length > 1 && (
<div className='absolute z-30 transform -translate-x-1 translate-y-1 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left rotate-1'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
{table.getSelectedRowModel().rows.length > 2 && (
<div className='absolute z-20 transform translate-x-1 -translate-y-1 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left -rotate-1'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
{table.getSelectedRowModel().rows.length > 3 && (
<div className='absolute z-10 transform translate-x-2 -translate-y-2 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left rotate-2'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
</div>
)}
{table.getSelectedRowModel().rows.length > 2 && (
<div className='absolute z-20 transform translate-x-1 -translate-y-1 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left -rotate-1'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
{table.getSelectedRowModel().rows.length > 3 && (
<div className='absolute z-10 transform translate-x-2 -translate-y-2 bg-white border rounded shadow opacity-100 hover:bg-white border-muted-foreground/50 top left rotate-2'>
<Table>
<TableBody>
{table.getRowModel().rows.map(
(row) =>
row.id === activeId && (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
className='p-1 align-top'
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
</div>
</DragOverlay>,
document.body
)}
</DragOverlay>,
document.body
)}
</CardContent>
<CardFooter>
<ButtonGroup>
<AppendEmptyRowButton onClick={() => table.options.meta?.appendItem()} />
<AppendCatalogArticleRowButton
onClick={() => table.options.meta?.pickCatalogArticle()}
/>
</ButtonGroup>
</CardFooter>
</Card>
</DndContext>
);
}

View File

@ -1,102 +1,92 @@
import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from "@/ui";
import { Table } from "@tanstack/react-table";
import { t } from "i18next";
import { CirclePlusIcon, CopyPlusIcon, PackagePlusIcon, ScanIcon, Trash2Icon } from "lucide-react";
import { CopyPlusIcon, ScanIcon, Trash2Icon } from "lucide-react";
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<unknown> }) => {
const selectedRowsCount = table.getSelectedRowModel().rows.length;
if (selectedRowsCount) {
return (
<nav className='sticky z-10 pt-4 bg-background top-16'>
<div className='flex items-center h-12 p-1 rounded-md text-muted-foreground bg-primary '>
<div className='flex items-center gap-2'>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.options.meta?.duplicateItems()}
>
<CopyPlusIcon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>
{t("common.duplicate_selected_rows")}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.duplicate_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.options.meta?.deleteItems()}
>
<Trash2Icon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>{t("common.remove_selected_rows")}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.remove_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.resetRowSelection()}
>
<ScanIcon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>{t("common.reset_selected_rows")}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.reset_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Separator orientation='vertical' className='h-6 mx-1 bg-muted-foreground' />
<p className='text-sm'>{t("common.rows_selected", { count: selectedRowsCount })}</p>
</div>
</div>
</nav>
);
}
return (
<nav className='sticky z-10 pt-4 bg-background top-16'>
<div className='flex items-center h-12 p-1 rounded-md bg-accent text-muted-foreground'>
<nav className='flex items-center h-12 p-1 rounded-md text-muted-foreground bg-muted '>
<div className='flex items-center gap-2'>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
onClick={() => table.options.meta?.appendItem()}
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.options.meta?.duplicateItems()}
>
<CirclePlusIcon className='w-4 h-4 mr-2' />
<span>{t("common.append_empty_row")}</span>
<CopyPlusIcon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>
{t("common.duplicate_selected_rows")}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.append_empty_row_tooltip")}</TooltipContent>
<TooltipContent>{t("common.duplicate_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
onClick={() => table.options.meta?.pickCatalogArticle()}
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.options.meta?.deleteItems()}
>
<PackagePlusIcon className='w-4 h-4 mr-2' />
<span>{t("common.append_article")}</span>
<Trash2Icon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>{t("common.remove_selected_rows")}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
<TooltipContent>{t("common.remove_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
type='button'
variant='ghost'
disabled={!table.getSelectedRowModel().rows.length}
onClick={() => table.resetRowSelection()}
>
<ScanIcon className='w-4 h-4 sm:mr-2' />
<span className='sr-only sm:not-sr-only'>{t("common.reset_selected_rows")}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.reset_selected_rows_tooltip")}</TooltipContent>
</Tooltip>
<Separator orientation='vertical' className='h-6 mx-1 bg-muted-foreground' />
<p className='text-sm'>{t("common.rows_selected", { count: selectedRowsCount })}</p>
</div>
<div className='flex items-center gap-2 ml-auto'></div>
</nav>
);
}
return (
<nav className='flex items-center h-12 p-1 rounded-md bg-accent/75 text-muted-foreground'>
<div className='flex space-x-2'>
<Tooltip>
<TooltipTrigger asChild>
<AppendEmptyRowButton
variant='ghost'
onClick={() => table.options.meta?.appendItem()}
/>
</TooltipTrigger>
<TooltipContent>{t("common.append_empty_row_tooltip")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<AppendCatalogArticleRowButton
variant='ghost'
onClick={() => table.options.meta?.pickCatalogArticle()}
/>
</TooltipTrigger>
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
</Tooltip>
</div>
<div className='flex items-center gap-2 ml-auto'></div>
</nav>
);
};

View File

@ -1,4 +1,5 @@
import {
ButtonGroup,
ColorBadge,
DataTable,
DataTableSkeleton,
@ -74,7 +75,7 @@ export const QuotesDataTable = ({
<Button
size='sm'
variant='link'
className='h-8 gap-1 text-left text-ellipsis'
className='h-8 gap-1 px-0 text-left text-ellipsis'
onClick={(e) => {
e.preventDefault();
navigate(`/quotes/edit/${original.id}`, { relative: "path" });
@ -83,7 +84,6 @@ export const QuotesDataTable = ({
<div className=''>{renderValue()}</div>
</Button>
),
enableResizing: false,
},
{
@ -92,7 +92,6 @@ export const QuotesDataTable = ({
header: () => <>{t("quotes.list.columns.status")}</>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cell: ({ row: { original } }) => <ColorBadge label={original.status} />,
enableResizing: false,
},
{
id: "date" as const,
@ -108,14 +107,12 @@ export const QuotesDataTable = ({
</div>
);
},
enableResizing: false,
},
{
id: "customer_reference" as const,
accessorKey: "customer_reference",
header: () => <>{t("quotes.list.columns.customer_reference")}</>,
cell: ({ renderValue }) => <div className='text-left text-ellipsis'>{renderValue()}</div>,
enableResizing: false,
},
{
id: "customer_information" as const,
@ -138,8 +135,7 @@ export const QuotesDataTable = ({
})}
</div>
),
enableResizing: false,
size: 640,
size: 600,
},
{
@ -152,13 +148,12 @@ export const QuotesDataTable = ({
<div className='text-right'>{price.isSuccess ? price.object.toFormat() : "-"}</div>
);
},
enableResizing: false,
},
{
id: "row-actions",
header: () => null,
cell: ({ row }: { row: Row<IListQuotes_Response_DTO> }) => (
<div className='flex items-center gap-1 ml-auto'>
<ButtonGroup>
<Tooltip>
<TooltipTrigger asChild>
<Button
@ -200,9 +195,8 @@ export const QuotesDataTable = ({
<DropdownMenuItem>{t("common.archive")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</ButtonGroup>
),
enableResizing: false,
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -33,7 +33,9 @@ export const CatalogPickerDialog = ({
</DataTableProvider>
<DialogFooter>
<Button type='submit'>{t("common.close")}</Button>
<Button type='submit' onClick={() => onOpenChange(false)}>
{t("common.close")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@ -11,7 +11,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui";
import { CurrencyData, Language, Quantity } from "@shared/contexts";
import { ColumnDef } from "@tanstack/react-table";
import { t } from "i18next";
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
import { ChevronDownIcon, ChevronUpIcon, Trash2Icon } from "lucide-react";
import { useCallback, useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useDetailColumns } from "../../hooks";
@ -46,21 +46,20 @@ export const QuoteDetailsCardEditor = ({
header: () => (
<HashIcon aria-label='Orden de fila' className='items-center justify-center w-4 h-4' />
),
accessorFn: (originalRow: unknown, index: number) => index + 1,
accessorFn: (_: unknown, index: number) => index + 1,
size: 5,
enableHiding: false,
enableSorting: false,
enableResizing: false,
},*/
{
id: "description" as const,
accessorKey: "description",
header: t("quotes.form_fields.items.description.label"),
size: 24,
cell: ({ row: { index } }) => {
return <FormTextAreaField autoSize {...register(`items.${index}.description`)} />;
},
size: 500,
},
{
id: "quantity" as const,
@ -68,7 +67,6 @@ export const QuoteDetailsCardEditor = ({
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.quantity.label")}</div>
),
size: 6,
cell: ({ row: { index } }) => {
return (
<FormQuantityField
@ -103,7 +101,6 @@ export const QuoteDetailsCardEditor = ({
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.subtotal_price.label")}</div>
),
size: 12,
cell: ({ row: { index } }) => {
return (
<FormCurrencyField
@ -120,7 +117,6 @@ export const QuoteDetailsCardEditor = ({
{
id: "discount" as const,
accessorKey: "discount",
size: 6,
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
),
@ -140,7 +136,6 @@ export const QuoteDetailsCardEditor = ({
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.total_price.label")}</div>
),
size: 12,
cell: ({ row: { index } }) => {
return (
<FormCurrencyField
@ -157,17 +152,17 @@ export const QuoteDetailsCardEditor = ({
},
],
{
enableDragHandleColumn: true,
enableDragHandleColumn: false, // <--- Desactivado temporalmente
enableSelectionColumn: true,
enableActionsColumn: true,
rowActionFn: (props) => {
const { table, row } = props;
return [
{
/*{
label: t("common.duplicate_row"),
icon: <CopyIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.duplicateItems(row.index),
},
},*/
{
label: t("common.insert_row_above"),
icon: <ChevronUpIcon className='w-4 h-4 mr-2' />,

View File

@ -36,12 +36,9 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
),*/
header: () => null,
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
size: 2,
minSize: 2,
maxSize: 2,
enableSorting: false,
enableHiding: false,
size: 40,
});
}
@ -74,9 +71,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
),
enableSorting: false,
enableHiding: false,
size: 2,
minSize: 2,
maxSize: 2,
size: 40,
});
}
@ -90,18 +85,11 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
/>
),*/
cell: (props) => {
return (
<div className='w-full mx-auto text-center'>
<DataTableRowActions rowContext={props} actions={rowActionFn} />
</div>
);
return <DataTableRowActions rowContext={props} actions={rowActionFn} />;
},
size: 4,
minSize: 4,
maxSize: 4,
enableSorting: false,
enableHiding: false,
size: 48,
});
}

View File

@ -1,14 +1,9 @@
import { cn } from "@/lib/utils";
import React from "react";
export const ButtonGroup = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("ml-auto flex items-center gap-2", className)}
{...props}
/>
));
export const ButtonGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center gap-2", className)} {...props} />
)
);
ButtonGroup.displayName = "ButtonGroup";

View File

@ -89,17 +89,18 @@ export function DataTable<TData>({
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className={rowClassName}>
{headerGroup.headers.map((header) => {
return (
<TableHead
key={header.id}
colSpan={header.colSpan}
style={{ width: header.getSize() }}
>
<DataTableColumnHeader table={table} header={header} />
</TableHead>
);
})}
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
colSpan={header.colSpan}
style={{
width:
header.getSize() === Number.MAX_SAFE_INTEGER ? "auto" : header.getSize(),
}}
>
<DataTableColumnHeader table={table} header={header} />
</TableHead>
))}
</TableRow>
))}
</TableHeader>
@ -124,7 +125,16 @@ export function DataTable<TData>({
)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className={cellClassName}>
<TableCell
key={cell.id}
className={cellClassName}
style={{
width:
cell.column.getSize() === Number.MAX_SAFE_INTEGER
? "auto"
: cell.column.getSize(),
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}

View File

@ -32,7 +32,7 @@ export function DataTableColumnHeader<TData, TValue>({
: flexRender(header.column.columnDef.header, header.getContext())}
</div>
{header.column.getCanResize() && (
{false && header.column.getCanResize() && (
<Separator
orientation='vertical'
className={cn(

View File

@ -75,7 +75,7 @@ export function DataTablePagination<TData>({
enablePageSizeSelector ? "justify-end" : "justify-between"
)}
>
<div className='flex w-[100px] items-center justify-center'>
<div className='flex w-[150px] items-center justify-start'>
<p className='text-sm font-medium '>
{t("common.num_page_of_total", {
count: table.getState().pagination.pageIndex + 1,

View File

@ -1,6 +1,5 @@
"use client";
import { cn } from "@/lib/utils";
import {
Button,
DropdownMenu,
@ -42,13 +41,8 @@ export function DataTableRowActions<TData = any, TValue = unknown>({
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-haspopup='true'
size='icon'
variant='link'
className={cn("w-4 h-4 mt-2 text-ring hover:text-muted-foreground", className)}
>
<MoreVerticalIcon className='w-4 h-4' />
<Button size='icon' variant='outline' className='w-8 h-8'>
<MoreVerticalIcon className='h-3.5 w-3.5' />
<span className='sr-only'>{t("common.open_menu")}</span>
</Button>
</DropdownMenuTrigger>

View File

@ -134,7 +134,11 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
columns,
enableColumnResizing: false,
columnResizeMode: "onChange",
//defaultColumn,
defaultColumn: {
minSize: 0,
size: Number.MAX_SAFE_INTEGER,
maxSize: Number.MAX_SAFE_INTEGER,
},
state: {
rowSelection,
columnVisibility,

View File

@ -207,9 +207,9 @@ export function useDataTable<TData, TValue>({
debugColumns: false,
defaultColumn: {
size: 96, //starting column size
minSize: 96, //enforced during column resizing
maxSize: 500, //enforced during column resizing
minSize: 0, //starting column size
size: Number.MAX_SAFE_INTEGER, //enforced during column resizing
maxSize: Number.MAX_SAFE_INTEGER, //enforced during column resizing
},
});

View File

@ -13,6 +13,7 @@
"upload": "Upload",
"continue": "Continue",
"close": "Close",
"add": "Add",
"sort_asc": "Asc",
"sort_asc_description": "In ascending order. Click to sort descending order.",
"sort_desc": "Desc",

View File

@ -13,6 +13,7 @@
"upload": "Cargar",
"continue": "Continuar",
"close": "Cerrar",
"add": "Añadir",
"sort_asc": "Asc",
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
"sort_desc": "Desc",