diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index 06f9dc11..7059a11d 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -106,6 +106,7 @@ "no_customer": "No customer", "created_at": "Created", "updated_at": "Updated", + "metadata": "Created on {{createdAt}} · Last updated {{updatedAt}}", "inherited": "Inherited", "general": { "title": "General information" diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index f4882a0c..6ca25c62 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -106,6 +106,7 @@ "no_customer": "Sin cliente", "created_at": "Creada", "updated_at": "Actualizada", + "metadata": "Creada el {{createdAt}} · Última actualización {{updatedAt}}", "inherited": "Heredado", "general": { "title": "Informacion general" diff --git a/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.test.ts b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.test.ts new file mode 100644 index 00000000..d6423372 --- /dev/null +++ b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.test.ts @@ -0,0 +1,19 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { getRecurringProformaDetailActions } from "./recurring-proforma-detail-actions"; + +test("prioriza la activación de un borrador y conserva las acciones terminales en el menú", () => { + assert.deepEqual(getRecurringProformaDetailActions("draft"), [ + { key: "activate", placement: "primary" }, + { key: "duplicate", placement: "secondary" }, + { key: "complete", placement: "menu", terminal: true }, + { key: "delete", placement: "menu", terminal: true, destructive: true }, + ]); +}); + +test("no ofrece acciones de ciclo de vida para recurrencias archivadas", () => { + assert.deepEqual(getRecurringProformaDetailActions("archived"), [ + { key: "duplicate", placement: "primary" }, + ]); +}); diff --git a/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.ts b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.ts new file mode 100644 index 00000000..12a9750c --- /dev/null +++ b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-actions.ts @@ -0,0 +1,44 @@ +import type { RecurringProformaAction } from "../shared/hooks/use-recurring-proforma-action-mutation"; +import type { RecurringProformaStatus } from "../shared/recurring-proforma-list"; + +export type RecurringProformaDetailActionPlacement = "primary" | "secondary" | "menu"; + +export type RecurringProformaDetailAction = { + key: RecurringProformaAction; + placement: RecurringProformaDetailActionPlacement; + terminal?: boolean; + destructive?: boolean; +}; + +export const getRecurringProformaDetailActions = ( + status: RecurringProformaStatus +): RecurringProformaDetailAction[] => { + switch (status) { + case "draft": + return [ + { key: "activate", placement: "primary" }, + { key: "duplicate", placement: "secondary" }, + { key: "complete", placement: "menu", terminal: true }, + { key: "delete", placement: "menu", terminal: true, destructive: true }, + ]; + case "active": + return [ + { key: "pause", placement: "primary" }, + { key: "duplicate", placement: "secondary" }, + { key: "complete", placement: "menu", terminal: true }, + ]; + case "paused": + return [ + { key: "resume", placement: "primary" }, + { key: "duplicate", placement: "secondary" }, + { key: "complete", placement: "menu", terminal: true }, + ]; + case "completed": + return [ + { key: "duplicate", placement: "primary" }, + { key: "archive", placement: "menu", terminal: true }, + ]; + case "archived": + return [{ key: "duplicate", placement: "primary" }]; + } +}; diff --git a/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-utils.ts b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-utils.ts index 66aa23b0..0fb5a5a7 100644 --- a/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-utils.ts +++ b/modules/customer-invoices/src/web/recurring-proformas/detail/recurring-proforma-detail-utils.ts @@ -11,9 +11,15 @@ import type { type Translate = (key: string) => string; -export const formatRecurringProformaDateOnly = (value: string, locale: string) => +export const formatRecurringProformaDateOnly = (value: string, locale?: string) => DateHelper.format(value, locale); +export const formatRecurringProformaDateTime = (value: string, locale: string) => + new Intl.DateTimeFormat(locale, { + dateStyle: "short", + timeStyle: "short", + }).format(new Date(value)); + export const formatRecurringProformaOverride = (value: string | null, t: Translate) => value ?? t("recurring_proformas.detail.inherited"); diff --git a/modules/customer-invoices/src/web/recurring-proformas/detail/ui/blocks/recurring-proforma-detail-header-actions.tsx b/modules/customer-invoices/src/web/recurring-proformas/detail/ui/blocks/recurring-proforma-detail-header-actions.tsx new file mode 100644 index 00000000..176bbee9 --- /dev/null +++ b/modules/customer-invoices/src/web/recurring-proformas/detail/ui/blocks/recurring-proforma-detail-header-actions.tsx @@ -0,0 +1,133 @@ +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@repo/shadcn-ui/components"; +import { + ArchiveIcon, + CheckCircle2Icon, + CirclePauseIcon, + CopyIcon, + MoreHorizontalIcon, + PlayCircleIcon, + RefreshCwIcon, + Trash2Icon, +} from "lucide-react"; + +import { useTranslation } from "../../../../i18n"; +import type { RecurringProformaAction } from "../../../shared/hooks/use-recurring-proforma-action-mutation"; +import type { RecurringProformaDetailAction } from "../../recurring-proforma-detail-actions"; + +type RecurringProformaDetailHeaderActionsProps = { + actions: RecurringProformaDetailAction[]; + isPending: boolean; + onExecuteAction: (action: RecurringProformaAction) => void; +}; + +const ActionIcon = ({ action }: { action: RecurringProformaAction }) => { + switch (action) { + case "activate": + case "resume": + return