.
This commit is contained in:
parent
bb3ab3dbb4
commit
09cbfc12c5
@ -2,6 +2,7 @@ import { Outlet, RouterProvider, createBrowserRouter } from "react-router-dom";
|
|||||||
import {
|
import {
|
||||||
DealerLayout,
|
DealerLayout,
|
||||||
DealersList,
|
DealersList,
|
||||||
|
ErrorPage,
|
||||||
LoginPage,
|
LoginPage,
|
||||||
LogoutPage,
|
LogoutPage,
|
||||||
QuoteCreate,
|
QuoteCreate,
|
||||||
@ -25,6 +26,13 @@ export const Routes = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const routesForErrors = [
|
||||||
|
{
|
||||||
|
path: "*",
|
||||||
|
Component: ErrorPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Define routes accessible only to authenticated users
|
// Define routes accessible only to authenticated users
|
||||||
const routesForAuthenticatedOnly = [
|
const routesForAuthenticatedOnly = [
|
||||||
{
|
{
|
||||||
@ -123,7 +131,12 @@ export const Routes = () => {
|
|||||||
|
|
||||||
// Combine and conditionally include routes based on authentication status
|
// Combine and conditionally include routes based on authentication status
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[...routesForPublic, ...routesForAuthenticatedOnly, ...routesForNotAuthenticatedOnly],
|
[
|
||||||
|
...routesForPublic,
|
||||||
|
...routesForAuthenticatedOnly,
|
||||||
|
...routesForNotAuthenticatedOnly,
|
||||||
|
...routesForErrors,
|
||||||
|
],
|
||||||
{
|
{
|
||||||
//basename: "/app",
|
//basename: "/app",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,29 @@ export const ErrorPage = (props: ErrorPageProps) => {
|
|||||||
try again.
|
try again.
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex min-h-[100dvh] flex-col items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8'>
|
||||||
|
<div className='max-w-md mx-auto text-center'>
|
||||||
|
<div className='w-12 h-12 mx-auto text-primary' />
|
||||||
|
<h1 className='mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl'>
|
||||||
|
Oops, page not found!
|
||||||
|
</h1>
|
||||||
|
<p className='mt-4 text-muted-foreground'>
|
||||||
|
The page you're looking for doesn't exist or has been moved.
|
||||||
|
</p>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Button
|
||||||
|
className='inline-flex items-center px-4 py-2 text-sm font-medium transition-colors rounded-md shadow-sm bg-primary text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2'
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
Go to Homepage
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id='Error' className='flex flex-col items-center w-full h-full'>
|
<section id='Error' className='flex flex-col items-center w-full h-full'>
|
||||||
{msg}
|
{msg}
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button, ButtonProps } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { PackagePlusIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export interface AppendCatalogArticleRowButtonProps extends ButtonProps {
|
||||||
|
label?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppendCatalogArticleRowButton = ({
|
||||||
|
label = t("common.append_article"),
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||||
|
{label && <>{label}</>}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
AppendCatalogArticleRowButton.displayName = "AddNewRowButton";
|
||||||
@ -1,17 +1,18 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button, ButtonProps } from "@/ui";
|
import { Button, ButtonProps } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
import { PlusCircleIcon } from "lucide-react";
|
import { PlusCircleIcon } from "lucide-react";
|
||||||
|
|
||||||
export interface AddNewRowButtonProps extends ButtonProps {
|
export interface AppendEmptyRowButtonProps extends ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddNewRowButton = ({
|
export const AppendEmptyRowButton = ({
|
||||||
label = "Añade nueva fila",
|
label = t("common.append_empty_row"),
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: AddNewRowButtonProps): JSX.Element => (
|
}: AppendEmptyRowButtonProps): JSX.Element => (
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
@ -27,4 +28,4 @@ export const AddNewRowButton = ({
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
AddNewRowButton.displayName = "AddNewRowButton";
|
AppendEmptyRowButton.displayName = "AddNewRowButton";
|
||||||
@ -1,5 +1,3 @@
|
|||||||
import { Card, CardContent } from "@/ui";
|
|
||||||
|
|
||||||
import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
||||||
|
|
||||||
import { useCatalogList } from "@/app/catalog/hooks";
|
import { useCatalogList } from "@/app/catalog/hooks";
|
||||||
@ -13,7 +11,7 @@ import { t } from "i18next";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) => void }) => {
|
export const CatalogPickerDataTable = ({ onSelect }: { onSelect: (data: unknown) => void }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
||||||
|
|
||||||
@ -48,7 +46,7 @@ export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) =
|
|||||||
onClick={
|
onClick={
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onClick && onClick(row.original);
|
onSelect && onSelect(row.original);
|
||||||
}
|
}
|
||||||
/*setMail({
|
/*setMail({
|
||||||
...mail,
|
...mail,
|
||||||
@ -57,10 +55,10 @@ export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) =
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className='flex flex-row justify-between w-full space-x-6'>
|
<div className='flex flex-row justify-between w-full space-x-6'>
|
||||||
<div className='text-left grow line-clamp-2 text-muted-foreground hover:text-foreground'>
|
<div className='w-3/4 text-left grow line-clamp-2 text-muted-foreground hover:text-foreground'>
|
||||||
{renderValue()}
|
{renderValue()}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-right'>
|
<div className='w-1/4 text-right'>
|
||||||
<dl className='flex flex-row justify-end space-x-1'>
|
<dl className='flex flex-row justify-end space-x-1'>
|
||||||
<dt className='text-xs font-medium text-accent-foreground/75'>
|
<dt className='text-xs font-medium text-accent-foreground/75'>
|
||||||
{t("catalog.list.columns.points")}:
|
{t("catalog.list.columns.points")}:
|
||||||
@ -94,17 +92,13 @@ export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) =
|
|||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DataTableSkeleton
|
||||||
<CardContent>
|
columnCount={6}
|
||||||
<DataTableSkeleton
|
searchableColumnCount={1}
|
||||||
columnCount={6}
|
filterableColumnCount={2}
|
||||||
searchableColumnCount={1}
|
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
||||||
filterableColumnCount={2}
|
shrinkZero
|
||||||
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
/>
|
||||||
shrinkZero
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,10 +114,12 @@ export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) =
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
className='bg-transparent border-0 shadow-none'
|
||||||
table={table}
|
table={table}
|
||||||
headerOptions={{ visible: false }}
|
headerOptions={{ visible: false }}
|
||||||
paginationOptions={{ visible: true }}
|
paginationOptions={{ visible: true, enablePageSizeSelector: false }}
|
||||||
title='Catálogo'
|
contentClassName='p-0'
|
||||||
|
footerClassName='p-0'
|
||||||
rowClassName='border-b-0'
|
rowClassName='border-b-0'
|
||||||
cellClassName='px-0'
|
cellClassName='px-0'
|
||||||
>
|
>
|
||||||
|
|||||||
@ -38,10 +38,11 @@ import {
|
|||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
||||||
import { AddNewRowButton } from "./AddNewRowButton";
|
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
||||||
|
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
||||||
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
||||||
import { QuoteItemsSortableTableRow } from "./QuoteItemsSortableTableRow";
|
import { QuoteItemsSortableTableRow } from "./QuoteItemsSortableTableRow";
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ declare module "@tanstack/react-table" {
|
|||||||
interface TableMeta<TData extends RowData> {
|
interface TableMeta<TData extends RowData> {
|
||||||
insertItem: (rowIndex: number, data?: unknown) => void;
|
insertItem: (rowIndex: number, data?: unknown) => void;
|
||||||
appendItem: (data?: unknown) => void;
|
appendItem: (data?: unknown) => void;
|
||||||
|
pickCatalogArticle: () => void;
|
||||||
duplicateItems: (rowIndex?: number) => void;
|
duplicateItems: (rowIndex?: number) => void;
|
||||||
deleteItems: (rowIndex?: number | number[]) => void;
|
deleteItems: (rowIndex?: number | number[]) => void;
|
||||||
updateItem: (rowIndex: number, rowData: TData, fieldName: string, value: unknown) => void;
|
updateItem: (rowIndex: number, rowData: TData, fieldName: string, value: unknown) => void;
|
||||||
@ -63,7 +65,7 @@ export type QuoteItemsSortableDataTableProps = {
|
|||||||
columns: ColumnDef<unknown, unknown>[];
|
columns: ColumnDef<unknown, unknown>[];
|
||||||
data: Record<"id", string>[];
|
data: Record<"id", string>[];
|
||||||
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
||||||
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields">;
|
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const measuringConfig = {
|
const measuringConfig = {
|
||||||
@ -139,6 +141,9 @@ export function QuoteItemsSortableDataTable({
|
|||||||
appendItem: (data?: unknown) => {
|
appendItem: (data?: unknown) => {
|
||||||
actions.append(data || defaultValues?.items[0], { shouldFocus: true });
|
actions.append(data || defaultValues?.items[0], { shouldFocus: true });
|
||||||
},
|
},
|
||||||
|
pickCatalogArticle: () => {
|
||||||
|
actions.pickCatalogArticle();
|
||||||
|
},
|
||||||
duplicateItems: (rowIndex?: number) => {
|
duplicateItems: (rowIndex?: number) => {
|
||||||
if (rowIndex != undefined) {
|
if (rowIndex != undefined) {
|
||||||
const originalData = table.getRowModel().rows[rowIndex].original;
|
const originalData = table.getRowModel().rows[rowIndex].original;
|
||||||
@ -146,6 +151,7 @@ export function QuoteItemsSortableDataTable({
|
|||||||
} else if (table.getSelectedRowModel().rows.length) {
|
} else if (table.getSelectedRowModel().rows.length) {
|
||||||
const lastIndex =
|
const lastIndex =
|
||||||
table.getSelectedRowModel().rows[table.getSelectedRowModel().rows.length - 1].index;
|
table.getSelectedRowModel().rows[table.getSelectedRowModel().rows.length - 1].index;
|
||||||
|
|
||||||
const data = table
|
const data = table
|
||||||
.getSelectedRowModel()
|
.getSelectedRowModel()
|
||||||
.rows.map((row) => ({ ...row.original, id: undefined }));
|
.rows.map((row) => ({ ...row.original, id: undefined }));
|
||||||
@ -238,70 +244,6 @@ export function QuoteItemsSortableDataTable({
|
|||||||
setActiveId(null);
|
setActiveId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hadleNewItem = useCallback(() => {
|
|
||||||
actions.append([
|
|
||||||
{
|
|
||||||
article_id: 2000004503,
|
|
||||||
description: "Lacquered Norma with 1 spline for lacquered panel up to 700 mm (27.56 in)",
|
|
||||||
quantity: { amount: "15", scale: 0 },
|
|
||||||
unit_price: {
|
|
||||||
amount: "150000",
|
|
||||||
scale: 4,
|
|
||||||
currency_code: "EUR",
|
|
||||||
},
|
|
||||||
discount: {
|
|
||||||
amount: 3500,
|
|
||||||
scale: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
article_id: 2000005891,
|
|
||||||
description:
|
|
||||||
"Split walnut HPL 3 elephant gray faux-leather central earring tray compartment 150x410x50 mm (5.91 in x 16.14 in x 1.97 in)",
|
|
||||||
quantity: { amount: "8", scale: 0 },
|
|
||||||
unit_price: {
|
|
||||||
amount: "384560",
|
|
||||||
scale: 4,
|
|
||||||
currency_code: "EUR",
|
|
||||||
},
|
|
||||||
discount: {
|
|
||||||
amount: null,
|
|
||||||
scale: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
article_id: 2000007412,
|
|
||||||
description:
|
|
||||||
"Nara H=3000 mm (118.11 in) fabric-covered glass panel up to 600 mm (23.62 in) wide",
|
|
||||||
quantity: { amount: "4", scale: 0 },
|
|
||||||
unit_price: {
|
|
||||||
amount: "8450000",
|
|
||||||
scale: 4,
|
|
||||||
currency_code: "EUR",
|
|
||||||
},
|
|
||||||
discount: {
|
|
||||||
amount: 500,
|
|
||||||
scale: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
article_id: 2000002589,
|
|
||||||
description:
|
|
||||||
"Panoramic anodized sliding H=2600 mm (102.36 in) GR3 glass panel up to 1200 mm (47.24 in) wide",
|
|
||||||
quantity: { amount: "25", scale: 0 },
|
|
||||||
unit_price: {
|
|
||||||
amount: "67481",
|
|
||||||
scale: 4,
|
|
||||||
currency_code: "EUR",
|
|
||||||
},
|
|
||||||
discount: {
|
|
||||||
amount: 100,
|
|
||||||
scale: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}, [actions]);
|
|
||||||
|
|
||||||
function filterItems(items: string[] | Row<unknown>[]) {
|
function filterItems(items: string[] | Row<unknown>[]) {
|
||||||
if (!activeId) {
|
if (!activeId) {
|
||||||
return items;
|
return items;
|
||||||
@ -357,9 +299,16 @@ export function QuoteItemsSortableDataTable({
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter className='bg-default'>
|
<TableFooter className='bg-default'>
|
||||||
<TableRow className='hover:bg-default'>
|
<TableRow className='hover:bg-default'>
|
||||||
<TableCell colSpan={6} className='py-6'>
|
<TableCell colSpan={3} className='py-6'></TableCell>
|
||||||
<AddNewRowButton onClick={hadleNewItem} />
|
<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>
|
||||||
|
<TableCell className='py-6'></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
|
|||||||
@ -8,81 +8,95 @@ export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<unk
|
|||||||
|
|
||||||
if (selectedRowsCount) {
|
if (selectedRowsCount) {
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center h-12 p-1 text-white rounded-md bg-primary '>
|
<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>{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'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Button>{`${selectedRowsCount} filas seleccionadas`}</Button>
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
size='icon'
|
onClick={() => table.options.meta?.appendItem()}
|
||||||
disabled={!table.getSelectedRowModel().rows.length}
|
|
||||||
onClick={() => table.options.meta?.duplicateItems()}
|
|
||||||
>
|
>
|
||||||
<CopyPlusIcon className='w-4 h-4' />
|
<CirclePlusIcon className='w-4 h-4 mr-2' />
|
||||||
<span className='sm:sr-only'>{t("common.duplicate_rows")}</span>
|
<span>{t("common.append_empty_row")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>{t("common.duplicate_rows_tooltip")}</TooltipContent>
|
<TooltipContent>{t("common.append_empty_row_tooltip")}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
type='button'
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
size='icon'
|
onClick={() => table.options.meta?.pickCatalogArticle()}
|
||||||
disabled={!table.getSelectedRowModel().rows.length}
|
|
||||||
onClick={() => table.options.meta?.deleteItems()}
|
|
||||||
>
|
>
|
||||||
<Trash2Icon className='w-4 h-4' />
|
<PackagePlusIcon className='w-4 h-4 mr-2' />
|
||||||
<span className='sm:sr-only'>Eliminar</span>
|
<span>{t("common.append_article")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Elimina las fila(s) seleccionada(s)</TooltipContent>
|
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
|
||||||
</Tooltip>
|
|
||||||
<Separator orientation='vertical' className='h-6 mx-1 bg-muted/50' />
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant='ghost'
|
|
||||||
size='sm'
|
|
||||||
disabled={!table.getSelectedRowModel().rows.length}
|
|
||||||
onClick={() => table.resetRowSelection()}
|
|
||||||
>
|
|
||||||
<ScanIcon className='w-4 h-4 md:mr-2' />
|
|
||||||
<span className='sm:sr-only'>Quitar selección</span>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Quita la selección</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex items-center gap-2 ml-auto'></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</nav>
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex items-center h-12 p-1 rounded-md bg-accent/50 text-muted-foreground'>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button type='button' variant='ghost' onClick={() => table.options.meta?.appendItem()}>
|
|
||||||
<CirclePlusIcon className='w-4 h-4 mr-2' />
|
|
||||||
<span>{t("common.append_empty_row")}</span>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>{t("common.append_empty_row_tooltip")}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button type='button' variant='ghost' onClick={() => table.options.meta?.appendItem()}>
|
|
||||||
<PackagePlusIcon className='w-4 h-4 mr-2' />
|
|
||||||
<span>{t("common.append_article")}</span>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center gap-2 ml-auto'></div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { Button, Dialog, DialogContent, DialogFooter } from "@/ui";
|
||||||
|
|
||||||
|
import { DataTableProvider } from "@/lib/hooks";
|
||||||
|
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
||||||
|
|
||||||
|
export const CatalogPickerDialog = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onSelect: (data: unknown) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog modal open={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className='w-11/12 max-w-full'>
|
||||||
|
<DataTableProvider syncWithLocation={false} initialPageSize={5}>
|
||||||
|
<CatalogPickerDataTable onSelect={onSelect} />
|
||||||
|
</DataTableProvider>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type='submit'>Choose</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -4,6 +4,7 @@ import {
|
|||||||
FormQuantityField,
|
FormQuantityField,
|
||||||
FormTextAreaField,
|
FormTextAreaField,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
|
|
||||||
import { DataTableProvider } from "@/lib/hooks";
|
import { DataTableProvider } from "@/lib/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui";
|
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui";
|
||||||
@ -13,7 +14,9 @@ import { t } from "i18next";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||||
import { useDetailColumns } from "../../hooks";
|
import { useDetailColumns } from "../../hooks";
|
||||||
|
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
||||||
import { QuoteItemsSortableDataTable } from "../QuoteItemsSortableDataTable";
|
import { QuoteItemsSortableDataTable } from "../QuoteItemsSortableDataTable";
|
||||||
|
import { CatalogPickerDialog } from "./CatalogPickerDialog";
|
||||||
|
|
||||||
export const QuoteDetailsCardEditor = ({
|
export const QuoteDetailsCardEditor = ({
|
||||||
currency,
|
currency,
|
||||||
@ -26,6 +29,10 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { control, register } = useFormContext();
|
const { control, register } = useFormContext();
|
||||||
|
|
||||||
|
const [pickerMode] = useState<"dialog" | "panel">("dialog");
|
||||||
|
|
||||||
|
const [pickerDialogOpen, setPickerDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const { fields, ...fieldActions } = useFieldArray({
|
const { fields, ...fieldActions } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "items",
|
name: "items",
|
||||||
@ -192,15 +199,15 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInsertArticle = useCallback(
|
const handleAppendCatalogArticle = useCallback(
|
||||||
(newArticle: any) => {
|
(article: any) => {
|
||||||
fieldActions.append({
|
fieldActions.append({
|
||||||
...newArticle,
|
...article,
|
||||||
quantity: {
|
quantity: {
|
||||||
amount: 100,
|
amount: 100,
|
||||||
scale: Quantity.DEFAULT_SCALE,
|
scale: Quantity.DEFAULT_SCALE,
|
||||||
},
|
},
|
||||||
unit_price: newArticle.retail_price,
|
unit_price: article.retail_price,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[fieldActions]
|
[fieldActions]
|
||||||
@ -211,24 +218,26 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
const defaultLayout = [265, 440, 655];
|
const defaultLayout = [265, 440, 655];
|
||||||
const navCollapsedSize = 4;
|
const navCollapsedSize = 4;
|
||||||
|
|
||||||
return (
|
if (pickerMode === "dialog") {
|
||||||
<>
|
return (
|
||||||
<QuoteItemsSortableDataTable
|
<div className='relative'>
|
||||||
actions={fieldActions}
|
<QuoteItemsSortableDataTable
|
||||||
columns={columns}
|
actions={{
|
||||||
data={fields}
|
...fieldActions,
|
||||||
defaultValues={defaultValues}
|
pickCatalogArticle: () => setPickerDialogOpen(true),
|
||||||
/>
|
}}
|
||||||
<FormCurrencyField
|
columns={columns}
|
||||||
variant='outline'
|
data={fields}
|
||||||
currency={currency}
|
defaultValues={defaultValues}
|
||||||
language={language}
|
/>
|
||||||
scale={4}
|
<CatalogPickerDialog
|
||||||
className='text-right'
|
onSelect={handleAppendCatalogArticle}
|
||||||
{...register("subtotal_price")}
|
isOpen={pickerDialogOpen}
|
||||||
/>
|
onOpenChange={setPickerDialogOpen}
|
||||||
</>
|
/>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
@ -250,12 +259,17 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
}}
|
}}
|
||||||
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
|
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
|
||||||
>
|
>
|
||||||
<QuoteItemsSortableDataTable actions={fieldActions} columns={columns} data={fields} />
|
<QuoteItemsSortableDataTable
|
||||||
|
actions={fieldActions}
|
||||||
|
columns={columns}
|
||||||
|
data={fields}
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle withHandle className='mx-3' />
|
<ResizableHandle withHandle className='mx-3' />
|
||||||
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
|
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
|
||||||
<DataTableProvider syncWithLocation={false}>
|
<DataTableProvider syncWithLocation={false}>
|
||||||
<CatalogPickerDataTable onClick={handleInsertArticle} />
|
<CatalogPickerDataTable onSelect={handleAppendCatalogArticle} />
|
||||||
</DataTableProvider>
|
</DataTableProvider>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export type DataTableColumnProps<TData, TValue> = ColumnDef<TData, TValue>;
|
|||||||
|
|
||||||
export type DataTablePaginationOptionsProps<TData> = Pick<
|
export type DataTablePaginationOptionsProps<TData> = Pick<
|
||||||
DataTablePaginationProps<TData>,
|
DataTablePaginationProps<TData>,
|
||||||
"visible"
|
"visible" | "enablePageSizeSelector"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type DataTableHeaderOptionsProps = {
|
export type DataTableHeaderOptionsProps = {
|
||||||
@ -41,6 +41,8 @@ export type DataTableProps<TData> = PropsWithChildren<{
|
|||||||
paginationOptions?: DataTablePaginationOptionsProps<TData>;
|
paginationOptions?: DataTablePaginationOptionsProps<TData>;
|
||||||
headerOptions?: DataTableHeaderOptionsProps;
|
headerOptions?: DataTableHeaderOptionsProps;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
contentClassName?: string;
|
||||||
|
footerClassName?: string;
|
||||||
rowClassName?: string;
|
rowClassName?: string;
|
||||||
cellClassName?: string;
|
cellClassName?: string;
|
||||||
}>;
|
}>;
|
||||||
@ -54,6 +56,8 @@ export function DataTable<TData>({
|
|||||||
headerOptions = { visible: true },
|
headerOptions = { visible: true },
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
contentClassName,
|
||||||
|
footerClassName,
|
||||||
rowClassName,
|
rowClassName,
|
||||||
cellClassName,
|
cellClassName,
|
||||||
}: DataTableProps<TData>) {
|
}: DataTableProps<TData>) {
|
||||||
@ -67,7 +71,7 @@ export function DataTable<TData>({
|
|||||||
<CardDescription>{description}</CardDescription>
|
<CardDescription>{description}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
)}
|
)}
|
||||||
<CardContent className='pt-6'>
|
<CardContent className={cn("pt-6", contentClassName)}>
|
||||||
{children && (
|
{children && (
|
||||||
<>
|
<>
|
||||||
<div className='flex space-x-2'>{children}</div>
|
<div className='flex space-x-2'>{children}</div>
|
||||||
@ -124,12 +128,8 @@ export function DataTable<TData>({
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter className={footerClassName}>
|
||||||
<DataTablePagination
|
<DataTablePagination className='flex-1' table={table} {...paginationOptions} />
|
||||||
className='flex-1'
|
|
||||||
visible={paginationOptions?.visible}
|
|
||||||
table={table}
|
|
||||||
/>
|
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,12 +15,14 @@ import { useMemo } from "react";
|
|||||||
export type DataTablePaginationProps<TData> = {
|
export type DataTablePaginationProps<TData> = {
|
||||||
table: Table<TData>;
|
table: Table<TData>;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
enablePageSizeSelector?: boolean;
|
||||||
visible?: boolean | "auto";
|
visible?: boolean | "auto";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DataTablePagination<TData>({
|
export function DataTablePagination<TData>({
|
||||||
table,
|
table,
|
||||||
className,
|
className,
|
||||||
|
enablePageSizeSelector = true,
|
||||||
visible = "auto",
|
visible = "auto",
|
||||||
}: DataTablePaginationProps<TData>) {
|
}: DataTablePaginationProps<TData>) {
|
||||||
const isVisible = useMemo(() => visible === true, [visible]);
|
const isVisible = useMemo(() => visible === true, [visible]);
|
||||||
@ -31,11 +33,11 @@ export function DataTablePagination<TData>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center justify-between px-2", className)}>
|
<div className={className}>
|
||||||
<div className='flex-1 text-base text-muted-foreground'>
|
<div className='flex-1 text-base text-muted-foreground'>
|
||||||
{table.getSelectedRowModel().rows.length > 0 && (
|
{table.getSelectedRowModel().rows.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{t("common.rows_selected", {
|
{t("common.rows_selected_of_total", {
|
||||||
count: table.getFilteredSelectedRowModel().rows.length,
|
count: table.getFilteredSelectedRowModel().rows.length,
|
||||||
total: table.getFilteredRowModel().rows.length,
|
total: table.getFilteredRowModel().rows.length,
|
||||||
})}
|
})}
|
||||||
@ -43,75 +45,86 @@ export function DataTablePagination<TData>({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center space-x-6 lg:space-x-8'>
|
<div className='flex justify-between space-x-6 lg:space-x-8'>
|
||||||
<div className='flex items-center space-x-2'>
|
{enablePageSizeSelector && (
|
||||||
<p className='text-base font-medium'>{t("common.rows_per_page")}</p>
|
<div className='flex items-center space-x-2 grow'>
|
||||||
|
<p className='text-sm font-medium'>{t("common.rows_per_page")}</p>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={`${table.getState().pagination.pageSize}`}
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
table.setPageSize(Number(value));
|
table.setPageSize(Number(value));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className='h-8 w-[70px]'>
|
<SelectTrigger className='h-8 w-[70px]'>
|
||||||
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent side='top'>
|
<SelectContent side='top'>
|
||||||
{DEFAULT_PAGE_SIZES.map((pageSize) => (
|
{DEFAULT_PAGE_SIZES.map((pageSize) => (
|
||||||
<SelectItem key={pageSize} value={`${pageSize}`}>
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
{pageSize}
|
{pageSize}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex w-[100px] items-center justify-center text-base font-medium'>
|
)}
|
||||||
{t("common.num_page_of_total", {
|
<div
|
||||||
count: table.getState().pagination.pageIndex + 1,
|
className={cn(
|
||||||
total: table.getPageCount(),
|
"flex space-x-2 flex-1",
|
||||||
})}
|
enablePageSizeSelector ? "justify-end" : "justify-between"
|
||||||
</div>
|
)}
|
||||||
<div className='flex items-center space-x-2'>
|
>
|
||||||
<Button
|
<div className='flex w-[100px] items-center justify-center'>
|
||||||
type='button'
|
<p className='text-sm font-medium '>
|
||||||
variant='outline'
|
{t("common.num_page_of_total", {
|
||||||
className='hidden w-8 h-8 p-0 lg:flex'
|
count: table.getState().pagination.pageIndex + 1,
|
||||||
onClick={() => table.setPageIndex(INITIAL_PAGE_INDEX)}
|
total: table.getPageCount(),
|
||||||
disabled={!table.getCanPreviousPage()}
|
})}
|
||||||
>
|
</p>
|
||||||
<span className='sr-only'>{t("common.go_to_first_page")}</span>
|
</div>
|
||||||
<ChevronsLeftIcon className='w-4 h-4' />
|
<div className='flex items-center space-x-2'>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
type='button'
|
||||||
type='button'
|
variant='outline'
|
||||||
variant='outline'
|
className='hidden w-8 h-8 p-0 lg:flex'
|
||||||
className='w-8 h-8 p-0'
|
onClick={() => table.setPageIndex(INITIAL_PAGE_INDEX)}
|
||||||
onClick={() => table.previousPage()}
|
disabled={!table.getCanPreviousPage()}
|
||||||
disabled={!table.getCanPreviousPage()}
|
>
|
||||||
>
|
<span className='sr-only'>{t("common.go_to_first_page")}</span>
|
||||||
<span className='sr-only'>{t("common.go_to_prev_page")}</span>
|
<ChevronsLeftIcon className='w-4 h-4' />
|
||||||
<ChevronLeftIcon className='w-4 h-4' />
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
type='button'
|
||||||
type='button'
|
variant='outline'
|
||||||
variant='outline'
|
className='w-8 h-8 p-0'
|
||||||
className='w-8 h-8 p-0'
|
onClick={() => table.previousPage()}
|
||||||
onClick={() => table.nextPage()}
|
disabled={!table.getCanPreviousPage()}
|
||||||
disabled={!table.getCanNextPage()}
|
>
|
||||||
>
|
<span className='sr-only'>{t("common.go_to_prev_page")}</span>
|
||||||
<span className='sr-only'>{t("common.go_to_next_page")}</span>
|
<ChevronLeftIcon className='w-4 h-4' />
|
||||||
<ChevronRightIcon className='w-4 h-4' />
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
type='button'
|
||||||
type='button'
|
variant='outline'
|
||||||
variant='outline'
|
className='w-8 h-8 p-0'
|
||||||
className='hidden w-8 h-8 p-0 lg:flex'
|
onClick={() => table.nextPage()}
|
||||||
onClick={() => table.setPageIndex(table.getPageCount() + 1)}
|
disabled={!table.getCanNextPage()}
|
||||||
disabled={!table.getCanNextPage()}
|
>
|
||||||
>
|
<span className='sr-only'>{t("common.go_to_next_page")}</span>
|
||||||
<span className='sr-only'>{t("common.go_to_last_page")}</span>
|
<ChevronRightIcon className='w-4 h-4' />
|
||||||
<ChevronsRightIcon className='w-4 h-4' />
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='outline'
|
||||||
|
className='hidden w-8 h-8 p-0 lg:flex'
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() + 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className='sr-only'>{t("common.go_to_last_page")}</span>
|
||||||
|
<ChevronsRightIcon className='w-4 h-4' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,12 +27,20 @@ export const DataTableContext = createContext<IDataTableContextState | null>(nul
|
|||||||
export const DataTableProvider = ({
|
export const DataTableProvider = ({
|
||||||
syncWithLocation = true,
|
syncWithLocation = true,
|
||||||
initialGlobalFilter = "",
|
initialGlobalFilter = "",
|
||||||
|
initialPageIndex,
|
||||||
|
initialPageSize,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<{
|
}: PropsWithChildren<{
|
||||||
syncWithLocation?: boolean;
|
syncWithLocation?: boolean;
|
||||||
initialGlobalFilter?: string;
|
initialGlobalFilter?: string;
|
||||||
|
initialPageIndex?: number;
|
||||||
|
initialPageSize?: number;
|
||||||
}>) => {
|
}>) => {
|
||||||
const [pagination, setPagination] = useSyncedPagination(syncWithLocation);
|
const [pagination, setPagination] = useSyncedPagination({
|
||||||
|
syncWithLocation,
|
||||||
|
initialPageIndex,
|
||||||
|
initialPageSize,
|
||||||
|
});
|
||||||
const [globalFilter, setGlobalFilter] = useState<string>(initialGlobalFilter);
|
const [globalFilter, setGlobalFilter] = useState<string>(initialGlobalFilter);
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
import { usePagination, usePaginationSyncWithLocation } from "../usePagination";
|
import { usePagination, usePaginationSyncWithLocation } from "../usePagination";
|
||||||
|
|
||||||
export const useSyncedPagination = (syncWithLocation: boolean) => {
|
type UseSyncedPaginationProps = {
|
||||||
|
syncWithLocation?: boolean;
|
||||||
|
initialPageIndex?: number;
|
||||||
|
initialPageSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSyncedPagination = ({
|
||||||
|
syncWithLocation = true,
|
||||||
|
initialPageIndex,
|
||||||
|
initialPageSize,
|
||||||
|
}: UseSyncedPaginationProps) => {
|
||||||
const [paginationWithLocation, setPaginationWithLocation] = usePaginationSyncWithLocation();
|
const [paginationWithLocation, setPaginationWithLocation] = usePaginationSyncWithLocation();
|
||||||
const [paginationWithoutLocation, setPaginationWithoutLocation] = usePagination();
|
const [paginationWithoutLocation, setPaginationWithoutLocation] = usePagination(
|
||||||
|
initialPageIndex,
|
||||||
|
initialPageSize
|
||||||
|
);
|
||||||
|
|
||||||
if (syncWithLocation) {
|
if (syncWithLocation) {
|
||||||
return [paginationWithLocation, setPaginationWithLocation] as const;
|
return [paginationWithLocation, setPaginationWithLocation] as const;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const DEFAULT_PAGE_SIZES = [15, 30, 50, 75, 100];
|
export const DEFAULT_PAGE_SIZES = [5, 10, 15, 30, 50, 75, 100];
|
||||||
|
|
||||||
export interface PaginationState {
|
export interface PaginationState {
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { CustomDialog } from "@/components";
|
|
||||||
import { NullOr } from "@shared/utilities";
|
import { NullOr } from "@shared/utilities";
|
||||||
import { PropsWithChildren, createContext, useCallback, useMemo, useState } from "react";
|
import { createContext } from "react";
|
||||||
import { UnsavedChangesNotifierProps } from "./useUnsavedChangesNotifier";
|
import { UnsavedChangesNotifierProps } from "./useUnsavedChangesNotifier";
|
||||||
|
|
||||||
export interface IUnsavedWarnContextState {
|
export interface IUnsavedWarnContextState {
|
||||||
@ -8,48 +7,3 @@ export interface IUnsavedWarnContextState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const UnsavedWarnContext = createContext<NullOr<IUnsavedWarnContextState>>(null);
|
export const UnsavedWarnContext = createContext<NullOr<IUnsavedWarnContextState>>(null);
|
||||||
|
|
||||||
export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => {
|
|
||||||
const [confirm, setConfirm] = useState<NullOr<UnsavedChangesNotifierProps>>(null);
|
|
||||||
|
|
||||||
const [open, toggle] = useState(false);
|
|
||||||
|
|
||||||
const show = useCallback(
|
|
||||||
(confirmOptions: NullOr<UnsavedChangesNotifierProps>) => {
|
|
||||||
setConfirm(confirmOptions);
|
|
||||||
toggle(true);
|
|
||||||
},
|
|
||||||
[toggle, setConfirm]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onConfirm = () => {
|
|
||||||
confirm?.onConfirm?.();
|
|
||||||
toggle(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
confirm?.onCancel?.();
|
|
||||||
toggle(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = useMemo(() => ({ show }), [show]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UnsavedWarnContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
<CustomDialog
|
|
||||||
//type='warning'
|
|
||||||
onCancel={() => {
|
|
||||||
console.log("onCancel");
|
|
||||||
onCancel();
|
|
||||||
}}
|
|
||||||
onConfirm={() => onConfirm()}
|
|
||||||
title={confirm?.title}
|
|
||||||
description={confirm?.subtitle}
|
|
||||||
confirmLabel={confirm?.confirmText}
|
|
||||||
cancelLabel={confirm?.cancelText}
|
|
||||||
isOpen={open}
|
|
||||||
/>
|
|
||||||
</UnsavedWarnContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { CustomDialog } from "@/components";
|
||||||
|
import { NullOr } from "@shared/utilities";
|
||||||
|
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
||||||
|
import { UnsavedChangesNotifierProps } from "./useUnsavedChangesNotifier";
|
||||||
|
import { UnsavedWarnContext } from "./WarnAboutChangeContext";
|
||||||
|
|
||||||
|
export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => {
|
||||||
|
const [confirm, setConfirm] = useState<NullOr<UnsavedChangesNotifierProps>>(null);
|
||||||
|
|
||||||
|
const [open, toggle] = useState(false);
|
||||||
|
|
||||||
|
const show = useCallback(
|
||||||
|
(confirmOptions: NullOr<UnsavedChangesNotifierProps>) => {
|
||||||
|
setConfirm(confirmOptions);
|
||||||
|
toggle(true);
|
||||||
|
},
|
||||||
|
[toggle, setConfirm]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
confirm?.onConfirm?.();
|
||||||
|
toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
confirm?.onCancel?.();
|
||||||
|
toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = useMemo(() => ({ show }), [show]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UnsavedWarnContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
<CustomDialog
|
||||||
|
//type='warning'
|
||||||
|
onCancel={() => {
|
||||||
|
console.log("onCancel");
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
onConfirm={() => onConfirm()}
|
||||||
|
title={confirm?.title}
|
||||||
|
description={confirm?.subtitle}
|
||||||
|
confirmLabel={confirm?.confirmText}
|
||||||
|
cancelLabel={confirm?.cancelText}
|
||||||
|
isOpen={open}
|
||||||
|
/>
|
||||||
|
</UnsavedWarnContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./WarnAboutChangeContext";
|
|
||||||
export * from "./useUnsavedChangesNotifier";
|
export * from "./useUnsavedChangesNotifier";
|
||||||
export * from "./useWarnAboutChange";
|
export * from "./useWarnAboutChange";
|
||||||
|
export * from "./WarnAboutChangeContext";
|
||||||
|
export * from "./WarnAboutChangeProvider";
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
"sort_desc": "Desc",
|
"sort_desc": "Desc",
|
||||||
"sort_desc_description": "In descending order. Click to sort in ascending order.",
|
"sort_desc_description": "In descending order. Click to sort in ascending order.",
|
||||||
"sort_none_description": "No sorting order. Click to sort in ascending order.",
|
"sort_none_description": "No sorting order. Click to sort in ascending order.",
|
||||||
"rows_selected": "{{count}} of {{total}} row(s) selected.",
|
"rows_selected": "{{count}} row(s) selected.",
|
||||||
|
"rows_selected_of_total": "{{count}} of {{total}} row(s) selected.",
|
||||||
"rows_per_page": "Rows per page",
|
"rows_per_page": "Rows per page",
|
||||||
"num_page_of_total": "Page {{count}} of {{total}}",
|
"num_page_of_total": "Page {{count}} of {{total}}",
|
||||||
"go_to_first_page": "Go to first page",
|
"go_to_first_page": "Go to first page",
|
||||||
@ -29,13 +30,17 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"open_menu": "Open menu",
|
"open_menu": "Open menu",
|
||||||
"duplicate_rows": "Duplicate",
|
"duplicate_selected_rows": "Duplicate",
|
||||||
"duplicate_rows_tooltip": "Duplicate selected row(s)",
|
"duplicate_selected_rows_tooltip": "Duplicate selected row(s)",
|
||||||
"append_empty_row": "Append row",
|
"append_empty_row": "Append row",
|
||||||
"append_empty_row_tooltip": "Append a empty row",
|
"append_empty_row_tooltip": "Append a empty row",
|
||||||
"append_article": "Append article",
|
"append_article": "Append article",
|
||||||
"append_article_tooltip": "Select and add an item from the catalog",
|
"append_article_tooltip": "Select and add an item from the catalog",
|
||||||
"remove_row": "Remove",
|
"remove_row": "Remove",
|
||||||
|
"remove_selected_rows": "Remove",
|
||||||
|
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
||||||
|
"reset_selected_rows": "Reset selection",
|
||||||
|
"reset_selected_rows_tooltip": "Reset selected row(s)",
|
||||||
"insert_row_above": "Insert row above",
|
"insert_row_above": "Insert row above",
|
||||||
"insert_row_below": "Insert row below",
|
"insert_row_below": "Insert row below",
|
||||||
"pick_date": "Select a date",
|
"pick_date": "Select a date",
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
"sort_desc": "Desc",
|
"sort_desc": "Desc",
|
||||||
"sort_desc_description": "En orden descendente. Click para ordenar ascendentemente.",
|
"sort_desc_description": "En orden descendente. Click para ordenar ascendentemente.",
|
||||||
"sort_none_description": "Sin orden. Click para ordenar ascendentemente.",
|
"sort_none_description": "Sin orden. Click para ordenar ascendentemente.",
|
||||||
"rows_selected": "{{count}} de {{total}} fila(s) seleccionadas.",
|
"rows_selected": "{{count}} fila(s) seleccionadas.",
|
||||||
|
"rows_selected_of_total": "{{count}} de {{total}} fila(s) seleccionadas.",
|
||||||
"rows_per_page": "Filas por página",
|
"rows_per_page": "Filas por página",
|
||||||
"num_page_of_total": "Página {{count}} de {{total}}",
|
"num_page_of_total": "Página {{count}} de {{total}}",
|
||||||
"go_to_first_page": "Ir a la primera página",
|
"go_to_first_page": "Ir a la primera página",
|
||||||
@ -29,13 +30,17 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"actions": "Acciones",
|
"actions": "Acciones",
|
||||||
"open_menu": "Abrir el menú",
|
"open_menu": "Abrir el menú",
|
||||||
"duplicate_rows": "Duplicar",
|
"duplicate_selected_rows": "Duplicar",
|
||||||
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
|
"duplicate_selected_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
|
||||||
"append_empty_row": "Añadir fila",
|
"append_empty_row": "Añadir fila",
|
||||||
"append_empty_row_tooltip": "Añadir una fila vacía",
|
"append_empty_row_tooltip": "Añadir una fila vacía",
|
||||||
"append_article": "Añadir artículo",
|
"append_article": "Añadir artículo",
|
||||||
"append_article_tooltip": "Elegir un artículo del catálogo y añadirlo",
|
"append_article_tooltip": "Elegir un artículo del catálogo y añadirlo",
|
||||||
"remove_row": "Eliminar",
|
"remove_row": "Eliminar",
|
||||||
|
"remove_selected_rows": "Eliminar",
|
||||||
|
"remove_selected_rows_tooltip": "Elimina las fila(s) seleccionadas(s)",
|
||||||
|
"reset_selected_rows": "Quitar selection",
|
||||||
|
"reset_selected_rows_tooltip": "Dejar de seleccionar la(s) fila(s)",
|
||||||
"insert_row_above": "Insertar fila encima",
|
"insert_row_above": "Insertar fila encima",
|
||||||
"insert_row_below": "Insertar fila debajo",
|
"insert_row_below": "Insertar fila debajo",
|
||||||
"pick_date": "Elige una fecha",
|
"pick_date": "Elige una fecha",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export const INITIAL_PAGE_INDEX = 0;
|
export const INITIAL_PAGE_INDEX = 0;
|
||||||
export const INITIAL_PAGE_SIZE = 15;
|
export const INITIAL_PAGE_SIZE = 5;
|
||||||
|
|
||||||
export const MIN_PAGE_INDEX = 0;
|
export const MIN_PAGE_INDEX = 0;
|
||||||
export const MIN_PAGE_SIZE = 1;
|
export const MIN_PAGE_SIZE = 1;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user