This commit is contained in:
David Arranz 2024-11-14 17:46:31 +01:00
parent 2edc6f4ecd
commit 7eae1295ca
4 changed files with 252 additions and 158 deletions

View File

@ -1,6 +1,6 @@
import { DownloadIcon, FilePenLineIcon } from "lucide-react";
import { ArchiveIcon, CopyIcon, DownloadIcon, EditIcon } from "lucide-react";
import { QuoteStatusBadge } from "@/components";
import { ButtonGroup } from "@/components";
import { useCustomLocalization } from "@/lib/hooks";
import { cn } from "@/lib/utils";
import {
@ -38,10 +38,12 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
const navigate = useNavigate();
const { toast } = useToast();
const { useOne, useSetStatus, useSentTo, useDownloader, getQuotePDFFilename } = useQuotes();
const { useOne, useSetStatus, useSentTo, useDownloader, useDuplicate, getQuotePDFFilename } =
useQuotes();
const { data, status } = useOne(quoteId);
const { mutate: setStatusMutation } = useSetStatus();
const { mutate: sentToMutation } = useSentTo(quoteId);
const { mutate: duplicate } = useDuplicate();
const { download, ...downloadProps } = useDownloader();
const { formatCurrency, formatNumber } = useCustomLocalization({
@ -78,7 +80,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
}, [data]);
const allowToSent = useMemo(() => data?.status === "accepted" && !data?.date_sent, [data]);
const isSent = useMemo(() => data?.status === "accepted" && data?.date_sent, [data]);
const isSent = useMemo(() => data?.status === "accepted" && !!data?.date_sent, [data]);
const handleOnChangeStatus = (newStatus: string) => {
setStatusMutation(
@ -88,6 +90,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
onSuccess: () => {
toast({
description: t("quotes.quote_status_editor.toast_status_changed"),
variant: "success",
});
},
}
@ -101,6 +104,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
onSuccess: () => {
toast({
description: t("quotes.quote_sent_to_editor.toast_status_changed"),
variant: "success",
});
},
}
@ -110,6 +114,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
const handleFinishDownload = useCallback(() => {
toast({
description: t("quotes.downloading_dialog.toast_success"),
variant: "success",
});
}, [toast]);
@ -135,6 +140,41 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
);
}
const handleDuplicateQuote = (id: string) => {
duplicate(
{
id,
},
{
onSuccess(data) {
toast({
description: t("quotes.duplicate_action.toast_success"),
variant: "success",
});
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
},
}
);
};
const handleArchiveQuote = (id: string) => {
setStatusMutation(
{ id, newStatus: "archived" },
{
onSuccess: () => {
toast({
description: t("quotes.quote_status_editor.toast_status_changed", {
newStatus: t("quotes.status.archived"),
}),
variant: "success",
});
},
}
);
};
return (
<>
<DownloadQuoteDialog {...downloadProps} onFinishDownload={handleFinishDownload} />
@ -143,74 +183,94 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
<CardHeader className='gap-3 border-b bg-accent'>
<CardTitle className='flex items-center justify-between text-lg'>
<span>{t("quotes.list.resume.title")}</span>
<QuoteStatusBadge className='text-sm' status={data.status} />
<QuoteStatusEditor
status={data.status}
onChangeStatus={handleOnChangeStatus}
type='badge'
/>
</CardTitle>
<div className='flex mr-auto text-foreground'>
<div className='flex items-center gap-1'>
{allowToSent && !isSent && (
<>
<QuoteSentToEditor quote={data} onSentTo={handleOnSentTo} />
<QuoteStatusEditor status={data.status} onChangeStatus={handleOnChangeStatus} />
</>
)}
<div className='flex w-full mr-auto text-foreground'>
<div className='flex justify-between w-full'>
<ButtonGroup className='gap-0'>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='icon'
disabled={isSent}
onClick={(e) => {
e.preventDefault();
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
}}
>
<EditIcon className='w-4 h-4' />
<span className='sr-only'>Editar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
{!isSent && (
<>
<Button
size='sm'
variant='default'
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
{t("quotes.list.columns.actions.edit")}
</span>
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='icon'
onClick={(e) => {
e.preventDefault();
handleDuplicateQuote(data.id);
}}
>
<CopyIcon className='w-4 h-4' />
<span className='sr-only'>Duplicar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Duplicar</p>
</TooltipContent>
</Tooltip>
<QuoteStatusEditor status={data.status} onChangeStatus={handleOnChangeStatus} />
</>
)}
<Tooltip>
<TooltipTrigger asChild>
<Button variant='outline' size='icon' onClick={handleDownload}>
<DownloadIcon className='w-4 h-4' />
<span className='sr-only'>Descargar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Descargar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
size='sm'
variant='outline'
className='h-8 gap-1'
onClick={handleDownload}
>
<DownloadIcon className='h-3.5 w-3.5 ' />
<span className={isSent ? "" : "sr-only"}>
{t("quotes.list.resume.download_quote")}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("quotes.list.resume.download_quote")}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='icon'
disabled={isSent}
onClick={(e) => {
e.preventDefault();
handleArchiveQuote(data.id);
}}
>
<ArchiveIcon className='w-4 h-4' />
<span className='sr-only'>Archivar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Archivar</p>
</TooltipContent>
</Tooltip>
</ButtonGroup>
{/*<DropdownMenu>
<DropdownMenuTrigger>
<Tooltip>
<TooltipTrigger asChild>
<Button size='icon' variant='outline' className='w-8 h-8'>
<MoreVertical className='h-3.5 w-3.5' />
<span className='sr-only'>{t("common.more")}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{t("common.more")}</TooltipContent>
</Tooltip>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem className='not-sr-only 2xl:sr-only'>
<DownloadIcon className='h-3.5 w-3.5 mr-2' />
<span>{t("quotes.list.preview.download_quote")}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>*/}
<ButtonGroup>
<QuoteSentToEditor
disabled={isSent || !allowToSent}
id={data.id}
onSentTo={handleOnSentTo}
/>
</ButtonGroup>
</div>
</div>
</CardHeader>

View File

@ -19,7 +19,7 @@ import { useNavigate } from "react-router-dom";
import { useQuotes } from "../hooks";
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
import { QuoteResume } from "./QuoteResume";
import { QuoteStatusEditor } from "./editors";
import { QuoteSentToEditor, QuoteStatusEditor } from "./editors";
export const QuotesDataTable = ({
status = "all",
@ -89,6 +89,23 @@ export const QuotesDataTable = ({
);
};
const handleArchiveQuote = (id: string) => {
setStatusMutation(
{ id, newStatus: "archived" },
{
onSuccess: () => {
toast({
description: t("quotes.quote_status_editor.toast_status_changed", {
newStatus: t("quotes.status.archived"),
}),
variant: "success",
});
},
}
);
};
const handleOnChangeStatus = (id: string, newStatus: string) => {
setStatusMutation(
{ id, newStatus },
@ -148,9 +165,7 @@ export const QuotesDataTable = ({
<QuoteStatusEditor
type='badge'
status={original.status}
onChangeStatus={(newStatus: string, oldStatus: string) =>
handleOnChangeStatus(original.id, newStatus)
}
onChangeStatus={(newStatus: string) => handleOnChangeStatus(original.id, newStatus)}
/>
),
},
@ -162,12 +177,17 @@ export const QuotesDataTable = ({
),
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const quoteDate = UTCDateValue.create(original.date_sent);
const isSuccess = quoteDate.isSuccess && !quoteDate.object.isEmpty();
return (
<div className='text-left text-ellipsis'>
{quoteDate.isSuccess ? (
<ColorBadge label={quoteDate.object.toLocaleDateString("es-ES")} />
{isSuccess ? (
<ColorBadge
label={quoteDate.object.toLocaleDateString("es-ES")}
className='text-green-800 bg-green-100 hover:text-green-800 hover:bg-green-100'
/>
) : (
<>-</>
<></>
)}
</div>
);
@ -217,7 +237,7 @@ export const QuotesDataTable = ({
})}
</div>
),
size: 600,
size: 500,
},
/*{
@ -240,86 +260,96 @@ export const QuotesDataTable = ({
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const allowToSent = original?.status === "accepted" && !original?.date_sent;
const isSent = original?.status === "accepted" && !!original?.date_sent;
const isArchived = original?.status === "archived";
return (
<ButtonGroup className='gap-0'>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
disabled={isSent}
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<EditIcon className='w-4 h-4' />
<span className='sr-only'>Editar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
<div className='flex gap-1'>
<ButtonGroup className='gap-0'>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
disabled={isSent}
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<EditIcon className='w-4 h-4' />
<span className='sr-only'>Editar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={(e) => {
e.preventDefault();
handleDuplicateQuote(original.id);
}}
>
<CopyIcon className='w-4 h-4' />
<span className='sr-only'>Duplicar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Duplicar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={(e) => {
e.preventDefault();
handleDuplicateQuote(original.id);
}}
>
<CopyIcon className='w-4 h-4' />
<span className='sr-only'>Duplicar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Duplicar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={() => {
download(original.id, getQuotePDFFilename(original));
}}
>
<DownloadIcon className='w-4 h-4' />
<span className='sr-only'>Descargar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Descargar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={() => {
download(original.id, getQuotePDFFilename(original));
}}
>
<DownloadIcon className='w-4 h-4' />
<span className='sr-only'>Descargar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Descargar</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
disabled={isSent}
onClick={(e) => {
e.preventDefault();
//handleArchiveQuote(original)
}}
>
<ArchiveIcon className='w-4 h-4' />
<span className='sr-only'>Archivar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Archivar</p>
</TooltipContent>
</Tooltip>
</ButtonGroup>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
disabled={isArchived || isSent}
onClick={(e) => {
e.preventDefault();
handleArchiveQuote(original.id);
}}
>
<ArchiveIcon className='w-4 h-4' />
<span className='sr-only'>Archivar</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Archivar</p>
</TooltipContent>
</Tooltip>
</ButtonGroup>
<ButtonGroup>
<QuoteSentToEditor
id={original.id}
onSentTo={() => null}
disabled={isSent || !allowToSent}
/>
</ButtonGroup>
</div>
);
},
},

View File

@ -10,26 +10,30 @@ import {
AlertDialogTrigger,
Button,
} from "@/ui";
import { IGetQuote_Response_DTO } from "@shared/contexts";
import { t } from "i18next";
import { SendIcon } from "lucide-react";
export const QuoteSentToEditor = ({
quote,
id,
onSentTo,
disabled,
}: {
quote: IGetQuote_Response_DTO;
id: string;
onSentTo: (quoteId: string) => void;
disabled?: boolean;
}) => {
const handleSentTo = () => {
onSentTo(quote.id);
onSentTo(id);
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size='sm' variant='default' className='h-8 gap-1'>
<SendIcon className='h-3.5 w-3.5' />
<AlertDialogTrigger asChild disabled={disabled}>
<Button
variant='ghost'
className='h-10 gap-1 text-primary disabled:text-secondary-foreground'
>
<SendIcon className='w-4 h-4' />
{t("quotes.quote_sent_to_editor.trigger_button")}
</Button>
</AlertDialogTrigger>

View File

@ -50,7 +50,7 @@ export const ColorBadge = ({ label, className }: ColorBadgeProps) => {
const [color, backgroundColor] = stringToColorPair(label);
return (
<Badge className={className} style={{ backgroundColor, color }}>
<Badge className={className} style={className ? {} : { backgroundColor, color }}>
{label}
</Badge>
);