diff --git a/client/src/app/quotes/components/SortableDataTable.tsx b/client/src/app/quotes/components/SortableDataTable.tsx index 76b5ef6..bc88621 100644 --- a/client/src/app/quotes/components/SortableDataTable.tsx +++ b/client/src/app/quotes/components/SortableDataTable.tsx @@ -148,6 +148,11 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP getRowId: (originalRow: unknown) => originalRow?.id, debugHeaders: false, debugColumns: false, + defaultColumn: { + size: 8, //starting column size + minSize: 1, //enforced during column resizing + maxSize: 96, //enforced during column resizing + }, meta: { insertItem: (rowIndex: number, data: object = {}) => { actions.insert(rowIndex, data, { shouldFocus: true }); @@ -326,13 +331,14 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP collisionDetection={closestCenter} > - +
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { + console.log(header.getSize()); return ( - + {header.isPlaceholder ? null : ( )} @@ -350,11 +356,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP {filterItems(table.getRowModel().rows).map((row) => ( {row.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} diff --git a/client/src/app/quotes/components/SortableTableRow.tsx b/client/src/app/quotes/components/SortableTableRow.tsx index b4e5546..4a95e1d 100644 --- a/client/src/app/quotes/components/SortableTableRow.tsx +++ b/client/src/app/quotes/components/SortableTableRow.tsx @@ -58,7 +58,10 @@ export function SortableTableRow({ id, children }: PropsWithChildren diff --git a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx index 62328e6..590e4c1 100644 --- a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx @@ -1,13 +1,9 @@ -import { - FormCurrencyField, - FormQuantityField, - FormTextAreaField, - FormTextField, -} from "@/components"; +import { FormQuantityField, FormTextField } from "@/components"; import { DataTableProvider } from "@/lib/hooks"; import { cn } from "@/lib/utils"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui"; import { CurrencyData, Quantity } from "@shared/contexts"; +import { t } from "i18next"; import { useCallback, useState } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; import { useDetailColumns } from "../../hooks"; @@ -38,32 +34,42 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) { id: "quantity" as const, accessorKey: "quantity", - header: "quantity", + header: () => ( +
{t("quotes.form_fields.items.quantity.label")}
+ ), size: 5, cell: ({ row: { index } }) => { - return ; + return ( + + ); }, }, { id: "description" as const, accessorKey: "description", - header: "description", + header: t("quotes.form_fields.items.description.label"), + size: 24, cell: ({ row: { index } }) => { - return ; + return ; }, }, - { id: "unit_price" as const, accessorKey: "unit_price", - header: "unit_price", - size: 10, + header: () => ( +
{t("quotes.form_fields.items.unit_price.label")}
+ ), cell: ({ row: { index }, column: { id } }) => { return ( - ); @@ -72,8 +78,9 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) { id: "subtotal_price" as const, accessorKey: "subtotal_price", - header: "subtotal_price", - size: 10, + header: () => ( +
{t("quotes.form_fields.items.subtotal_price.label")}
+ ), cell: ({ row: { index }, column: { id } }) => { return ; }, @@ -81,17 +88,20 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) { id: "discount" as const, accessorKey: "discount", - header: "discount", size: 5, + header: () => ( +
{t("quotes.form_fields.items.discount.label")}
+ ), cell: ({ row: { index }, column: { id } }) => { - return ; + return ; }, }, { id: "total_price" as const, accessorKey: "total_price", - header: "total_price", - size: 10, + header: () => ( +
{t("quotes.form_fields.items.total_price.label")}
+ ), cell: ({ row: { index }, column: { id } }) => { return ; }, @@ -151,6 +161,8 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) const defaultLayout = [265, 440, 655]; const navCollapsedSize = 4; + return ; + return ( ( - rows: Row[], - currentID: number, - selectedID: number, -): Row[] { - const rangeStart = selectedID > currentID ? currentID : selectedID; - const rangeEnd = rangeStart === currentID ? selectedID : currentID; - return rows.slice(rangeStart, rangeEnd + 1); -}*/ - -export function useDetailColumns( +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function useDetailColumns( columns: ColumnDef[], options: { enableDragHandleColumn?: boolean; enableSelectionColumn?: boolean; enableActionsColumn?: boolean; - rowActionFn?: DataTablaRowActionFunction; + rowActionFn?: DataTablaRowActionFunction; } = {} ): ColumnDef[] { const { @@ -67,7 +57,9 @@ export function useDetailColumns( ), enableSorting: false, enableHiding: false, - size: 30, + size: 2, + minSize: 2, + maxSize: 2, }); } @@ -79,7 +71,10 @@ export function useDetailColumns( ),*/ header: () => null, cell: (info) => , - size: 16, + size: 2, + minSize: 2, + maxSize: 2, + enableSorting: false, enableHiding: false, }); @@ -88,16 +83,23 @@ export function useDetailColumns( if (enableActionsColumn) { columns.push({ id: "row_actions", - header: () => ( + /*header: () => ( - ), + ),*/ cell: (props) => { - return ; + return ( +
+ +
+ ); }, - size: 16, + size: 4, + minSize: 4, + maxSize: 4, + enableSorting: false, enableHiding: false, }); diff --git a/client/src/components/DataTable/DataTableColumnHeader.tsx b/client/src/components/DataTable/DataTableColumnHeader.tsx index 470d9e4..d887f74 100644 --- a/client/src/components/DataTable/DataTableColumnHeader.tsx +++ b/client/src/components/DataTable/DataTableColumnHeader.tsx @@ -28,7 +28,7 @@ export function DataTableColumnHeader({ <>
diff --git a/client/src/components/DataTable/DataTableRowActions.tsx b/client/src/components/DataTable/DataTableRowActions.tsx index 308225b..e34b69e 100644 --- a/client/src/components/DataTable/DataTableRowActions.tsx +++ b/client/src/components/DataTable/DataTableRowActions.tsx @@ -1,5 +1,6 @@ "use client"; +import { cn } from "@/lib/utils"; import { Button, DropdownMenu, @@ -10,32 +11,31 @@ import { DropdownMenuShortcut, DropdownMenuTrigger, } from "@/ui"; -import { CellContext, Row } from "@tanstack/react-table"; +import { CellContext } from "@tanstack/react-table"; import { t } from "i18next"; -import { MoreHorizontalIcon } from "lucide-react"; +import { MoreVerticalIcon } from "lucide-react"; -type DataTableRowActionContext = CellContext & { - row: Row; -}; +export type DataTablaRowActionFunction = ( + props: CellContext +) => DataTableRowActionDefinition[]; -export type DataTablaRowActionFunction = ( - props: DataTableRowActionContext -) => DataTableRowActionDefinition[]; - -export type DataTableRowActionDefinition = { +export type DataTableRowActionDefinition = { label: string | "-"; shortcut?: string; - onClick?: (props: DataTableRowActionContext, e: React.BaseSyntheticEvent) => void; + onClick?: (props: CellContext, e: React.BaseSyntheticEvent) => void; }; -export type DataTableRowActionsProps = { - actions?: DataTablaRowActionFunction; - row?: DataTableRowActionContext; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type DataTableRowActionsProps = { + className?: string; + actions?: DataTablaRowActionFunction; + rowContext: CellContext; }; -export function DataTableRowActions({ +export function DataTableRowActions({ actions, - ...props + rowContext, + className, }: DataTableRowActionsProps) { return ( @@ -44,9 +44,9 @@ export function DataTableRowActions({ aria-haspopup='true' size='icon' 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)} > - + {t("common.open_menu")} @@ -54,13 +54,13 @@ export function DataTableRowActions({ {t("common.actions")} {actions && - actions(props).map((action, index) => + actions(rowContext).map((action, index) => action.label === "-" ? ( ) : ( (action.onClick ? action.onClick(props, event) : null)} + onClick={(event) => (action.onClick ? action.onClick(rowContext, event) : null)} > {action.label} {action.shortcut} diff --git a/client/src/components/Forms/FormCurrencyField.tsx b/client/src/components/Forms/FormCurrencyField.tsx index bb8ad1f..df0e96f 100644 --- a/client/src/components/Forms/FormCurrencyField.tsx +++ b/client/src/components/Forms/FormCurrencyField.tsx @@ -9,15 +9,14 @@ import { FormErrorMessage } from "./FormErrorMessage"; import { FormLabel, FormLabelProps } from "./FormLabel"; 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", { variants: { variant: { default: "border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ", - outline: - "ring-offset-background focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 ", + outline: "focus-visible:border focus-visible:border-input", }, }, defaultVariants: { @@ -31,6 +30,7 @@ export type FormCurrencyFieldProps< TName extends FieldPath = FieldPath > = { button?: (props?: React.PropsWithChildren) => React.ReactNode; + defaultValue?: any; } & InputProps & FormInputProps & Partial & @@ -48,6 +48,7 @@ export const FormCurrencyField = React.forwardRef = FieldPath @@ -24,139 +35,82 @@ export type FormQuantityFieldProps< FormInputProps & Partial & FormInputWithIconProps & - UseControllerProps; - -export const FormQuantityField = forwardRef< - HTMLDivElement, - React.HTMLAttributes & FormQuantityFieldProps ->((props, ref) => { - const { - name, - label, - hint, - placeholder, - description, - - required, - className, - leadIcon, - trailIcon, - button, - defaultValue, - } = props; - - const { control } = useFormContext(); - - const [precision, setPrecision] = useState(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): 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(); - }, + UseControllerProps & + VariantProps & { + precision: number; }; - return ( - { - return ( - field.onChange(transform.output(e))} - value={transform.input(field.value)} - /> - ); +export const FormQuantityField = React.forwardRef( + (props, ref) => { + const { + name, + label, + hint, + description, + placeholder, + className, + disabled, + defaultValue, + rules, + precision, + variant, + } = props; - return ( - - {label && } -
-
- {leadIcon && ( -
- {createElement( - leadIcon, - { - className: "h-5 w-5 text-muted-foreground", - "aria-hidden": true, - }, - null - )} -
- )} + const { control } = useFormContext(); - - field.onChange(transform.output(e))} - value={transform.input(field.value)} - /> - + const transformToInput = (value: any) => { + if (typeof value !== "object") { + return value; + } - {trailIcon && ( -
- {createElement( - trailIcon, - { - className: "h-5 w-5 text-muted-foreground", - "aria-hidden": true, - }, - null - )} -
- )} -
- {button && <>{createElement(button)}} -
+ const quantityOrError = Quantity.create(value); + if (quantityOrError.isFailure) { + throw quantityOrError.error; + } - {description && {description}} - -
- ); - }} - /> - ); -}); + return ( + quantityOrError.object + .toNumber() + //.toPrecision(precision ?? value.precision) + .toString() + ); + }; + + return ( + { + return ( + + {label && } + + { + // "value" ya viene con los "0" de la precisión + console.log(value); + field.onChange(value ?? ""); + }} + /> + + {description && {description}} + + + ); + }} + /> + ); + } +); diff --git a/client/src/lib/hooks/useDataTable/useDataTable.tsx b/client/src/lib/hooks/useDataTable/useDataTable.tsx index ca6c349..42b059e 100644 --- a/client/src/lib/hooks/useDataTable/useDataTable.tsx +++ b/client/src/lib/hooks/useDataTable/useDataTable.tsx @@ -190,6 +190,12 @@ export function useDataTable({ debugTable: false, debugHeaders: false, debugColumns: false, + + defaultColumn: { + size: 5, //starting column size + minSize: 0, //enforced during column resizing + maxSize: 96, //enforced during column resizing + }, }); return { table }; diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 949ec05..f0e47d8 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -86,9 +86,6 @@ "total_price": "Imp. total" } }, - "status": { - "draft": "Borrador" - }, "create": { "title": "Nueva cotización", "buttons": { @@ -123,41 +120,76 @@ "desc": "" } }, - "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" - } + "edit": { + "title": "Cotización" } }, - "edit": { - "title": "Cotización" + "status": { + "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": {