From e32dbfa3f14af0c92e10fc08bd1283044649f4f2 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 5 Apr 2026 21:02:00 +0200 Subject: [PATCH] PROFORMAS DELETE --- apps/web/src/main.tsx | 6 +- .../use-delete-proforma-dialog-controller.ts | 205 ++++++++---------- .../entities/delete-proforma-target.entity.ts | 4 + .../web/proformas/delete/entities/index.ts | 1 + .../ui/components/delete-proforma-dialog.tsx | 80 ++++--- .../utils/build-delete-proforma-targets.ts | 18 ++ .../src/web/proformas/delete/utils/index.ts | 1 + .../use-list-proformas.controller.ts | 2 +- .../blocks/proformas-grid/proformas-grid.tsx | 5 +- .../use-proforma-grid-columns.tsx | 10 +- .../list/ui/pages/list-proformas-page.tsx | 42 ++-- modules/customers/src/common/locales/en.json | 10 + modules/customers/src/common/locales/es.json | 12 + 13 files changed, 210 insertions(+), 186 deletions(-) create mode 100644 modules/customer-invoices/src/web/proformas/delete/entities/delete-proforma-target.entity.ts create mode 100644 modules/customer-invoices/src/web/proformas/delete/entities/index.ts create mode 100644 modules/customer-invoices/src/web/proformas/delete/utils/build-delete-proforma-targets.ts create mode 100644 modules/customer-invoices/src/web/proformas/delete/utils/index.ts diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 80830795..4d5bdbe4 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -13,8 +13,10 @@ if (rootElement) { createRoot(rootElement).render( - - + <> + + + ); diff --git a/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts b/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts index 9b699ac2..9c25c8b0 100644 --- a/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts +++ b/modules/customer-invoices/src/web/proformas/delete/controllers/use-delete-proforma-dialog-controller.ts @@ -1,60 +1,52 @@ +// proformas/delete/controllers/use-delete-proforma-dialog-controller.ts import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; -import React from "react"; +import * as React from "react"; import { useTranslation } from "../../../i18n"; -import { type ProformaListRow, useDeleteProformaMutation } from "../../shared"; +import type { DeleteProformaByIdParams } from "../../shared"; +import { useProformaDeleteMutation } from "../../shared"; +import type { DeleteProformaTarget } from "../entities"; + +type ConfirmStep = "initial" | "second"; interface DeleteProformaDialogState { open: boolean; - proformas: ProformaListRow[]; - isSubmitting: boolean; - requiresSecondConfirm: boolean; - confirmStep: "initial" | "second"; + targets: DeleteProformaTarget[]; + confirmStep: ConfirmStep; } const INITIAL_STATE: DeleteProformaDialogState = { open: false, - proformas: [], - isSubmitting: false, - requiresSecondConfirm: false, + targets: [], confirmStep: "initial", }; -const SECOND_CONFIRM_THRESHOLD = 5; +const buildDeleteParams = (target: DeleteProformaTarget): DeleteProformaByIdParams => ({ + id: target.id, +}); -function canSubmitDelete(isSubmitting: boolean, proformas: ProformaListRow[]): boolean { - return !isSubmitting && proformas.length > 0; -} +const getProformaLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`; -function shouldMoveToSecondConfirmStep( - requiresSecondConfirm: boolean, - confirmStep: "initial" | "second" -): boolean { - return requiresSecondConfirm && confirmStep === "initial"; -} - -export function useDeleteProformaDialogController() { +export const useDeleteProformaDialogController = () => { const { t } = useTranslation(); - const { deleteProforma } = useDeleteProformaMutation(); + const deleteMutation = useProformaDeleteMutation(); const [state, setState] = React.useState(INITIAL_STATE); - const { isSubmitting, proformas, requiresSecondConfirm, confirmStep } = state; - const openDialog = React.useCallback((proformas: ProformaListRow[]) => { - const requiresSecondConfirm = proformas.length > SECOND_CONFIRM_THRESHOLD; + const openDialog = React.useCallback((targets: DeleteProformaTarget[]) => { + if (targets.length === 0) return; setState({ open: true, - proformas, - isSubmitting: false, - requiresSecondConfirm, + targets, confirmStep: "initial", }); }, []); const closeDialog = React.useCallback(() => { + if (deleteMutation.isPending) return; setState(INITIAL_STATE); - }, []); + }, [deleteMutation.isPending]); const moveToSecondConfirmStep = React.useCallback(() => { setState((current) => ({ @@ -63,124 +55,109 @@ export function useDeleteProformaDialogController() { })); }, []); - const deleteSelectedProformas = React.useCallback( - async (proformas: ProformaListRow[]) => { - const results = await Promise.allSettled( - proformas.map((proforma) => - deleteProforma({ - proformaId: proforma.id, - }) - ) - ); - - const successCount = results.filter((result) => result.status === "fulfilled").length; - const errorCount = results.length - successCount; - - return { - successCount, - errorCount, - }; - }, - [deleteProforma] - ); - - const notifyDeleteResult = React.useCallback( - (proformas: ProformaListRow[], successCount: number, errorCount: number) => { - if (proformas.length === 1 && successCount === 1) { - const proforma = proformas[0]; - + const notifySuccess = React.useCallback( + (targets: DeleteProformaTarget[]) => { + if (targets.length === 1) { showSuccessToast( t("pages.proformas.delete.successTitle"), t("pages.proformas.delete.successSingleMessage", { - reference: proforma.reference || `#${proforma.id}`, - }) - ); - } else if (successCount > 0) { - showSuccessToast( - t("pages.proformas.delete.successTitle"), - t("pages.proformas.delete.successMultipleMessage", { - count: successCount, + reference: getProformaLabel(targets[0]), }) ); + return; } - if (errorCount > 0) { + showSuccessToast( + t("pages.proformas.delete.successTitle"), + t("pages.proformas.delete.successMultipleMessage", { + count: targets.length, + }) + ); + }, + [t] + ); + + const notifyPartialError = React.useCallback( + (targets: DeleteProformaTarget[], errorCount: number) => { + if (targets.length === 1) { showErrorToast( t("pages.proformas.delete.errorTitle"), - proformas.length === 1 - ? t("pages.proformas.delete.errorSingleMessage") - : t("pages.proformas.delete.errorMultipleMessage", { - count: errorCount, - }) + t("pages.proformas.delete.errorSingleMessage") ); + return; } + + showErrorToast( + t("pages.proformas.delete.errorTitle"), + t("pages.proformas.delete.errorMultipleMessage", { + count: errorCount, + }) + ); }, [t] ); const submitDelete = React.useCallback(async () => { - setState((current) => ({ - ...current, - isSubmitting: true, - })); + const { targets } = state; - try { - const { successCount, errorCount } = await deleteSelectedProformas(proformas); + const results = await Promise.allSettled( + targets.map((target) => deleteMutation.mutateAsync(buildDeleteParams(target))) + ); - notifyDeleteResult(proformas, successCount, errorCount); + const successCount = results.filter((result) => result.status === "fulfilled").length; + const errorCount = results.length - successCount; - if (errorCount === 0) { - closeDialog(); - return; + if (successCount > 0) { + const fulfilledTargets = targets.filter((_, index) => results[index]?.status === "fulfilled"); + const rejectedTargets = targets.filter((_, index) => results[index]?.status === "rejected"); + + if (fulfilledTargets.length > 0) { + notifySuccess(fulfilledTargets); } - setState((current) => ({ - ...current, - isSubmitting: false, - })); + if (rejectedTargets.length > 0) { + notifyPartialError(targets, rejectedTargets.length); + return; + } + } + + if (errorCount > 0) { + notifyPartialError(targets, errorCount); + return; + } + + setState(INITIAL_STATE); + }, [deleteMutation, notifyPartialError, notifySuccess, state]); + + const confirmDelete = React.useCallback(async () => { + if (deleteMutation.isPending || state.targets.length === 0) { + return; + } + + if (state.confirmStep === "initial") { + moveToSecondConfirmStep(); + return; + } + + try { + await submitDelete(); } catch { showErrorToast( t("pages.proformas.delete.errorTitle"), t("pages.proformas.delete.errorUnexpectedMessage") ); - - setState((current) => ({ - ...current, - isSubmitting: false, - })); } - }, [closeDialog, deleteSelectedProformas, notifyDeleteResult, proformas, t]); - - const confirmDelete = React.useCallback(async () => { - if (!canSubmitDelete(isSubmitting, proformas)) { - return; - } - - if (shouldMoveToSecondConfirmStep(requiresSecondConfirm, confirmStep)) { - moveToSecondConfirmStep(); - return; - } - - await submitDelete(); - }, [ - moveToSecondConfirmStep, - submitDelete, - isSubmitting, - proformas, - requiresSecondConfirm, - confirmStep, - ]); + }, [deleteMutation.isPending, moveToSecondConfirmStep, state, submitDelete, t]); return { open: state.open, - proformas: state.proformas, - isSubmitting: state.isSubmitting, - requiresSecondConfirm: state.requiresSecondConfirm, + targets: state.targets, + isSubmitting: deleteMutation.isPending, isSecondConfirmStep: state.confirmStep === "second", - isBulkDelete: state.proformas.length > 1, + isBulkDelete: state.targets.length > 1, openDialog, closeDialog, confirmDelete, }; -} +}; diff --git a/modules/customer-invoices/src/web/proformas/delete/entities/delete-proforma-target.entity.ts b/modules/customer-invoices/src/web/proformas/delete/entities/delete-proforma-target.entity.ts new file mode 100644 index 00000000..e07335b9 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/entities/delete-proforma-target.entity.ts @@ -0,0 +1,4 @@ +export interface DeleteProformaTarget { + id: string; + reference?: string; +} diff --git a/modules/customer-invoices/src/web/proformas/delete/entities/index.ts b/modules/customer-invoices/src/web/proformas/delete/entities/index.ts new file mode 100644 index 00000000..a7a8c94f --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/entities/index.ts @@ -0,0 +1 @@ +export * from "./delete-proforma-target.entity"; diff --git a/modules/customer-invoices/src/web/proformas/delete/ui/components/delete-proforma-dialog.tsx b/modules/customer-invoices/src/web/proformas/delete/ui/components/delete-proforma-dialog.tsx index b3005a60..e91b9c3e 100644 --- a/modules/customer-invoices/src/web/proformas/delete/ui/components/delete-proforma-dialog.tsx +++ b/modules/customer-invoices/src/web/proformas/delete/ui/components/delete-proforma-dialog.tsx @@ -10,46 +10,60 @@ import { } from "@repo/shadcn-ui/components"; import { useTranslation } from "../../../../i18n"; -import type { ProformaListRow } from "../../../shared"; +import type { DeleteProformaTarget } from "../../entities"; interface DeleteProformaDialogProps { open: boolean; onOpenChange: (open: boolean) => void; - proformas: ProformaListRow[]; + targets: DeleteProformaTarget[]; isSubmitting: boolean; onConfirm: () => void; - requiresSecondConfirm: boolean; isSecondConfirmStep: boolean; } -export function DeleteProformaDialog({ +const getTargetLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`; + +export const DeleteProformaDialog = ({ open, onOpenChange, - proformas, + targets, isSubmitting, onConfirm, - requiresSecondConfirm, isSecondConfirmStep, -}: DeleteProformaDialogProps) { +}: DeleteProformaDialogProps) => { const { t } = useTranslation(); - const total = proformas.length; + const total = targets.length; const isSingle = total === 1; - const firstProforma = proformas[0]; + const firstTarget = targets[0]; const title = isSecondConfirmStep - ? t("proformas.delete_proforma_dialog.second_confirm_title", { count: total }) - : isSingle - ? t("proformas.delete_proforma_dialog.single_title", { - reference: firstProforma?.reference ?? `#${firstProforma?.id}`, + ? isSingle + ? t("components.delete_proforma_dialog.second_confirm_single_title", { + reference: getTargetLabel(firstTarget), }) - : t("proformas.delete_proforma_dialog.multiple_title", { count: total }); + : t("components.delete_proforma_dialog.second_confirm_multiple_title", { + count: total, + }) + : isSingle + ? t("components.delete_proforma_dialog.single_title", { + reference: getTargetLabel(firstTarget), + }) + : t("components.delete_proforma_dialog.multiple_title", { + count: total, + }); const description = isSecondConfirmStep - ? t("proformas.delete_proforma_dialog.second_confirm_description", { count: total }) + ? isSingle + ? t("components.delete_proforma_dialog.second_confirm_single_description") + : t("components.delete_proforma_dialog.second_confirm_multiple_description", { + count: total, + }) : isSingle - ? t("proformas.delete_proforma_dialog.single_description") - : t("proformas.delete_proforma_dialog.multiple_description", { count: total }); + ? t("components.delete_proforma_dialog.single_description") + : t("components.delete_proforma_dialog.multiple_description", { + count: total, + }); return ( {description} - {!isSecondConfirmStep && total > 1 && ( + {!isSecondConfirmStep && total > 1 ? (
    - {proformas.map((proforma) => ( -
  • - - {t("proformas.delete_proforma_dialog.list_item", { - reference: proforma.reference ?? `#${proforma.id}`, - })} - + {targets.map((target) => ( +
  • + {t("components.delete_proforma_dialog.list_item", { + reference: getTargetLabel(target), + })}
  • ))}
- )} + ) : null} -
); -} +}; diff --git a/modules/customer-invoices/src/web/proformas/delete/utils/build-delete-proforma-targets.ts b/modules/customer-invoices/src/web/proformas/delete/utils/build-delete-proforma-targets.ts new file mode 100644 index 00000000..cc27efb9 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/utils/build-delete-proforma-targets.ts @@ -0,0 +1,18 @@ +import type { Proforma, ProformaListRow } from "../../shared"; +import type { DeleteProformaTarget } from "../entities"; + +export const buildDeleteProformaTargetFromListRow = ( + proforma: ProformaListRow +): DeleteProformaTarget => ({ + id: proforma.id, + reference: proforma.reference, +}); + +export const buildDeleteProformaTargetFromEntity = (proforma: Proforma): DeleteProformaTarget => ({ + id: proforma.id, + reference: proforma.reference, +}); + +export const buildDeleteProformaTargetsFromListRows = ( + proformas: ProformaListRow[] +): DeleteProformaTarget[] => proformas.map(buildDeleteProformaTargetFromListRow); diff --git a/modules/customer-invoices/src/web/proformas/delete/utils/index.ts b/modules/customer-invoices/src/web/proformas/delete/utils/index.ts new file mode 100644 index 00000000..76f2170c --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/delete/utils/index.ts @@ -0,0 +1 @@ +export * from "./build-delete-proforma-targets"; diff --git a/modules/customer-invoices/src/web/proformas/list/controllers/use-list-proformas.controller.ts b/modules/customer-invoices/src/web/proformas/list/controllers/use-list-proformas.controller.ts index b147a162..cf1d065a 100644 --- a/modules/customer-invoices/src/web/proformas/list/controllers/use-list-proformas.controller.ts +++ b/modules/customer-invoices/src/web/proformas/list/controllers/use-list-proformas.controller.ts @@ -32,7 +32,7 @@ export const useListProformasController = () => { pageNumber: pageIndex, pageSize, order: "desc", - orderBy: "invoiceDate", + orderBy: "invoice_date", filters: statusFilter === "all" ? [] : [{ field: "status", operator: "eq", value: statusFilter }], }), diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx index 5cbca394..158d84f3 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx @@ -22,6 +22,7 @@ interface ProformasGridProps { export const ProformasGrid = ({ data, loading, + fetching, columns, pageIndex, pageSize, @@ -32,7 +33,7 @@ export const ProformasGrid = ({ const { t } = useTranslation(); const { items, totalItems } = data || { items: [], totalItems: 0 }; - if (loading) + if (loading) { return ( ); + } return ( onRowClick?.(row.id)} pageIndex={pageIndex} pageSize={pageSize} totalItems={totalItems} diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx index 08106042..6c247176 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx @@ -96,7 +96,6 @@ export function useProformasGridColumns( return (
- {/* Enlace discreto a factura real */} {isIssued && ( @@ -105,12 +104,13 @@ export function useProformasGridColumns( Ver factura {invoiceId} @@ -148,8 +148,6 @@ export function useProformasGridColumns( > {proforma.recipient.name} - -
{proforma.recipient.tin}
); diff --git a/modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx b/modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx index 67bcdde6..1a86b4c4 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx @@ -15,8 +15,9 @@ import { FilterIcon, PlusIcon } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "../../../../i18n"; -import { ChangeStatusDialog } from "../../../change-status"; -import { ProformaIssueDialog } from "../../../issue-proforma"; +import { DeleteProformaDialog, useDeleteProformaDialogController } from "../../../delete"; +import { buildDeleteProformaTargetFromListRow } from "../../../delete/utils"; +import type { ProformaListRow } from "../../../shared"; import { useListProformasPageController } from "../../controllers"; import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks"; @@ -25,12 +26,17 @@ export const ListProformasPage = () => { const navigate = useNavigate(); const { listCtrl, panelCtrl } = useListProformasPageController(); + const deleteDialogCtrl = useDeleteProformaDialogController(); + + const handleDeleteProforma = (proforma: ProformaListRow) => { + deleteDialogCtrl.openDialog([buildDeleteProformaTargetFromListRow(proforma)]); + }; const columns = useProformasGridColumns({ onEditClick: (proforma) => navigate(`/proformas/${proforma.id}/edit`), - onIssueClick: handleIssueProforma, + //onIssueClick: handleIssueProforma, onDeleteClick: handleDeleteProforma, - onChangeStatusClick: handleChangeStatusProforma, + //onChangeStatusClick: handleChangeStatusProforma, }); const isPanelOpen = panelCtrl.panelState.isOpen; @@ -143,32 +149,18 @@ export const ListProformasPage = () => {
{listContent}
)} <> - {/* Emitir factura */} - !open && issueDialogCtrl.closeDialog()} - open={issueDialogCtrl.open} - proforma={issueDialogCtrl.proforma} - /> - - {/* Cambiar estado */} - !open && changeStatusDialogCtrl.closeDialog()} - open={changeStatusDialogCtrl.open} - proformas={changeStatusDialogCtrl.proformas} // ← recibe el status seleccionado - /> - {/* Eliminar */} !open && deleteDialogCtrl.closeDialog()} + onOpenChange={(open) => { + if (!open) { + deleteDialogCtrl.closeDialog(); + } + }} open={deleteDialogCtrl.open} - proformas={deleteDialogCtrl.proformas} - requireSecondConfirm={true} + targets={deleteDialogCtrl.targets} /> diff --git a/modules/customers/src/common/locales/en.json b/modules/customers/src/common/locales/en.json index 9579c12c..2aebca0a 100644 --- a/modules/customers/src/common/locales/en.json +++ b/modules/customers/src/common/locales/en.json @@ -192,6 +192,16 @@ } }, "components": { + "delete_proforma_dialog": { + "single_title": "Eliminar proforoma", + "single_description": "", + "second_confirm_single_title": "Confirmar eliminación", + "second_confirm_single_description": "", + "confirm_delete": "Confirmar eliminación", + "deleting": "Eliminando...", + "cancel": "Cancelar", + "continue": "Eliminar" + }, "entity_selector": { "close": "Close", "select_entity": "Select entity", diff --git a/modules/customers/src/common/locales/es.json b/modules/customers/src/common/locales/es.json index 4bc6c92b..e5085891 100644 --- a/modules/customers/src/common/locales/es.json +++ b/modules/customers/src/common/locales/es.json @@ -194,6 +194,18 @@ } }, "components": { + "delete_proforma_dialog": { + "single_title": "Eliminar proforoma", + "single_description": "", + "second_confirm_single_title": "Confirmar eliminación", + "second_confirm_single_description": "", + "multiple_description": "", + "confirm_delete": "Confirmar eliminación", + "deleting": "Eliminando...", + "cancel": "Cancelar", + "continue": "Eliminar", + "list_item": "" + }, "entity_selector": { "close": "Cerrar", "select_entity": "Seleccionar entidad",