.
This commit is contained in:
parent
0b5f180b6f
commit
8e80bfe31e
@ -148,6 +148,11 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
getRowId: (originalRow: unknown) => originalRow?.id,
|
getRowId: (originalRow: unknown) => originalRow?.id,
|
||||||
debugHeaders: false,
|
debugHeaders: false,
|
||||||
debugColumns: false,
|
debugColumns: false,
|
||||||
|
defaultColumn: {
|
||||||
|
size: 8, //starting column size
|
||||||
|
minSize: 1, //enforced during column resizing
|
||||||
|
maxSize: 96, //enforced during column resizing
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
insertItem: (rowIndex: number, data: object = {}) => {
|
insertItem: (rowIndex: number, data: object = {}) => {
|
||||||
actions.insert(rowIndex, data, { shouldFocus: true });
|
actions.insert(rowIndex, data, { shouldFocus: true });
|
||||||
@ -326,13 +331,14 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
>
|
>
|
||||||
<SortableDataTableToolbar table={table} />
|
<SortableDataTableToolbar table={table} />
|
||||||
<Table className='table-auto'>
|
<Table className='table-fixed'>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
|
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
|
console.log(header.getSize());
|
||||||
return (
|
return (
|
||||||
<TableHead key={header.id} className='px-1'>
|
<TableHead key={header.id} className={`px-1 w-${header.getSize()}`}>
|
||||||
{header.isPlaceholder ? null : (
|
{header.isPlaceholder ? null : (
|
||||||
<DataTableColumnHeader table={table} header={header} />
|
<DataTableColumnHeader table={table} header={header} />
|
||||||
)}
|
)}
|
||||||
@ -350,11 +356,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
{filterItems(table.getRowModel().rows).map((row) => (
|
{filterItems(table.getRowModel().rows).map((row) => (
|
||||||
<SortableTableRow key={row.id} id={row.id}>
|
<SortableTableRow key={row.id} id={row.id}>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell
|
<TableCell key={cell.id} className='px-2 py-1 align-top'>
|
||||||
className='p-1 align-top'
|
|
||||||
key={cell.id}
|
|
||||||
style={{ width: cell.column.getSize() }}
|
|
||||||
>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -58,7 +58,10 @@ export function SortableTableRow({ id, children }: PropsWithChildren<SortablePro
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={id}
|
key={id}
|
||||||
id={String(id)}
|
id={String(id)}
|
||||||
className={cn(isDragging ? "opacity-40" : "opacity-100", "hover:bg-muted/30 m-0")}
|
className={cn(
|
||||||
|
isDragging ? "opacity-40" : "opacity-100",
|
||||||
|
"m-0 hover:bg-muted hover:focus-within:bg-accent focus-within:bg-accent"
|
||||||
|
)}
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {
|
import { FormQuantityField, FormTextField } from "@/components";
|
||||||
FormCurrencyField,
|
|
||||||
FormQuantityField,
|
|
||||||
FormTextAreaField,
|
|
||||||
FormTextField,
|
|
||||||
} 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";
|
||||||
import { CurrencyData, Quantity } from "@shared/contexts";
|
import { CurrencyData, Quantity } from "@shared/contexts";
|
||||||
|
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";
|
||||||
@ -38,32 +34,42 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
{
|
{
|
||||||
id: "quantity" as const,
|
id: "quantity" as const,
|
||||||
accessorKey: "quantity",
|
accessorKey: "quantity",
|
||||||
header: "quantity",
|
header: () => (
|
||||||
|
<div className='text-right'>{t("quotes.form_fields.items.quantity.label")}</div>
|
||||||
|
),
|
||||||
size: 5,
|
size: 5,
|
||||||
cell: ({ row: { index } }) => {
|
cell: ({ row: { index } }) => {
|
||||||
return <FormQuantityField {...register(`items.${index}.quantity`)} />;
|
return (
|
||||||
|
<FormQuantityField
|
||||||
|
variant='outline'
|
||||||
|
precision={2}
|
||||||
|
{...register(`items.${index}.quantity`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "description" as const,
|
id: "description" as const,
|
||||||
accessorKey: "description",
|
accessorKey: "description",
|
||||||
header: "description",
|
header: t("quotes.form_fields.items.description.label"),
|
||||||
|
size: 24,
|
||||||
cell: ({ row: { index } }) => {
|
cell: ({ row: { index } }) => {
|
||||||
return <FormTextAreaField autoSize {...register(`items.${index}.description`)} />;
|
return <FormTextField autoSize {...register(`items.${index}.description`)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "unit_price" as const,
|
id: "unit_price" as const,
|
||||||
accessorKey: "unit_price",
|
accessorKey: "unit_price",
|
||||||
header: "unit_price",
|
header: () => (
|
||||||
size: 10,
|
<div className='text-right'>{t("quotes.form_fields.items.unit_price.label")}</div>
|
||||||
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return (
|
return (
|
||||||
<FormCurrencyField
|
<FormTextField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
currency={currency}
|
currency={currency}
|
||||||
precision={4}
|
precision={4}
|
||||||
|
className='text-right'
|
||||||
{...register(`items.${index}.unit_price`)}
|
{...register(`items.${index}.unit_price`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -72,8 +78,9 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
{
|
{
|
||||||
id: "subtotal_price" as const,
|
id: "subtotal_price" as const,
|
||||||
accessorKey: "subtotal_price",
|
accessorKey: "subtotal_price",
|
||||||
header: "subtotal_price",
|
header: () => (
|
||||||
size: 10,
|
<div className='text-right'>{t("quotes.form_fields.items.subtotal_price.label")}</div>
|
||||||
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormTextField {...register(`items.${index}.subtotal_price`)} />;
|
return <FormTextField {...register(`items.${index}.subtotal_price`)} />;
|
||||||
},
|
},
|
||||||
@ -81,17 +88,20 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
{
|
{
|
||||||
id: "discount" as const,
|
id: "discount" as const,
|
||||||
accessorKey: "discount",
|
accessorKey: "discount",
|
||||||
header: "discount",
|
|
||||||
size: 5,
|
size: 5,
|
||||||
|
header: () => (
|
||||||
|
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
|
||||||
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormTextField {...register(`items.${index}.discount`)} />;
|
return <FormTextField className='text-right' {...register(`items.${index}.discount`)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "total_price" as const,
|
id: "total_price" as const,
|
||||||
accessorKey: "total_price",
|
accessorKey: "total_price",
|
||||||
header: "total_price",
|
header: () => (
|
||||||
size: 10,
|
<div className='text-right'>{t("quotes.form_fields.items.total_price.label")}</div>
|
||||||
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormTextField {...register(`items.${index}.total_price`)} />;
|
return <FormTextField {...register(`items.${index}.total_price`)} />;
|
||||||
},
|
},
|
||||||
@ -151,6 +161,8 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
const defaultLayout = [265, 440, 655];
|
const defaultLayout = [265, 440, 655];
|
||||||
const navCollapsedSize = 4;
|
const navCollapsedSize = 4;
|
||||||
|
|
||||||
|
return <SortableDataTable actions={fieldActions} columns={columns} data={fields} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
direction='horizontal'
|
direction='horizontal'
|
||||||
|
|||||||
@ -5,27 +5,17 @@ import {
|
|||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { Checkbox } from "@/ui";
|
import { Checkbox } from "@/ui";
|
||||||
import { ColumnDef, Row, Table } from "@tanstack/react-table";
|
import { ColumnDef, Row, Table } from "@tanstack/react-table";
|
||||||
import { MoreHorizontalIcon } from "lucide-react";
|
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
/*function getSelectedRowRange<T>(
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
rows: Row<T>[],
|
export function useDetailColumns<TData, TValue = unknown>(
|
||||||
currentID: number,
|
|
||||||
selectedID: number,
|
|
||||||
): Row<T>[] {
|
|
||||||
const rangeStart = selectedID > currentID ? currentID : selectedID;
|
|
||||||
const rangeEnd = rangeStart === currentID ? selectedID : currentID;
|
|
||||||
return rows.slice(rangeStart, rangeEnd + 1);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
export function useDetailColumns<TData, TValue>(
|
|
||||||
columns: ColumnDef<TData>[],
|
columns: ColumnDef<TData>[],
|
||||||
options: {
|
options: {
|
||||||
enableDragHandleColumn?: boolean;
|
enableDragHandleColumn?: boolean;
|
||||||
enableSelectionColumn?: boolean;
|
enableSelectionColumn?: boolean;
|
||||||
enableActionsColumn?: boolean;
|
enableActionsColumn?: boolean;
|
||||||
rowActionFn?: DataTablaRowActionFunction<TData, TValue>;
|
rowActionFn?: DataTablaRowActionFunction<TData>;
|
||||||
} = {}
|
} = {}
|
||||||
): ColumnDef<TData>[] {
|
): ColumnDef<TData>[] {
|
||||||
const {
|
const {
|
||||||
@ -67,7 +57,9 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
),
|
),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
size: 30,
|
size: 2,
|
||||||
|
minSize: 2,
|
||||||
|
maxSize: 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +71,10 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
),*/
|
),*/
|
||||||
header: () => null,
|
header: () => null,
|
||||||
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
|
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
|
||||||
size: 16,
|
size: 2,
|
||||||
|
minSize: 2,
|
||||||
|
maxSize: 2,
|
||||||
|
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
});
|
});
|
||||||
@ -88,16 +83,23 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
if (enableActionsColumn) {
|
if (enableActionsColumn) {
|
||||||
columns.push({
|
columns.push({
|
||||||
id: "row_actions",
|
id: "row_actions",
|
||||||
header: () => (
|
/*header: () => (
|
||||||
<MoreHorizontalIcon
|
<MoreHorizontalIcon
|
||||||
aria-label='Acciones'
|
aria-label='Acciones'
|
||||||
className='items-center justify-center w-4 h-4'
|
className='items-center justify-center w-4 h-4'
|
||||||
/>
|
/>
|
||||||
),
|
),*/
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
return <DataTableRowActions {...props} actions={rowActionFn} />;
|
return (
|
||||||
|
<div className='w-full mx-auto text-center'>
|
||||||
|
<DataTableRowActions rowContext={props} actions={rowActionFn} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
size: 16,
|
size: 4,
|
||||||
|
minSize: 4,
|
||||||
|
maxSize: 4,
|
||||||
|
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=open]:bg-accent font-bold text-muted-foreground uppercase text-xs tracking-wide",
|
"data-[state=open]:bg-accent font-bold text-muted-foreground uppercase text-xs tracking-wide text-ellipsis",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -10,32 +11,31 @@ import {
|
|||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { CellContext, Row } from "@tanstack/react-table";
|
import { CellContext } from "@tanstack/react-table";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { MoreHorizontalIcon } from "lucide-react";
|
import { MoreVerticalIcon } from "lucide-react";
|
||||||
|
|
||||||
type DataTableRowActionContext<TData, TValue> = CellContext<TData, TValue> & {
|
export type DataTablaRowActionFunction<TData> = (
|
||||||
row: Row<TData>;
|
props: CellContext<TData, unknown>
|
||||||
};
|
) => DataTableRowActionDefinition<TData>[];
|
||||||
|
|
||||||
export type DataTablaRowActionFunction<TData, TValue> = (
|
export type DataTableRowActionDefinition<TData> = {
|
||||||
props: DataTableRowActionContext<TData, TValue>
|
|
||||||
) => DataTableRowActionDefinition<TData, TValue>[];
|
|
||||||
|
|
||||||
export type DataTableRowActionDefinition<TData, TValue> = {
|
|
||||||
label: string | "-";
|
label: string | "-";
|
||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
onClick?: (props: DataTableRowActionContext<TData, TValue>, e: React.BaseSyntheticEvent) => void;
|
onClick?: (props: CellContext<TData, unknown>, e: React.BaseSyntheticEvent) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataTableRowActionsProps<TData, TValue> = {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
actions?: DataTablaRowActionFunction<TData, TValue>;
|
export type DataTableRowActionsProps<TData, TValue = unknown> = {
|
||||||
row?: DataTableRowActionContext<TData, TValue>;
|
className?: string;
|
||||||
|
actions?: DataTablaRowActionFunction<TData>;
|
||||||
|
rowContext: CellContext<TData, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DataTableRowActions<TData = any, TValue = any>({
|
export function DataTableRowActions<TData = any, TValue = unknown>({
|
||||||
actions,
|
actions,
|
||||||
...props
|
rowContext,
|
||||||
|
className,
|
||||||
}: DataTableRowActionsProps<TData, TValue>) {
|
}: DataTableRowActionsProps<TData, TValue>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -44,9 +44,9 @@ export function DataTableRowActions<TData = any, TValue = any>({
|
|||||||
aria-haspopup='true'
|
aria-haspopup='true'
|
||||||
size='icon'
|
size='icon'
|
||||||
variant='link'
|
variant='link'
|
||||||
className='w-4 h-4 translate-y-[2px]'
|
className={cn("w-4 h-4 mt-2 text-ring hover:text-muted-foreground", className)}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon className='w-4 h-4' />
|
<MoreVerticalIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>{t("common.open_menu")}</span>
|
<span className='sr-only'>{t("common.open_menu")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@ -54,13 +54,13 @@ export function DataTableRowActions<TData = any, TValue = any>({
|
|||||||
<DropdownMenuContent align='end'>
|
<DropdownMenuContent align='end'>
|
||||||
<DropdownMenuLabel>{t("common.actions")} </DropdownMenuLabel>
|
<DropdownMenuLabel>{t("common.actions")} </DropdownMenuLabel>
|
||||||
{actions &&
|
{actions &&
|
||||||
actions(props).map((action, index) =>
|
actions(rowContext).map((action, index) =>
|
||||||
action.label === "-" ? (
|
action.label === "-" ? (
|
||||||
<DropdownMenuSeparator key={index} />
|
<DropdownMenuSeparator key={index} />
|
||||||
) : (
|
) : (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={index}
|
key={index}
|
||||||
onClick={(event) => (action.onClick ? action.onClick(props, event) : null)}
|
onClick={(event) => (action.onClick ? action.onClick(rowContext, event) : null)}
|
||||||
>
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
<DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut>
|
<DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut>
|
||||||
|
|||||||
@ -9,15 +9,14 @@ import { FormErrorMessage } from "./FormErrorMessage";
|
|||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
||||||
|
|
||||||
export const formCurrencyFieldVariants = cva(
|
const formCurrencyFieldVariants = cva(
|
||||||
"flex h-10 w-full rounded-md bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ",
|
"border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ",
|
||||||
outline:
|
outline: "focus-visible:border focus-visible:border-input",
|
||||||
"ring-offset-background focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 ",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@ -31,6 +30,7 @@ export type FormCurrencyFieldProps<
|
|||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
> = {
|
> = {
|
||||||
button?: (props?: React.PropsWithChildren) => React.ReactNode;
|
button?: (props?: React.PropsWithChildren) => React.ReactNode;
|
||||||
|
defaultValue?: any;
|
||||||
} & InputProps &
|
} & InputProps &
|
||||||
FormInputProps &
|
FormInputProps &
|
||||||
Partial<FormLabelProps> &
|
Partial<FormLabelProps> &
|
||||||
@ -48,6 +48,7 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
label,
|
label,
|
||||||
hint,
|
hint,
|
||||||
description,
|
description,
|
||||||
|
placeholder,
|
||||||
className,
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
@ -97,8 +98,7 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
suffix={` ${currency?.symbol}`}
|
suffix={` ${currency?.symbol}`}
|
||||||
groupSeparator='.'
|
groupSeparator='.'
|
||||||
decimalSeparator=','
|
decimalSeparator=','
|
||||||
//placeholder={`0 ${fieldCurrenty.value?.symbol}`}
|
placeholder={placeholder}
|
||||||
|
|
||||||
//fixedDecimalLength={precision} <- no activar para que sea más cómodo escribir las cantidades
|
//fixedDecimalLength={precision} <- no activar para que sea más cómodo escribir las cantidades
|
||||||
decimalsLimit={precision}
|
decimalsLimit={precision}
|
||||||
decimalScale={precision}
|
decimalScale={precision}
|
||||||
|
|||||||
@ -1,19 +1,30 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import * as React from "react";
|
||||||
import { FormControl, FormDescription, FormItem, InputProps } from "@/ui";
|
|
||||||
|
|
||||||
import { Quantity, QuantityObject } from "@shared/contexts";
|
import { cn } from "@/lib/utils";
|
||||||
import { createElement, forwardRef, useState } from "react";
|
import { FormControl, FormDescription, FormField, FormItem, Input, InputProps } from "@/ui";
|
||||||
import {
|
import { Quantity } from "@shared/contexts";
|
||||||
Controller,
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
FieldPath,
|
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
FieldValues,
|
|
||||||
UseControllerProps,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { FormErrorMessage } from "./FormErrorMessage";
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
||||||
|
|
||||||
|
const formQuantityFieldVariants = cva(
|
||||||
|
"text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "",
|
||||||
|
outline:
|
||||||
|
"border-0 focus-visible:border focus-visible:border-input focus-visible:ring-0 focus-visible:ring-offset-0 ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export type FormQuantityFieldProps<
|
export type FormQuantityFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
@ -24,139 +35,82 @@ export type FormQuantityFieldProps<
|
|||||||
FormInputProps &
|
FormInputProps &
|
||||||
Partial<FormLabelProps> &
|
Partial<FormLabelProps> &
|
||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName>;
|
UseControllerProps<TFieldValues, TName> &
|
||||||
|
VariantProps<typeof formQuantityFieldVariants> & {
|
||||||
export const FormQuantityField = forwardRef<
|
precision: number;
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement> & FormQuantityFieldProps
|
|
||||||
>((props, ref) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
hint,
|
|
||||||
placeholder,
|
|
||||||
description,
|
|
||||||
|
|
||||||
required,
|
|
||||||
className,
|
|
||||||
leadIcon,
|
|
||||||
trailIcon,
|
|
||||||
button,
|
|
||||||
defaultValue,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { control } = useFormContext();
|
|
||||||
|
|
||||||
const [precision, setPrecision] = useState<number>(Quantity.DEFAULT_PRECISION);
|
|
||||||
|
|
||||||
const transform = {
|
|
||||||
input: (value: QuantityObject) => {
|
|
||||||
const quantityOrError = Quantity.create(value);
|
|
||||||
|
|
||||||
if (quantityOrError.isFailure) {
|
|
||||||
throw quantityOrError.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quantityValue = quantityOrError.object;
|
|
||||||
setPrecision(quantityValue.getPrecision());
|
|
||||||
return quantityValue.toString();
|
|
||||||
},
|
|
||||||
output: (event: React.ChangeEvent<HTMLInputElement>): QuantityObject => {
|
|
||||||
const value = parseFloat(event.target.value);
|
|
||||||
const output = !isNaN(value) ? value : 0;
|
|
||||||
|
|
||||||
const quantityOrError = Quantity.create({
|
|
||||||
amount: output * Math.pow(10, precision),
|
|
||||||
precision,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (quantityOrError.isFailure) {
|
|
||||||
throw quantityOrError.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return quantityOrError.object.toObject();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantityFieldProps>(
|
||||||
<Controller
|
(props, ref) => {
|
||||||
defaultValue={defaultValue}
|
const {
|
||||||
control={control}
|
name,
|
||||||
name={name}
|
label,
|
||||||
rules={{
|
hint,
|
||||||
required,
|
description,
|
||||||
}}
|
placeholder,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
className,
|
||||||
render={({ field, fieldState, formState }) => {
|
disabled,
|
||||||
return (
|
defaultValue,
|
||||||
<input
|
rules,
|
||||||
type='number'
|
precision,
|
||||||
{...field}
|
variant,
|
||||||
className='text-right'
|
} = props;
|
||||||
placeholder={placeholder}
|
|
||||||
onChange={(e) => field.onChange(transform.output(e))}
|
|
||||||
value={transform.input(field.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
const { control } = useFormContext();
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
|
||||||
{label && <FormLabel label={label} hint={hint} required={required} />}
|
|
||||||
<div className={cn(button ? "flex" : null)}>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{leadIcon && (
|
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
|
||||||
{createElement(
|
|
||||||
leadIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormControl
|
const transformToInput = (value: any) => {
|
||||||
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
if (typeof value !== "object") {
|
||||||
>
|
return value;
|
||||||
<input
|
}
|
||||||
type='number'
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={cn(
|
|
||||||
fieldState.error ? "border-destructive focus-visible:ring-destructive" : ""
|
|
||||||
)}
|
|
||||||
{...field}
|
|
||||||
onInput={(e) => field.onChange(transform.output(e))}
|
|
||||||
value={transform.input(field.value)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
{trailIcon && (
|
const quantityOrError = Quantity.create(value);
|
||||||
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
if (quantityOrError.isFailure) {
|
||||||
{createElement(
|
throw quantityOrError.error;
|
||||||
trailIcon,
|
}
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{button && <>{createElement(button)}</>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
return (
|
||||||
<FormErrorMessage />
|
quantityOrError.object
|
||||||
</FormItem>
|
.toNumber()
|
||||||
);
|
//.toPrecision(precision ?? value.precision)
|
||||||
}}
|
.toString()
|
||||||
/>
|
);
|
||||||
);
|
};
|
||||||
});
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
rules={rules}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
|
{label && <FormLabel label={label} hint={hint} required={rules?.required ?? false} />}
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
name={field.name}
|
||||||
|
//ref={field.ref} <-- no activar que hace cosas raras
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
disabled={field.disabled}
|
||||||
|
className={cn(formQuantityFieldVariants({ variant, className }))}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={transformToInput(field.value)}
|
||||||
|
onChange={(value) => {
|
||||||
|
// "value" ya viene con los "0" de la precisión
|
||||||
|
console.log(value);
|
||||||
|
field.onChange(value ?? "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
<FormErrorMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -190,6 +190,12 @@ export function useDataTable<TData, TValue>({
|
|||||||
debugTable: false,
|
debugTable: false,
|
||||||
debugHeaders: false,
|
debugHeaders: false,
|
||||||
debugColumns: false,
|
debugColumns: false,
|
||||||
|
|
||||||
|
defaultColumn: {
|
||||||
|
size: 5, //starting column size
|
||||||
|
minSize: 0, //enforced during column resizing
|
||||||
|
maxSize: 96, //enforced during column resizing
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { table };
|
return { table };
|
||||||
|
|||||||
@ -86,9 +86,6 @@
|
|||||||
"total_price": "Imp. total"
|
"total_price": "Imp. total"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
|
||||||
"draft": "Borrador"
|
|
||||||
},
|
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Nueva cotización",
|
"title": "Nueva cotización",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@ -123,41 +120,76 @@
|
|||||||
"desc": ""
|
"desc": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form_fields": {
|
"edit": {
|
||||||
"date": {
|
"title": "Cotización"
|
||||||
"label": "Fecha",
|
|
||||||
"desc": "Fecha de esta cotización",
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"label": "Referencia",
|
|
||||||
"desc": "Referencia para esta cotización",
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
"customer_information": {
|
|
||||||
"label": "Datos del cliente",
|
|
||||||
"desc": "Escriba el nombre del cliente en la primera línea, la direccion en la segunda y el código postal y ciudad en la tercera.",
|
|
||||||
"placeholder": "Nombre y apellidos\nCalle y número\nCódigo postal y ciudad..."
|
|
||||||
},
|
|
||||||
"payment_method": {
|
|
||||||
"label": "Forma de pago",
|
|
||||||
"placeholder": "placeholder",
|
|
||||||
"desc": "desc"
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"label": "Notas",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "desc"
|
|
||||||
},
|
|
||||||
"validity": {
|
|
||||||
"label": "Validez de la cotización",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "desc"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edit": {
|
"status": {
|
||||||
"title": "Cotización"
|
"draft": "Borrador"
|
||||||
|
},
|
||||||
|
"form_fields": {
|
||||||
|
"date": {
|
||||||
|
"label": "Fecha",
|
||||||
|
"desc": "Fecha de esta cotización",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"label": "Referencia",
|
||||||
|
"desc": "Referencia para esta cotización",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"customer_information": {
|
||||||
|
"label": "Datos del cliente",
|
||||||
|
"desc": "Escriba el nombre del cliente en la primera línea, la direccion en la segunda y el código postal y ciudad en la tercera.",
|
||||||
|
"placeholder": "Nombre y apellidos\nCalle y número\nCódigo postal y ciudad..."
|
||||||
|
},
|
||||||
|
"payment_method": {
|
||||||
|
"label": "Forma de pago",
|
||||||
|
"placeholder": "placeholder",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"label": "Notas",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"validity": {
|
||||||
|
"label": "Validez de la cotización",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"quantity": {
|
||||||
|
"label": "Cantidad",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Descripción",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"unit_price": {
|
||||||
|
"label": "Imp. unitario",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Importe unitario del artículo"
|
||||||
|
},
|
||||||
|
"subtotal_price": {
|
||||||
|
"label": "Subtotal",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"discount": {
|
||||||
|
"label": "Dto (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Porcentaje de descuento"
|
||||||
|
},
|
||||||
|
"total_price": {
|
||||||
|
"label": "Imp. total",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Importe total con el descuento ya aplicado"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user