Arreglos de errores antes de subir a producción

This commit is contained in:
David Arranz 2024-09-02 17:44:49 +02:00
parent b1cf2fdd18
commit 9300868ce3
48 changed files with 196 additions and 652 deletions

View File

@ -49,6 +49,7 @@
"class-variance-authority": "^0.7.0",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.2.0",
"i18next": "^23.12.2",
"i18next-browser-languagedetector": "^8.0.0",
"joi": "^17.13.1",

View File

@ -77,13 +77,7 @@ export const Routes = () => {
},
{
path: "/quotes",
element: (
<ProtectedRoute>
<QuotesLayout>
<Outlet />
</QuotesLayout>
</ProtectedRoute>
),
element: <QuotesLayout />,
children: [
{
index: true,

View File

@ -1,31 +1,6 @@
import { Button } from "@/ui";
import { useLocation, useNavigate } from "react-router-dom";
// import Button
/*const isDev = process.env.REACT_APP_NODE_ENV === "development";
const hostname = `${
isDev ? process.env.REACT_APP_DEV_API_URL : process.env.REACT_APP_PROD_API_URL
}`;*/
type ErrorPageProps = {
error?: string;
};
export const ErrorPage = (props: ErrorPageProps) => {
const navigate = useNavigate();
const location = useLocation();
const navMsg = location.state?.error;
const msg = navMsg ? (
<p className='text-lg'>{navMsg}</p>
) : props.error ? (
<p className='text-lg'>{props.error}</p>
) : (
<p className='text-lg'>
The targeted page <b>"{location.pathname}"</b> was not found, please confirm the spelling and
try again.
</p>
);
export const ErrorPage = () => {
return (
<div className='flex min-h-[100dvh] flex-col items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8'>
<div className='max-w-md mx-auto text-center'>
@ -37,37 +12,11 @@ export const ErrorPage = (props: ErrorPageProps) => {
The page you're looking for doesn't exist or has been moved.
</p>
<div className='mt-6'>
<Button
className='inline-flex items-center px-4 py-2 text-sm font-medium transition-colors rounded-md shadow-sm bg-primary text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2'
prefetch={false}
>
<Button className='inline-flex items-center px-4 py-2 text-sm font-medium transition-colors rounded-md shadow-sm bg-primary text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2'>
Go to Homepage
</Button>
</div>
</div>
</div>
);
return (
<section id='Error' className='flex flex-col items-center w-full h-full'>
{msg}
<span className='flex flex-row items-center justify-center my-10 space-x-8 '>
<Button id='backButton' onClick={() => navigate(-1)}>
Return to Previous Page
</Button>
<Button id='homeButton' onClick={() => navigate("/")}>
Return to Home Page
</Button>
<Button
id='logout'
onClick={() => {
//const endpoint = `${hostname}/logout`;
//window.open(endpoint, "_blank");
}}
>
Reset Authentication
</Button>
</span>
</section>
);
};

View File

@ -6,7 +6,7 @@ import { Badge } from "@/ui";
import { useMemo } from "react";
export const useCatalogTableColumns = (
actions: DataTablaRowActionFunction<IListArticles_Response_DTO, unknown>
actions: DataTablaRowActionFunction<IListArticles_Response_DTO>
): ColumnDef<IListArticles_Response_DTO>[] => {
const customerColumns: ColumnDef<IListArticles_Response_DTO>[] = useMemo(
() => [
@ -66,8 +66,8 @@ export const useCatalogTableColumns = (
{
id: "actions",
header: "Acciones",
cell: ({ row }) => {
return <DataTableRowActions row={row} actions={actions} />;
cell: (context) => {
return <DataTableRowActions rowContext={context} actions={actions} />;
},
},
],

View File

@ -2,10 +2,8 @@ import { Layout, LayoutContent, LayoutHeader } from "@/components";
import { useGetProfile } from "@/lib/hooks";
import { Skeleton } from "@/ui";
import { t } from "i18next";
import { useNavigate } from "react-router-dom";
export const DashboardPage = () => {
const navigate = useNavigate();
const { data: userIdentity, status } = useGetProfile();
return (

View File

@ -66,8 +66,8 @@ export const useCustomerInvoiceDataTableColumns = (
{
id: "actions",
header: "Acciones",
cell: ({ row }) => {
return <DataTableRowActions row={row} actions={actions} />;
cell: (context) => {
return <DataTableRowActions rowContext={context} actions={actions} />;
},
},
],

View File

@ -1,8 +1,8 @@
export * from "./ErrorPage";
export * from "./StartPage";
export * from "./auth";
export * from "./catalog";
export * from "./dashboard";
export * from "./dealers";
export * from "./ErrorPage";
export * from "./quotes";
export * from "./settings";
export * from "./StartPage";

View File

@ -11,7 +11,7 @@ export interface AppendCatalogArticleRowButtonProps extends ButtonProps {
export const AppendCatalogArticleRowButton = forwardRef(
(
{ label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps,
ref
_ref
): JSX.Element => (
<Button type='button' variant='outline' {...props}>
{" "}

View File

@ -11,7 +11,7 @@ export interface AppendEmptyRowButtonProps extends ButtonProps {
export const AppendEmptyRowButton = forwardRef(
(
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
ref
_ref
): JSX.Element => (
<Button type='button' variant='outline' {...props}>
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />

View File

@ -45,7 +45,7 @@ declare module "@tanstack/react-table" {
interface TableMeta<TData extends RowData> {
insertItem: (rowIndex: number, data?: unknown) => void;
appendItem: (data?: unknown) => void;
pickCatalogArticle: () => void;
pickCatalogArticle?: () => void;
duplicateItems: (rowIndex?: number) => void;
deleteItems: (rowIndex?: number | number[]) => void;
updateItem: (
@ -70,7 +70,7 @@ export type QuoteItemsSortableDataTableProps<
defaultValues: Readonly<{ [x: string]: any }> | undefined;
initialState?: InitialTableState;
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & {
pickCatalogArticle: () => void;
pickCatalogArticle?: () => void;
};
};
@ -113,8 +113,7 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [activeId, setActiveId] = useState<UniqueIdentifier | null>();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
const [columnVisibility, _setColumnVisibility] = useState<VisibilityState>(
initialState?.columnVisibility || {}
);
@ -160,7 +159,9 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
actions.append(data || defaultValues?.items[0], { shouldFocus: true });
},
pickCatalogArticle: () => {
actions.pickCatalogArticle();
if (actions.pickCatalogArticle) {
actions?.pickCatalogArticle();
}
},
duplicateItems: (rowIndex?: number) => {
if (rowIndex != undefined) {
@ -460,7 +461,11 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
<ButtonGroup>
<AppendEmptyRowButton onClick={() => table.options.meta?.appendItem()} />
<AppendCatalogArticleRowButton
onClick={() => table.options.meta?.pickCatalogArticle()}
onClick={() => {
if (table.options.meta && table.options.meta.pickCatalogArticle) {
table.options.meta?.pickCatalogArticle();
}
}}
/>
</ButtonGroup>
</CardFooter>

View File

@ -80,7 +80,11 @@ export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<any
<TooltipTrigger asChild>
<AppendCatalogArticleRowButton
variant='ghost'
onClick={() => table.options.meta?.pickCatalogArticle()}
onClick={() => {
if (table.options.meta && table.options.meta.pickCatalogArticle) {
table.options.meta?.pickCatalogArticle();
}
}}
/>
</TooltipTrigger>
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>

View File

@ -1,11 +1,8 @@
import { PDFViewer } from "@/components";
import { cn } from "@/lib/utils";
import { Card, CardContent, CardHeader, Skeleton } from "@/ui";
import { useToast } from "@/ui/use-toast";
import { IListQuotes_Response_DTO } from "@shared/contexts";
import { t } from "i18next";
import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuotes } from "../hooks";
const QuotePDFPreview = ({
@ -15,18 +12,18 @@ const QuotePDFPreview = ({
quote?: IListQuotes_Response_DTO;
className?: string;
}) => {
const navigate = useNavigate();
const { toast } = useToast();
//const navigate = useNavigate();
//const { toast } = useToast();
const { useReport, getQuotePDFFilename, useDownloader } = useQuotes();
const { download, ...downloadProps } = useDownloader();
const { download } = useDownloader();
const { report, preview, isInProgress } = useReport();
const [URLReport, setURLReport] = useState<string | undefined>(undefined);
const handleFinishDownload = useCallback(() => {
/*const handleFinishDownload = useCallback(() => {
toast({
description: t("quotes.downloading_dialog.toast_success"),
});
}, [toast]);
}, [toast]);*/
const handleDownload = useCallback(() => {
if (quote) download(quote.id, getQuotePDFFilename(quote));

View File

@ -7,7 +7,6 @@ import {
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
@ -21,7 +20,6 @@ import {
TooltipTrigger,
} from "@/ui";
import { useToast } from "@/ui/use-toast";
import { CurrencyData } from "@shared/contexts";
import { t } from "i18next";
import { useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
@ -40,7 +38,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
const { toast } = useToast();
const { useOne, useSetStatus, useDownloader, getQuotePDFFilename } = useQuotes();
const { data, status, error: queryError } = useOne(quoteId);
const { data, status } = useOne(quoteId);
const { mutate: setStatusMutation } = useSetStatus(quoteId);
const { download, ...downloadProps } = useDownloader();
@ -48,12 +46,12 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
locale: data?.lang_code || "ES",
});
const currency_symbol = useMemo(() => {
/*const currency_symbol = useMemo(() => {
const currencyOrError = data
? CurrencyData.createFromCode(data?.currency_code)
: CurrencyData.createDefaultCode();
return currencyOrError.isSuccess ? currencyOrError.object.symbol : "";
}, [data]);
}, [data]);*/
const totals = useMemo(() => {
return data
@ -129,7 +127,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
<span>{t("quotes.list.resume.title")}</span>
<ColorBadge className='text-sm' label={t(`quotes.status.${data.status}`)} />
</CardTitle>
<CardDescription className='flex mr-auto text-foreground'>
<div className='flex mr-auto text-foreground'>
<div className='flex items-center gap-1'>
<Button
size='sm'
@ -182,7 +180,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
</DropdownMenuContent>
</DropdownMenu>*/}
</div>
</CardDescription>
</div>
</CardHeader>
<CardContent className='p-6 text-sm'>
<TabsList className='grid w-full grid-cols-2'>

View File

@ -39,7 +39,6 @@ export const QuotesDataTable = ({
status?: string;
preview?: boolean;
}) => {
const location = useLocation();
const navigate = useNavigate();
const { toast } = useToast();
@ -63,20 +62,44 @@ export const QuotesDataTable = ({
const { download, ...downloadProps } = useDownloader();
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, any>[]>(() => {
const handleOnRowClick = (row: Row<IListQuotes_Response_DTO>) => {
setActiveRow(row);
};
const handleFinishDownload = useCallback(() => {
toast({
description: t("quotes.downloading_dialog.toast_success"),
});
}, [toast]);
const handleEditQuote = useCallback(
(quote: IListQuotes_Response_DTO) => {
toast({ title: "Guardo => " + quote.id });
navigate(`/quotes/edit/${quote.id}`, { relative: "path" });
},
[navigate, toast]
);
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, unknown>[]>(() => {
const columns = [
{
id: "reference" as const,
accessorKey: "reference",
header: () => <>{t("quotes.list.columns.reference")}</>,
cell: ({ row: { original }, renderValue }) => (
cell: ({
row: { original },
renderValue,
}: {
row: { original: IListQuotes_Response_DTO };
renderValue: () => any;
}) => (
<Button
size='sm'
variant='link'
className='h-8 gap-1 px-0 text-left text-ellipsis'
onClick={(e) => {
e.preventDefault();
navigate(`/quotes/edit/${original.id}`, { relative: "path" });
handleEditQuote(original);
}}
>
<div className=''>{renderValue()}</div>
@ -89,7 +112,7 @@ 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 } }: { row: { original: IListQuotes_Response_DTO } }) => (
<ColorBadge label={t(`quotes.status.${original.status}`)} />
),
},
@ -99,7 +122,7 @@ export const QuotesDataTable = ({
header: () => (
<div className='text-right text-ellipsis'>{t("quotes.list.columns.date")}</div>
),
cell: ({ row: { original } }) => {
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const quoteDate = UTCDateValue.create(original.date);
return (
<div className='text-right text-ellipsis'>
@ -112,13 +135,15 @@ export const QuotesDataTable = ({
id: "customer_reference" as const,
accessorKey: "customer_reference",
header: () => <>{t("quotes.list.columns.customer_reference")}</>,
cell: ({ renderValue }) => <div className='text-left text-ellipsis'>{renderValue()}</div>,
cell: ({ renderValue }: { renderValue: () => any }) => (
<div className='text-left text-ellipsis'>{renderValue()}</div>
),
},
{
id: "customer_information" as const,
accessorKey: "customer_information",
header: () => <>{t("quotes.list.columns.customer_information")}</>,
cell: ({ row: { original } }) => (
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => (
<div className='text-left text-ellipsis'>
{original.customer_information.split("\n").map((item, index) => {
return (
@ -164,7 +189,7 @@ export const QuotesDataTable = ({
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
navigate(`/quotes/edit/${row.original.id}`, { relative: "path" });
handleEditQuote(row.original);
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
@ -205,33 +230,18 @@ export const QuotesDataTable = ({
return preview ? columns : [...columns, ...actionColumn];
}, [preview]);
const handleOnPaginationChange = () => {
//setActiveRow(undefined);
};
const { table } = useDataTable({
data: data?.items ?? [],
columns: columns,
pageCount: data?.total_pages ?? -1,
onPaginationChange: handleOnPaginationChange,
});
const handleOnRowClick = (row: Row<IListQuotes_Response_DTO>) => {
setActiveRow(row);
};
useEffect(() => {
if (table && data && data?.total_pages > 0) {
setActiveRow(table.getRowModel().rows[0]);
}
}, [data, table]);
const handleFinishDownload = useCallback(() => {
toast({
description: t("quotes.downloading_dialog.toast_success"),
});
}, [toast]);
if (isError) {
return <ErrorOverlay subtitle={(error as Error).message} />;
}
@ -287,7 +297,6 @@ export const QuotesDataTable = ({
{preview && (
<div id={previewId} className='flex items-stretch '>
<QuoteResume quoteId={activeRow?.original.id} />
{/*<QuotePDFPreview quote={activeRow?.original} className='flex-1' />*/}
</div>
)}
</div>

View File

@ -1,278 +0,0 @@
import {
FormCurrencyField,
FormPercentageField,
FormQuantityField,
FormTextAreaField,
} from "@/components";
import { DataTableProvider } from "@/lib/hooks";
import { cn } from "@/lib/utils";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui";
import { useToast } from "@/ui/use-toast";
import { CurrencyData, Language, Quantity } from "@shared/contexts";
import { ColumnDef } from "@tanstack/react-table";
import { t } from "i18next";
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
import { useCallback, useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useDetailColumns } from "../../hooks";
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
import { QuoteItemsSortableDataTable } from "../QuoteItemsSortableDataTable";
import { CatalogPickerDialog } from "./CatalogPickerDialog";
export const QuoteDetailsCardEditor = ({
currency,
language,
defaultValues,
}: {
currency: CurrencyData;
language: Language;
defaultValues: Readonly<{ [x: string]: any }> | undefined;
}) => {
const { toast } = useToast();
const { control, register } = useFormContext();
const [pickerMode] = useState<"dialog" | "panel">("dialog");
const [pickerDialogOpen, setPickerDialogOpen] = useState<boolean>(false);
const { fields, ...fieldActions } = useFieldArray({
control,
name: "items",
});
const columns: ColumnDef<unknown, unknown>[] = useDetailColumns(
[
/*{
id: "row_id" as const,
header: () => (
<HashIcon aria-label='Orden de fila' className='items-center justify-center w-4 h-4' />
),
accessorFn: (_: unknown, index: number) => index + 1,
size: 5,
enableHiding: false,
enableSorting: false,
enableResizing: false,
},*/
{
id: "description" as const,
accessorKey: "description",
header: t("quotes.form_fields.items.description.label"),
cell: ({ row: { index } }) => {
return <FormTextAreaField autoSize {...register(`items.${index}.description`)} />;
},
size: 500,
},
{
id: "quantity" as const,
accessorKey: "quantity",
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.quantity.label")}</div>
),
cell: ({ row: { index } }) => {
return (
<FormQuantityField
scale={0}
className='text-right'
{...register(`items.${index}.quantity`)}
/>
);
},
},
{
id: "unit_price" as const,
accessorKey: "unit_price",
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.unit_price.label")}</div>
),
cell: ({ row: { index } }) => {
return (
<FormCurrencyField
currency={currency}
language={language}
scale={4}
className='text-right'
{...register(`items.${index}.unit_price`)}
/>
);
},
},
{
id: "subtotal_price" as const,
accessorKey: "subtotal_price",
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.subtotal_price.label")}</div>
),
cell: ({ row: { index } }) => {
return (
<FormCurrencyField
currency={currency}
language={language}
scale={4}
readOnly
className='text-right'
{...register(`items.${index}.subtotal_price`)}
/>
);
},
},
{
id: "discount" as const,
accessorKey: "discount",
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
),
cell: ({ row: { index } }) => {
return (
<FormPercentageField
scale={2}
className='text-right'
{...register(`items.${index}.discount`)}
/>
);
},
},
{
id: "total_price" as const,
accessorKey: "total_price",
header: () => (
<div className='text-right'>{t("quotes.form_fields.items.total_price.label")}</div>
),
cell: ({ row: { index } }) => {
return (
<FormCurrencyField
variant='ghost'
currency={currency}
language={language}
scale={4}
readOnly
className='font-semibold text-right'
{...register(`items.${index}.total_price`)}
/>
);
},
},
],
{
enableDragHandleColumn: false, // <--- Desactivado temporalmente
enableSelectionColumn: true,
enableActionsColumn: true,
rowActionFn: (props) => {
const { table, row } = props;
return [
{
label: t("common.duplicate_row"),
icon: <CopyIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.duplicateItems(row.index),
},
{
label: t("common.insert_row_above"),
icon: <ChevronUpIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.insertItem(row.index),
},
{
label: t("common.insert_row_below"),
icon: <ChevronDownIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.insertItem(row.index + 1),
},
{
label: "-",
},
{
label: t("common.remove_row"),
//shortcut: "⌘⌫",
icon: <Trash2Icon className='w-4 h-4 mr-2' />,
onClick: () => {
table.options.meta?.deleteItems(row.index);
},
},
];
},
}
);
const handleAppendCatalogArticle = useCallback(
(article: any) => {
fieldActions.append({
...article,
quantity: {
amount: 100,
scale: Quantity.DEFAULT_SCALE,
},
unit_price: article.retail_price,
discount: {
amount: null,
scale: 2,
},
});
toast({
title: t("quotes.catalog_picker_dialog.toast_article_added"),
description: article.description,
});
},
[fieldActions]
);
const [isCollapsed, setIsCollapsed] = useState(false);
const defaultLayout = [265, 440, 655];
const navCollapsedSize = 4;
if (pickerMode === "dialog") {
return (
<div className='relative'>
<QuoteItemsSortableDataTable
actions={{
...fieldActions,
pickCatalogArticle: () => setPickerDialogOpen(true),
}}
columns={columns}
data={fields}
defaultValues={defaultValues}
/>
<CatalogPickerDialog
onSelect={handleAppendCatalogArticle}
isOpen={pickerDialogOpen}
onOpenChange={setPickerDialogOpen}
/>
</div>
);
}
return (
<ResizablePanelGroup
direction='horizontal'
autoSaveId='uecko.quotes.details_layout'
className='items-stretch h-full'
>
<ResizablePanel
defaultSize={defaultLayout[0]}
collapsedSize={navCollapsedSize}
collapsible={true}
minSize={50}
maxSize={90}
onCollapse={() => {
setIsCollapsed(true);
}}
onExpand={() => {
setIsCollapsed(false);
}}
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
>
<QuoteItemsSortableDataTable
actions={fieldActions}
columns={columns}
data={fields}
defaultValues={defaultValues}
/>
</ResizablePanel>
<ResizableHandle withHandle className='mx-3' />
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
<DataTableProvider syncWithLocation={false}>
<CatalogPickerDataTable onSelect={handleAppendCatalogArticle} />
</DataTableProvider>
</ResizablePanel>
</ResizablePanelGroup>
);
};

View File

@ -17,7 +17,7 @@ import { useCallback, useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useDetailColumns } from "../../hooks";
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
import { QuoteItemsSortableDataTable } from "../QuoteItemsSortableDataTable";
import { QuoteItemsSortableDataTable, RowIdData } from "../QuoteItemsSortableDataTable";
import { CatalogPickerDialog } from "./CatalogPickerDialog";
export const QuoteDetailsCardEditor = ({
@ -41,7 +41,7 @@ export const QuoteDetailsCardEditor = ({
name: "items",
});
const columns: ColumnDef<unknown, unknown>[] = useDetailColumns(
const columns: ColumnDef<RowIdData, unknown>[] = useDetailColumns(
[
/*{
id: "row_id" as const,

View File

@ -9,7 +9,7 @@ export const QuoteDocumentsCardEditor = () => {
defaultUploadedFiles: [],
});*/
const { control, register, formState } = useFormContext();
const { control, formState } = useFormContext();
return (
<div className='grid gap-6 md:grid-cols-6'>
@ -21,7 +21,7 @@ export const QuoteDocumentsCardEditor = () => {
<FormField
control={control}
name='images'
render={({ field }) => (
render={() => (
<div className='space-y-6'>
<FormItem className='w-full'>
<FormLabel>Images</FormLabel>

View File

@ -6,7 +6,7 @@ import { Badge } from "@/ui";
import { useMemo } from "react";
export const useQuoteDataTableColumns = (
actions: DataTablaRowActionFunction<IListQuotes_Response_DTO, unknown>
actions: DataTablaRowActionFunction<IListQuotes_Response_DTO>
): ColumnDef<IListQuotes_Response_DTO>[] => {
const customerColumns: ColumnDef<IListQuotes_Response_DTO>[] = useMemo(
() => [
@ -59,8 +59,8 @@ export const useQuoteDataTableColumns = (
{
id: "actions",
header: "Acciones",
cell: ({ row }) => {
return <DataTableRowActions row={row} actions={actions} />;
cell: (context) => {
return <DataTableRowActions rowContext={context} actions={actions} />;
},
},
],

View File

@ -159,8 +159,7 @@ export const QuoteEdit = () => {
};
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { unsubscribe } = watch((_, { name, type }) => {
const { unsubscribe } = watch((_, { name }) => {
const quote = getValues();
if (name) {

View File

@ -4,7 +4,7 @@ import {
DataTableRowDragHandleCell,
} from "@/components";
import { Checkbox } from "@/ui";
import { ColumnDef, Row, Table } from "@tanstack/react-table";
import { ColumnDef, Row } from "@tanstack/react-table";
import { useMemo } from "react";
@ -58,8 +58,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
/>
),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
cell: ({ row, table }: { row: Row<TData>; table: Table<TData> }) => (
cell: ({ row }: { row: Row<TData> }) => (
<Checkbox
id={`select-row-${row.id}`}
checked={row.getIsSelected()}

View File

@ -1,8 +1,36 @@
import { Layout, LayoutContent, LayoutHeader } from "@/components";
import { Layout, LayoutContent, LayoutHeader, ProtectedRoute } from "@/components";
import { PropsWithChildren } from "react";
import { Outlet, useLocation } from "react-router-dom";
import { QuotesProvider } from "./QuotesContext";
export const QuotesLayout = ({ children }: PropsWithChildren) => {
const location = useLocation();
const isEditing = location.pathname.includes("/edit");
return (
<ProtectedRoute>
<QuotesProvider>
<Layout>
<LayoutHeader />
<LayoutContent>
{" "}
<div className={`layout ${isEditing ? "editing" : ""}`}>
<div className='quotes-list'>
<Outlet />
</div>
{isEditing && (
<div className='quotes-editor'>
<Outlet />
</div>
)}
</div>
</LayoutContent>
</Layout>
</QuotesProvider>
</ProtectedRoute>
);
return (
<QuotesProvider>
<Layout>

View File

@ -26,8 +26,7 @@ export type DataTableRowActionDefinition<TData> = {
onClick?: (props: CellContext<TData, unknown>, e: React.BaseSyntheticEvent) => void;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type DataTableRowActionsProps<TData, TValue = unknown> = {
export type DataTableRowActionsProps<TData, _TValue = unknown> = {
className?: string;
actions?: DataTablaRowActionFunction<TData>;
rowContext: CellContext<TData, unknown>;
@ -36,7 +35,6 @@ export type DataTableRowActionsProps<TData, TValue = unknown> = {
export function DataTableRowActions<TData = any, TValue = unknown>({
actions,
rowContext,
className,
}: DataTableRowActionsProps<TData, TValue>) {
return (
<DropdownMenu>

View File

@ -16,7 +16,7 @@ export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnPro
/>
),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
cell: ({ row, table }) => (
cell: ({ row }) => (
<Checkbox
id={`select-row-${row.id}`}
checked={row.getIsSelected()}

View File

@ -81,7 +81,11 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
const result = moneyOrError.object.toString();
return inputValue.endsWith(",") ? result.replace(/.0$/, ",") : result;
},
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
output: (
_value: string | undefined,
_name?: string,
values?: CurrencyInputOnChangeValues
) => {
const { value: amount } = values ?? { value: null };
setInputValue(amount ?? "");
@ -103,7 +107,7 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
disabled={disabled}
rules={rules}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render={({ field, fieldState, formState }) => {
render={({ field }) => {
return (
<FormItem ref={ref} className={cn(className, "space-y-3")}>
{label && (

View File

@ -57,8 +57,7 @@ export const FormDatePickerField = React.forwardRef<
control={control}
name={name}
rules={{ required }}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render={({ field, fieldState, formState }) => (
render={({ field }) => (
<FormItem ref={ref} className={cn(className, "flex flex-col")}>
{label && <FormLabel label={label} hint={hint} required={required} />}

View File

@ -79,7 +79,7 @@ export const FormPercentageField = React.forwardRef<
const result = percentageOrError.object.toString();
return inputValue.endsWith(",") ? result.replace(/.0$/, ",") : result;
},
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
output: (_value: string | undefined, _name?: string, values?: CurrencyInputOnChangeValues) => {
const { value: amount } = values ?? { value: null };
setInputValue(amount ?? "");

View File

@ -76,7 +76,11 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
const result = quantityOrError.object.toString();
return inputValue.endsWith(",") ? result.replace(/.0$/, ",") : result;
},
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
output: (
_value: string | undefined,
_name?: string,
values?: CurrencyInputOnChangeValues
) => {
const { value: amount } = values ?? { value: null };
setInputValue(amount ?? "");

View File

@ -75,7 +75,7 @@ export const FormTextAreaField = React.forwardRef<
name={name}
rules={{ required }}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render={({ field, fieldState, formState }) => (
render={({ field, fieldState }) => (
<FormItem ref={ref} className={cn(className, "flex flex-col space-y-3")}>
{label && <FormLabel label={label} hint={hint} required={required} />}
<FormControl className='grow'>

View File

@ -67,10 +67,8 @@ export const PDFViewer = ({ file, onThumbnailClick, className }: PDFViewerProps)
const goToNextPage = useCallback(() => changePage(1), [changePage]);
const goToPrevPage = useCallback(() => changePage(-1), [changePage]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const goToFirstPage = useCallback(() => setPageNumber(1), [setPageNumber]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const goToLastPage = useCallback(() => setPageNumber(numPages), [setPageNumber, numPages]);
//const goToFirstPage = useCallback(() => setPageNumber(1), [setPageNumber]);
//const goToLastPage = useCallback(() => setPageNumber(numPages), [setPageNumber, numPages]);
const isLoading = useMemo(
() => renderedPageNumber !== pageNumber,

View File

@ -1,3 +1,4 @@
import { RowIdData } from "@/app/quotes/components/QuoteItemsSortableDataTable";
import { DataTableColumnHeader } from "@/components";
import { Badge } from "@/ui";
import {
@ -47,11 +48,16 @@ import { SortableTableRow } from "./SortableTableRow";
declare module "@tanstack/react-table" {
interface TableMeta<TData extends RowData> {
insertItem: (rowIndex: number, data: TData) => void;
appendItem: (data?: TData) => void;
insertItem: (rowIndex: number, data?: unknown) => void;
appendItem: (data?: unknown) => void;
duplicateItems: (rowIndex?: number) => void;
deleteItems: (rowIndex?: number | number[]) => void;
updateItem: (rowIndex: number, rowData: TData, fieldName: string, value: unknown) => void;
updateItem: (
rowIndex: number,
rowData: TData & RowIdData,
fieldName: string,
value: unknown
) => void;
}
}
@ -125,8 +131,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [activeId, setActiveId] = useState<UniqueIdentifier | null>();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [columnVisibility, _setColumnVisibility] = useState<VisibilityState>({});
const sorteableRowIds = useMemo(() => data.map((item) => item.id), [data]);
const table = useReactTable({
@ -149,14 +154,14 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
enableHiding: true,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
getRowId: (originalRow: unknown) => originalRow?.id,
getRowId: (originalRow: any) => originalRow?.id,
debugHeaders: true,
debugColumns: true,
meta: {
insertItem: (rowIndex: number, data: object = {}) => {
insertItem: (rowIndex: number, data: unknown = {}) => {
actions.insert(rowIndex, data, { shouldFocus: true });
},
appendItem: (data: object = {}) => {
appendItem: (data: unknown = {}) => {
actions.append(data, { shouldFocus: true });
},
duplicateItems: (rowIndex?: number) => {
@ -168,7 +173,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
table.getSelectedRowModel().rows[table.getSelectedRowModel().rows.length - 1].index;
const data = table
.getSelectedRowModel()
.rows.map((row) => ({ ...row.original, id: undefined }));
.rows.map((row: any) => ({ ...row.original, id: undefined }));
if (table.getRowModel().rows.length < lastIndex + 1) {
actions.append(data);
@ -198,7 +203,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
actions.remove();
}
},
updateItem: (rowIndex: number, rowData: unknown, fieldName: string, value: unknown) => {
updateItem: (rowIndex: number, rowData: any, fieldName: string, value: unknown) => {
// Skip page index reset until after next rerender
// skipAutoResetPageIndex();
console.log({
@ -324,9 +329,9 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
items={filterItems(sorteableRowIds)}
strategy={verticalListSortingStrategy}
>
{filterItems(table.getRowModel().rows).map((row) => (
{filterItems(table.getRowModel().rows).map((row: any) => (
<SortableTableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
{row.getVisibleCells().map((cell: any) => (
<TableCell
className='p-1 align-top'
key={cell.id}

View File

@ -3,12 +3,7 @@ import { TableRow } from "@/ui/table";
import { DraggableSyntheticListeners } from "@dnd-kit/core";
import { defaultAnimateLayoutChanges, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
CSSProperties,
PropsWithChildren,
createContext,
useMemo,
} from "react";
import { CSSProperties, PropsWithChildren, createContext, useMemo } from "react";
import { SortableProps } from "./SortableDataTable";
interface Context {
@ -22,7 +17,7 @@ export const SortableTableRowContext = createContext<Context>({
ref() {},
});
function animateLayoutChanges(args) {
function animateLayoutChanges(args: any) {
if (args.isSorting || args.wasDragging) {
return defaultAnimateLayoutChanges(args);
}
@ -30,10 +25,7 @@ function animateLayoutChanges(args) {
return true;
}
export function SortableTableRow({
id,
children,
}: PropsWithChildren<SortableProps>) {
export function SortableTableRow({ id, children }: PropsWithChildren<SortableProps>) {
const {
attributes,
isDragging,
@ -58,7 +50,7 @@ export function SortableTableRow({
listeners,
ref: setActivatorNodeRef,
}),
[attributes, listeners, setActivatorNodeRef],
[attributes, listeners, setActivatorNodeRef]
);
return (
@ -66,10 +58,7 @@ export function SortableTableRow({
<TableRow
key={id}
id={String(id)}
className={cn(
isDragging ? "opacity-40" : "opacity-100",
"hover:bg-muted/30 m-0",
)}
className={cn(isDragging ? "opacity-40" : "opacity-100", "hover:bg-muted/30 m-0")}
ref={setNodeRef}
style={style}
>

View File

@ -19,7 +19,7 @@ const DEFAULT_REFETCH_INTERVAL = 2 * 60 * 1000; // 2 minutes
const DEFAULT_STALE_TIME = 60 * 1000; // 1 minute
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type UseListQueryOptions<TUseListQueryData, TUseListQueryError> = {
export type UseListQueryOptions<TUseListQueryData, _TUseListQueryError> = {
queryKey: QueryKey;
queryFn: (context: QueryFunctionContext) => Promise<TUseListQueryData>;
enabled?: boolean;
@ -51,8 +51,8 @@ export const useList = <TUseListQueryData, TUseListQueryError>({
queryKey,
queryFn,
placeholderData: keepPreviousData,
//staleTime: DEFAULT_STALE_TIME,
//refetchInterval: DEFAULT_REFETCH_INTERVAL,
staleTime: DEFAULT_STALE_TIME,
refetchInterval: DEFAULT_REFETCH_INTERVAL,
refetchOnWindowFocus: true,
enabled: enabled && !!queryFn,
select,

View File

@ -1,10 +1,6 @@
import { QueryFunction, QueryKey, UseQueryResult, useQuery } from "@tanstack/react-query";
export interface IUseManyQueryOptions<
TUseManyQueryData = unknown,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TUseManyQueryError = unknown
> {
export interface IUseManyQueryOptions<TUseManyQueryData = unknown, _TUseManyQueryError = unknown> {
queryKey: QueryKey;
queryFn: QueryFunction<TUseManyQueryData, QueryKey>;
enabled?: boolean;

View File

@ -2,8 +2,7 @@ import { useMutation } from "@tanstack/react-query";
export interface IUseRemoveMutationOptions<
TUseRemoveMutationData,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TUseRemoveMutationError,
_TUseRemoveMutationError,
TUseRemoveMutationVariables
> {
mutationFn: (variables: TUseRemoveMutationVariables) => Promise<TUseRemoveMutationData>;

View File

@ -3,11 +3,11 @@ import { useGetProfile } from "../useAuth";
export const usePreferredLanguage = () => {
const { data } = useGetProfile();
if (!data || !data.language) {
if (!data || !data.lang_code) {
return navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
}
return data.language;
return data.lang_code;
};

View File

@ -35,7 +35,7 @@ type KeySegment = string | IdType | IdsType | ParamsType;
export function arrayFindIndex<T>(array: T[], slice: T[]): number {
return array.findIndex(
(item, index) =>
(_, index) =>
index <= array.length - slice.length &&
slice.every((sliceItem, sliceIndex) => array[index + sliceIndex] === sliceItem)
);

View File

@ -1,4 +1,5 @@
import { createContext, useContext, useEffect, useState } from "react";
import secureLocalStorage from "react-secure-storage";
type Theme = "dark" | "light" | "system";
@ -27,7 +28,7 @@ export function ThemeProvider({
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
() => (secureLocalStorage.getItem(storageKey) as Theme) || defaultTheme
);
useEffect(() => {
@ -35,8 +36,7 @@ export function ThemeProvider({
root.classList.remove("light", "dark");
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
@ -50,7 +50,7 @@ export function ThemeProvider({
const value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
secureLocalStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
@ -65,8 +65,7 @@ export function ThemeProvider({
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider");
if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider");
return context;
};

View File

@ -1 +0,0 @@
export * from "./useUploadFile";

View File

@ -1,57 +0,0 @@
import * as React from "react";
import { toast } from "react-toastify";
import { type OurFileRouter } from "@/app/api/uploadthing/core";
import { getErrorMessage } from "@/lib/handle-error";
import { uploadFiles } from "@/lib/uploadthing";
export interface UploadedFile {}
interface UseUploadFileProps
extends Pick<
UploadFilesOptions<OurFileRouter, keyof OurFileRouter>,
"headers" | "onUploadBegin" | "onUploadProgress" | "skipPolling"
> {
defaultUploadedFiles?: UploadedFile[];
}
export function useUploadFile(
endpoint: keyof OurFileRouter,
{ defaultUploadedFiles = [], ...props }: UseUploadFileProps = {}
) {
const [uploadedFiles, setUploadedFiles] = React.useState<UploadedFile[]>(defaultUploadedFiles);
const [progresses, setProgresses] = React.useState<Record<string, number>>({});
const [isUploading, setIsUploading] = React.useState(false);
async function uploadThings(files: File[]) {
setIsUploading(true);
try {
const res = await uploadFiles(endpoint, {
...props,
files,
onUploadProgress: ({ file, progress }) => {
setProgresses((prev) => {
return {
...prev,
[file]: progress,
};
});
},
});
setUploadedFiles((prev) => (prev ? [...prev, ...res] : res));
} catch (err) {
toast.error(getErrorMessage(err));
} finally {
setProgresses({});
setIsUploading(false);
}
}
return {
uploadedFiles,
progresses,
uploadFiles: uploadThings,
isUploading,
};
}

View File

@ -1,69 +0,0 @@
import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { Dot } from "lucide-react"
import { cn } from "@/lib/utils"
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
))
InputOTP.displayName = "InputOTP"
const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} />
))
InputOTPGroup.displayName = "InputOTPGroup"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
return (
<div
ref={ref}
className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-2 ring-ring ring-offset-background",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
})
InputOTPSlot.displayName = "InputOTPSlot"
const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
))
InputOTPSeparator.displayName = "InputOTPSeparator"
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

@ -51,7 +51,7 @@ export class RuleValidator {
}
public static validateFnc(ruleFnc: (value: any) => any) {
return (value: any, helpers) => {
return (value: any, helpers: any) => {
const result = ruleFnc(value);
return result.isSuccess
? value

View File

@ -4,18 +4,10 @@ import { UndefinedOr } from "../../../../../utilities";
import { RuleValidator } from "../../RuleValidator";
import { DomainError, handleDomainError } from "../../errors";
import { Result } from "../Result";
import {
IStringValueObjectOptions,
StringValueObject,
} from "../StringValueObject";
import { IStringValueObjectOptions, StringValueObject } from "../StringValueObject";
export class PostalCode extends StringValueObject {
private static readonly LENGTH = 5;
protected static validate(
value: UndefinedOr<string>,
options: IStringValueObjectOptions
) {
protected static validate(value: UndefinedOr<string>, options: IStringValueObjectOptions) {
const rule = Joi.string()
.allow(null)
.allow("")
@ -26,10 +18,7 @@ export class PostalCode extends StringValueObject {
return RuleValidator.validate<string>(rule, value);
}
public static create(
value: UndefinedOr<string>,
options: IStringValueObjectOptions = {}
) {
public static create(value: UndefinedOr<string>, options: IStringValueObjectOptions = {}) {
const _options = {
label: "postal_code",
...options,
@ -39,11 +28,7 @@ export class PostalCode extends StringValueObject {
if (validationResult.isFailure) {
return Result.fail(
handleDomainError(
DomainError.INVALID_INPUT_DATA,
validationResult.error.message,
_options
)
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
);
}
return Result.ok(new PostalCode(validationResult.object));

View File

@ -56,7 +56,7 @@ export abstract class Entity<T extends IEntityProps> {
}
protected _flattenProps(props: T): { [s: string]: any } {
return Object.entries(props).reduce((result, [key, valueObject]) => {
return Object.entries(props).reduce((result: any, [key, valueObject]) => {
console.log(key, valueObject.value);
result[key] = valueObject.value;

View File

@ -1,6 +1,4 @@
/* eslint-disable no-use-before-define */
import DineroFactory, { Currency, Dinero } from "dinero.js";
import Joi from "joi";
import { isNull } from "lodash";
import { NullOr } from "../../../../utilities";
@ -25,7 +23,7 @@ export interface MoneyValueObject {
currency_code: string;
}
type RoundingMode =
export type RoundingMode =
| "HALF_ODD"
| "HALF_EVEN"
| "HALF_UP"
@ -34,8 +32,6 @@ type RoundingMode =
| "HALF_AWAY_FROM_ZERO"
| "DOWN";
export { RoundingMode };
export interface IMoneyValueProps {
amount: NullOr<number | string>;
currencyCode?: string;
@ -92,6 +88,7 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
public static readonly DEFAULT_CURRENCY_CODE = defaultMoneyValueProps.currencyCode;
private readonly _isNull: boolean;
private readonly _options: IMoneyValueOptions;
protected static validate(amount: NullOr<number | string>, options: IMoneyValueOptions) {
@ -250,12 +247,16 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
.object;
}
private static _toString(value: NullOr<number>, scale: number): string {
private static _toString(
value: NullOr<number>,
scale: number,
locales?: Intl.LocalesArgument
): string {
if (value === null) {
return "";
}
new Intl.NumberFormat("es", {
new Intl.NumberFormat(locales, {
/*minimumSignificantDigits: scale,
maximumSignificantDigits: scale,
minimumFractionDigits: scale,*/
@ -278,7 +279,11 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
};
public toString(): string {
return MoneyValue._toString(this.isNull() ? null : this.getAmount(), this.getScale());
return MoneyValue._toString(
this.isNull() ? null : this.getAmount(),
this.getScale(),
this._options.locale
);
}
public toJSON() {

View File

@ -5,16 +5,10 @@ import { UndefinedOr } from "../../../../utilities";
import { RuleValidator } from "../RuleValidator";
import { DomainError, handleDomainError } from "../errors";
import { Result } from "./Result";
import {
IStringValueObjectOptions,
StringValueObject,
} from "./StringValueObject";
import { IStringValueObjectOptions, StringValueObject } from "./StringValueObject";
export class Phone extends StringValueObject {
protected static validate(
value: UndefinedOr<string>,
options: IStringValueObjectOptions
) {
protected static validate(value: UndefinedOr<string>, options: IStringValueObjectOptions) {
const rule = Joi.string() //.extend(JoiPhoneNumber)
.allow(null)
.allow("")
@ -25,10 +19,7 @@ export class Phone extends StringValueObject {
return RuleValidator.validate<string>(rule, value);
}
public static create(
value: UndefinedOr<string>,
options: IStringValueObjectOptions = {}
) {
public static create(value: UndefinedOr<string>, options: IStringValueObjectOptions = {}) {
const _options = {
label: "phone",
...options,
@ -38,26 +29,21 @@ export class Phone extends StringValueObject {
if (validationResult.isFailure) {
return Result.fail(
handleDomainError(
DomainError.INVALID_INPUT_DATA,
validationResult.error.message,
_options
)
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
);
}
return Result.ok(new Phone(validationResult.object));
}
public format(countryCode: string) {
public format(/*countryCode: string*/) {
const rule = Joi /*.extend(JoiPhoneNumber)*/.string();
/*.phoneNumber({
defaultCountry: countryCode,
format: "international",
})*/ const validationResult = RuleValidator.validate(rule, this.value);
})*/
const validationResult = RuleValidator.validate(rule, this.value);
return validationResult.isSuccess
? validationResult.object
: validationResult.error.message;
return validationResult.isSuccess ? validationResult.object : validationResult.error.message;
}
}

View File

@ -1,4 +1,4 @@
export const Currencies = {
export const Currencies: any = {
USD: {
symbol: "$",
name: "US Dollar",

View File

@ -1,6 +1,6 @@
// https://github.com/meikidd/iso-639-1/
const LANGUAGES_LIST = {
const LANGUAGES_LIST: any = {
aa: {
name: "Afar",
nativeName: "Afaraf",

View File

@ -11,8 +11,9 @@
},
"dependencies": {
"dinero.js": "^1.9.1",
"joi": "^17.12.3",
"joi": "^17.13.3",
"joi-phone-number": "^5.1.1",
"lodash": "^4.17.21",
"shallow-equal-object": "^1.1.1",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2",