.
This commit is contained in:
parent
3cfe438f1b
commit
65ea9f212c
@ -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";
|
||||
|
||||
@ -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} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
);
|
||||
|
||||
@ -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": "",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user