.
This commit is contained in:
parent
313659689e
commit
fe34458899
@ -1,22 +1,24 @@
|
||||
import { Button, ButtonProps } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
import { PackagePlusIcon } from "lucide-react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export interface AppendCatalogArticleRowButtonProps extends ButtonProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AppendCatalogArticleRowButton = ({
|
||||
label = t("common.append_article"),
|
||||
className,
|
||||
...props
|
||||
}: AppendCatalogArticleRowButtonProps): JSX.Element => (
|
||||
<Button type='button' variant='outline' {...props}>
|
||||
{" "}
|
||||
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||
{label && <>{label}</>}
|
||||
</Button>
|
||||
export const AppendCatalogArticleRowButton = forwardRef(
|
||||
(
|
||||
{ label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps,
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<Button type='button' variant='outline' {...props}>
|
||||
{" "}
|
||||
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||
{label && <>{label}</>}
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
AppendCatalogArticleRowButton.displayName = "AddNewRowButton";
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { CreditCard, DownloadIcon, FilePenLineIcon, MoreVertical } from "lucide-react";
|
||||
import { DownloadIcon, FilePenLineIcon } from "lucide-react";
|
||||
|
||||
import { ColorBadge } from "@/components";
|
||||
import { useCustomLocalization } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Button,
|
||||
@ -9,10 +11,6 @@ import {
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Separator,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
@ -23,8 +21,9 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/ui";
|
||||
import { useToast } from "@/ui/use-toast";
|
||||
import { CurrencyData } from "@shared/contexts";
|
||||
import { t } from "i18next";
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useQuotes } from "../hooks";
|
||||
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
||||
@ -44,6 +43,33 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
||||
const { download, ...downloadProps } = useDownloader();
|
||||
|
||||
const { formatCurrency } = useCustomLocalization({
|
||||
locale: data?.lang_code || "ES",
|
||||
});
|
||||
|
||||
const currency_symbol = useMemo(() => {
|
||||
const currencyOrError = data
|
||||
? CurrencyData.createFromCode(data?.currency_code)
|
||||
: CurrencyData.createDefaultCode();
|
||||
return currencyOrError.isSuccess ? currencyOrError.object.symbol : "";
|
||||
}, [data]);
|
||||
|
||||
const totals = useMemo(() => {
|
||||
return data
|
||||
? {
|
||||
subtotal_price: formatCurrency(data.subtotal_price),
|
||||
discount_price: formatCurrency(data.discount_price),
|
||||
tax_price: formatCurrency(data.tax_price),
|
||||
total_price: formatCurrency(data.total_price),
|
||||
}
|
||||
: {
|
||||
subtotal_price: "0,00",
|
||||
discount_price: "0,00",
|
||||
tax_price: "0,00",
|
||||
total_price: "0,00",
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
const handleOnChangeStatus = (_: string, newStatus: string) => {
|
||||
setStatusMutation(
|
||||
{ newStatus },
|
||||
@ -90,183 +116,150 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
<>
|
||||
<DownloadQuoteDialog {...downloadProps} onFinishDownload={handleFinishDownload} />
|
||||
<Tabs defaultValue='resume'>
|
||||
<Card className='w-full overflow-hidden'>
|
||||
<Card className='w-[390px] overflow-hidden'>
|
||||
<CardHeader className='gap-3 border-b bg-accent'>
|
||||
<CardTitle className='text-lg'>
|
||||
{`${t("quotes.list.preview.quote")} #${data.reference}`}
|
||||
<CardTitle className='flex items-center justify-between text-lg'>
|
||||
<span>{t("quotes.list.resume.title")}</span>
|
||||
<ColorBadge className='text-sm' label={t(`quotes.status.${data.status}`)} />
|
||||
</CardTitle>
|
||||
<CardDescription className='flex items-center gap-1 mr-auto text-foreground'>
|
||||
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='h-8 gap-1'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||
}}
|
||||
>
|
||||
<FilePenLineIcon className='h-3.5 w-3.5' />
|
||||
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
||||
{t("quotes.list.columns.actions.edit")}
|
||||
</span>
|
||||
</Button>
|
||||
<CardDescription className='flex mr-auto text-foreground'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='h-8 gap-1'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||
}}
|
||||
>
|
||||
<FilePenLineIcon className='h-3.5 w-3.5' />
|
||||
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
||||
{t("quotes.list.columns.actions.edit")}
|
||||
</span>
|
||||
</Button>
|
||||
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='h-8 gap-1'
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<DownloadIcon className='h-3.5 w-3.5 ' />
|
||||
<span className='sr-only'>{t("quotes.list.preview.download_quote")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("quotes.list.preview.download_quote")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size='icon' variant='outline' className='hidden w-8 h-8'>
|
||||
<MoreVertical className='h-3.5 w-3.5' />
|
||||
<span className='sr-only'>{t("common.more")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("common.more")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
<DropdownMenuItem className='not-sr-only 2xl:sr-only'>
|
||||
<DownloadIcon className='h-3.5 w-3.5 mr-2' />
|
||||
<span>{t("quotes.list.preview.download_quote")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
className='h-8 gap-1'
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<DownloadIcon className='h-3.5 w-3.5 ' />
|
||||
<span className='sr-only'>{t("quotes.list.resume.download_quote")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("quotes.list.resume.download_quote")}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/*<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size='icon' variant='outline' className='w-8 h-8'>
|
||||
<MoreVertical className='h-3.5 w-3.5' />
|
||||
<span className='sr-only'>{t("common.more")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("common.more")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
<DropdownMenuItem className='not-sr-only 2xl:sr-only'>
|
||||
<DownloadIcon className='h-3.5 w-3.5 mr-2' />
|
||||
<span>{t("quotes.list.preview.download_quote")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>*/}
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='p-6 text-sm'>
|
||||
<TabsList className='grid w-full grid-cols-2'>
|
||||
<TabsTrigger value='resume'>Resume</TabsTrigger>
|
||||
<TabsTrigger value='preview'>Preview</TabsTrigger>
|
||||
<TabsTrigger value='resume'>{t("quotes.list.resume.tabs.resume")}</TabsTrigger>
|
||||
<TabsTrigger value='preview'>{t("quotes.list.resume.tabs.preview")}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='resume' className='pt-4'>
|
||||
<div className='grid gap-3'>
|
||||
<div className='grid gap-3'>
|
||||
<div className='font-semibold'>Quote information</div>
|
||||
<div className='font-semibold'>{t("quotes.list.resume.quote_information")}</div>
|
||||
|
||||
<dl className='grid gap-3'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.reference.label")}
|
||||
</dt>
|
||||
<dd className='font-medium'>{data.reference}</dd>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.date.label")}
|
||||
</dt>
|
||||
<dd>{new Date(data.date).toLocaleDateString()}</dd>
|
||||
<dd className='font-medium'>{new Date(data.date).toLocaleDateString()}</dd>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>Email</dt>
|
||||
<dd>
|
||||
<a href='mailto:'>liam@acme.com</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>Phone</dt>
|
||||
<dd>
|
||||
<a href='tel:'>+1 234 567 890</a>
|
||||
<div className='flex items-start justify-between'>
|
||||
<dt className='text-muted-foreground whitespace-nowrap'>
|
||||
{t("quotes.form_fields.customer_reference.label")}
|
||||
</dt>
|
||||
<dd className='font-medium text-right whitespace-break-spaces'>
|
||||
{data.customer_reference}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<Separator className='my-4' />
|
||||
<div className='grid gap-3'>
|
||||
<div className='font-semibold'>Customer Information</div>
|
||||
Date: {new Date(data.date).toLocaleDateString()}
|
||||
<div className='font-semibold'>
|
||||
{t("quotes.list.resume.customer_information")}
|
||||
</div>
|
||||
<div>{data.customer_information}</div>
|
||||
<dl className='grid gap-3'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>Customer</dt>
|
||||
<dd>Liam Johnson</dd>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>Email</dt>
|
||||
<dd>
|
||||
<a href='mailto:'>liam@acme.com</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='text-muted-foreground'>Phone</dt>
|
||||
<dd>
|
||||
<a href='tel:'>+1 234 567 890</a>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<Separator className='my-4' />
|
||||
<div className='font-semibold'>Order Details</div>
|
||||
<div className='font-semibold'>{t("quotes.list.resume.price_information")}</div>
|
||||
<ul className='grid gap-3'>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>
|
||||
Glimmer Lamps x <span>2</span>
|
||||
{t("quotes.form_fields.subtotal_price.label")}
|
||||
</span>
|
||||
<span>$250.00</span>
|
||||
<span>{totals.subtotal_price}</span>
|
||||
</li>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>
|
||||
Aqua Filters x <span>1</span>
|
||||
{t("quotes.form_fields.discount.label")}
|
||||
</span>
|
||||
<span>$49.00</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator className='my-2' />
|
||||
<ul className='grid gap-3'>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Subtotal</span>
|
||||
<span>$299.00</span>
|
||||
</li>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Shipping</span>
|
||||
<span>$5.00</span>
|
||||
</li>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Tax</span>
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.discount_price.label")}
|
||||
</span>
|
||||
<span>$25.00</span>
|
||||
</li>
|
||||
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.tax.label")}
|
||||
</span>
|
||||
<span>$5.00</span>
|
||||
</li>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.tax_price.label")}
|
||||
</span>
|
||||
<span>$25.00</span>
|
||||
</li>
|
||||
|
||||
<li className='flex items-center justify-between font-semibold'>
|
||||
<span className='text-muted-foreground'>Total</span>
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.total_price.label")}
|
||||
</span>
|
||||
<span>$329.00</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Separator className='my-4' />
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='grid gap-3'>
|
||||
<div className='font-semibold'>Shipping Information</div>
|
||||
<address className='grid gap-0.5 not-italic text-muted-foreground'>
|
||||
<span>Liam Johnson</span>
|
||||
<span>1234 Main St.</span>
|
||||
<span>Anytown, CA 12345</span>
|
||||
</address>
|
||||
</div>
|
||||
<div className='grid gap-3 auto-rows-max'>
|
||||
<div className='font-semibold'>Billing Information</div>
|
||||
<div className='text-muted-foreground'>Same as shipping address</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className='my-4' />
|
||||
|
||||
<div className='grid gap-3'>
|
||||
<div className='font-semibold'>Payment Information</div>
|
||||
<dl className='grid gap-3'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<dt className='flex items-center gap-1 text-muted-foreground'>
|
||||
<CreditCard className='w-4 h-4' />
|
||||
Visa
|
||||
</dt>
|
||||
<dd>**** **** **** 4532</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value='preview'></TabsContent>
|
||||
</CardContent>
|
||||
|
||||
@ -88,7 +88,9 @@ export const QuotesDataTable = ({
|
||||
accessorKey: "status",
|
||||
header: () => <>{t("quotes.list.columns.status")}</>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: ({ row: { original } }) => <ColorBadge label={original.status} />,
|
||||
cell: ({ row: { original } }) => (
|
||||
<ColorBadge label={t(`quotes.status.${original.status}`)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "date" as const,
|
||||
@ -282,7 +284,7 @@ export const QuotesDataTable = ({
|
||||
</div>
|
||||
|
||||
{preview && (
|
||||
<div id={previewId} className='flex items-stretch'>
|
||||
<div id={previewId} className='flex items-stretch '>
|
||||
<QuoteResume quoteId={activeRow?.original.id} />
|
||||
{/*<QuotePDFPreview quote={activeRow?.original} className='flex-1' />*/}
|
||||
</div>
|
||||
|
||||
@ -25,8 +25,8 @@ const quoteStatusTransitions: Record<TQuoteStatus, TQuoteStatus[]> = {
|
||||
draft: ["draft", "ready", "archived"],
|
||||
ready: ["ready", "delivered", "archived"],
|
||||
delivered: ["delivered", "accepted", "rejected", "archived"],
|
||||
accepted: ["accepted", "archived"],
|
||||
rejected: ["rejected", "archived"],
|
||||
accepted: ["accepted", "rejected", "archived"],
|
||||
rejected: ["rejected", "accepted", "archived"],
|
||||
archived: ["archived", "draft", "ready", "delivered", "accepted", "rejected"],
|
||||
};
|
||||
|
||||
@ -53,8 +53,6 @@ export const QuoteStatusEditor = ({
|
||||
}
|
||||
};
|
||||
|
||||
console.log(newStatus);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
|
||||
@ -1,23 +1,50 @@
|
||||
import { Badge } from "@/ui";
|
||||
|
||||
function stringToColor(str: string) {
|
||||
function stringToColorPair(str: string) {
|
||||
const TEXT_DARKEN_FACTOR = 0.7; // Factor para oscurecer el color del texto
|
||||
const BACKGROUND_LIGHTEN_FACTOR = 0.7; // Factor para aclarar el color de fondo
|
||||
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
let color = "#";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color;
|
||||
|
||||
// Convert the color from hex to RGB
|
||||
const r = parseInt(color.substr(1, 2), 16);
|
||||
const g = parseInt(color.substr(3, 2), 16);
|
||||
const b = parseInt(color.substr(5, 2), 16);
|
||||
|
||||
// Generate a darker shade for the text color
|
||||
const textColor = `#${((r * TEXT_DARKEN_FACTOR) | 0).toString(16).padStart(2, "0")}${(
|
||||
(g * TEXT_DARKEN_FACTOR) |
|
||||
0
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}${((b * TEXT_DARKEN_FACTOR) | 0).toString(16).padStart(2, "0")}`;
|
||||
|
||||
// Generate a much lighter shade for the background color
|
||||
const bgColor = `#${Math.min(255, Math.floor(r + (255 - r) * BACKGROUND_LIGHTEN_FACTOR))
|
||||
.toString(16)
|
||||
.padStart(2, "0")}${Math.min(255, Math.floor(g + (255 - g) * BACKGROUND_LIGHTEN_FACTOR))
|
||||
.toString(16)
|
||||
.padStart(2, "0")}${Math.min(255, Math.floor(b + (255 - b) * BACKGROUND_LIGHTEN_FACTOR))
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`;
|
||||
|
||||
return [textColor, bgColor];
|
||||
}
|
||||
|
||||
export const ColorBadge = ({ label, className }: { label: string; className?: string }) => {
|
||||
const backgroundColor = stringToColor(label);
|
||||
const [color, backgroundColor] = stringToColorPair(label);
|
||||
|
||||
return (
|
||||
<Badge className={className} style={{ backgroundColor, color: "#fff" }}>
|
||||
<Badge className={className} style={{ backgroundColor, color }}>
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
/* https://github.com/mayank8aug/use-localization/blob/main/src/index.ts */
|
||||
|
||||
import { IMoney, IPercentage, IQuantity } from "@/lib/types";
|
||||
import { adjustPrecision } from "@shared/utilities/helpers";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type UseLocalizationProps = {
|
||||
locale: string;
|
||||
};
|
||||
|
||||
const adjustPrecision = ({ amount, scale }: { amount: number; scale: number }) => {
|
||||
const factor = 10 ** scale;
|
||||
return Number(amount) / factor;
|
||||
locale?: string;
|
||||
};
|
||||
|
||||
export const useLocalization = () => {
|
||||
@ -32,10 +28,12 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
|
||||
|
||||
const { amount, scale, currency_code } = value;
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
return new Intl.NumberFormat(locale ?? "ES", {
|
||||
style: "currency",
|
||||
currency: currency_code,
|
||||
currencyDisplay: "symbol",
|
||||
useGrouping: true,
|
||||
|
||||
maximumFractionDigits: scale,
|
||||
}).format(amount === null ? 0 : amount);
|
||||
},
|
||||
@ -53,8 +51,8 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
|
||||
const result = new Intl.NumberFormat("es", {
|
||||
/*minimumSignificantDigits: scale,
|
||||
maximumSignificantDigits: scale,
|
||||
minimumFractionDigits: scale,
|
||||
useGrouping: true,*/
|
||||
minimumFractionDigits: scale,*/
|
||||
useGrouping: true,
|
||||
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));
|
||||
|
||||
//console.log(value, result);
|
||||
|
||||
@ -138,10 +138,19 @@
|
||||
"edit": "Edit quote"
|
||||
}
|
||||
},
|
||||
"resume": {},
|
||||
"preview": {
|
||||
"quote": "Quote",
|
||||
"download_quote": "Download quote"
|
||||
|
||||
"resume": {
|
||||
"title": "Quote",
|
||||
"download_quote": "Download quote",
|
||||
"tabs": {
|
||||
"resume": "Resume",
|
||||
"preview": "Preview"
|
||||
},
|
||||
|
||||
"quote_information": "Quote Information",
|
||||
"customer_information": "Customer Information",
|
||||
"payment_information": "Payment Information",
|
||||
"price_information": "Quote totals"
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
@ -250,6 +259,11 @@
|
||||
"desc": "Quote reference",
|
||||
"placeholder": ""
|
||||
},
|
||||
"status": {
|
||||
"label": "Status",
|
||||
"desc": "Quote status",
|
||||
"placeholder": ""
|
||||
},
|
||||
"lang_code": {
|
||||
"label": "Language",
|
||||
"desc": "Quote language",
|
||||
|
||||
@ -4,6 +4,7 @@ import DineroFactory, { Currency, Dinero } from "dinero.js";
|
||||
import Joi from "joi";
|
||||
import { isNull } from "lodash";
|
||||
import { NullOr } from "../../../../utilities";
|
||||
import { adjustPrecision } from "../../../../utilities/helpers";
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { CurrencyData } from "./CurrencyData";
|
||||
@ -254,6 +255,13 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
||||
return "";
|
||||
}
|
||||
|
||||
new Intl.NumberFormat("es", {
|
||||
/*minimumSignificantDigits: scale,
|
||||
maximumSignificantDigits: scale,
|
||||
minimumFractionDigits: scale,*/
|
||||
useGrouping: true,
|
||||
}).format(value === null ? 0 : adjustPrecision({ amount: value, scale }));
|
||||
|
||||
const factor = Math.pow(10, scale);
|
||||
const amount = Number(value) / factor;
|
||||
return amount.toFixed(scale);
|
||||
|
||||
4
shared/lib/utilities/helpers.ts
Normal file
4
shared/lib/utilities/helpers.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const adjustPrecision = ({ amount, scale }: { amount: number; scale: number }) => {
|
||||
const factor = 10 ** scale;
|
||||
return Number(amount) / factor;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user