.
This commit is contained in:
parent
8e80bfe31e
commit
3e73eac05a
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "Launch Chrome localhost",
|
"name": "Launch Chrome localhost",
|
||||||
"type": "pwa-chrome",
|
"type": "chrome",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"reAttach": true,
|
"reAttach": true,
|
||||||
"url": "http://localhost:5173",
|
"url": "http://localhost:5173",
|
||||||
@ -26,6 +26,7 @@
|
|||||||
"url": "http://localhost:5173",
|
"url": "http://localhost:5173",
|
||||||
"webRoot": "${workspaceFolder}/client"
|
"webRoot": "${workspaceFolder}/client"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
@ -34,6 +35,7 @@
|
|||||||
"restart": true,
|
"restart": true,
|
||||||
"cwd": "${workspaceRoot}"
|
"cwd": "${workspaceRoot}"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Launch via YARN",
|
"name": "Launch via YARN",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|||||||
@ -271,7 +271,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
{
|
{
|
||||||
quantity: { amount: "123" },
|
quantity: { amount: "123" },
|
||||||
description: "aaaa",
|
description: "aaaa",
|
||||||
retail_price: {
|
unit_price: {
|
||||||
amount: "10000",
|
amount: "10000",
|
||||||
precision: 4,
|
precision: 4,
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
@ -336,9 +336,8 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
{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 w-${header.getSize()}`}>
|
<TableHead key={header.id} className={`px-2 py-1 w-${header.getSize()}`}>
|
||||||
{header.isPlaceholder ? null : (
|
{header.isPlaceholder ? null : (
|
||||||
<DataTableColumnHeader table={table} header={header} />
|
<DataTableColumnHeader table={table} header={header} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { FormQuantityField, FormTextField } from "@/components";
|
import {
|
||||||
|
FormCurrencyField,
|
||||||
|
FormPercentageField,
|
||||||
|
FormQuantityField,
|
||||||
|
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";
|
||||||
@ -11,7 +16,7 @@ import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
|||||||
import { SortableDataTable } from "../SortableDataTable";
|
import { SortableDataTable } from "../SortableDataTable";
|
||||||
|
|
||||||
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
|
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
|
||||||
const { control, register } = useFormContext();
|
const { control, register, getValues } = useFormContext();
|
||||||
|
|
||||||
const { fields, ...fieldActions } = useFieldArray({
|
const { fields, ...fieldActions } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@ -42,7 +47,7 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
return (
|
return (
|
||||||
<FormQuantityField
|
<FormQuantityField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
precision={2}
|
precision={Quantity.DEFAULT_PRECISION}
|
||||||
{...register(`items.${index}.quantity`)}
|
{...register(`items.${index}.quantity`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -65,7 +70,7 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
),
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return (
|
return (
|
||||||
<FormTextField
|
<FormCurrencyField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
currency={currency}
|
currency={currency}
|
||||||
precision={4}
|
precision={4}
|
||||||
@ -82,7 +87,16 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<div className='text-right'>{t("quotes.form_fields.items.subtotal_price.label")}</div>
|
<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 (
|
||||||
|
<FormCurrencyField
|
||||||
|
variant='outline'
|
||||||
|
disabled
|
||||||
|
currency={currency}
|
||||||
|
precision={4}
|
||||||
|
className='text-right'
|
||||||
|
{...register(`items.${index}.subtotal_price`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,7 +107,16 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
|
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
|
||||||
),
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index }, column: { id } }) => {
|
||||||
return <FormTextField className='text-right' {...register(`items.${index}.discount`)} />;
|
return (
|
||||||
|
<>
|
||||||
|
<FormPercentageField
|
||||||
|
variant='outline'
|
||||||
|
precision={0}
|
||||||
|
className='text-right'
|
||||||
|
{...register(`items.${index}.discount`)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -103,7 +126,16 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<div className='text-right'>{t("quotes.form_fields.items.total_price.label")}</div>
|
<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 (
|
||||||
|
<FormCurrencyField
|
||||||
|
variant='outline'
|
||||||
|
disabled
|
||||||
|
currency={currency}
|
||||||
|
precision={4}
|
||||||
|
className='text-right'
|
||||||
|
{...register(`items.${index}.total_price`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -147,7 +179,7 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
fieldActions.append({
|
fieldActions.append({
|
||||||
...newArticle,
|
...newArticle,
|
||||||
quantity: {
|
quantity: {
|
||||||
amount: 1,
|
amount: 12,
|
||||||
precision: Quantity.DEFAULT_PRECISION,
|
precision: Quantity.DEFAULT_PRECISION,
|
||||||
},
|
},
|
||||||
unit_price: newArticle.retail_price,
|
unit_price: newArticle.retail_price,
|
||||||
@ -161,8 +193,6 @@ 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'
|
||||||
|
|||||||
@ -17,30 +17,30 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
className='row-span-2'
|
className='row-span-2'
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.customer_information.label")}
|
label={t("quotes.form_fields.customer_information.label")}
|
||||||
description={t("quotes.create.form_fields.customer_information.desc")}
|
description={t("quotes.form_fields.customer_information.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.customer_information.placeholder")}
|
placeholder={t("quotes.form_fields.customer_information.placeholder")}
|
||||||
{...register("customer_information", {
|
{...register("customer_information", {
|
||||||
required: true,
|
required: true,
|
||||||
})}
|
})}
|
||||||
errors={formState.errors}
|
errors={formState.errors}
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
label={t("quotes.create.form_fields.reference.label")}
|
label={t("quotes.form_fields.reference.label")}
|
||||||
description={t("quotes.create.form_fields.reference.desc")}
|
description={t("quotes.form_fields.reference.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.reference.placeholder")}
|
placeholder={t("quotes.form_fields.reference.placeholder")}
|
||||||
{...register("reference", {
|
{...register("reference", {
|
||||||
required: false,
|
required: false,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<FormDatePickerField
|
<FormDatePickerField
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.date.label")}
|
label={t("quotes.form_fields.date.label")}
|
||||||
description={t("quotes.create.form_fields.date.desc")}
|
description={t("quotes.form_fields.date.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.date.placeholder")}
|
placeholder={t("quotes.form_fields.date.placeholder")}
|
||||||
{...register("date", {
|
{...register("date", {
|
||||||
required: true,
|
required: true,
|
||||||
})}
|
})}
|
||||||
@ -48,20 +48,20 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className='grid grid-cols-2 grid-rows-2 gap-6'>
|
<div className='grid grid-cols-2 grid-rows-2 gap-6'>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
label={t("quotes.create.form_fields.validity.label")}
|
label={t("quotes.form_fields.validity.label")}
|
||||||
description={t("quotes.create.form_fields.validity.desc")}
|
description={t("quotes.form_fields.validity.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.validity.placeholder")}
|
placeholder={t("quotes.form_fields.validity.placeholder")}
|
||||||
{...register("validity", {
|
{...register("validity", {
|
||||||
required: false,
|
required: false,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
label={t("quotes.create.form_fields.payment_method.label")}
|
label={t("quotes.form_fields.payment_method.label")}
|
||||||
description={t("quotes.create.form_fields.payment_method.desc")}
|
description={t("quotes.form_fields.payment_method.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.payment_method.placeholder")}
|
placeholder={t("quotes.form_fields.payment_method.placeholder")}
|
||||||
{...register("payment_method", {
|
{...register("payment_method", {
|
||||||
required: false,
|
required: false,
|
||||||
})}
|
})}
|
||||||
@ -69,10 +69,10 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
|
|
||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
className='col-span-2'
|
className='col-span-2'
|
||||||
label={t("quotes.create.form_fields.notes.label")}
|
label={t("quotes.form_fields.notes.label")}
|
||||||
description={t("quotes.create.form_fields.notes.desc")}
|
description={t("quotes.form_fields.notes.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.notes.placeholder")}
|
placeholder={t("quotes.form_fields.notes.placeholder")}
|
||||||
{...register("notes", {
|
{...register("notes", {
|
||||||
required: false,
|
required: false,
|
||||||
})}
|
})}
|
||||||
@ -100,20 +100,20 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
</div>
|
</div>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.lang_code.label")}
|
label={t("quotes.form_fields.lang_code.label")}
|
||||||
description={t("quotes.create.form_fields.lang_code.desc")}
|
description={t("quotes.form_fields.lang_code.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.lang_code.placeholder")}
|
placeholder={t("quotes.form_fields.lang_code.placeholder")}
|
||||||
{...register("lang_code", {
|
{...register("lang_code", {
|
||||||
required: true,
|
required: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.currency_code.label")}
|
label={t("quotes.form_fields.currency_code.label")}
|
||||||
description={t("quotes.create.form_fields.currency_code.desc")}
|
description={t("quotes.form_fields.currency_code.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.create.form_fields.currency_code.placeholder")}
|
placeholder={t("quotes.form_fields.currency_code.placeholder")}
|
||||||
{...register("currency_code", {
|
{...register("currency_code", {
|
||||||
required: true,
|
required: true,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -90,16 +90,16 @@ export const QuoteCreate = () => {
|
|||||||
className='row-span-2'
|
className='row-span-2'
|
||||||
name='reference'
|
name='reference'
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.reference.label")}
|
label={t("quotes.form_fields.reference.label")}
|
||||||
description={t("quotes.create.form_fields.reference.desc")}
|
description={t("quotes.form_fields.reference.desc")}
|
||||||
placeholder={t("quotes.create.form_fields.reference.placeholder")}
|
placeholder={t("quotes.form_fields.reference.placeholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormDatePickerField
|
<FormDatePickerField
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.date.label")}
|
label={t("quotes.form_fields.date.label")}
|
||||||
description={t("quotes.create.form_fields.date.desc")}
|
description={t("quotes.form_fields.date.desc")}
|
||||||
placeholder={t("quotes.create.form_fields.date.placeholder")}
|
placeholder={t("quotes.form_fields.date.placeholder")}
|
||||||
name='date'
|
name='date'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -108,9 +108,9 @@ export const QuoteCreate = () => {
|
|||||||
className='row-span-2'
|
className='row-span-2'
|
||||||
name='customer_information'
|
name='customer_information'
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.customer_information.label")}
|
label={t("quotes.form_fields.customer_information.label")}
|
||||||
description={t("quotes.create.form_fields.customer_information.desc")}
|
description={t("quotes.form_fields.customer_information.desc")}
|
||||||
placeholder={t("quotes.create.form_fields.customer_information.placeholder")}
|
placeholder={t("quotes.form_fields.customer_information.placeholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-around gap-2'>
|
<div className='flex items-center justify-around gap-2'>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ErrorOverlay, FormTextField, LoadingOverlay, SubmitButton } from "@/components";
|
import { ErrorOverlay, FormCurrencyField, 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";
|
||||||
@ -10,11 +10,6 @@ import { SubmitHandler, useForm } from "react-hook-form";
|
|||||||
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
||||||
import { useQuotes } from "./hooks";
|
import { useQuotes } from "./hooks";
|
||||||
|
|
||||||
// simple typesafe helperfunction
|
|
||||||
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> =>
|
|
||||||
str.endsWith(prefix);
|
|
||||||
|
|
||||||
interface QuoteDataForm extends IUpdateQuote_Request_DTO {}
|
interface QuoteDataForm extends IUpdateQuote_Request_DTO {}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@ -51,11 +46,6 @@ export const QuoteEdit = () => {
|
|||||||
payment_method: "",
|
payment_method: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
validity: "",
|
validity: "",
|
||||||
subtotal_price: {
|
|
||||||
amount: "",
|
|
||||||
precision: "",
|
|
||||||
currency_code: "",
|
|
||||||
},
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -67,21 +57,21 @@ export const QuoteEdit = () => {
|
|||||||
// Transformación del form -> typo de request
|
// Transformación del form -> typo de request
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
alert(error.message);
|
console.debug(error);
|
||||||
|
//alert(error.message);
|
||||||
},
|
},
|
||||||
//onSettled: () => {},
|
//onSettled: () => {},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
alert("guardado");
|
//alert("guardado");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { unsubscribe } = watch((_, { name, type }) => {
|
const { unsubscribe } = watch((_, { name, type }) => {
|
||||||
const value = getValues();
|
const value = getValues();
|
||||||
|
|
||||||
//console.debug({ name, type });
|
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
if (name === "currency_code") {
|
if (name === "currency_code") {
|
||||||
setQuoteCurrency(
|
setQuoteCurrency(
|
||||||
@ -97,6 +87,11 @@ export const QuoteEdit = () => {
|
|||||||
// Recálculo líneas
|
// Recálculo líneas
|
||||||
items.map((item, index) => {
|
items.map((item, index) => {
|
||||||
const itemTotals = calculateItemTotals(item);
|
const itemTotals = calculateItemTotals(item);
|
||||||
|
|
||||||
|
if (itemTotals === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||||
|
|
||||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||||
@ -107,17 +102,16 @@ export const QuoteEdit = () => {
|
|||||||
setValue("subtotal_price", quoteSubtotal.toObject());
|
setValue("subtotal_price", quoteSubtotal.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (name.endsWith("quantity") || name.endsWith("unit_price") || name.endsWith("discount")) {
|
||||||
endsWith(name, "quantity") ||
|
|
||||||
endsWith(name, "unit_price") ||
|
|
||||||
endsWith(name, "discount")
|
|
||||||
) {
|
|
||||||
const { items } = value;
|
const { items } = value;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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);
|
||||||
|
|
||||||
const itemTotals = calculateItemTotals(items[index]);
|
const itemTotals = calculateItemTotals(items[index]);
|
||||||
|
if (itemTotals === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||||
@ -141,8 +135,6 @@ 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)}>
|
||||||
@ -168,7 +160,10 @@ export const QuoteEdit = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormTextField
|
<FormCurrencyField
|
||||||
|
currency={quoteCurrency}
|
||||||
|
precision={4}
|
||||||
|
className='text-right'
|
||||||
label={"subtotal_price"}
|
label={"subtotal_price"}
|
||||||
disabled={form.formState.disabled}
|
disabled={form.formState.disabled}
|
||||||
{...form.register("subtotal_price")}
|
{...form.register("subtotal_price")}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function useDetailColumns<TData, TValue = unknown>(
|
|||||||
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={
|
||||||
@ -42,8 +42,8 @@ export function useDetailColumns<TData, TValue = unknown>(
|
|||||||
aria-label='Seleccionar todo'
|
aria-label='Seleccionar todo'
|
||||||
className='translate-y-[0px]'
|
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,7 +61,9 @@ export const useQuotes = () => {
|
|||||||
return dataSource.updateOne({
|
return dataSource.updateOne({
|
||||||
resource: "quotes",
|
resource: "quotes",
|
||||||
id,
|
id,
|
||||||
data,
|
data: {
|
||||||
|
...data,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
disabled,
|
disabled,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
rules,
|
rules,
|
||||||
|
readOnly,
|
||||||
precision,
|
precision,
|
||||||
currency,
|
currency,
|
||||||
variant,
|
variant,
|
||||||
@ -60,20 +61,35 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
|
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
|
||||||
const transformToInput = (value: any) => {
|
const transform = {
|
||||||
if (typeof value !== "object") {
|
input: (value: any) => {
|
||||||
return value;
|
if (typeof value !== "object") {
|
||||||
}
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
const moneyOrError = MoneyValue.create(value);
|
const moneyOrError = MoneyValue.create(value);
|
||||||
if (moneyOrError.isFailure) {
|
if (moneyOrError.isFailure) {
|
||||||
throw moneyOrError.error;
|
throw moneyOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return moneyOrError.object
|
return moneyOrError.object
|
||||||
.convertPrecision(precision ?? value.precision)
|
.convertPrecision(precision ?? value.precision)
|
||||||
.toUnit()
|
.toUnit()
|
||||||
.toString();
|
.toString();
|
||||||
|
},
|
||||||
|
|
||||||
|
output: (value: string | undefined) => {
|
||||||
|
const moneyOrError = MoneyValue.create({
|
||||||
|
amount: value?.replace(",", "") ?? null,
|
||||||
|
precision,
|
||||||
|
currencyCode: currency.code,
|
||||||
|
});
|
||||||
|
if (moneyOrError.isFailure) {
|
||||||
|
throw moneyOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moneyOrError.object.toObject();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -87,13 +103,16 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
render={({ field, fieldState, formState }) => {
|
render={({ field, fieldState, formState }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
{label && <FormLabel label={label} hint={hint} required={rules?.required ?? false} />}
|
{label && (
|
||||||
|
<FormLabel label={label} hint={hint} required={Boolean(rules?.required ?? false)} />
|
||||||
|
)}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
name={field.name}
|
name={field.name}
|
||||||
//ref={field.ref} <-- no activar que hace cosas raras
|
//ref={field.ref} <-- no activar que hace cosas raras
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
|
readOnly={readOnly}
|
||||||
className={cn(formCurrencyFieldVariants({ variant, className }))}
|
className={cn(formCurrencyFieldVariants({ variant, className }))}
|
||||||
suffix={` ${currency?.symbol}`}
|
suffix={` ${currency?.symbol}`}
|
||||||
groupSeparator='.'
|
groupSeparator='.'
|
||||||
@ -102,11 +121,8 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
//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}
|
||||||
value={transformToInput(field.value)}
|
value={transform.input(field.value)}
|
||||||
onValueChange={(value) => {
|
onValueChange={(e) => field.onChange(transform.output(e))}
|
||||||
// "value" ya viene con los "0" de la precisión
|
|
||||||
field.onChange(value ?? "");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
|||||||
@ -1,19 +1,32 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import * as React from "react";
|
||||||
import { FormControl, FormDescription, FormItem, InputProps } from "@/ui";
|
|
||||||
|
|
||||||
import { Percentage, PercentageObject } from "@shared/contexts";
|
import { cn } from "@/lib/utils";
|
||||||
import { createElement, forwardRef, useState } from "react";
|
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
||||||
import {
|
import { Percentage } from "@shared/contexts";
|
||||||
Controller,
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
FieldPath,
|
import CurrencyInput from "react-currency-input-field";
|
||||||
FieldValues,
|
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
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 formPercentageFieldVariants = 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: "focus-visible:border focus-visible:border-input",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export type FormPercentageFieldProps<
|
export type FormPercentageFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
@ -24,9 +37,12 @@ export type FormPercentageFieldProps<
|
|||||||
FormInputProps &
|
FormInputProps &
|
||||||
Partial<FormLabelProps> &
|
Partial<FormLabelProps> &
|
||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName>;
|
UseControllerProps<TFieldValues, TName> &
|
||||||
|
VariantProps<typeof formPercentageFieldVariants> & {
|
||||||
|
precision: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const FormPercentageField = forwardRef<
|
export const FormPercentageField = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & FormPercentageFieldProps
|
React.HTMLAttributes<HTMLDivElement> & FormPercentageFieldProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
@ -34,42 +50,42 @@ export const FormPercentageField = forwardRef<
|
|||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
hint,
|
hint,
|
||||||
placeholder,
|
|
||||||
description,
|
description,
|
||||||
|
placeholder,
|
||||||
required,
|
|
||||||
className,
|
className,
|
||||||
leadIcon,
|
disabled,
|
||||||
trailIcon,
|
|
||||||
button,
|
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
rules,
|
||||||
|
readOnly,
|
||||||
|
precision,
|
||||||
|
variant,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
|
||||||
const [precision, setPrecision] = useState<number>(Percentage.DEFAULT_PRECISION);
|
|
||||||
|
|
||||||
const transform = {
|
const transform = {
|
||||||
input: (value: PercentageObject) => {
|
input: (value: any) => {
|
||||||
const percentageOrError = Percentage.create(value);
|
if (typeof value !== "object") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentageOrError = Percentage.create(value);
|
||||||
if (percentageOrError.isFailure) {
|
if (percentageOrError.isFailure) {
|
||||||
throw percentageOrError.error;
|
throw percentageOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentageValue = percentageOrError.object;
|
return (
|
||||||
setPrecision(percentageValue.getPrecision());
|
percentageOrError.object
|
||||||
return percentageValue.toString();
|
.toNumber()
|
||||||
|
//.toPrecision(precision ?? value.precision)
|
||||||
|
.toString()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
output: (event: React.ChangeEvent<HTMLInputElement>): PercentageObject => {
|
output: (value: string | undefined) => {
|
||||||
const value = parseFloat(event.target.value);
|
|
||||||
const output = !isNaN(value) ? value : 0;
|
|
||||||
|
|
||||||
const percentageOrError = Percentage.create({
|
const percentageOrError = Percentage.create({
|
||||||
amount: output * Math.pow(10, precision),
|
amount: value?.replace(",", "") ?? null,
|
||||||
precision,
|
precision,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (percentageOrError.isFailure) {
|
if (percentageOrError.isFailure) {
|
||||||
throw percentageOrError.error;
|
throw percentageOrError.error;
|
||||||
}
|
}
|
||||||
@ -79,81 +95,39 @@ export const FormPercentageField = forwardRef<
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<FormField
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
rules={{
|
rules={{
|
||||||
required,
|
max: 100,
|
||||||
min: Percentage.MIN_VALUE,
|
min: 0,
|
||||||
max: Percentage.MAX_VALUE,
|
...rules,
|
||||||
}}
|
}}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
render={({ field }) => {
|
||||||
render={({ field, fieldState, formState }) => {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type='number'
|
|
||||||
{...field}
|
|
||||||
className='text-right'
|
|
||||||
placeholder='number'
|
|
||||||
onChange={(e) => field.onChange(transform.output(e))}
|
|
||||||
value={transform.input(field.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
{label && <FormLabel label={label} hint={hint} required={required} />}
|
{label && (
|
||||||
<div className={cn(button ? "flex" : null)}>
|
<FormLabel label={label} hint={hint} required={Boolean(rules?.required ?? false)} />
|
||||||
<div
|
)}
|
||||||
className={cn(
|
<FormControl>
|
||||||
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
<CurrencyInput
|
||||||
)}
|
name={field.name}
|
||||||
>
|
//ref={field.ref} <-- no activar que hace cosas raras
|
||||||
{leadIcon && (
|
onBlur={field.onBlur}
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
disabled={field.disabled}
|
||||||
{createElement(
|
readOnly={readOnly}
|
||||||
leadIcon,
|
className={cn(formPercentageFieldVariants({ variant, className }))}
|
||||||
{
|
groupSeparator='.'
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
decimalSeparator=','
|
||||||
"aria-hidden": true,
|
placeholder={placeholder}
|
||||||
},
|
decimalsLimit={precision}
|
||||||
null
|
decimalScale={precision}
|
||||||
)}
|
value={transform.input(field.value)}
|
||||||
</div>
|
onValueChange={(e) => field.onChange(transform.output(e))}
|
||||||
)}
|
/>
|
||||||
|
</FormControl>
|
||||||
<FormControl
|
|
||||||
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
|
||||||
>
|
|
||||||
<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 && (
|
|
||||||
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
|
||||||
{createElement(
|
|
||||||
trailIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{button && <>{createElement(button)}</>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
<FormErrorMessage />
|
<FormErrorMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { FormControl, FormDescription, FormField, FormItem, Input, InputProps } from "@/ui";
|
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
||||||
import { Quantity } from "@shared/contexts";
|
import { Quantity } from "@shared/contexts";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import CurrencyInput from "react-currency-input-field";
|
||||||
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
import { FieldPath, 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(
|
const formQuantityFieldVariants = cva(
|
||||||
"text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
|
"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:
|
||||||
outline:
|
"border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ",
|
||||||
"border-0 focus-visible:border focus-visible:border-input focus-visible:ring-0 focus-visible:ring-offset-0 ",
|
outline: "focus-visible:border focus-visible:border-input",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@ -52,28 +53,42 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
disabled,
|
disabled,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
rules,
|
rules,
|
||||||
|
readOnly,
|
||||||
precision,
|
precision,
|
||||||
variant,
|
variant,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
|
||||||
const transformToInput = (value: any) => {
|
const transform = {
|
||||||
if (typeof value !== "object") {
|
input: (value: any) => {
|
||||||
return value;
|
if (typeof value !== "object") {
|
||||||
}
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
const quantityOrError = Quantity.create(value);
|
const quantityOrError = Quantity.create(value);
|
||||||
if (quantityOrError.isFailure) {
|
if (quantityOrError.isFailure) {
|
||||||
throw quantityOrError.error;
|
throw quantityOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
quantityOrError.object
|
quantityOrError.object
|
||||||
.toNumber()
|
.toNumber()
|
||||||
//.toPrecision(precision ?? value.precision)
|
//.toPrecision(precision ?? value.precision)
|
||||||
.toString()
|
.toString()
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
output: (value: string | undefined) => {
|
||||||
|
const quantityOrError = Quantity.create({
|
||||||
|
amount: value?.replace(",", "") ?? null,
|
||||||
|
precision,
|
||||||
|
});
|
||||||
|
if (quantityOrError.isFailure) {
|
||||||
|
throw quantityOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantityOrError.object.toObject();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -83,26 +98,27 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
name={name}
|
name={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
rules={rules}
|
rules={rules}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
{label && <FormLabel label={label} hint={hint} required={rules?.required ?? false} />}
|
{label && (
|
||||||
|
<FormLabel label={label} hint={hint} required={Boolean(rules?.required ?? false)} />
|
||||||
|
)}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<CurrencyInput
|
||||||
type='number'
|
|
||||||
name={field.name}
|
name={field.name}
|
||||||
//ref={field.ref} <-- no activar que hace cosas raras
|
//ref={field.ref} <-- no activar que hace cosas raras
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
|
readOnly={readOnly}
|
||||||
className={cn(formQuantityFieldVariants({ variant, className }))}
|
className={cn(formQuantityFieldVariants({ variant, className }))}
|
||||||
|
groupSeparator='.'
|
||||||
|
decimalSeparator=','
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={transformToInput(field.value)}
|
decimalsLimit={precision}
|
||||||
onChange={(value) => {
|
decimalScale={precision}
|
||||||
// "value" ya viene con los "0" de la precisión
|
value={transform.input(field.value)}
|
||||||
console.log(value);
|
onValueChange={(e) => field.onChange(transform.output(e))}
|
||||||
field.onChange(value ?? "");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { FormControl, FormDescription, FormField, FormItem, Input, InputProps } from "@/ui";
|
import { FormControl, FormDescription, FormField, FormItem, Input, InputProps } from "@/ui";
|
||||||
|
|
||||||
|
import { cva } from "class-variance-authority";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
@ -8,6 +9,19 @@ 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 FormTextFieldVariants = cva("", {
|
||||||
|
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 FormTextFieldProps<
|
export type FormTextFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
@ -19,94 +33,96 @@ export type FormTextFieldProps<
|
|||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName>;
|
UseControllerProps<TFieldValues, TName>;
|
||||||
|
|
||||||
export const FormTextField = React.forwardRef<
|
export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldProps>(
|
||||||
HTMLDivElement,
|
(props, ref) => {
|
||||||
React.HTMLAttributes<HTMLDivElement> & FormTextFieldProps
|
const {
|
||||||
>((props, ref) => {
|
name,
|
||||||
const {
|
label,
|
||||||
name,
|
hint,
|
||||||
label,
|
description,
|
||||||
hint,
|
placeholder,
|
||||||
placeholder,
|
className,
|
||||||
description,
|
disabled,
|
||||||
|
defaultValue,
|
||||||
|
rules,
|
||||||
|
type,
|
||||||
|
variant,
|
||||||
|
|
||||||
required,
|
button,
|
||||||
className,
|
leadIcon,
|
||||||
leadIcon,
|
trailIcon,
|
||||||
trailIcon,
|
} = props;
|
||||||
button,
|
|
||||||
|
|
||||||
defaultValue,
|
const { control } = useFormContext();
|
||||||
|
|
||||||
type,
|
return (
|
||||||
} = props;
|
<FormField
|
||||||
|
defaultValue={defaultValue}
|
||||||
const { control } = useFormContext();
|
control={control}
|
||||||
|
name={name}
|
||||||
return (
|
disabled={disabled}
|
||||||
<FormField
|
rules={rules}
|
||||||
defaultValue={defaultValue}
|
render={({ field, fieldState }) => {
|
||||||
control={control}
|
return (
|
||||||
name={name}
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
rules={{ required }}
|
{label && (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
<FormLabel label={label} hint={hint} required={Boolean(rules?.required ?? false)} />
|
||||||
render={({ field, fieldState, formState }) => {
|
)}
|
||||||
return (
|
<div className={cn(button ? "flex" : null)}>
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
<div
|
||||||
{label && <FormLabel label={label} hint={hint} required={required} />}
|
className={cn(
|
||||||
<div className={cn(button ? "flex" : null)}>
|
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
||||||
<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'>
|
|
||||||
{React.createElement(
|
|
||||||
leadIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormControl
|
|
||||||
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
|
||||||
>
|
>
|
||||||
<Input
|
{leadIcon && (
|
||||||
type={type}
|
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
||||||
placeholder={placeholder}
|
{React.createElement(
|
||||||
className={cn(
|
leadIcon,
|
||||||
fieldState.error ? "border-destructive focus-visible:ring-destructive" : ""
|
{
|
||||||
)}
|
className: "h-5 w-5 text-muted-foreground",
|
||||||
{...field}
|
"aria-hidden": true,
|
||||||
/>
|
},
|
||||||
</FormControl>
|
null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{trailIcon && (
|
<FormControl
|
||||||
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
||||||
{createElement(
|
>
|
||||||
trailIcon,
|
<Input
|
||||||
{
|
type={type}
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
placeholder={placeholder}
|
||||||
"aria-hidden": true,
|
className={cn(
|
||||||
},
|
fieldState.error ? "border-destructive focus-visible:ring-destructive" : "",
|
||||||
null
|
FormTextFieldVariants({ variant, className })
|
||||||
)}
|
)}
|
||||||
</div>
|
{...field}
|
||||||
)}
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{trailIcon && (
|
||||||
|
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
||||||
|
{createElement(
|
||||||
|
trailIcon,
|
||||||
|
{
|
||||||
|
className: "h-5 w-5 text-muted-foreground",
|
||||||
|
"aria-hidden": true,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{button && <>{createElement(button)}</>}
|
||||||
</div>
|
</div>
|
||||||
{button && <>{createElement(button)}</>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
<FormErrorMessage />
|
<FormErrorMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -11,22 +11,22 @@ export const calculateItemTotals = (item: {
|
|||||||
subtotalPrice: MoneyValue;
|
subtotalPrice: MoneyValue;
|
||||||
discount: Percentage;
|
discount: Percentage;
|
||||||
totalPrice: MoneyValue;
|
totalPrice: MoneyValue;
|
||||||
} => {
|
} | null => {
|
||||||
const { quantity: quantity_value, unit_price: unit_price_value, discount: discount_value } = item;
|
const { quantity: quantity_dto, unit_price: unit_price_dto, discount: discount_dto } = item;
|
||||||
|
|
||||||
const quantityOrError = Quantity.create(quantity_value);
|
const quantityOrError = Quantity.create(quantity_dto);
|
||||||
if (quantityOrError.isFailure) {
|
if (quantityOrError.isFailure) {
|
||||||
throw quantityOrError.error;
|
throw quantityOrError.error;
|
||||||
}
|
}
|
||||||
const quantity = quantityOrError.object;
|
const quantity = quantityOrError.object;
|
||||||
|
|
||||||
const unitPriceOrError = MoneyValue.create(unit_price_value);
|
const unitPriceOrError = MoneyValue.create(unit_price_dto);
|
||||||
if (unitPriceOrError.isFailure) {
|
if (unitPriceOrError.isFailure) {
|
||||||
throw unitPriceOrError.error;
|
throw unitPriceOrError.error;
|
||||||
}
|
}
|
||||||
const unitPrice = unitPriceOrError.object;
|
const unitPrice = unitPriceOrError.object;
|
||||||
|
|
||||||
const discountOrError = Percentage.create(discount_value);
|
const discountOrError = Percentage.create(discount_dto);
|
||||||
if (discountOrError.isFailure) {
|
if (discountOrError.isFailure) {
|
||||||
throw discountOrError.error;
|
throw discountOrError.error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export * from "./useCustomDialog";
|
|||||||
export * from "./useDataSource";
|
export * from "./useDataSource";
|
||||||
export * from "./useDataTable";
|
export * from "./useDataTable";
|
||||||
export * from "./useLocalization";
|
export * from "./useLocalization";
|
||||||
|
export * from "./useMediaQuery";
|
||||||
export * from "./usePagination";
|
export * from "./usePagination";
|
||||||
export * from "./useTheme";
|
export * from "./useTheme";
|
||||||
export * from "./useUnsavedChangesNotifier";
|
export * from "./useUnsavedChangesNotifier";
|
||||||
|
|||||||
1
client/src/lib/hooks/useMediaQuery/index.ts
Normal file
1
client/src/lib/hooks/useMediaQuery/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./useMediaQuery";
|
||||||
19
client/src/lib/hooks/useMediaQuery/useMediaQuery.tsx
Normal file
19
client/src/lib/hooks/useMediaQuery/useMediaQuery.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export function useMediaQuery(query: string) {
|
||||||
|
const [value, setValue] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function onChange(event: MediaQueryListEvent) {
|
||||||
|
setValue(event.matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = matchMedia(query);
|
||||||
|
result.addEventListener("change", onChange);
|
||||||
|
setValue(result.matches);
|
||||||
|
|
||||||
|
return () => result.removeEventListener("change", onChange);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
@ -124,6 +124,9 @@
|
|||||||
"title": "Cotización"
|
"title": "Cotización"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Cotización"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"draft": "Borrador"
|
"draft": "Borrador"
|
||||||
},
|
},
|
||||||
@ -138,6 +141,16 @@
|
|||||||
"desc": "Referencia para esta cotización",
|
"desc": "Referencia para esta cotización",
|
||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
},
|
},
|
||||||
|
"lang_code": {
|
||||||
|
"label": "Idioma",
|
||||||
|
"desc": "Idioma de la cotización",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"currency_code": {
|
||||||
|
"label": "Moneda",
|
||||||
|
"desc": "Moneda de la cotización",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
"customer_information": {
|
"customer_information": {
|
||||||
"label": "Datos del cliente",
|
"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.",
|
"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.",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user