.
This commit is contained in:
parent
3cfe438f1b
commit
65ea9f212c
@ -1,21 +1,23 @@
|
|||||||
import { Button, ButtonProps } from "@/ui";
|
import { Button, ButtonProps } from "@/ui";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { PlusCircleIcon } from "lucide-react";
|
import { PlusCircleIcon } from "lucide-react";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
export interface AppendEmptyRowButtonProps extends ButtonProps {
|
export interface AppendEmptyRowButtonProps extends ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppendEmptyRowButton = ({
|
export const AppendEmptyRowButton = forwardRef(
|
||||||
label = t("common.append_empty_row"),
|
(
|
||||||
className,
|
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
|
||||||
...props
|
ref
|
||||||
}: AppendEmptyRowButtonProps): JSX.Element => (
|
): JSX.Element => (
|
||||||
<Button type='button' variant='outline' {...props}>
|
<Button type='button' variant='outline' {...props}>
|
||||||
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||||
{label && <>{label}</>}
|
{label && <>{label}</>}
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
AppendEmptyRowButton.displayName = "AddNewRowButton";
|
AppendEmptyRowButton.displayName = "AddNewRowButton";
|
||||||
|
|||||||
@ -1,35 +1,19 @@
|
|||||||
import { PDFViewer } from "@/components";
|
import { PDFViewer } from "@/components";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import { Card, CardContent, CardHeader, Skeleton } from "@/ui";
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Skeleton,
|
|
||||||
} from "@/ui";
|
|
||||||
import { useToast } from "@/ui/use-toast";
|
import { useToast } from "@/ui/use-toast";
|
||||||
import { IListQuotes_Response_DTO } from "@shared/contexts";
|
import { IListQuotes_Response_DTO } from "@shared/contexts";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { DownloadIcon, MoreVerticalIcon } from "lucide-react";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Trans } from "react-i18next";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
|
||||||
|
|
||||||
const QuotePDFPreview = ({
|
const QuotePDFPreview = ({
|
||||||
quote,
|
quote,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
quote?: IListQuotes_Response_DTO;
|
quote?: IListQuotes_Response_DTO;
|
||||||
className: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -99,84 +83,7 @@ const QuotePDFPreview = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PDFViewer file={URLReport} className='object-contain' onThumbnailClick={handleDownload} />
|
||||||
<Card className={cn("overflow-hidden", className)}>
|
|
||||||
<CardHeader className='bg-muted/50'>
|
|
||||||
<CardTitle className='flex items-center gap-2 text-lg group'>
|
|
||||||
{t("quotes.list.preview.quote")}
|
|
||||||
|
|
||||||
<div className='flex items-center gap-1 ml-auto'>
|
|
||||||
{/*<Button
|
|
||||||
size='sm'
|
|
||||||
variant='outline'
|
|
||||||
className='h-8 gap-1'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
printJS({
|
|
||||||
printable: reportData?.original,
|
|
||||||
type: "pdf",
|
|
||||||
showModal: false,
|
|
||||||
modalMessage: "Cargando...",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PrinterIcon className='h-3.5 w-3.5' />
|
|
||||||
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
|
|
||||||
{t("common.print")}
|
|
||||||
</span>
|
|
||||||
</Button>*/}
|
|
||||||
<Button size='sm' variant='outline' className='h-8 gap-1' onClick={handleDownload}>
|
|
||||||
<DownloadIcon className='h-3.5 w-3.5' />
|
|
||||||
<span className='xl:sr-only 2xl:not-sr-only 2xl:whitespace-nowrap'>
|
|
||||||
{t("quotes.list.preview.download_quote")}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size='icon' variant='outline' className='w-8 h-8'>
|
|
||||||
<MoreVerticalIcon className='h-3.5 w-3.5' />
|
|
||||||
<span className='sr-only'>
|
|
||||||
<Trans i18nKey={"common.more"} />
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align='end'>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
navigate(`/quotes/edit/${quote.id}`, { relative: "path" });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trans i18nKey={"common.edit"} />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<Trans i18nKey={"common.duplicate"} />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<Trans i18nKey={"common.archive"} />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</CardTitle>
|
|
||||||
{false && (
|
|
||||||
<CardDescription className='grid grid-cols-1 gap-2 space-y-2'>
|
|
||||||
{quote?.reference}
|
|
||||||
{quote?.date.toString()}
|
|
||||||
</CardDescription>
|
|
||||||
)}
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='py-4'>
|
|
||||||
<PDFViewer
|
|
||||||
file={URLReport}
|
|
||||||
className='object-contain'
|
|
||||||
onThumbnailClick={handleDownload}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<DownloadQuoteDialog {...downloadProps} onFinishDownload={handleFinishDownload} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
||||||
import { QuoteStatusEditor } from "./editors";
|
import { QuoteStatusEditor } from "./editors";
|
||||||
|
import { QuotePDFPreview } from "./QuotePDFPreview";
|
||||||
|
|
||||||
type QuoteResumeProps = {
|
type QuoteResumeProps = {
|
||||||
quoteId?: string;
|
quoteId?: string;
|
||||||
@ -43,7 +44,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
||||||
const { download, ...downloadProps } = useDownloader();
|
const { download, ...downloadProps } = useDownloader();
|
||||||
|
|
||||||
const { formatCurrency } = useCustomLocalization({
|
const { formatCurrency, formatNumber } = useCustomLocalization({
|
||||||
locale: data?.lang_code || "ES",
|
locale: data?.lang_code || "ES",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,15 +59,21 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
return data
|
return data
|
||||||
? {
|
? {
|
||||||
subtotal_price: formatCurrency(data.subtotal_price),
|
subtotal_price: formatCurrency(data.subtotal_price),
|
||||||
|
|
||||||
|
discount: formatNumber(data.discount),
|
||||||
discount_price: formatCurrency(data.discount_price),
|
discount_price: formatCurrency(data.discount_price),
|
||||||
|
|
||||||
|
tax: formatNumber(data.tax),
|
||||||
tax_price: formatCurrency(data.tax_price),
|
tax_price: formatCurrency(data.tax_price),
|
||||||
total_price: formatCurrency(data.total_price),
|
total_price: formatCurrency(data.total_price),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
subtotal_price: "0,00",
|
subtotal_price: "0,00 €",
|
||||||
discount_price: "0,00",
|
discount: "0",
|
||||||
tax_price: "0,00",
|
discount_price: "0,00 €",
|
||||||
total_price: "0,00",
|
tax: "0",
|
||||||
|
tax_price: "0,00 €",
|
||||||
|
total_price: "0,00 €",
|
||||||
};
|
};
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
@ -228,43 +235,32 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
</li>
|
</li>
|
||||||
<li className='flex items-center justify-between'>
|
<li className='flex items-center justify-between'>
|
||||||
<span className='text-muted-foreground'>
|
<span className='text-muted-foreground'>
|
||||||
{t("quotes.form_fields.discount.label")}
|
{t("quotes.form_fields.discount_value.label", { value: totals.discount })}
|
||||||
</span>
|
</span>
|
||||||
<span>$5.00</span>
|
<span>{totals.discount_price}</span>
|
||||||
</li>
|
|
||||||
<li className='flex items-center justify-between'>
|
|
||||||
<span className='text-muted-foreground'>
|
|
||||||
{t("quotes.form_fields.discount_price.label")}
|
|
||||||
</span>
|
|
||||||
<span>$25.00</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className='flex items-center justify-between'>
|
<li className='flex items-center justify-between'>
|
||||||
<span className='text-muted-foreground'>
|
<span className='text-muted-foreground'>
|
||||||
{t("quotes.form_fields.tax.label")}
|
{t("quotes.form_fields.tax_value.label", { value: totals.tax })}
|
||||||
</span>
|
</span>
|
||||||
<span>$5.00</span>
|
<span>{totals.tax_price}</span>
|
||||||
</li>
|
</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'>
|
<li className='flex items-center justify-between font-semibold'>
|
||||||
<span className='text-muted-foreground'>
|
<span className='text-muted-foreground'>
|
||||||
{t("quotes.form_fields.total_price.label")}
|
{t("quotes.form_fields.total_price.label")}
|
||||||
</span>
|
</span>
|
||||||
<span>$329.00</span>
|
<span>{totals.total_price}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='preview'></TabsContent>
|
<TabsContent value='preview'>
|
||||||
|
<QuotePDFPreview quote={data} />
|
||||||
|
</TabsContent>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className='flex flex-row items-center px-6 py-3 border-t bg-accent'>
|
<CardFooter className='flex flex-row items-center px-6 py-3 border-t bg-accent'>
|
||||||
<div className='text-xs text-muted-foreground'>Updated...</div>
|
<div className='text-xs text-muted-foreground'></div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@ -15,14 +15,13 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
>
|
>
|
||||||
<div className='grid grid-cols-6 gap-6'>
|
<div className='grid grid-cols-6 gap-6'>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
|
required
|
||||||
className='col-span-2'
|
className='col-span-2'
|
||||||
label={t("quotes.form_fields.customer_reference.label")}
|
label={t("quotes.form_fields.customer_reference.label")}
|
||||||
description={t("quotes.form_fields.customer_reference.desc")}
|
description={t("quotes.form_fields.customer_reference.desc")}
|
||||||
disabled={formState.disabled}
|
disabled={formState.disabled}
|
||||||
placeholder={t("quotes.form_fields.customer_reference.placeholder")}
|
placeholder={t("quotes.form_fields.customer_reference.placeholder")}
|
||||||
{...register("customer_reference", {
|
{...register("customer_reference")}
|
||||||
required: false,
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormDatePickerField
|
<FormDatePickerField
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { calculateQuoteItemTotals, calculateQuoteTotals } from "@/lib/calc";
|
|||||||
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
||||||
import { useUrlId } from "@/lib/hooks/useUrlId";
|
import { useUrlId } from "@/lib/hooks/useUrlId";
|
||||||
import { Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
import { Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
||||||
|
import { useToast } from "@/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
CurrencyData,
|
CurrencyData,
|
||||||
IGetQuote_QuoteItem_Response_DTO,
|
IGetQuote_QuoteItem_Response_DTO,
|
||||||
@ -20,7 +21,6 @@ import { t } from "i18next";
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
|
||||||
import { QuotePricesResume } from "./components";
|
import { QuotePricesResume } from "./components";
|
||||||
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
||||||
import { useQuotes } from "./hooks";
|
import { useQuotes } from "./hooks";
|
||||||
@ -32,6 +32,7 @@ export type QuoteDataFormItem = IGetQuote_QuoteItem_Response_DTO;
|
|||||||
export const QuoteEdit = () => {
|
export const QuoteEdit = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const quoteId = useUrlId();
|
const quoteId = useUrlId();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState("general");
|
const [activeTab, setActiveTab] = useState("general");
|
||||||
|
|
||||||
@ -142,14 +143,13 @@ export const QuoteEdit = () => {
|
|||||||
mutate(data, {
|
mutate(data, {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.debug(error);
|
console.debug(error);
|
||||||
toast.error(error.message);
|
toast({ description: error.message });
|
||||||
//alert(error.message);
|
//alert(error.message);
|
||||||
},
|
},
|
||||||
//onSettled: () => {},
|
//onSettled: () => {},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
console.log("onsuccess 2");
|
|
||||||
reset(getValues());
|
reset(getValues());
|
||||||
toast.success("Cotización guardada");
|
toast({ description: "Cotización guardada" });
|
||||||
if (shouldRedirect) {
|
if (shouldRedirect) {
|
||||||
navigate("/quotes");
|
navigate("/quotes");
|
||||||
}
|
}
|
||||||
@ -161,7 +161,6 @@ export const QuoteEdit = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { unsubscribe } = watch((_, { name, type }) => {
|
const { unsubscribe } = watch((_, { name, type }) => {
|
||||||
console.log("useEffect");
|
|
||||||
const quote = getValues();
|
const quote = getValues();
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button, Pagination, PaginationContent, PaginationItem } from "@/ui";
|
import { Button } from "@/ui";
|
||||||
import { useResizeObserver } from "@wojtekmaj/react-hooks";
|
import { useResizeObserver } from "@wojtekmaj/react-hooks";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||||
@ -132,7 +132,7 @@ export const PDFViewer = ({ file, onThumbnailClick, className }: PDFViewerProps)
|
|||||||
/>
|
/>
|
||||||
</Document>
|
</Document>
|
||||||
|
|
||||||
<Pagination className='w-auto ml-auto mr-0'>
|
{/* <Pagination className='w-auto ml-auto mr-0'>
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<Button size='icon' variant='outline' className='w-6 h-6'>
|
<Button size='icon' variant='outline' className='w-6 h-6'>
|
||||||
@ -147,7 +147,7 @@ export const PDFViewer = ({ file, onThumbnailClick, className }: PDFViewerProps)
|
|||||||
</Button>
|
</Button>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
</Pagination>
|
</Pagination>*/}
|
||||||
|
|
||||||
<div className='flex flex-row justify-center w-full mt-4 space-x-4'>
|
<div className='flex flex-row justify-center w-full mt-4 space-x-4'>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
|
|||||||
useGrouping: true,
|
useGrouping: true,
|
||||||
|
|
||||||
maximumFractionDigits: scale,
|
maximumFractionDigits: scale,
|
||||||
}).format(amount === null ? 0 : amount);
|
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));
|
||||||
},
|
},
|
||||||
[locale]
|
[locale]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -309,6 +309,11 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Percentage discount"
|
"desc": "Percentage discount"
|
||||||
},
|
},
|
||||||
|
"discount_value": {
|
||||||
|
"label": "Discount ({{value}}%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage discount"
|
||||||
|
},
|
||||||
"discount_price": {
|
"discount_price": {
|
||||||
"label": "Discount price",
|
"label": "Discount price",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
@ -324,6 +329,11 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Percentage Tax"
|
"desc": "Percentage Tax"
|
||||||
},
|
},
|
||||||
|
"tax_value": {
|
||||||
|
"label": "Tax ({{value}}%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage Tax"
|
||||||
|
},
|
||||||
"tax_price": {
|
"tax_price": {
|
||||||
"label": "Tax price",
|
"label": "Tax price",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user