From 1fd2d79bbe5384f342172c3df7bc2b586c4bf987 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Tue, 27 Aug 2024 20:50:31 +0200 Subject: [PATCH] . --- .../src/app/quotes/components/QuoteResume.tsx | 379 ++++++++++-------- .../components/editors/QuoteStatusEditor.tsx | 137 +++++-- client/src/app/quotes/hooks/useQuotes.tsx | 45 +++ client/src/index.css | 87 ++-- .../src/lib/axios/createAxiosDataProvider.ts | 7 +- client/src/locales/en.json | 38 +- client/src/locales/es.json | 43 +- 7 files changed, 491 insertions(+), 245 deletions(-) diff --git a/client/src/app/quotes/components/QuoteResume.tsx b/client/src/app/quotes/components/QuoteResume.tsx index 4fd100f..1603172 100644 --- a/client/src/app/quotes/components/QuoteResume.tsx +++ b/client/src/app/quotes/components/QuoteResume.tsx @@ -30,9 +30,12 @@ import { TabsList, TabsTrigger, } from "@/ui"; +import { useToast } from "@/ui/use-toast"; import { t } from "i18next"; +import { useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { useQuotes } from "../hooks"; +import { DownloadQuoteDialog } from "./DownloadQuoteDialog"; import { QuoteStatusEditor } from "./editors"; type QuoteResumeProps = { @@ -41,9 +44,37 @@ type QuoteResumeProps = { }; export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { - const { useOne, useUpdate } = useQuotes(); - const { data, status, error: queryError } = useOne(quoteId); const navigate = useNavigate(); + const { toast } = useToast(); + + const { useOne, useSetStatus, useDownloader, getQuotePDFFilename } = useQuotes(); + const { data, status, error: queryError } = useOne(quoteId); + const { mutate: setStatusMutation } = useSetStatus(quoteId); + const { download, ...downloadProps } = useDownloader(); + + const handleOnChangeStatus = (quoteId: string, newStatus: string) => { + setStatusMutation( + { newStatus }, + + { + onSuccess: () => { + toast({ + description: t("quotes.quote_status_editor.toast_status_changed"), + }); + }, + } + ); + }; + + const handleFinishDownload = useCallback(() => { + toast({ + description: t("quotes.downloading_dialog.toast_success"), + }); + }, [toast]); + + const handleDownload = useCallback(() => { + if (data) download(data.id, getQuotePDFFilename(data)); + }, [data]); if (status === "error") { return null; @@ -64,173 +95,197 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => { } return ( - - - -
- + <> + + + + + {`${t("quotes.list.preview.quote")} #${data.reference}`} - assaasassa -
-
- - - - - - - - Edit - Export - - Trash - - -
-
- - null} /> - - - - Resume - Preview - - -
+ + + + + + + + + + + Edit + Export + + Trash + + + + + + + Resume + Preview + +
-
Customer Information
- Date: {new Date(data.date).toLocaleDateString()} -
{data.customer_information}
+
+
Quote information
+ +
+
+
+ {t("quotes.form_fields.date.label")} +
+
{new Date(data.date).toLocaleDateString()}
+
+
+
Email
+
+ liam@acme.com +
+
+
+
Phone
+
+ +1 234 567 890 +
+
+
+
+ +
+
Customer Information
+ Date: {new Date(data.date).toLocaleDateString()} +
{data.customer_information}
+
+
+
Customer
+
Liam Johnson
+
+
+
Email
+
+ liam@acme.com +
+
+
+
Phone
+
+ +1 234 567 890 +
+
+
+
+ +
Order Details
+
    +
  • + + Glimmer Lamps x 2 + + $250.00 +
  • +
  • + + Aqua Filters x 1 + + $49.00 +
  • +
+ +
    +
  • + Subtotal + $299.00 +
  • +
  • + Shipping + $5.00 +
  • +
  • + Tax + $25.00 +
  • +
  • + Total + $329.00 +
  • +
+
+ +
+
+
Shipping Information
+
+ Liam Johnson + 1234 Main St. + Anytown, CA 12345 +
+
+
+
Billing Information
+
Same as shipping address
+
+
+ + +
+
Payment Information
-
Customer
-
Liam Johnson
-
-
-
Email
-
- liam@acme.com -
-
-
-
Phone
-
- +1 234 567 890 -
+
+ + Visa +
+
**** **** **** 4532
- -
Order Details
-
    -
  • - - Glimmer Lamps x 2 - - $250.00 -
  • -
  • - - Aqua Filters x 1 - - $49.00 -
  • -
- -
    -
  • - Subtotal - $299.00 -
  • -
  • - Shipping - $5.00 -
  • -
  • - Tax - $25.00 -
  • -
  • - Total - $329.00 -
  • -
-
- -
-
-
Shipping Information
-
- Liam Johnson - 1234 Main St. - Anytown, CA 12345 -
-
-
-
Billing Information
-
Same as shipping address
-
-
- - -
-
Payment Information
-
-
-
- - Visa -
-
**** **** **** 4532
-
-
-
-
- -
- -
Updated...
- - - - - - - - - - -
-
-
+ + + + +
Updated...
+ + + + + + + + + + +
+ + + ); }; diff --git a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx index f177c3b..b2cca68 100644 --- a/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteStatusEditor.tsx @@ -1,50 +1,113 @@ -import { Button } from "@/ui/button"; -import { Select, SelectContent, SelectTrigger, SelectValue } from "@/ui/select"; -import { SelectItem } from "@radix-ui/react-select"; -import { useState } from "react"; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, + Label, + 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"; -const QUOTE_STATUS = ["draft", "ready", "delivered", "accepted", "rejected", "archived"]; +const QuoteStatus = ["draft", "ready", "delivered", "accepted", "rejected", "archived"]; -const QUOTE_NEXT_STATUS = { - draft: ["ready", "archived"], - ready: ["delivered", "archived"], - delivered: ["accepted", "rejected", "archived"], - accepted: ["archived"], - rejected: ["archived"], - archived: [], +type TQuoteStatus = "draft" | "ready" | "delivered" | "accepted" | "rejected" | "archived"; + +const quoteStatusTransitions: Record = { + draft: ["draft", "ready", "archived"], + ready: ["ready", "delivered", "archived"], + delivered: ["delivered", "accepted", "rejected", "archived"], + accepted: ["accepted", "archived"], + rejected: ["rejected", "archived"], + archived: ["archived", "draft", "ready", "delivered", "accepted", "rejected"], }; -export const QuoteStatusEditor = ({ quote, onChangeStatus }) => { - const [status, changeStatus] = useState(quote.status); +export const QuoteStatusEditor = ({ + quote, + onChangeStatus, +}: { + quote: IGetQuote_Response_DTO; + onChangeStatus: (quoteId: string, newStatus: string) => void; +}) => { + const [newStatus, setNewStatus] = useState(""); + + const changeStatus = (newStatus: string) => setNewStatus(newStatus); + + useEffect(() => { + if (quote) { + setNewStatus(quote.status); + } + }, [quote]); const handleChangeStatus = () => { - if (status !== quote.status) { - onChangeStatus(quote.id, status); + if (newStatus !== quote.status) { + onChangeStatus(quote.id, newStatus); } }; + console.log(newStatus); + return ( -
- - -
+ + + + + + + + + + + + + ); }; diff --git a/client/src/app/quotes/hooks/useQuotes.tsx b/client/src/app/quotes/hooks/useQuotes.tsx index e7a1c0c..308caa7 100644 --- a/client/src/app/quotes/hooks/useQuotes.tsx +++ b/client/src/app/quotes/hooks/useQuotes.tsx @@ -10,6 +10,7 @@ import { IGetQuote_Response_DTO, IListQuotes_Response_DTO, IListResponse_DTO, + ISetStatusQuote_Request_DTO, IUpdateQuote_Request_DTO, IUpdateQuote_Response_DTO, UniqueID, @@ -150,6 +151,50 @@ export const useQuotes = () => { }); }, + useSetStatus: (id: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(), + mutationFn: (data) => { + const { newStatus } = data; + + return dataSource.custom({ + url: `${dataSource.getApiUrl()}/quotes/${id}/setStatus`, + method: "put", + data: { + newStatus, + }, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["data", "default", "quotes"], + }); + }, + }); + + /*return useMutation({ + mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(), + mutationFn: (data) => { + const { newStatus } = data; + + return dataSource.updateOne({ + resource: "quotes", + id, + data: { + newStatus, + }, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["data", "default", "quotes"], + }); + }, + });*/ + }, + useOne: (id?: string, params?: UseQuotesGetParamsType) => useOne({ queryKey: keys().data().resource("quotes").action("one").id(id).params().get(), diff --git a/client/src/index.css b/client/src/index.css index 470d1dc..592061a 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -50,51 +50,60 @@ } }*/ -/* https://ui.jln.dev/ unicscode89-stripe */ @layer base { :root { - --background: 210 40% 96.08%; - --foreground: 334 55% 1%; - --muted: 214.29 31.82% 91.37%; - --muted-foreground: 334 9% 37%; - --popover: 334 62% 100%; - --popover-foreground: 334 55% 1%; - --card: 334 62% 100%; - --card-foreground: 334 55% 1%; - --border: 334 5% 95%; - --input: 214.29 31.82% 91.37%; - --primary: 242.93 100% 67.84%; - --primary-foreground: 0 0% 100%; - --secondary: 213.75 20.25% 69.02%; - --secondary-foreground: 334 0% 100%; - --accent: 214.29 31.82% 91.37%; - --accent-foreground: 334 20% 22%; - --destructive: 348.37 78.4% 49.02%; - --destructive-foreground: 18 0% 100%; - --ring: 228.33 94.74% 62.75%; + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; } .dark { - --background: 222.22 47.37% 11.18%; - --foreground: 334 34% 98%; - --muted: 215.38 16.32% 46.86%; - --muted-foreground: 334 0% 87.69%; - --popover: 217.24 32.58% 17.45%; - --popover-foreground: 334 34% 98%; - --card: 217.24 32.58% 17.45%; - --card-foreground: 334 34% 98%; - --border: 334 0% 32.31%; - --input: 215.29 25% 26.67%; - --primary: 227.56 53.78% 49.22%; - --primary-foreground: 0 0% 100%; - --secondary: 214.29 5.04% 27.25%; - --secondary-foreground: 334 0% 100%; - --accent: 222.22 47.37% 11.18%; - --accent-foreground: 226.73 0% 100%; - --destructive: 358.82 84.44% 64.71%; - --destructive-foreground: 0 0% 100%; - --ring: 227.56 53.78% 49.22%; + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; } } diff --git a/client/src/lib/axios/createAxiosDataProvider.ts b/client/src/lib/axios/createAxiosDataProvider.ts index 9eb0e4e..97fdad0 100644 --- a/client/src/lib/axios/createAxiosDataProvider.ts +++ b/client/src/lib/axios/createAxiosDataProvider.ts @@ -150,7 +150,7 @@ export const createAxiosDataProvider = ( }, custom: async (params: ICustomDataProviderParam): Promise => { - const { url, method, responseType, headers, signal, ...payload } = params; + const { url, method, responseType, headers, signal, data, ...payload } = params; const requestUrl = `${url}?`; /*if (sort) { @@ -186,9 +186,12 @@ export const createAxiosDataProvider = ( case "put": case "post": case "patch": - customResponse = await httpClient[method](url, { + customResponse = await httpClient.request({ + url, + method, responseType, headers, + data, ...payload, }); break; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 36e3b44..5e3d395 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -197,8 +197,44 @@ "description": "To complete your quote, you can add items from the catalog.", "toast_article_added": "Catalog item added:" }, + "quote_status_editor": { + "title": "Change quote status", + "status": { + "draft": { + "title": "Draft", + "description": "The quote is in the initial stages of creation." + }, + "ready": { + "title": "Ready", + "description": "The quote is completed and ready to be delivered to the customer." + }, + "delivered": { + "title": "Delivered", + "description": "The quote has been sent to the client and a response is awaited." + }, + "accepted": { + "title": "Accepted", + "description": "The customer has approved the quote." + }, + "rejected": { + "title": "Rejected", + "description": "The customer has not accepted the quotation." + }, + "archived": { + "title": "Archived", + "description": "The quote is archived." + } + }, + "submit_button": "Change status", + "toast_status_changed": "Quote status changed to:" + }, "status": { - "draft": "Draft" + "draft": "Draft", + "ready": "Ready", + "delivered": "Delivered", + "accepted": "Accepted", + "rejected": "Rejected", + "archived": "Archived" }, "form_fields": { "date": { diff --git a/client/src/locales/es.json b/client/src/locales/es.json index f3a8e8b..a5bc749 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -122,9 +122,9 @@ "draft": "Borradores", "ready": "Preparados", "delivered": "Entregado", - "accepted": "Accepted", - "rejected": "Rejected", - "archived": "Archivadas" + "accepted": "Aceptados", + "rejected": "Rechazados", + "archived": "Archivados" }, "columns": { "date": "Fecha", @@ -193,8 +193,43 @@ "description": "Para rellenar su cotización, puede añadir artículos del catálogo.", "toast_article_added": "Artículo del catálogo añadido:" }, + "quote_status_editor": { + "title": "Cambiar el estado de la cotización", + "status": { + "draft": { + "title": "Borrador", + "description": "La cotización está en fase inicial de creación." + }, + "ready": { + "title": "Preparado", + "description": "La cotización está completo y listo para ser entregado al cliente." + }, + "delivered": { + "title": "Entregado", + "description": "La cotización ha sido enviado al cliente y se espera su respuesta." + }, + "accepted": { + "title": "Aceptado", + "description": "El cliente ha aprobado la cotización." + }, + "rejected": { + "title": "Rechazado", + "description": "El cliente no ha aceptado la cotización." + }, + "archived": { + "title": "Archivado", + "description": "La cotización se ha guardado para referencia futura." + } + }, + "submit_button": "Change status" + }, "status": { - "draft": "Borrador" + "draft": "Borrador", + "ready": "Preparado", + "delivered": "Entregado", + "accepted": "Aceptado", + "rejected": "Rechazado", + "archived": "Archivado" }, "form_fields": { "date": {