From fe344588991fd23ed94e5afcc07c253184435c80 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Wed, 28 Aug 2024 20:38:20 +0200 Subject: [PATCH] . --- .../AppendCatalogArticleRowButton.tsx | 22 +- .../src/app/quotes/components/QuoteResume.tsx | 269 +++++++++--------- .../app/quotes/components/QuotesDataTable.tsx | 6 +- .../components/editors/QuoteStatusEditor.tsx | 6 +- .../components/ColorBadget/ColorBadget.tsx | 35 ++- .../useLocalization/useLocatlization.tsx | 16 +- client/src/locales/en.json | 22 +- .../common/domain/entities/MoneyValue.ts | 8 + shared/lib/utilities/helpers.ts | 4 + 9 files changed, 217 insertions(+), 171 deletions(-) create mode 100644 shared/lib/utilities/helpers.ts diff --git a/client/src/app/quotes/components/AppendCatalogArticleRowButton.tsx b/client/src/app/quotes/components/AppendCatalogArticleRowButton.tsx index 409644e..3953ec8 100644 --- a/client/src/app/quotes/components/AppendCatalogArticleRowButton.tsx +++ b/client/src/app/quotes/components/AppendCatalogArticleRowButton.tsx @@ -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 => ( - +export const AppendCatalogArticleRowButton = forwardRef( + ( + { label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps, + ref + ): JSX.Element => ( + + ) ); AppendCatalogArticleRowButton.displayName = "AddNewRowButton"; diff --git a/client/src/app/quotes/components/QuoteResume.tsx b/client/src/app/quotes/components/QuoteResume.tsx index 44089d0..07583da 100644 --- a/client/src/app/quotes/components/QuoteResume.tsx +++ b/client/src/app/quotes/components/QuoteResume.tsx @@ -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) => { <> - + - - {`${t("quotes.list.preview.quote")} #${data.reference}`} + + {t("quotes.list.resume.title")} + - - - + +
+ + - - - - - {t("quotes.list.preview.download_quote")} - - - - - - - - {t("common.more")} - - - - - - {t("quotes.list.preview.download_quote")} - - - + + + + + {t("quotes.list.resume.download_quote")} + + + {/* + + + + + + {t("common.more")} + + + + + + {t("quotes.list.preview.download_quote")} + + + */} +
- Resume - Preview + {t("quotes.list.resume.tabs.resume")} + {t("quotes.list.resume.tabs.preview")}
-
Quote information
+
{t("quotes.list.resume.quote_information")}
+
+
+ {t("quotes.form_fields.reference.label")} +
+
{data.reference}
+
{t("quotes.form_fields.date.label")}
-
{new Date(data.date).toLocaleDateString()}
+
{new Date(data.date).toLocaleDateString()}
-
-
Email
-
- liam@acme.com -
-
-
-
Phone
-
- +1 234 567 890 +
+
+ {t("quotes.form_fields.customer_reference.label")} +
+
+ {data.customer_reference}
-
Customer Information
- Date: {new Date(data.date).toLocaleDateString()} +
+ {t("quotes.list.resume.customer_information")} +
{data.customer_information}
-
-
-
Customer
-
Liam Johnson
-
-
-
Email
-
- liam@acme.com -
-
-
-
Phone
-
- +1 234 567 890 -
-
-
-
Order Details
+
{t("quotes.list.resume.price_information")}
  • - Glimmer Lamps x 2 + {t("quotes.form_fields.subtotal_price.label")} - $250.00 + {totals.subtotal_price}
  • - Aqua Filters x 1 + {t("quotes.form_fields.discount.label")} - $49.00 -
  • -
- -
    -
  • - Subtotal - $299.00 -
  • -
  • - Shipping $5.00
  • - Tax + + {t("quotes.form_fields.discount_price.label")} + $25.00
  • + +
  • + + {t("quotes.form_fields.tax.label")} + + $5.00 +
  • +
  • + + {t("quotes.form_fields.tax_price.label")} + + $25.00 +
  • +
  • - Total + + {t("quotes.form_fields.total_price.label")} + $329.00
- -
-
-
Shipping Information
-
- Liam Johnson - 1234 Main St. - Anytown, CA 12345 -
-
-
-
Billing Information
-
Same as shipping address
-
-
- - -
-
Payment Information
-
-
-
- - Visa -
-
**** **** **** 4532
-
-
-
diff --git a/client/src/app/quotes/components/QuotesDataTable.tsx b/client/src/app/quotes/components/QuotesDataTable.tsx index 66a7413..b54eb2c 100644 --- a/client/src/app/quotes/components/QuotesDataTable.tsx +++ b/client/src/app/quotes/components/QuotesDataTable.tsx @@ -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 } }) => , + cell: ({ row: { original } }) => ( + + ), }, { id: "date" as const, @@ -282,7 +284,7 @@ export const QuotesDataTable = ({ {preview && ( -
+
{/**/}
diff --git a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx index d19247a..d5a8008 100644 --- a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx @@ -25,8 +25,8 @@ const quoteStatusTransitions: Record = { 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 ( diff --git a/client/src/components/ColorBadget/ColorBadget.tsx b/client/src/components/ColorBadget/ColorBadget.tsx index 75b8983..6cbdf05 100644 --- a/client/src/components/ColorBadget/ColorBadget.tsx +++ b/client/src/components/ColorBadget/ColorBadget.tsx @@ -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 ( - + {label} ); diff --git a/client/src/lib/hooks/useLocalization/useLocatlization.tsx b/client/src/lib/hooks/useLocalization/useLocatlization.tsx index f2a0ff8..a67f841 100644 --- a/client/src/lib/hooks/useLocalization/useLocatlization.tsx +++ b/client/src/lib/hooks/useLocalization/useLocatlization.tsx @@ -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); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 90335d6..ca26296 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -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", diff --git a/shared/lib/contexts/common/domain/entities/MoneyValue.ts b/shared/lib/contexts/common/domain/entities/MoneyValue.ts index 02f0c61..7c48334 100644 --- a/shared/lib/contexts/common/domain/entities/MoneyValue.ts +++ b/shared/lib/contexts/common/domain/entities/MoneyValue.ts @@ -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 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); diff --git a/shared/lib/utilities/helpers.ts b/shared/lib/utilities/helpers.ts new file mode 100644 index 0000000..7486de1 --- /dev/null +++ b/shared/lib/utilities/helpers.ts @@ -0,0 +1,4 @@ +export const adjustPrecision = ({ amount, scale }: { amount: number; scale: number }) => { + const factor = 10 ** scale; + return Number(amount) / factor; +};