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 (
+