.
This commit is contained in:
parent
95882ee10d
commit
0b5f180b6f
@ -54,6 +54,7 @@
|
|||||||
"lucide-react": "^0.379.0",
|
"lucide-react": "^0.379.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
"react-currency-input-field": "^3.8.0",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.51.5",
|
"react-hook-form": "^7.51.5",
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export const CatalogPickerDataTable = ({ onClick }: { onClick: (data: unknown) =
|
|||||||
className={cn("rounded-lg border p-3 transition-all hover:bg-accent w-full", "")}
|
className={cn("rounded-lg border p-3 transition-all hover:bg-accent w-full", "")}
|
||||||
onClick={
|
onClick={
|
||||||
(event) => {
|
(event) => {
|
||||||
console.log("hola");
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onClick && onClick(row.original);
|
onClick && onClick(row.original);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,7 @@ import { TableRow } from "@/ui/table";
|
|||||||
import { DraggableSyntheticListeners } from "@dnd-kit/core";
|
import { DraggableSyntheticListeners } from "@dnd-kit/core";
|
||||||
import { defaultAnimateLayoutChanges, useSortable } from "@dnd-kit/sortable";
|
import { defaultAnimateLayoutChanges, useSortable } from "@dnd-kit/sortable";
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import {
|
import { CSSProperties, PropsWithChildren, createContext, useMemo } from "react";
|
||||||
CSSProperties,
|
|
||||||
PropsWithChildren,
|
|
||||||
createContext,
|
|
||||||
useMemo,
|
|
||||||
} from "react";
|
|
||||||
import { SortableProps } from "./SortableDataTable";
|
import { SortableProps } from "./SortableDataTable";
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
@ -30,10 +25,7 @@ function animateLayoutChanges(args) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortableTableRow({
|
export function SortableTableRow({ id, children }: PropsWithChildren<SortableProps>) {
|
||||||
id,
|
|
||||||
children,
|
|
||||||
}: PropsWithChildren<SortableProps>) {
|
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
isDragging,
|
isDragging,
|
||||||
@ -58,7 +50,7 @@ export function SortableTableRow({
|
|||||||
listeners,
|
listeners,
|
||||||
ref: setActivatorNodeRef,
|
ref: setActivatorNodeRef,
|
||||||
}),
|
}),
|
||||||
[attributes, listeners, setActivatorNodeRef],
|
[attributes, listeners, setActivatorNodeRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -66,10 +58,7 @@ export function SortableTableRow({
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={id}
|
key={id}
|
||||||
id={String(id)}
|
id={String(id)}
|
||||||
className={cn(
|
className={cn(isDragging ? "opacity-40" : "opacity-100", "hover:bg-muted/30 m-0")}
|
||||||
isDragging ? "opacity-40" : "opacity-100",
|
|
||||||
"hover:bg-muted/30 m-0",
|
|
||||||
)}
|
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
FormMoneyField,
|
FormCurrencyField,
|
||||||
FormPercentageField,
|
|
||||||
FormQuantityField,
|
FormQuantityField,
|
||||||
FormTextAreaField,
|
FormTextAreaField,
|
||||||
|
FormTextField,
|
||||||
} 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";
|
||||||
import { Quantity } from "@shared/contexts";
|
import { CurrencyData, Quantity } from "@shared/contexts";
|
||||||
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 { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
||||||
import { SortableDataTable } from "../SortableDataTable";
|
import { SortableDataTable } from "../SortableDataTable";
|
||||||
|
|
||||||
export const QuoteDetailsCardEditor = () => {
|
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
|
||||||
const { control, register, watch, getValues, setValue } = useFormContext();
|
const { control, register } = useFormContext();
|
||||||
|
|
||||||
const { fields, ...fieldActions } = useFieldArray({
|
const { fields, ...fieldActions } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@ -59,7 +59,14 @@ export const QuoteDetailsCardEditor = () => {
|
|||||||
header: "unit_price",
|
header: "unit_price",
|
||||||
size: 10,
|
size: 10,
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormMoneyField {...register(`items.${index}.unit_price`)} />;
|
return (
|
||||||
|
<FormCurrencyField
|
||||||
|
variant='outline'
|
||||||
|
currency={currency}
|
||||||
|
precision={4}
|
||||||
|
{...register(`items.${index}.unit_price`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -68,7 +75,7 @@ export const QuoteDetailsCardEditor = () => {
|
|||||||
header: "subtotal_price",
|
header: "subtotal_price",
|
||||||
size: 10,
|
size: 10,
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormMoneyField {...register(`items.${index}.subtotal_price`)} />;
|
return <FormTextField {...register(`items.${index}.subtotal_price`)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,7 +84,7 @@ export const QuoteDetailsCardEditor = () => {
|
|||||||
header: "discount",
|
header: "discount",
|
||||||
size: 5,
|
size: 5,
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormPercentageField {...register(`items.${index}.discount`)} />;
|
return <FormTextField {...register(`items.${index}.discount`)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,7 +93,7 @@ export const QuoteDetailsCardEditor = () => {
|
|||||||
header: "total_price",
|
header: "total_price",
|
||||||
size: 10,
|
size: 10,
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormMoneyField {...register(`items.${index}.total_price`)} />;
|
return <FormTextField {...register(`items.${index}.total_price`)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ErrorOverlay, FormMoneyField, LoadingOverlay, SubmitButton } from "@/components";
|
import { ErrorOverlay, FormTextField, LoadingOverlay, SubmitButton } from "@/components";
|
||||||
import { calculateItemTotals } from "@/lib/calc";
|
import { calculateItemTotals } from "@/lib/calc";
|
||||||
import { useUrlId } from "@/lib/hooks/useUrlId";
|
import { useUrlId } from "@/lib/hooks/useUrlId";
|
||||||
import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
||||||
import { IUpdateQuote_Request_DTO, MoneyValue } from "@shared/contexts";
|
import { CurrencyData, IUpdateQuote_Request_DTO, MoneyValue } from "@shared/contexts";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { ChevronLeftIcon } from "lucide-react";
|
import { ChevronLeftIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@ -15,40 +15,28 @@ type EndsWith<T, b extends string> = T extends `${infer f}${b}` ? T : never;
|
|||||||
const endsWith = <T extends string, b extends string>(str: T, prefix: b): str is EndsWith<T, b> =>
|
const endsWith = <T extends string, b extends string>(str: T, prefix: b): str is EndsWith<T, b> =>
|
||||||
str.endsWith(prefix);
|
str.endsWith(prefix);
|
||||||
|
|
||||||
interface QuoteDataForm extends IUpdateQuote_Request_DTO {
|
interface QuoteDataForm extends IUpdateQuote_Request_DTO {}
|
||||||
/*status: string;
|
|
||||||
date: string;
|
|
||||||
reference: string;
|
|
||||||
customer_information: string;
|
|
||||||
lang_code: string;
|
|
||||||
currency_code: string;
|
|
||||||
payment_method: string;
|
|
||||||
notes: string;
|
|
||||||
validity: string;
|
|
||||||
discount: IPercentage;
|
|
||||||
|
|
||||||
subtotal: IMoney;
|
|
||||||
|
|
||||||
items: {
|
|
||||||
quantity: IQuantity;
|
|
||||||
description: string;
|
|
||||||
unit_price: IMoney;
|
|
||||||
price: IMoney;
|
|
||||||
discount: IPercentage;
|
|
||||||
total: IMoney;
|
|
||||||
}[];*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export const QuoteEdit = () => {
|
export const QuoteEdit = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const quoteId = useUrlId();
|
const quoteId = useUrlId();
|
||||||
|
const [quoteCurrency, setQuoteCurrency] = useState<CurrencyData>(
|
||||||
|
CurrencyData.createDefaultCode().object
|
||||||
|
);
|
||||||
|
|
||||||
//const { data: userIdentity } = useGetIdentity();
|
/*const { data: userIdentity } = useGetIdentity();
|
||||||
|
console.log(userIdentity);
|
||||||
|
|
||||||
|
const { flag } = useLocalization({
|
||||||
|
locale: userIdentity?.language ?? "es-es",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(flag);*/
|
||||||
|
|
||||||
const { useOne, useUpdate } = useQuotes();
|
const { useOne, useUpdate } = useQuotes();
|
||||||
|
|
||||||
const { data, status } = useOne(quoteId);
|
const { data, status, error: queryError } = useOne(quoteId);
|
||||||
|
|
||||||
const { mutate } = useUpdate(String(quoteId));
|
const { mutate } = useUpdate(String(quoteId));
|
||||||
|
|
||||||
const form = useForm<QuoteDataForm>({
|
const form = useForm<QuoteDataForm>({
|
||||||
@ -63,7 +51,11 @@ export const QuoteEdit = () => {
|
|||||||
payment_method: "",
|
payment_method: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
validity: "",
|
validity: "",
|
||||||
subtotal_price: "",
|
subtotal_price: {
|
||||||
|
amount: "",
|
||||||
|
precision: "",
|
||||||
|
currency_code: "",
|
||||||
|
},
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -72,32 +64,32 @@ export const QuoteEdit = () => {
|
|||||||
const { isSubmitting } = formState;
|
const { isSubmitting } = formState;
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<QuoteDataForm> = async (data) => {
|
const onSubmit: SubmitHandler<QuoteDataForm> = async (data) => {
|
||||||
console.debug(JSON.stringify(data));
|
// Transformación del form -> typo de request
|
||||||
|
mutate(data, {
|
||||||
try {
|
onError: (error) => {
|
||||||
setLoading(true);
|
alert(error.message);
|
||||||
// Transformación del form -> typo de request
|
},
|
||||||
mutate(data, {
|
//onSettled: () => {},
|
||||||
onError: (error) => {
|
onSuccess: () => {
|
||||||
alert(error);
|
alert("guardado");
|
||||||
},
|
},
|
||||||
//onSettled: () => {},
|
});
|
||||||
onSuccess: () => {
|
|
||||||
alert("guardado");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = watch((_, { name, type }) => {
|
const { unsubscribe } = watch((_, { name, type }) => {
|
||||||
const value = getValues();
|
const value = getValues();
|
||||||
|
|
||||||
console.debug({ name, type });
|
//console.debug({ name, type });
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
if (name === "currency_code") {
|
||||||
|
setQuoteCurrency(
|
||||||
|
CurrencyData.createFromCode(value.lang_code ?? CurrencyData.DEFAULT_CURRENCY_CODE)
|
||||||
|
.object
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === "items") {
|
if (name === "items") {
|
||||||
const { items } = value;
|
const { items } = value;
|
||||||
let quoteSubtotal = MoneyValue.create().object;
|
let quoteSubtotal = MoneyValue.create().object;
|
||||||
@ -111,18 +103,17 @@ export const QuoteEdit = () => {
|
|||||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(quoteSubtotal.toFormat());
|
|
||||||
|
|
||||||
// Recálculo completo
|
// Recálculo completo
|
||||||
setValue("subtotal", quoteSubtotal.toObject());
|
setValue("subtotal_price", quoteSubtotal.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
endsWith(name, "quantity") ||
|
endsWith(name, "quantity") ||
|
||||||
endsWith(name, "retail_price") ||
|
endsWith(name, "unit_price") ||
|
||||||
endsWith(name, "discount")
|
endsWith(name, "discount")
|
||||||
) {
|
) {
|
||||||
const { items } = value;
|
const { items } = value;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [, indexString, fieldName] = String(name).split(".");
|
const [, indexString, fieldName] = String(name).split(".");
|
||||||
const index = parseInt(indexString);
|
const index = parseInt(indexString);
|
||||||
|
|
||||||
@ -150,6 +141,8 @@ export const QuoteEdit = () => {
|
|||||||
return <LoadingOverlay />;
|
return <LoadingOverlay />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(quoteCurrency);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
@ -175,7 +168,7 @@ export const QuoteEdit = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormMoneyField
|
<FormTextField
|
||||||
label={"subtotal_price"}
|
label={"subtotal_price"}
|
||||||
disabled={form.formState.disabled}
|
disabled={form.formState.disabled}
|
||||||
{...form.register("subtotal_price")}
|
{...form.register("subtotal_price")}
|
||||||
@ -191,7 +184,7 @@ export const QuoteEdit = () => {
|
|||||||
<QuoteGeneralCardEditor />
|
<QuoteGeneralCardEditor />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='items'>
|
<TabsContent value='items'>
|
||||||
<QuoteDetailsCardEditor />
|
<QuoteDetailsCardEditor currency={quoteCurrency} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value='history'></TabsContent>
|
<TabsContent value='history'></TabsContent>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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, UnfoldVertical } from "lucide-react";
|
import { MoreHorizontalIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
if (enableSelectionColumn) {
|
if (enableSelectionColumn) {
|
||||||
columns.unshift({
|
columns.unshift({
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
/*header: ({ table }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id='select-all'
|
id='select-all'
|
||||||
checked={
|
checked={
|
||||||
@ -50,9 +50,10 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
}
|
}
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
aria-label='Seleccionar todo'
|
aria-label='Seleccionar todo'
|
||||||
className='translate-y-[2px]'
|
className='translate-y-[0px]'
|
||||||
/>
|
/>
|
||||||
),
|
),*/
|
||||||
|
header: () => null,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
cell: ({ row, table }: { row: Row<TData>; table: Table<TData> }) => (
|
cell: ({ row, table }: { row: Row<TData>; table: Table<TData> }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -61,25 +62,7 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
disabled={!row.getCanSelect()}
|
disabled={!row.getCanSelect()}
|
||||||
onCheckedChange={row.getToggleSelectedHandler()}
|
onCheckedChange={row.getToggleSelectedHandler()}
|
||||||
aria-label='Seleccionar fila'
|
aria-label='Seleccionar fila'
|
||||||
className='translate-y-[2px]'
|
className='mt-2'
|
||||||
/*onClick={(e) => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
const { rows, rowsById } = table.getRowModel();
|
|
||||||
const rowsToToggle = getSelectedRowRange(
|
|
||||||
rows,
|
|
||||||
Number(row.id),
|
|
||||||
Number(lastSelectedId),
|
|
||||||
);
|
|
||||||
const isCellSelected = rowsById[row.id].getIsSelected();
|
|
||||||
rowsToToggle.forEach((_row) =>
|
|
||||||
_row.toggleSelected(!isCellSelected),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
row.toggleSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSelectedId = row.id;
|
|
||||||
}}*/
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
@ -91,10 +74,11 @@ export function useDetailColumns<TData, TValue>(
|
|||||||
if (enableDragHandleColumn) {
|
if (enableDragHandleColumn) {
|
||||||
columns.unshift({
|
columns.unshift({
|
||||||
id: "row_drag_handle",
|
id: "row_drag_handle",
|
||||||
header: () => (
|
/*header: () => (
|
||||||
<UnfoldVertical aria-label='Mover fila' className='items-center justify-center w-4 h-4' />
|
<UnfoldVertical aria-label='Mover fila' className='items-center justify-center w-4 h-4' />
|
||||||
),
|
),*/
|
||||||
cell: ({ row }: { row: Row<TData> }) => <DataTableRowDragHandleCell rowId={row.id} />,
|
header: () => null,
|
||||||
|
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
|
||||||
size: 16,
|
size: 16,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
|
|||||||
@ -3,7 +3,15 @@ import { Button } from "@/ui";
|
|||||||
import { useSortable } from "@dnd-kit/sortable";
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
import { GripVerticalIcon } from "lucide-react";
|
import { GripVerticalIcon } from "lucide-react";
|
||||||
|
|
||||||
export const DataTableRowDragHandleCell = ({ rowId }: { rowId: string }) => {
|
export interface DataTableRowDragHandleCellProps {
|
||||||
|
rowId: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataTableRowDragHandleCell = ({
|
||||||
|
rowId,
|
||||||
|
className,
|
||||||
|
}: DataTableRowDragHandleCellProps) => {
|
||||||
const { attributes, listeners, isDragging } = useSortable({
|
const { attributes, listeners, isDragging } = useSortable({
|
||||||
id: rowId,
|
id: rowId,
|
||||||
});
|
});
|
||||||
@ -14,17 +22,18 @@ export const DataTableRowDragHandleCell = ({ rowId }: { rowId: string }) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
size="icon"
|
size='icon'
|
||||||
variant="link"
|
variant='link'
|
||||||
className={cn(
|
className={cn(
|
||||||
isDragging ? "cursor-grabbing" : "cursor-grab",
|
isDragging ? "cursor-grabbing" : "cursor-grab",
|
||||||
"w-4 h-4 translate-y-[2px]"
|
"w-4 h-4 mt-2 text-ring hover:text-muted-foreground",
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<GripVerticalIcon className="w-4 h-4" />
|
<GripVerticalIcon className='w-4 h-4' />
|
||||||
<span className="sr-only">Mover fila</span>
|
<span className='sr-only'>Mover fila</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
122
client/src/components/Forms/FormCurrencyField.tsx
Normal file
122
client/src/components/Forms/FormCurrencyField.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
||||||
|
import { CurrencyData, MoneyValue } from "@shared/contexts";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import * as React from "react";
|
||||||
|
import CurrencyInput from "react-currency-input-field";
|
||||||
|
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
|
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
||||||
|
|
||||||
|
export 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 ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export type FormCurrencyFieldProps<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
> = {
|
||||||
|
button?: (props?: React.PropsWithChildren) => React.ReactNode;
|
||||||
|
} & InputProps &
|
||||||
|
FormInputProps &
|
||||||
|
Partial<FormLabelProps> &
|
||||||
|
FormInputWithIconProps &
|
||||||
|
UseControllerProps<TFieldValues, TName> &
|
||||||
|
VariantProps<typeof formCurrencyFieldVariants> & {
|
||||||
|
currency: CurrencyData;
|
||||||
|
precision: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrencyFieldProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
hint,
|
||||||
|
description,
|
||||||
|
className,
|
||||||
|
disabled,
|
||||||
|
defaultValue,
|
||||||
|
rules,
|
||||||
|
precision,
|
||||||
|
currency,
|
||||||
|
variant,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
const transformToInput = (value: any) => {
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moneyOrError = MoneyValue.create(value);
|
||||||
|
if (moneyOrError.isFailure) {
|
||||||
|
throw moneyOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moneyOrError.object
|
||||||
|
.convertPrecision(precision ?? value.precision)
|
||||||
|
.toUnit()
|
||||||
|
.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
rules={rules}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
render={({ field, fieldState, formState }) => {
|
||||||
|
return (
|
||||||
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
|
{label && <FormLabel label={label} hint={hint} required={rules?.required ?? false} />}
|
||||||
|
<FormControl>
|
||||||
|
<CurrencyInput
|
||||||
|
name={field.name}
|
||||||
|
//ref={field.ref} <-- no activar que hace cosas raras
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
disabled={field.disabled}
|
||||||
|
className={cn(formCurrencyFieldVariants({ variant, className }))}
|
||||||
|
suffix={` ${currency?.symbol}`}
|
||||||
|
groupSeparator='.'
|
||||||
|
decimalSeparator=','
|
||||||
|
//placeholder={`0 ${fieldCurrenty.value?.symbol}`}
|
||||||
|
|
||||||
|
//fixedDecimalLength={precision} <- no activar para que sea más cómodo escribir las cantidades
|
||||||
|
decimalsLimit={precision}
|
||||||
|
decimalScale={precision}
|
||||||
|
value={transformToInput(field.value)}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
// "value" ya viene con los "0" de la precisión
|
||||||
|
field.onChange(value ?? "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
<FormErrorMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FormCurrencyField.displayName = "FormCurrencyField";
|
||||||
@ -36,6 +36,8 @@ export const FormTextField = React.forwardRef<
|
|||||||
trailIcon,
|
trailIcon,
|
||||||
button,
|
button,
|
||||||
|
|
||||||
|
defaultValue,
|
||||||
|
|
||||||
type,
|
type,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ export const FormTextField = React.forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
defaultValue={defaultValue}
|
||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
rules={{ required }}
|
rules={{ required }}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./FormCurrencyField";
|
||||||
export * from "./FormDatePickerField";
|
export * from "./FormDatePickerField";
|
||||||
export * from "./FormErrorMessage";
|
export * from "./FormErrorMessage";
|
||||||
export * from "./FormGroup";
|
export * from "./FormGroup";
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export const AuthProvider = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCheck = async () => {
|
const handleCheck = async () => {
|
||||||
console.trace("check");
|
|
||||||
try {
|
try {
|
||||||
return Promise.resolve(authActions.check?.());
|
return Promise.resolve(authActions.check?.());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -187,9 +187,9 @@ export function useDataTable<TData, TValue>({
|
|||||||
getFacetedRowModel: getFacetedRowModel(),
|
getFacetedRowModel: getFacetedRowModel(),
|
||||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||||
|
|
||||||
debugTable: true,
|
debugTable: false,
|
||||||
debugHeaders: true,
|
debugHeaders: false,
|
||||||
debugColumns: true,
|
debugColumns: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { table };
|
return { table };
|
||||||
|
|||||||
@ -19,9 +19,9 @@ export interface ICreateQuote_Request_DTO {
|
|||||||
notes: string;
|
notes: string;
|
||||||
validity: string;
|
validity: string;
|
||||||
|
|
||||||
subtotal: IMoney_Response_DTO;
|
subtotal_price: IMoney_Response_DTO;
|
||||||
discount: IPercentage_Response_DTO;
|
discount: IPercentage_Response_DTO;
|
||||||
total: IMoney_Response_DTO;
|
total_price: IMoney_Response_DTO;
|
||||||
|
|
||||||
items: ICreateQuoteItem_Request_DTO[];
|
items: ICreateQuoteItem_Request_DTO[];
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import {
|
import {
|
||||||
IMoney_Request_DTO,
|
|
||||||
IMoney_Response_DTO,
|
IMoney_Response_DTO,
|
||||||
IPercentage_Request_DTO,
|
|
||||||
IPercentage_Response_DTO,
|
IPercentage_Response_DTO,
|
||||||
IQuantity_Response_DTO,
|
IQuantity_Response_DTO,
|
||||||
Result,
|
Result,
|
||||||
@ -20,9 +18,13 @@ export interface IUpdateQuote_Request_DTO {
|
|||||||
notes: string;
|
notes: string;
|
||||||
validity: string;
|
validity: string;
|
||||||
|
|
||||||
subtotal: IMoney_Request_DTO;
|
subtotal_price: IMoney_Response_DTO;
|
||||||
discount: IPercentage_Request_DTO;
|
discount: IPercentage_Response_DTO;
|
||||||
|
total_price: IMoney_Response_DTO;
|
||||||
|
|
||||||
items: IUpdateQuoteItem_Request_DTO[];
|
items: IUpdateQuoteItem_Request_DTO[];
|
||||||
|
|
||||||
|
dealer_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpdateQuoteItem_Request_DTO {
|
export interface IUpdateQuoteItem_Request_DTO {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user