diff --git a/apps/server/package.json b/apps/server/package.json index b04938af..310879fb 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "@erp/factuges-server", - "version": "0.10.0", + "version": "0.10.1", "private": true, "scripts": { "build": "tsup src/index.ts --config tsup.config.ts", diff --git a/apps/web/package.json b/apps/web/package.json index 30721047..cb46c861 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@erp/factuges-web", "private": true, - "version": "0.10.0", + "version": "0.10.1", "type": "module", "scripts": { "typecheck": "tsc -p tsconfig.json --noEmit", diff --git a/modules/auth/package.json b/modules/auth/package.json index 19e7d84e..a0418ad6 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -1,6 +1,6 @@ { "name": "@erp/auth", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/catalogs/package.json b/modules/catalogs/package.json index b71490de..9f0cc73b 100644 --- a/modules/catalogs/package.json +++ b/modules/catalogs/package.json @@ -1,7 +1,7 @@ { "name": "@erp/catalogs", "description": "Catalogs module", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/companies/package.json b/modules/companies/package.json index 6e904536..da4b7aaf 100644 --- a/modules/companies/package.json +++ b/modules/companies/package.json @@ -1,7 +1,7 @@ { "name": "@erp/companies", "description": "Companies module", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/core/package.json b/modules/core/package.json index a5be1db8..53309d16 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -1,6 +1,6 @@ { "name": "@erp/core", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/customer-invoices/package.json b/modules/customer-invoices/package.json index 899ba4e5..4bcd71bf 100644 --- a/modules/customer-invoices/package.json +++ b/modules/customer-invoices/package.json @@ -1,6 +1,6 @@ { "name": "@erp/customer-invoices", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index 53c40a24..aa47e814 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -193,6 +193,27 @@ "title": "Customer proformas", "description": "List non-archived and non-deleted customer proformas", "loadErrorTitle": "Unable to load the proformas list", + "actions": { + "view": "View", + "edit": "Edit", + "change_status": "Change status", + "issue": "Issue invoice", + "duplicate": "Duplicate", + "archive": "Archive", + "unarchive": "Unarchive", + "download_pdf": "Download PDF", + "delete": "Delete", + "view_linked_invoice": "View linked invoice", + "disabled_reasons": { + "archived_scope": "This action is not available from the archived proformas list.", + "deleted_scope": "This action is not available from the deleted proformas list.", + "archived_only": "This action is only available for archived proformas.", + "edit_issued": "Issued proformas cannot be edited.", + "status_issued": "Issued proformas do not allow status changes.", + "linked_invoice_unavailable": "This action is only available when the issued proforma has a linked invoice.", + "operation_in_progress": "The operation is already in progress." + } + }, "empty": { "normal": "There are no proformas.", "archived": "There are no archived proformas.", diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index 9a809a34..5152e4d0 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -194,6 +194,27 @@ "title": "Proformas", "description": "Lista las proformas no archivadas ni eliminadas", "loadErrorTitle": "No se pudo cargar el listado de proformas", + "actions": { + "view": "Ver", + "edit": "Editar", + "change_status": "Cambiar estado", + "issue": "Emitir factura", + "duplicate": "Duplicar", + "archive": "Archivar", + "unarchive": "Desarchivar", + "download_pdf": "Descargar PDF", + "delete": "Eliminar", + "view_linked_invoice": "Ver factura vinculada", + "disabled_reasons": { + "archived_scope": "No se puede ejecutar desde el listado de proformas archivadas.", + "deleted_scope": "No se puede ejecutar desde el listado de proformas eliminadas.", + "archived_only": "Solo está disponible en proformas archivadas.", + "edit_issued": "No se puede editar una proforma emitida.", + "status_issued": "Una proforma emitida no admite cambios de estado.", + "linked_invoice_unavailable": "Solo está disponible cuando la proforma emitida tiene una factura vinculada.", + "operation_in_progress": "La operación está en curso." + } + }, "empty": { "normal": "No hay proformas.", "archived": "No hay proformas archivadas.", 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 757dcc89..3668c693 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 @@ -20,6 +20,7 @@ import { import type { ColumnDef } from "@tanstack/react-table"; import { CopyIcon, + EyeIcon, ExternalLinkIcon, FileDownIcon, FileTextIcon, @@ -44,7 +45,6 @@ import { ProformaStatusBadge } from "../../components"; type GridActionHandlers = { onViewClick?: (proforma: ProformaListRow) => void; - onPreviewClick?: (proforma: ProformaListRow) => void; onEditClick?: (proforma: ProformaListRow) => void; onIssueClick?: (proforma: ProformaListRow) => void; onArchiveClick?: (proforma: ProformaListRow) => void; @@ -64,6 +64,29 @@ type GridActionHandlers = { isPdfDownloading?: boolean; }; +type RowAction = { + key: string; + label: string; + icon: React.ComponentType<{ className?: string }>; + disabled: boolean; + disabledReason?: string; + destructive?: boolean; + loading?: boolean; + href?: string; + onClick?: () => void; +}; + +const stopRowPropagation = (event: React.MouseEvent | React.KeyboardEvent) => { + event.stopPropagation(); +}; + +const stopRowPropagationAndPreventDefault = ( + event: React.MouseEvent | React.KeyboardEvent +) => { + event.preventDefault(); + event.stopPropagation(); +}; + export function useProformasGridColumns( scope: ProformaListScope, actionHandlers: GridActionHandlers = {} @@ -297,16 +320,8 @@ export function useProformasGridColumns( ? (PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? []) : []; - const canEdit = isNormalScope && !isIssued && actionHandlers.onEditClick; - const canView = !isDeletedScope && actionHandlers.onViewClick; - const canDuplicate = !isDeletedScope && actionHandlers.onDuplicateClick; - const canIssue = isNormalScope && !isIssued && isApproved && actionHandlers.onIssueClick; - const canDelete = !isDeletedScope && isDraft && actionHandlers.onDeleteClick; - const canDownloadPdf = !isDeletedScope && actionHandlers.onDownloadPdf; - const isPDFLoading = actionHandlers.isPdfDownloading && actionHandlers.pdfDownloadingId === proforma.id; - const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation(); const nextTransition = availableTransitions[0] ?? null; const handleView = () => actionHandlers.onViewClick?.(proforma); @@ -325,263 +340,259 @@ export function useProformasGridColumns( actionHandlers.onChangeStatusClick?.(proforma, nextTransition); }; - const primaryAction = (() => { - if (isIssued && proforma.linkedInvoiceId) { - return { - href: `/issed-invoices/${proforma.linkedInvoiceId}`, - icon: ExternalLinkIcon, - label: "Ver factura", - }; + const getScopeReason = () => { + if (isArchivedScope) { + return t("pages.proformas.list.actions.disabled_reasons.archived_scope"); } - if (canIssue) { - return { - icon: FileTextIcon, - label: "Emitir factura", - onClick: handleIssue, - }; + if (isDeletedScope) { + return t("pages.proformas.list.actions.disabled_reasons.deleted_scope"); } - if (isUnarchivable) { - return { - icon: Undo2Icon, - label: "Desarchivar", - onClick: handleUnarchive, - }; - } + return undefined; + }; - if (!isDraft && nextTransition && actionHandlers.onChangeStatusClick) { - return { - icon: RefreshCwIcon, - label: "Cambiar estado", - onClick: handleChangeStatus, - }; - } + const visibleActions: RowAction[] = [ + { + key: "view", + label: t("pages.proformas.list.actions.view"), + icon: EyeIcon, + disabled: !actionHandlers.onViewClick || isDeletedScope, + disabledReason: isDeletedScope + ? t("pages.proformas.list.actions.disabled_reasons.deleted_scope") + : undefined, + onClick: handleView, + }, + { + key: "edit", + label: t("pages.proformas.list.actions.edit"), + icon: PencilIcon, + disabled: !actionHandlers.onEditClick || !isNormalScope || isIssued, + disabledReason: !isNormalScope + ? getScopeReason() + : isIssued + ? t("pages.proformas.list.actions.disabled_reasons.edit_issued") + : undefined, + onClick: handleEdit, + }, + ]; - if (canEdit) { - return { - icon: PencilIcon, - label: "Editar", - onClick: handleEdit, - }; - } + const menuActions: RowAction[] = [ + { + key: "linked-invoice", + label: t("pages.proformas.list.actions.view_linked_invoice"), + icon: ExternalLinkIcon, + disabled: !isIssued || !proforma.linkedInvoiceId, + disabledReason: + !isIssued || !proforma.linkedInvoiceId + ? t("pages.proformas.list.actions.disabled_reasons.linked_invoice_unavailable") + : undefined, + href: proforma.linkedInvoiceId + ? `/issed-invoices/${proforma.linkedInvoiceId}` + : undefined, + }, + { + key: "change-status", + label: t("pages.proformas.list.actions.change_status"), + icon: RefreshCwIcon, + disabled: + !actionHandlers.onChangeStatusClick || !isNormalScope || !nextTransition || isIssued, + disabledReason: !isNormalScope + ? getScopeReason() + : isIssued + ? t("pages.proformas.list.actions.disabled_reasons.status_issued") + : !nextTransition + ? t("proformas.change_proforma_status_dialog.empty_available_statuses") + : undefined, + onClick: handleChangeStatus, + }, + { + key: "issue", + label: t("pages.proformas.list.actions.issue"), + icon: FileTextIcon, + disabled: !actionHandlers.onIssueClick || !isNormalScope || !isApproved || isIssued, + disabledReason: !isNormalScope + ? getScopeReason() + : !isApproved + ? t("proformas.issue_proforma_dialog.not_allowed_description") + : undefined, + onClick: handleIssue, + }, + { + key: "duplicate", + label: t("pages.proformas.list.actions.duplicate"), + icon: CopyIcon, + disabled: !actionHandlers.onDuplicateClick || isDeletedScope, + disabledReason: isDeletedScope + ? t("pages.proformas.list.actions.disabled_reasons.deleted_scope") + : undefined, + onClick: handleDuplicate, + }, + { + key: "archive", + label: t("pages.proformas.list.actions.archive"), + icon: FolderArchiveIcon, + disabled: !actionHandlers.onArchiveClick || !isArchivable, + disabledReason: !actionHandlers.onArchiveClick + ? undefined + : !isNormalScope + ? getScopeReason() + : t("proformas.archive.error_conflict_message"), + onClick: handleArchive, + }, + { + key: "unarchive", + label: t("pages.proformas.list.actions.unarchive"), + icon: Undo2Icon, + disabled: !actionHandlers.onUnarchiveClick || !isUnarchivable, + disabledReason: !actionHandlers.onUnarchiveClick + ? undefined + : !isArchivedScope + ? t("pages.proformas.list.actions.disabled_reasons.archived_only") + : t("proformas.unarchive.error_conflict_message"), + onClick: handleUnarchive, + }, + { + key: "download-pdf", + label: t("pages.proformas.list.actions.download_pdf"), + icon: FileDownIcon, + disabled: !actionHandlers.onDownloadPdf || isDeletedScope || isPDFLoading, + disabledReason: isDeletedScope + ? t("pages.proformas.list.actions.disabled_reasons.deleted_scope") + : isPDFLoading + ? t("pages.proformas.list.actions.disabled_reasons.operation_in_progress") + : undefined, + loading: isPDFLoading, + onClick: handleDownloadPdf, + }, + { + key: "delete", + label: t("pages.proformas.list.actions.delete"), + icon: Trash2Icon, + destructive: true, + disabled: !actionHandlers.onDeleteClick || isDeletedScope || !isDraft, + disabledReason: isDeletedScope + ? t("pages.proformas.list.actions.disabled_reasons.deleted_scope") + : !isDraft + ? t("proformas.delete_proforma_dialog.error_conflict_message") + : undefined, + onClick: handleDelete, + }, + ]; - if (nextTransition && actionHandlers.onChangeStatusClick) { - return { - icon: RefreshCwIcon, - label: "Cambiar estado", - onClick: handleChangeStatus, - }; - } + const renderIconButton = (action: RowAction) => { + const button = ( + + ); - if (isArchivable && actionHandlers.onArchiveClick) { - return { - icon: FolderArchiveIcon, - label: "Archivar", - onClick: handleArchive, - }; - } + return ( + + {button} : button} + /> + {action.disabledReason ?? action.label} + + ); + }; - return null; - })(); + const renderMenuItem = (action: RowAction) => { + const item = ( + { + stopRowPropagationAndPreventDefault(event); + if (action.disabled) { + return; + } - const quickAction = (() => { - if (primaryAction?.label !== "Editar" && canEdit) { - return { - icon: PencilIcon, - label: "Editar", - loading: false, - onClick: handleEdit, - }; - } + if (action.href) { + window.location.href = action.href; + return; + } - return null; - })(); + action.onClick?.(); + }} + variant={action.destructive ? "destructive" : "default"} + > + {action.loading ? ( + + ) : ( + + )} + {action.label} + + ); - const menuActions = [ - canView - ? { - icon: ExternalLinkIcon, - label: "Ver", - onClick: handleView, - } - : null, - !isIssued && - nextTransition && - actionHandlers.onChangeStatusClick && - primaryAction?.label !== "Cambiar estado" - ? { - icon: RefreshCwIcon, - label: "Cambiar estado", - onClick: handleChangeStatus, - } - : null, - canIssue && primaryAction?.label !== "Emitir factura" - ? { - icon: FileTextIcon, - label: "Emitir factura", - onClick: handleIssue, - } - : null, - canEdit && primaryAction?.label !== "Editar" && quickAction?.label !== "Editar" - ? { - icon: PencilIcon, - label: "Editar", - onClick: handleEdit, - } - : null, - canDuplicate - ? { - icon: CopyIcon, - label: "Duplicar", - onClick: handleDuplicate, - } - : null, - isArchivable && actionHandlers.onArchiveClick && primaryAction?.label !== "Archivar" - ? { - icon: FolderArchiveIcon, - label: "Archivar", - onClick: handleArchive, - } - : null, - isUnarchivable && - actionHandlers.onUnarchiveClick && - primaryAction?.label !== "Desarchivar" - ? { - icon: Undo2Icon, - label: "Desarchivar", - onClick: handleUnarchive, - } - : null, - canDownloadPdf - ? { - icon: FileDownIcon, - label: "Descargar PDF", - onClick: handleDownloadPdf, - loading: isPDFLoading, - } - : null, - canDelete - ? { - destructive: true, - icon: Trash2Icon, - label: "Eliminar", - onClick: handleDelete, - } - : null, - ].filter(Boolean); + return ( + + {item} : item} /> + {action.disabledReason ?? action.label} + + ); + }; return ( -
- {primaryAction && "href" in primaryAction ? ( - - ) : primaryAction ? ( - - ) : null} - - {quickAction ? ( - + +
+ {visibleActions.map(renderIconButton)} + { - event.preventDefault(); - event.stopPropagation(); - quickAction.onClick(); - }} - size="icon" - variant="ghost" - > - {quickAction.loading ? ( - - ) : ( - - )} - {quickAction.label} - + + + + } + /> } /> - {quickAction.label} + {t("common.more_actions")} - - ) : null} - - {menuActions.length > 0 ? ( - - - - Más acciones - - } - /> - + - Acciones + {t("common.actions")} - {menuActions.length && - menuActions.map((action, index) => - action ? ( - - {action.destructive && index > 0 ? : null} - { - event.preventDefault(); - event.stopPropagation(); - action.onClick(); - }} - variant={action.destructive ? "destructive" : "default"} - > - {"loading" in action && action.loading ? ( - - ) : ( - - )} - {action.label} - - - ) : null - )} + {menuActions.map((action, index) => ( + + {action.destructive && index > 0 ? : null} + {renderMenuItem(action)} + + ))} - ) : null} -
+
+ ); }, }, diff --git a/modules/customers/package.json b/modules/customers/package.json index 6856b4b5..f94e68c2 100644 --- a/modules/customers/package.json +++ b/modules/customers/package.json @@ -1,7 +1,7 @@ { "name": "@erp/customers", "description": "Customers", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/document-series/package.json b/modules/document-series/package.json index 58aced60..507db352 100644 --- a/modules/document-series/package.json +++ b/modules/document-series/package.json @@ -1,7 +1,7 @@ { "name": "@erp/document-series", "description": "Document series module", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/factuges/package.json b/modules/factuges/package.json index 10bf7874..9dd76cca 100644 --- a/modules/factuges/package.json +++ b/modules/factuges/package.json @@ -1,6 +1,6 @@ { "name": "@erp/factuges", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/identity/package.json b/modules/identity/package.json index 281bb0b2..c434dc92 100644 --- a/modules/identity/package.json +++ b/modules/identity/package.json @@ -1,7 +1,7 @@ { "name": "@erp/identity", "description": "Identity module", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/supplier-invoices/package.json b/modules/supplier-invoices/package.json index a9fe3969..0284c6ef 100644 --- a/modules/supplier-invoices/package.json +++ b/modules/supplier-invoices/package.json @@ -1,7 +1,7 @@ { "name": "@erp/supplier-invoices", "description": "Supplier invoices", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/modules/supplier/package.json b/modules/supplier/package.json index 11d36a8f..d9b98176 100644 --- a/modules/supplier/package.json +++ b/modules/supplier/package.json @@ -1,7 +1,7 @@ { "name": "@erp/suppliers", "description": "Suppliers", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/package.json b/package.json index e83f0541..9d4ce92d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "uecko-erp-2025", "private": true, - "version": "0.10.0", + "version": "0.10.1", "workspaces": [ "apps/*", "modules/*", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 932e4006..7979386d 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@repo/i18next", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "scripts": { diff --git a/packages/rdx-criteria/package.json b/packages/rdx-criteria/package.json index 7d7e5ee2..6dce4802 100644 --- a/packages/rdx-criteria/package.json +++ b/packages/rdx-criteria/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-criteria", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-ddd/package.json b/packages/rdx-ddd/package.json index c3f0b6f6..8669f3b3 100644 --- a/packages/rdx-ddd/package.json +++ b/packages/rdx-ddd/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-ddd", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-logger/package.json b/packages/rdx-logger/package.json index 9515743e..5c70e29a 100644 --- a/packages/rdx-logger/package.json +++ b/packages/rdx-logger/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-logger", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-ui/package.json b/packages/rdx-ui/package.json index 7c3dc524..2064599a 100644 --- a/packages/rdx-ui/package.json +++ b/packages/rdx-ui/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-ui", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/rdx-utils/package.json b/packages/rdx-utils/package.json index a790cc6a..a818b755 100644 --- a/packages/rdx-utils/package.json +++ b/packages/rdx-utils/package.json @@ -1,6 +1,6 @@ { "name": "@repo/rdx-utils", - "version": "0.10.0", + "version": "0.10.1", "private": true, "type": "module", "sideEffects": false, diff --git a/packages/shadcn-ui/package.json b/packages/shadcn-ui/package.json index a919a851..aa141374 100644 --- a/packages/shadcn-ui/package.json +++ b/packages/shadcn-ui/package.json @@ -1,6 +1,6 @@ { "name": "@repo/shadcn-ui", - "version": "0.10.0", + "version": "0.10.1", "type": "module", "private": true, "exports": { diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index 66ccd05e..790bb610 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/typescript-config", - "version": "0.10.0", + "version": "0.10.1", "private": true, "publishConfig": { "access": "public"