This commit is contained in:
David Arranz 2024-08-29 16:29:10 +02:00
parent 3cfe438f1b
commit 65ea9f212c
8 changed files with 55 additions and 142 deletions

View File

@ -1,21 +1,23 @@
import { Button, ButtonProps } from "@/ui";
import { t } from "i18next";
import { PlusCircleIcon } from "lucide-react";
import { forwardRef } from "react";
export interface AppendEmptyRowButtonProps extends ButtonProps {
label?: string;
className?: string;
}
export const AppendEmptyRowButton = ({
label = t("common.append_empty_row"),
className,
...props
}: AppendEmptyRowButtonProps): JSX.Element => (
<Button type='button' variant='outline' {...props}>
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
{label && <>{label}</>}
</Button>
export const AppendEmptyRowButton = forwardRef(
(
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
ref
): JSX.Element => (
<Button type='button' variant='outline' {...props}>
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
{label && <>{label}</>}
</Button>
)
);
AppendEmptyRowButton.displayName = "AddNewRowButton";

View File

@ -1,35 +1,19 @@
import { PDFViewer } from "@/components";
import { cn } from "@/lib/utils";
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Skeleton,
} from "@/ui";
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 { DownloadIcon, MoreVerticalIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { useQuotes } from "../hooks";
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
const QuotePDFPreview = ({
quote,
className,
}: {
quote?: IListQuotes_Response_DTO;
className: string;
className?: string;
}) => {
const navigate = useNavigate();
const { toast } = useToast();
@ -99,84 +83,7 @@ const QuotePDFPreview = ({
}
return (
<>
<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} />
</>
<PDFViewer file={URLReport} className='object-contain' onThumbnailClick={handleDownload} />
);
};

View File

@ -28,6 +28,7 @@ import { useNavigate } from "react-router-dom";
import { useQuotes } from "../hooks";
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
import { QuoteStatusEditor } from "./editors";
import { QuotePDFPreview } from "./QuotePDFPreview";
type QuoteResumeProps = {
quoteId?: string;
@ -43,7 +44,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
const { mutate: setStatusMutation } = useSetStatus(quoteId);
const { download, ...downloadProps } = useDownloader();
const { formatCurrency } = useCustomLocalization({
const { formatCurrency, formatNumber } = useCustomLocalization({
locale: data?.lang_code || "ES",
});
@ -58,15 +59,21 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
return data
? {
subtotal_price: formatCurrency(data.subtotal_price),
discount: formatNumber(data.discount),
discount_price: formatCurrency(data.discount_price),
tax: formatNumber(data.tax),
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",
subtotal_price: "0,00 €",
discount: "0",
discount_price: "0,00 €",
tax: "0",
tax_price: "0,00 €",
total_price: "0,00 €",
};
}, [data]);
@ -228,43 +235,32 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>
{t("quotes.form_fields.discount.label")}
{t("quotes.form_fields.discount_value.label", { value: totals.discount })}
</span>
<span>$5.00</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>
<span>{totals.discount_price}</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>
{t("quotes.form_fields.tax.label")}
{t("quotes.form_fields.tax_value.label", { value: totals.tax })}
</span>
<span>$5.00</span>
<span>{totals.tax_price}</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'>
{t("quotes.form_fields.total_price.label")}
</span>
<span>$329.00</span>
<span>{totals.total_price}</span>
</li>
</ul>
</div>
</TabsContent>
<TabsContent value='preview'></TabsContent>
<TabsContent value='preview'>
<QuotePDFPreview quote={data} />
</TabsContent>
</CardContent>
<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>
</Card>
</Tabs>

View File

@ -15,14 +15,13 @@ export const QuoteGeneralCardEditor = () => {
>
<div className='grid grid-cols-6 gap-6'>
<FormTextField
required
className='col-span-2'
label={t("quotes.form_fields.customer_reference.label")}
description={t("quotes.form_fields.customer_reference.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.customer_reference.placeholder")}
{...register("customer_reference", {
required: false,
})}
{...register("customer_reference")}
/>
<FormDatePickerField

View File

@ -10,6 +10,7 @@ import { calculateQuoteItemTotals, calculateQuoteTotals } from "@/lib/calc";
import { useUnsavedChangesNotifier } from "@/lib/hooks";
import { useUrlId } from "@/lib/hooks/useUrlId";
import { Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
import { useToast } from "@/ui/use-toast";
import {
CurrencyData,
IGetQuote_QuoteItem_Response_DTO,
@ -20,7 +21,6 @@ import { t } from "i18next";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import { QuotePricesResume } from "./components";
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
import { useQuotes } from "./hooks";
@ -32,6 +32,7 @@ export type QuoteDataFormItem = IGetQuote_QuoteItem_Response_DTO;
export const QuoteEdit = () => {
const navigate = useNavigate();
const quoteId = useUrlId();
const { toast } = useToast();
const [activeTab, setActiveTab] = useState("general");
@ -142,14 +143,13 @@ export const QuoteEdit = () => {
mutate(data, {
onError: (error) => {
console.debug(error);
toast.error(error.message);
toast({ description: error.message });
//alert(error.message);
},
//onSettled: () => {},
onSuccess: () => {
console.log("onsuccess 2");
reset(getValues());
toast.success("Cotización guardada");
toast({ description: "Cotización guardada" });
if (shouldRedirect) {
navigate("/quotes");
}
@ -161,7 +161,6 @@ export const QuoteEdit = () => {
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { unsubscribe } = watch((_, { name, type }) => {
console.log("useEffect");
const quote = getValues();
if (name) {

View File

@ -1,5 +1,5 @@
import { cn } from "@/lib/utils";
import { Button, Pagination, PaginationContent, PaginationItem } from "@/ui";
import { Button } from "@/ui";
import { useResizeObserver } from "@wojtekmaj/react-hooks";
import { t } from "i18next";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
@ -132,7 +132,7 @@ export const PDFViewer = ({ file, onThumbnailClick, className }: PDFViewerProps)
/>
</Document>
<Pagination className='w-auto ml-auto mr-0'>
{/* <Pagination className='w-auto ml-auto mr-0'>
<PaginationContent>
<PaginationItem>
<Button size='icon' variant='outline' className='w-6 h-6'>
@ -147,7 +147,7 @@ export const PDFViewer = ({ file, onThumbnailClick, className }: PDFViewerProps)
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
</Pagination>*/}
<div className='flex flex-row justify-center w-full mt-4 space-x-4'>
<Button

View File

@ -35,7 +35,7 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
useGrouping: true,
maximumFractionDigits: scale,
}).format(amount === null ? 0 : amount);
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));
},
[locale]
);

View File

@ -309,6 +309,11 @@
"placeholder": "",
"desc": "Percentage discount"
},
"discount_value": {
"label": "Discount ({{value}}%)",
"placeholder": "",
"desc": "Percentage discount"
},
"discount_price": {
"label": "Discount price",
"placeholder": "",
@ -324,6 +329,11 @@
"placeholder": "",
"desc": "Percentage Tax"
},
"tax_value": {
"label": "Tax ({{value}}%)",
"placeholder": "",
"desc": "Percentage Tax"
},
"tax_price": {
"label": "Tax price",
"placeholder": "",