From 99f8b5fb8ea283f06732a73e430e97817afd143f Mon Sep 17 00:00:00 2001 From: david Date: Sun, 16 Nov 2025 22:11:46 +0100 Subject: [PATCH] Facturas de cliente --- .../src/common/locales/en.json | 29 +- .../src/common/locales/es.json | 29 +- .../src/web/customer-invoice-routes.tsx | 13 +- .../src/web/issued-invoices/adapters/index.ts | 1 + .../issued-invoice-summary-dto.adapter.ts} | 14 +- .../src/web/issued-invoices/index.ts | 2 +- .../issued-invoice.api.schema.ts | 26 -- .../src/web/issued-invoices/pages/index.ts | 1 + .../issued-invoices/pages/list/hooks/index.ts | 2 + .../use-issued-invoices-grid-columns.tsx | 348 ++++++++++++++++++ .../list/hooks/use-issued-invoices-list.ts | 53 +++ .../web/issued-invoices/pages/list/index.ts | 1 + .../pages/list/issued-invoice-list-page.tsx | 62 ++++ .../issued-invoices/pages/list/ui/index.ts | 2 + .../pages/list/ui/proforma-status-badge.tsx | 68 ++++ .../pages/list/ui/proformas-grid.tsx | 105 ++++++ .../src/web/issued-invoices/schema/index.ts | 2 + .../issued-invoice-summary.web.schema.ts} | 10 +- .../schema/issued-invoice.api.schema.ts | 22 ++ .../list/hooks/use-proformas-grid-columns.tsx | 45 +-- .../datatable/data-table-column-header.tsx | 54 +-- .../datatable/data-table-pagination.tsx | 70 ++-- .../datatable/data-table-view-options.tsx | 2 +- packages/rdx-ui/src/locales/en.json | 2 +- packages/rdx-ui/src/locales/es.json | 6 +- 25 files changed, 839 insertions(+), 130 deletions(-) create mode 100644 modules/customer-invoices/src/web/issued-invoices/adapters/index.ts rename modules/customer-invoices/src/web/issued-invoices/{issued-invoice-resume-dto.adapter.ts => adapters/issued-invoice-summary-dto.adapter.ts} (86%) delete mode 100644 modules/customer-invoices/src/web/issued-invoices/issued-invoice.api.schema.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/index.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/index.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-list.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/index.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/issued-invoice-list-page.tsx create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/ui/index.ts create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proforma-status-badge.tsx create mode 100644 modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proformas-grid.tsx create mode 100644 modules/customer-invoices/src/web/issued-invoices/schema/index.ts rename modules/customer-invoices/src/web/issued-invoices/{issued-invoice-resume.form.schema.ts => schema/issued-invoice-summary.web.schema.ts} (59%) create mode 100644 modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice.api.schema.ts diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index bcaa6349..d255619a 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -45,7 +45,7 @@ "title": "Customer proformas", "description": "List all customer proformas", "grid_columns": { - "invoice_number": "Inv. number", + "invoice_number": "#", "series": "Serie", "reference": "Reference", "status": "Status", @@ -82,6 +82,33 @@ "title": "View customer proforma", "description": "View the details of the selected customer proforma" } + }, + "issued_invoices": { + "title": "Customer Invoices", + "description": "Manage your customer invoices", + "list": { + "title": "Customer invoices", + "description": "List all customer invoices", + "grid_columns": { + "invoice_number": "Inv. number", + "series": "Serie", + "reference": "Reference", + "invoice_date": "Invoice date", + "operation_date": "Operation date", + "recipient": "Customer", + "recipient_tin": "TIN", + "recipient_name": "Customer name", + "recipient_street": "Street", + "recipient_city": "City", + "recipient_province": "Province", + "recipient_postal_code": "Postal code", + "recipient_country": "Country", + "subtotal_amount": "Subtotal", + "discount_amount": "Discount", + "taxes_amount": "Taxes", + "total_amount": "Total" + } + } } }, "form_groups": { diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index 6be4f815..e9190f03 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -44,7 +44,7 @@ "title": "Proformas", "description": "Lista todas las proformas", "grid_columns": { - "invoice_number": "Nº proforma", + "invoice_number": "#", "series": "Serie", "reference": "Reference", "status": "Estado", @@ -81,6 +81,33 @@ "title": "Ver proforma", "description": "Ver los detalles de la proforma seleccionada" } + }, + "issued_invoices": { + "title": "Facturas de cliente", + "description": "Gestiona tus facturas de cliente", + "list": { + "title": "Facturas de cliente", + "description": "Lista todas las facturas de cliente", + "grid_columns": { + "invoice_number": "Nº factura", + "series": "Serie", + "reference": "Reference", + "invoice_date": "Fecha de proforma", + "operation_date": "Fecha de operación", + "recipient": "Cliente", + "recipient_tin": "NIF/CIF", + "recipient_name": "Cliente", + "recipient_street": "Dirección", + "recipient_city": "Ciudad", + "recipient_province": "Provincia", + "recipient_postal_code": "Código postal", + "recipient_country": "País", + "subtotal_amount": "Subtotal", + "discount_amount": "Descuentos", + "taxes_amount": "Impuestos", + "total_amount": "Importe total" + } + } } }, diff --git a/modules/customer-invoices/src/web/customer-invoice-routes.tsx b/modules/customer-invoices/src/web/customer-invoice-routes.tsx index 02ac90f9..209fcaf9 100644 --- a/modules/customer-invoices/src/web/customer-invoice-routes.tsx +++ b/modules/customer-invoices/src/web/customer-invoice-routes.tsx @@ -2,6 +2,8 @@ import type { ModuleClientParams } from "@erp/core/client"; import { lazy } from "react"; import { Outlet, type RouteObject } from "react-router-dom"; +import { IssuedInvoiceListPage } from "./issued-invoices/pages/list/issued-invoice-list-page"; + // Lazy load components const InvoicesLayout = lazy(() => import("./shared/ui").then((m) => ({ default: m.CustomerInvoicesLayout })) @@ -34,7 +36,7 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] { path: ":id/edit", element: }, ], }, - /*{ + { path: "customer-invoices", element: ( @@ -42,9 +44,9 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] ), children: [ - //{ path: "", index: true, element: }, // index - //{ path: "list", element: }, - // + { path: "", index: true, element: }, // index + { path: "list", element: }, + /* { path: "create", element: }, { path: ":id", element: }, { path: ":id/edit", element: }, @@ -55,7 +57,8 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] { path: ":id/download", element: }, { path: ":id/duplicate", element: }, { path: ":id/preview", element: }, + */ ], - },*/ + }, ]; }; diff --git a/modules/customer-invoices/src/web/issued-invoices/adapters/index.ts b/modules/customer-invoices/src/web/issued-invoices/adapters/index.ts new file mode 100644 index 00000000..26ac0dcd --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/adapters/index.ts @@ -0,0 +1 @@ +export * from "./issued-invoice-summary-dto.adapter"; diff --git a/modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume-dto.adapter.ts b/modules/customer-invoices/src/web/issued-invoices/adapters/issued-invoice-summary-dto.adapter.ts similarity index 86% rename from modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume-dto.adapter.ts rename to modules/customer-invoices/src/web/issued-invoices/adapters/issued-invoice-summary-dto.adapter.ts index ee99761b..a1356b7e 100644 --- a/modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume-dto.adapter.ts +++ b/modules/customer-invoices/src/web/issued-invoices/adapters/issued-invoice-summary-dto.adapter.ts @@ -1,16 +1,16 @@ import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core"; -import type { IssuedInvoicesummaryPage } from "./issued-invoice.api.schema"; import type { - IssuedInvoicesummaryData, - IssuedInvoicesummaryPageData, -} from "./issued-invoice-resume.form.schema"; + IssuedInvoiceSummaryData, + IssuedInvoiceSummaryPage, + IssuedInvoiceSummaryPageData, +} from "../schema"; /** * Convierte el DTO completo de API a datos numéricos para el formulario. */ -export const IssueInvoiceResumeDtoAdapter = { - fromDto(pageDto: IssuedInvoicesummaryPage, context?: unknown): IssuedInvoicesummaryPageData { +export const IssuedInvoiceSummaryDtoAdapter = { + fromDto(pageDto: IssuedInvoiceSummaryPage, context?: unknown): IssuedInvoiceSummaryPageData { return { ...pageDto, items: pageDto.items.map( @@ -64,7 +64,7 @@ export const IssueInvoiceResumeDtoAdapter = { ), //taxes: dto.taxes, - }) as unknown as IssuedInvoicesummaryData + }) as unknown as IssuedInvoiceSummaryData ), }; }, diff --git a/modules/customer-invoices/src/web/issued-invoices/index.ts b/modules/customer-invoices/src/web/issued-invoices/index.ts index 007f69d0..c4e34b27 100644 --- a/modules/customer-invoices/src/web/issued-invoices/index.ts +++ b/modules/customer-invoices/src/web/issued-invoices/index.ts @@ -1 +1 @@ -export * from "./hooks"; +export * from "./pages"; diff --git a/modules/customer-invoices/src/web/issued-invoices/issued-invoice.api.schema.ts b/modules/customer-invoices/src/web/issued-invoices/issued-invoice.api.schema.ts deleted file mode 100644 index 39ec9442..00000000 --- a/modules/customer-invoices/src/web/issued-invoices/issued-invoice.api.schema.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - GetIssuedInvoiceByIdResponseSchema, - ListIssuedInvoicesResponseSchema, -} from "@erp/customer-invoices/common"; -import type { ArrayElement } from "@repo/rdx-utils"; -import type { z } from "zod/v4"; - -// IssuedInvoices -export const IssuedInvoiceschema = GetIssuedInvoiceByIdResponseSchema.omit({ - metadata: true, -}); - -export type IssueInvoice = z.infer; -export type IssueInvoiceRecipient = IssueInvoice["recipient"]; -export type IssueInvoiceItem = ArrayElement; - -// Resultado de consulta con criteria (paginado, etc.) -export const IssuedInvoicesummaryPageSchema = ListIssuedInvoicesResponseSchema.omit({ - metadata: true, -}); - -export type IssuedInvoicesummaryPage = z.infer; -export type IssuedInvoicesummary = Omit< - ArrayElement, - "metadata" ->; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/index.ts b/modules/customer-invoices/src/web/issued-invoices/pages/index.ts new file mode 100644 index 00000000..491ccf0c --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/index.ts @@ -0,0 +1 @@ +export * from "./list"; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/index.ts b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/index.ts new file mode 100644 index 00000000..abc4de9d --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./use-issued-invoices-grid-columns"; +export * from "./use-issued-invoices-list"; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx new file mode 100644 index 00000000..7b3d3d52 --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-grid-columns.tsx @@ -0,0 +1,348 @@ +import { formatDate } from "@erp/core/client"; +import { DataTableColumnHeader } from "@repo/rdx-ui/components"; +import { + Button, + ButtonGroup, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@repo/shadcn-ui/components"; +import type { ColumnDef } from "@tanstack/react-table"; +import { DownloadIcon, MailIcon, MoreVerticalIcon } from "lucide-react"; +import * as React from "react"; + +import { useTranslation } from "../../../../i18n"; +import type { IssuedInvoiceSummaryData } from "../../../schema"; + +type GridActionHandlers = { + onDownloadPdf?: (proforma: IssuedInvoiceSummaryData) => void; + onSendEmail?: (proforma: IssuedInvoiceSummaryData) => void; +}; + +export function useIssuedInvoicesGridColumns( + actionHandlers: GridActionHandlers = {} +): ColumnDef[] { + const { t } = useTranslation(); + const { onDownloadPdf, onSendEmail } = actionHandlers; + + return React.useMemo[]>( + () => [ + // Nº + { + accessorKey: "invoice_number", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.original.invoice_number}
+ ), + enableHiding: false, + enableSorting: false, + maxSize: 48, + size: 48, + minSize: 48, + meta: { + title: t("pages.issued_invoices.list.grid_columns.invoice_number"), + }, + }, + + { + id: "recipient", + header: ({ column }) => ( + + ), + accessorFn: (row) => row.recipient.name, // para ordenar/buscar por nombre + enableHiding: false, + size: 140, + minSize: 120, + cell: ({ row }) => { + const c = row.original.recipient; + return ( +
+
+
+ {c.name} +
+
+ {c.tin && {c.tin}} +
+
+
+ ); + }, + meta: { + title: t("pages.issued_invoices.list.grid_columns.recipient"), + }, + }, + // Serie + { + accessorKey: "series", + header: ({ column }) => ( + + ), + cell: ({ row }) =>
{row.original.series}
, + enableSorting: false, + size: 120, + minSize: 100, + meta: { + title: t("pages.issued_invoices.list.grid_columns.series"), + }, + }, + // Referencia + { + accessorKey: "reference", + header: ({ column }) => ( + + ), + cell: ({ row }) =>
{row.original.reference}
, + enableSorting: false, + size: 120, + minSize: 100, + meta: { + title: t("pages.issued_invoices.list.grid_columns.reference"), + }, + }, + + // Fecha factura + { + accessorKey: "invoice_date", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {formatDate(row.original.invoice_date)} +
+ ), + enableSorting: false, + size: 140, + minSize: 120, + meta: { + title: t("pages.issued_invoices.list.grid_columns.invoice_date"), + }, + }, + // Fecha operación + { + accessorKey: "operation_date", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {formatDate(row.original.operation_date)} +
+ ), + enableSorting: false, + size: 140, + minSize: 120, + meta: { + title: t("pages.issued_invoices.list.grid_columns.operation_date"), + }, + }, + + // Subtotal amount + { + accessorKey: "subtotal_amount_fmt", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.original.subtotal_amount_fmt} +
+ ), + enableSorting: false, + size: 120, + minSize: 100, + meta: { + title: t("pages.issued_invoices.list.grid_columns.subtotal_amount"), + }, + }, + + // Discount amount + { + accessorKey: "discount_amount_fmt", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.original.discount_amount_fmt} +
+ ), + enableSorting: false, + size: 120, + minSize: 100, + meta: { + title: t("pages.issued_invoices.list.grid_columns.discount_amount"), + }, + }, + + // Taxes amount + { + accessorKey: "taxes_amount_fmt", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.original.taxes_amount_fmt}
+ ), + enableSorting: false, + size: 120, + minSize: 100, + meta: { + title: t("pages.issued_invoices.list.grid_columns.taxes_amount"), + }, + }, + + // Total amount + { + accessorKey: "total_amount_fmt", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.original.total_amount_fmt} +
+ ), + enableSorting: false, + size: 140, + minSize: 120, + meta: { + title: t("pages.issued_invoices.list.grid_columns.total_amount"), + }, + }, + + // ───────────────────────────── + // Acciones + // ───────────────────────────── + { + id: "actions", + header: ({ column }) => ( + + ), + enableSorting: false, + enableHiding: false, + size: 64, + minSize: 64, + cell: ({ row }) => { + const proforma = row.original; + const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation(); + + return ( + + {/* Descargar en PDF */} + + + + + {t("common.download_pdf")} + + + {/* Menú demás acciones */} + {/** biome-ignore lint/suspicious/noSelfCompare: */} + {false !== false && ( + + + + + + onDownloadPdf?.(proforma)} + > + + {t("common.download_pdf")} + + onSendEmail?.(proforma)} + > + + {t("common.send_email")} + {" "} + + + )} + + ); + }, + meta: { + title: t("common.actions"), + }, + }, + ], + [t, onDownloadPdf, onSendEmail] + ); +} diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-list.ts b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-list.ts new file mode 100644 index 00000000..40d3056b --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/hooks/use-issued-invoices-list.ts @@ -0,0 +1,53 @@ +// src/modules/issued-invoices/hooks/use-proformas-list.ts + +import type { CriteriaDTO } from "@erp/core"; +import { useDebounce } from "@repo/rdx-ui/components"; +import { useMemo, useState } from "react"; + +import { IssuedInvoiceSummaryDtoAdapter } from "../../../adapters/issued-invoice-summary-dto.adapter"; +import { useIssuedInvoicesQuery } from "../../../hooks"; + +export const useIssuedInvoicesList = () => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [search, setSearch] = useState(""); + const [status, setStatus] = useState("all"); + + const debouncedQ = useDebounce(search, 300); + + const criteria = useMemo(() => { + const baseFilters = + status !== "all" ? [{ field: "status", operator: "CONTAINS", value: status }] : []; + + return { + q: debouncedQ || "", + pageSize, + pageNumber: pageIndex, + order: "desc", + orderBy: "invoice_date", + filters: baseFilters, + }; + }, [pageSize, pageIndex, debouncedQ, status]); + + const query = useIssuedInvoicesQuery({ criteria }); + const data = useMemo( + () => (query.data ? IssuedInvoiceSummaryDtoAdapter.fromDto(query.data) : undefined), + [query.data] + ); + + const setSearchValue = (value: string) => setSearch(value.trim().replace(/\s+/g, " ")); + + const setStatusFilter = (newStatus: string) => setStatus(newStatus); + + return { + ...query, + data, + pageIndex, + pageSize, + search, + setPageIndex, + setPageSize, + setSearchValue, + setStatusFilter, + }; +}; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/index.ts b/modules/customer-invoices/src/web/issued-invoices/pages/list/index.ts new file mode 100644 index 00000000..77624ddb --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/index.ts @@ -0,0 +1 @@ +export * from "./issued-invoice-list-page"; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/issued-invoice-list-page.tsx b/modules/customer-invoices/src/web/issued-invoices/pages/list/issued-invoice-list-page.tsx new file mode 100644 index 00000000..95e2f18a --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/issued-invoice-list-page.tsx @@ -0,0 +1,62 @@ +import { PageHeader } from "@erp/core/components"; +import { ErrorAlert } from "@erp/customers/components"; +import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { PlusIcon } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +import { useTranslation } from "../../../i18n"; + +import { useIssuedInvoicesList } from "./hooks"; +import { IssuedInvoicesGrid } from "./ui/proformas-grid"; + +export const IssuedInvoiceListPage = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const list = useIssuedInvoicesList(); + + if (list.isError || !list.data) { + return ( + + + + + ); + } + + return ( + <> + + navigate("/issued-invoices/create")} + > + + {t("pages.issued_invoices.create.title")} + + } + title={t("pages.issued_invoices.list.title")} + /> + + + + + + ); +}; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/index.ts b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/index.ts new file mode 100644 index 00000000..e41ab026 --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/index.ts @@ -0,0 +1,2 @@ +export * from "./issued-invoice-status-badge"; +export * from "./issued-invoices-grid"; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proforma-status-badge.tsx b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proforma-status-badge.tsx new file mode 100644 index 00000000..5c60a1b3 --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proforma-status-badge.tsx @@ -0,0 +1,68 @@ +import { Badge } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { forwardRef } from "react"; + +import { useTranslation } from "../../../../i18n"; + +export type IssuedInvoiceStatus = "draft" | "sent" | "approved" | "rejected" | "issued"; + +export type IssuedInvoiceStatusBadgeProps = { + status: string | IssuedInvoiceStatus; // permitir cualquier valor + dotVisible?: boolean; + className?: string; +}; + +const statusColorConfig: Record = { + draft: { + badge: + "bg-gray-500/10 dark:bg-gray-500/20 hover:bg-gray-500/10 text-gray-600 border-gray-400/60", + dot: "bg-gray-500", + }, + sent: { + badge: + "bg-amber-500/10 dark:bg-amber-500/20 hover:bg-amber-500/10 text-amber-500 border-amber-600/60", + dot: "bg-amber-500", + }, + approved: { + badge: + "bg-emerald-500/10 dark:bg-emerald-500/20 hover:bg-emerald-500/10 text-emerald-500 border-emerald-600/60", + dot: "bg-emerald-500", + }, + rejected: { + badge: "bg-red-500/10 dark:bg-red-500/20 hover:bg-red-500/10 text-red-500 border-red-600/60", + dot: "bg-red-500", + }, + issued: { + badge: + "bg-blue-600/10 dark:bg-blue-600/20 hover:bg-blue-600/10 text-blue-500 border-blue-600/60", + dot: "bg-blue-500", + }, +}; + +export const IssuedInvoiceStatusBadge = forwardRef( + ({ status, dotVisible, className, ...props }, ref) => { + const { t } = useTranslation(); + + const normalizedStatus = status.toLowerCase() as IssuedInvoiceStatus; + const config = statusColorConfig[normalizedStatus]; + const commonClassName = + "transition-colors duration-200 cursor-pointer shadow-none rounded-full"; + + if (!config) { + return ( + + {status} + + ); + } + + return ( + + {dotVisible &&
} + {t(`catalog.proformas.status.${normalizedStatus}`, { defaultValue: status })} + + ); + } +); + +IssuedInvoiceStatusBadge.displayName = "IssuedInvoiceStatusBadge"; diff --git a/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proformas-grid.tsx b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proformas-grid.tsx new file mode 100644 index 00000000..6be061ba --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/pages/list/ui/proformas-grid.tsx @@ -0,0 +1,105 @@ +import { SimpleSearchInput } from "@erp/core/components"; +import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@repo/shadcn-ui/components"; +import { FilterIcon } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +import { useTranslation } from "../../../../i18n"; +import type { IssuedInvoiceSummaryPageData } from "../../../schema/issued-invoice-summary.web.schema"; +import { useIssuedInvoicesGridColumns } from "../hooks"; + +interface IssuedInvoicesGridProps { + data: IssuedInvoiceSummaryPageData; + loading?: boolean; + pageIndex: number; + pageSize: number; + searchValue: string; + onSearchChange: (v: string) => void; + onPageChange: (p: number) => void; + onPageSizeChange: (s: number) => void; + onRowClick?: (id: string) => void; + onExportClick?: () => void; + onStatusFilterChange?: (newStatus: string) => void; +} + +export const IssuedInvoicesGrid = ({ + data, + loading, + pageIndex, + pageSize, + searchValue, + onSearchChange, + onPageChange, + onPageSizeChange, + onRowClick, + onExportClick, + onStatusFilterChange, +}: IssuedInvoicesGridProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + const { items, total_items } = data; + + const columns = useIssuedInvoicesGridColumns({ + onEdit: (proforma) => navigate(`/issued-invoices/${proforma.id}/edit`), + onDuplicate: (proforma) => null, //duplicateInvoice(inv.id), + onDownloadPdf: (proforma) => null, //downloadInvoicePdf(inv.id), + onSendEmail: (proforma) => null, //sendInvoiceEmail(inv.id), + onDelete: (proforma) => null, //confirmDelete(inv.id), + }); + + if (loading) + return ( + + ); + + return ( +
+
+ + +
+ + onRowClick?.(row.id)} + pageIndex={pageIndex} + pageSize={pageSize} + totalItems={total_items} + /> +
+ ); +}; diff --git a/modules/customer-invoices/src/web/issued-invoices/schema/index.ts b/modules/customer-invoices/src/web/issued-invoices/schema/index.ts new file mode 100644 index 00000000..03bc2fab --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/schema/index.ts @@ -0,0 +1,2 @@ +export * from "./issued-invoice.api.schema"; +export * from "./issued-invoice-summary.web.schema"; diff --git a/modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume.form.schema.ts b/modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice-summary.web.schema.ts similarity index 59% rename from modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume.form.schema.ts rename to modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice-summary.web.schema.ts index d657b331..7d15a57c 100644 --- a/modules/customer-invoices/src/web/issued-invoices/issued-invoice-resume.form.schema.ts +++ b/modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice-summary.web.schema.ts @@ -1,6 +1,6 @@ -import type { IssuedInvoicesummary, IssuedInvoicesummaryPage } from "./issued-invoice.api.schema"; +import type { IssuedInvoiceSummary, IssuedInvoiceSummaryPage } from "./issued-invoice.api.schema"; -export type IssuedInvoicesummaryData = IssuedInvoicesummary & { +export type IssuedInvoiceSummaryData = IssuedInvoiceSummary & { subtotal_amount_fmt: string; subtotal_amount: number; @@ -13,13 +13,13 @@ export type IssuedInvoicesummaryData = IssuedInvoicesummary & { taxable_amount_fmt: string; taxable_amount: number; - taxes_amoun_fmt: string; + taxes_amount_fmt: string; taxes_amount: number; total_amount_fmt: string; total_amount: number; }; -export type IssuedInvoicesummaryPageData = IssuedInvoicesummaryPage & { - items: IssuedInvoicesummary[]; +export type IssuedInvoiceSummaryPageData = IssuedInvoiceSummaryPage & { + items: IssuedInvoiceSummaryData[]; }; diff --git a/modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice.api.schema.ts b/modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice.api.schema.ts new file mode 100644 index 00000000..d8e681cf --- /dev/null +++ b/modules/customer-invoices/src/web/issued-invoices/schema/issued-invoice.api.schema.ts @@ -0,0 +1,22 @@ +import { + GetIssuedInvoiceByIdResponseSchema, + type ListIssuedInvoicesResponseDTO, +} from "@erp/customer-invoices/common"; +import type { ArrayElement } from "@repo/rdx-utils"; +import type { z } from "zod/v4"; + +// IssuedInvoices +export const IssuedInvoiceSchema = GetIssuedInvoiceByIdResponseSchema.omit({ + metadata: true, +}); + +export type IssuedInvoice = z.infer; +export type IssuedInvoiceRecipient = IssuedInvoice["recipient"]; +export type IssuedInvoiceItem = ArrayElement; + +// Resultado de consulta con criteria (paginado, etc.) +export type IssuedInvoiceSummaryPage = Omit; +export type IssuedInvoiceSummary = Omit< + ArrayElement, + "metadata" +>; diff --git a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns.tsx b/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns.tsx index 8e03940a..20223e69 100644 --- a/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/list/hooks/use-proformas-grid-columns.tsx @@ -48,18 +48,18 @@ export function useProformasGridColumns( accessorKey: "invoice_number", header: ({ column }) => ( ), cell: ({ row }) => ( -
{row.original.invoice_number}
+
{row.original.invoice_number}
), enableHiding: false, - enableSorting: false, - size: 160, - minSize: 120, + maxSize: 48, + size: 48, + minSize: 48, meta: { title: t("pages.proformas.list.grid_columns.invoice_number"), }, @@ -74,10 +74,10 @@ export function useProformasGridColumns( title={t("pages.proformas.list.grid_columns.status")} /> ), - cell: ({ row }) => , + cell: ({ row }) => , enableSorting: false, - size: 140, - minSize: 120, + size: 64, + minSize: 64, meta: { title: t("pages.proformas.list.grid_columns.status"), }, @@ -93,15 +93,14 @@ export function useProformasGridColumns( ), accessorFn: (row) => row.recipient.name, // para ordenar/buscar por nombre enableHiding: false, - size: 140, minSize: 120, cell: ({ row }) => { const c = row.original.recipient; return ( -
+
- {c.name} + {c.name}
{c.tin && {c.tin}} @@ -126,8 +125,8 @@ export function useProformasGridColumns( ), cell: ({ row }) =>
{row.original.series}
, enableSorting: false, - size: 120, - minSize: 100, + size: 64, + minSize: 64, meta: { title: t("pages.proformas.list.grid_columns.series"), }, @@ -166,9 +165,8 @@ export function useProformasGridColumns( {formatDate(row.original.invoice_date)}
), - enableSorting: false, - size: 140, - minSize: 120, + size: 96, + minSize: 96, meta: { title: t("pages.proformas.list.grid_columns.invoice_date"), }, @@ -188,9 +186,8 @@ export function useProformasGridColumns( {formatDate(row.original.operation_date)}
), - enableSorting: false, - size: 140, - minSize: 120, + size: 96, + minSize: 96, meta: { title: t("pages.proformas.list.grid_columns.operation_date"), }, @@ -291,7 +288,13 @@ export function useProformasGridColumns( // ───────────────────────────── { id: "actions", - header: () => {t("common.actions")}, + header: ({ column }) => ( + + ), enableSorting: false, enableHiding: false, size: 110, @@ -381,7 +384,7 @@ export function useProformasGridColumns( {/* Menú demás acciones */} {/** biome-ignore lint/suspicious/noSelfCompare: */} - {false === false && ( + {false !== false && (
- ) + ); } diff --git a/packages/rdx-ui/src/components/datatable/data-table-pagination.tsx b/packages/rdx-ui/src/components/datatable/data-table-pagination.tsx index 6975f66a..552cec27 100644 --- a/packages/rdx-ui/src/components/datatable/data-table-pagination.tsx +++ b/packages/rdx-ui/src/components/datatable/data-table-pagination.tsx @@ -1,23 +1,26 @@ -import { Table } from "@tanstack/react-table"; import { - ChevronLeftIcon, - ChevronRightIcon, - ChevronsLeftIcon, - ChevronsRightIcon -} from "lucide-react"; - -import { - Pagination, PaginationContent, - PaginationItem, PaginationLink, + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, Select, SelectContent, SelectItem, SelectTrigger, - SelectValue -} from '@repo/shadcn-ui/components'; -import { cn } from '@repo/shadcn-ui/lib/utils'; -import { useTranslation } from '../../locales/i18n.ts'; -import { DataTableMeta } from './data-table.tsx'; + SelectValue, +} from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import type { Table } from "@tanstack/react-table"; +import { + ChevronLeftIcon, + ChevronRightIcon, + ChevronsLeftIcon, + ChevronsRightIcon, +} from "lucide-react"; + +import { useTranslation } from "../../locales/i18n.ts"; + +import type { DataTableMeta } from "./data-table.tsx"; interface DataTablePaginationProps { table: Table; @@ -28,7 +31,11 @@ interface DataTablePaginationProps { } export function DataTablePagination({ - table, onPageChange, onPageSizeChange, className }: DataTablePaginationProps) { + table, + onPageChange, + onPageSizeChange, + className, +}: DataTablePaginationProps) { const { t } = useTranslation(); const { pageIndex: rawIndex, pageSize: rawSize } = table.getState().pagination; @@ -50,10 +57,10 @@ export function DataTablePagination({ const notify = (next: Partial<{ pageIndex: number; pageSize: number }>) => table.options.onPaginationChange?.({ pageIndex, pageSize, ...next }); - const gotoPage = (index: number) => onPageChange ? onPageChange(index) : notify({ pageIndex: index }); - const handlePageSizeChange = (size: string) => onPageSizeChange ? - onPageSizeChange(Number(size)) : - notify({ pageSize: Number(size) }); + const gotoPage = (index: number) => + onPageChange ? onPageChange(index) : notify({ pageIndex: index }); + const handlePageSizeChange = (size: string) => + onPageSizeChange ? onPageSizeChange(Number(size)) : notify({ pageSize: Number(size) }); const gotoPreviousPage = () => gotoPage(pageIndex - 1); const gotoNextPage = () => gotoPage(pageIndex + 1); @@ -79,7 +86,7 @@ export function DataTablePagination({
{t("components.datatable.pagination.rows_per_page")} - @@ -91,7 +98,6 @@ export function DataTablePagination({ ))} - {pageIndex + 1} / {pageCount}
@@ -102,12 +108,12 @@ export function DataTablePagination({ 0} onClick={() => { if (pageIndex > 0) gotoFirstPage(); }} - isActive={pageIndex > 0} size="sm" - className="px-2.5 cursor-pointer" > @@ -116,30 +122,30 @@ export function DataTablePagination({ 0} onClick={() => { if (pageIndex > 0) gotoPreviousPage(); }} - isActive={pageIndex > 0} size="sm" - className="px-2.5 cursor-pointer" > - + {t("components.datatable.pagination.page_of", { page: pageIndex + 1, of: pageCount })} { if (pageIndex < pageCount - 1) gotoNextPage(); }} - isActive={pageIndex < pageCount - 1} size="sm" - className="px-2.5 cursor-pointer" > @@ -148,12 +154,12 @@ export function DataTablePagination({ { if (pageIndex < pageCount - 1) gotoLastPage(); }} - isActive={pageIndex < pageCount - 1} size="sm" - className="px-2.5 cursor-pointer" > @@ -163,4 +169,4 @@ export function DataTablePagination({
); -} \ No newline at end of file +} diff --git a/packages/rdx-ui/src/components/datatable/data-table-view-options.tsx b/packages/rdx-ui/src/components/datatable/data-table-view-options.tsx index d15765ea..88da6916 100644 --- a/packages/rdx-ui/src/components/datatable/data-table-view-options.tsx +++ b/packages/rdx-ui/src/components/datatable/data-table-view-options.tsx @@ -21,7 +21,7 @@ export function DataTableViewOptions({ table }: { table: Table }) diff --git a/packages/rdx-ui/src/locales/en.json b/packages/rdx-ui/src/locales/en.json index 80151ee5..2836bb16 100644 --- a/packages/rdx-ui/src/locales/en.json +++ b/packages/rdx-ui/src/locales/en.json @@ -36,7 +36,7 @@ } }, "datatable_view_options": { - "view_button": "View", + "columns_button": "Columns", "toggle_columns": "Toggle columns" }, "loading_indicator": { diff --git a/packages/rdx-ui/src/locales/es.json b/packages/rdx-ui/src/locales/es.json index 46a6f013..19ac63ee 100644 --- a/packages/rdx-ui/src/locales/es.json +++ b/packages/rdx-ui/src/locales/es.json @@ -11,8 +11,8 @@ }, "components": { "datatable": { - "asc": "Asc", - "desc": "Desc", + "asc": "Ascendente", + "desc": "Descendente", "hide": "Ocultar", "empty": "No hay resultados", "selection_summary": "{{count}} filas seleccionadas de {{total}}", @@ -39,7 +39,7 @@ } }, "datatable_view_options": { - "view_button": "Ver", + "columns_button": "Columnas", "toggle_columns": "Alternar columnas" }, "loading_indicator": {