From 1be73865eaa63f442294530db63eb3c3593953ff Mon Sep 17 00:00:00 2001 From: David Arranz Date: Wed, 13 Nov 2024 17:28:58 +0100 Subject: [PATCH] Quote Status --- .../src/app/quotes/components/QuoteResume.tsx | 14 +- .../app/quotes/components/QuotesDataTable.tsx | 161 ++++++++++-------- .../components/editors/QuoteStatusEditor.tsx | 45 ++--- client/src/app/quotes/hooks/useQuotes.tsx | 7 +- .../QuoteStatusBadge/QuoteStatusBadge.tsx | 80 +++++++++ .../src/components/QuoteStatusBadge/index.ts | 1 + client/src/components/index.ts | 2 + 7 files changed, 212 insertions(+), 98 deletions(-) create mode 100644 client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx create mode 100644 client/src/components/QuoteStatusBadge/index.ts diff --git a/client/src/app/quotes/components/QuoteResume.tsx b/client/src/app/quotes/components/QuoteResume.tsx index 4cb5ac4..bec72e4 100644 --- a/client/src/app/quotes/components/QuoteResume.tsx +++ b/client/src/app/quotes/components/QuoteResume.tsx @@ -1,6 +1,6 @@ import { DownloadIcon, FilePenLineIcon } from "lucide-react"; -import { ColorBadge } from "@/components"; +import { QuoteStatusBadge } from "@/components"; import { useCustomLocalization } from "@/lib/hooks"; import { cn } from "@/lib/utils"; import { @@ -40,7 +40,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { const { useOne, useSetStatus, useSentTo, useDownloader, getQuotePDFFilename } = useQuotes(); const { data, status } = useOne(quoteId); - const { mutate: setStatusMutation } = useSetStatus(quoteId); + const { mutate: setStatusMutation } = useSetStatus(); const { mutate: sentToMutation } = useSentTo(quoteId); const { download, ...downloadProps } = useDownloader(); @@ -80,9 +80,9 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { const allowToSent = useMemo(() => data?.status === "accepted" && !data?.date_sent, [data]); const isSent = useMemo(() => data?.status === "accepted" && data?.date_sent, [data]); - const handleOnChangeStatus = (_: string, newStatus: string) => { + const handleOnChangeStatus = (newStatus: string) => { setStatusMutation( - { newStatus }, + { id: data!!.id, newStatus }, { onSuccess: () => { @@ -143,14 +143,14 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { {t("quotes.list.resume.title")} - +
{allowToSent && !isSent && ( <> - + )} @@ -171,7 +171,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { - + )} diff --git a/client/src/app/quotes/components/QuotesDataTable.tsx b/client/src/app/quotes/components/QuotesDataTable.tsx index f9577ca..82f339c 100644 --- a/client/src/app/quotes/components/QuotesDataTable.tsx +++ b/client/src/app/quotes/components/QuotesDataTable.tsx @@ -8,29 +8,18 @@ import { } from "@/components"; import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar"; import { useDataTable, useDataTableContext } from "@/lib/hooks"; -import { - Button, - Card, - CardContent, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/ui"; +import { Button, Card, CardContent, Tooltip, TooltipContent, TooltipTrigger } from "@/ui"; import { useToast } from "@/ui/use-toast"; import { IListQuotes_Response_DTO, UTCDateValue } from "@shared/contexts"; import { ColumnDef, Row } from "@tanstack/react-table"; import { t } from "i18next"; -import { FilePenLineIcon, MoreVerticalIcon, SendIcon } from "lucide-react"; +import { ArchiveIcon, CopyIcon, DownloadIcon, EditIcon } from "lucide-react"; import { useCallback, useEffect, useId, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuotes } from "../hooks"; import { DownloadQuoteDialog } from "./DownloadQuoteDialog"; import { QuoteResume } from "./QuoteResume"; +import { QuoteStatusEditor } from "./editors"; export const QuotesDataTable = ({ status = "all", @@ -49,7 +38,9 @@ export const QuotesDataTable = ({ const [activeRow, setActiveRow] = useState | undefined>(undefined); - const { useList, useDownloader, getQuotePDFFilename } = useQuotes(); + const { useList, useDownloader, useSetStatus, getQuotePDFFilename } = useQuotes(); + + const { mutate: setStatusMutation } = useSetStatus(); const { data, isPending, isError, error } = useList({ pagination: { @@ -79,6 +70,20 @@ export const QuotesDataTable = ({ [navigate, toast] ); + const handleOnChangeStatus = (id: string, newStatus: string) => { + setStatusMutation( + { id, newStatus }, + + { + onSuccess: () => { + toast({ + description: t("quotes.quote_status_editor.toast_status_changed"), + }); + }, + } + ); + }; + const columns = useMemo[]>(() => { const columns = [ { @@ -118,7 +123,13 @@ export const QuotesDataTable = ({ header: () => <>{t("quotes.list.columns.status")}, // eslint-disable-next-line @typescript-eslint/no-explicit-any cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => ( - + + handleOnChangeStatus(original.id, newStatus) + } + /> ), }, { @@ -206,72 +217,86 @@ export const QuotesDataTable = ({ header: () => null, 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 isSent = original?.status === "accepted" && !!original?.date_sent; return ( - + - <> - {allowToSent && !isSent && ( - - )} - - {!isSent && ( - - )} - + -

{t("quotes.list.columns.actions.sent_to_uecko")}

+

Editar

- - - - - - + +

Duplicar

+
+ + + + + + + +

Descargar

+
+
+ + + + + + +

Archivar

+
+
); }, diff --git a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx index d5a8008..b28adb7 100644 --- a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx @@ -1,3 +1,4 @@ +import { QuoteStatusBadge } from "@/components"; import { cn } from "@/lib/utils"; import { Button, @@ -12,7 +13,6 @@ import { Switch, } from "@/ui"; import { DialogDescription } from "@radix-ui/react-dialog"; -import { IGetQuote_Response_DTO } from "@shared/contexts"; import { t } from "i18next"; import { RefreshCwIcon } from "lucide-react"; import { useEffect, useState } from "react"; @@ -31,37 +31,43 @@ const quoteStatusTransitions: Record = { }; export const QuoteStatusEditor = ({ - quote, + type = "button", + status, onChangeStatus, }: { - quote: IGetQuote_Response_DTO; - onChangeStatus: (quoteId: string, newStatus: string) => void; + type?: "button" | "badge"; + status: string; + onChangeStatus: (newStatus: string, oldStatus: string) => void; }) => { - const [newStatus, setNewStatus] = useState(""); + const [newStatus, setNewStatus] = useState(status); const changeStatus = (newStatus: string) => setNewStatus(newStatus); useEffect(() => { - if (quote) { - setNewStatus(quote.status); + if (status) { + setNewStatus(status); } - }, [quote]); + }, [status]); const handleChangeStatus = () => { - if (newStatus !== quote.status) { - onChangeStatus(quote.id, newStatus); + if (newStatus !== status) { + onChangeStatus(newStatus, status); } }; return ( - + {type === "button" ? ( + + ) : ( + + )} @@ -71,9 +77,10 @@ export const QuoteStatusEditor = ({
{QuoteStatus.map((_status) => { - const isDisabled = !quoteStatusTransitions[quote.status as TQuoteStatus].includes( + const isDisabled = false; + /*const isDisabled = !quoteStatusTransitions[status as TQuoteStatus].includes( _status as TQuoteStatus - ); + );*/ return (
@@ -108,7 +115,7 @@ export const QuoteStatusEditor = ({ - diff --git a/client/src/app/quotes/hooks/useQuotes.tsx b/client/src/app/quotes/hooks/useQuotes.tsx index 07065f6..8762cc3 100644 --- a/client/src/app/quotes/hooks/useQuotes.tsx +++ b/client/src/app/quotes/hooks/useQuotes.tsx @@ -147,13 +147,12 @@ export const useQuotes = () => { }); }, - useSetStatus: (id?: string) => { + useSetStatus: () => { const queryClient = useQueryClient(); - return useMutation({ - mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(), + return useMutation({ mutationFn: (data) => { - const { newStatus } = data; + const { id, newStatus } = data; return dataSource.custom({ url: `${dataSource.getApiUrl()}/quotes/${id}/setStatus`, diff --git a/client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx b/client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx new file mode 100644 index 0000000..6d032b2 --- /dev/null +++ b/client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx @@ -0,0 +1,80 @@ +import { cn } from "@/lib/utils"; +import { Badge } from "@/ui"; +import { t } from "i18next"; +import { RefreshCwIcon } from "lucide-react"; + +export type QuoteStatusBadgeProps = { + status: string; + isEditable?: boolean; + className?: string; +}; + +type QuoteStatus = "draft" | "ready" | "delivered" | "accepted" | "rejected" | "archived"; + +const statusColorConfig: Record< + QuoteStatus, + { color: string; bgColor: string; hoverColor: string; hoverBgColor: string } +> = { + draft: { + color: "text-gray-700", + bgColor: "bg-gray-200", + hoverColor: "hover:text-gray-900", + hoverBgColor: "hover:bg-gray-300", + }, + ready: { + color: "text-blue-700", + bgColor: "bg-blue-200", + hoverColor: "hover:text-blue-900", + hoverBgColor: "hover:bg-blue-300", + }, + delivered: { + color: "text-yellow-700", + bgColor: "bg-yellow-200", + hoverColor: "hover:text-yellow-900", + hoverBgColor: "hover:bg-yellow-300", + }, + accepted: { + color: "text-green-700", + bgColor: "bg-green-200", + hoverColor: "hover:text-green-900", + hoverBgColor: "hover:bg-green-300", + }, + rejected: { + color: "text-red-700", + bgColor: "bg-red-200", + hoverColor: "hover:text-red-900", + hoverBgColor: "hover:bg-red-300", + }, + archived: { + color: "text-purple-700", + bgColor: "bg-purple-200", + hoverColor: "hover:text-purple-900", + hoverBgColor: "hover:bg-purple-300", + }, +}; + +export const QuoteStatusBadge = ({ + status, + isEditable, + className, + ...props +}: QuoteStatusBadgeProps) => { + return ( + + {t(`quotes.status.${status}`)} + {isEditable && ( + + )} + + ); +}; diff --git a/client/src/components/QuoteStatusBadge/index.ts b/client/src/components/QuoteStatusBadge/index.ts new file mode 100644 index 0000000..0021c16 --- /dev/null +++ b/client/src/components/QuoteStatusBadge/index.ts @@ -0,0 +1 @@ +export * from "./QuoteStatusBadge"; diff --git a/client/src/components/index.ts b/client/src/components/index.ts index ce9e226..4cb0cd2 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -13,6 +13,8 @@ export * from "./LoadingOverlay"; export * from "./LoadingSpinner"; export * from "./PDFViewer"; export * from "./ProtectedRoute"; +export * from "./QuoteStatusBadge"; + //export * from "./SorteableDataTable"; export * from "./TailwindIndicator";