From 24f7bd0fb9ec3debc8ac01f60782e5d895d51d4a Mon Sep 17 00:00:00 2001 From: david Date: Tue, 10 Mar 2026 18:10:11 +0100 Subject: [PATCH] Clientes --- .../core/src/web/components/error-alert.tsx | 15 + modules/core/src/web/components/index.ts | 1 + .../src/web/components/editor/index.ts | 4 +- .../src/web/components/error-alert.tsx | 16 - modules/customers/src/web/components/index.ts | 10 +- modules/customers/src/web/customer-routes.tsx | 4 +- modules/customers/src/web/hooks/index.ts | 8 +- .../web/hooks/use-create-customer-mutation.ts | 8 +- .../web/hooks/use-update-customer-mutation.ts | 6 +- .../adapters/customer-summary-dto.adapter.ts | 3 +- .../customers/src/web/list/api/api-types.ts | 6 + .../src/web/list/api/get-customer-list.api.ts | 3 +- modules/customers/src/web/list/api/index.ts | 1 + .../list/hooks/use-customer-list-query.tsx | 15 +- modules/customers/src/web/list/types/index.ts | 1 + .../types/types.ts} | 5 +- .../blocks/customers-grid/customers-grid.tsx | 4 +- .../use-customer-grid-columns.tsx | 111 +++--- .../web/list/ui/components/address-cell.tsx | 9 +- .../web/list/ui/components/contact-cell.tsx | 11 +- .../web/list/ui/pages/customer-list-page.tsx | 26 +- .../src/web/schemas/customer.api.schema.ts | 6 +- .../src/web/types/customer.api.schema.ts | 17 - modules/customers/src/web/types/index.ts | 2 - .../web/view/adapters/customer-dto.adapter.ts | 13 + .../customers/src/web/view/adapters/index.ts | 1 + .../customers/src/web/view/api/api-types.ts | 3 + .../web/view/api/get-customer-by-ip.api.ts | 9 + modules/customers/src/web/view/api/index.ts | 2 + .../src/web/view/controllers/index.ts | 2 + .../use-customer-view-page.controller.ts | 9 + .../use-customer-view.controller.ts | 21 ++ modules/customers/src/web/view/hooks/index.ts | 1 + .../src/web/view/hooks/use-customer-query.ts | 47 +++ modules/customers/src/web/view/index.ts | 1 + modules/customers/src/web/view/types/index.ts | 1 + modules/customers/src/web/view/types/types.ts | 3 + .../components}/customer-editor-skeleton.tsx | 2 +- .../src/web/view/ui/components/index.ts | 1 + modules/customers/src/web/view/ui/index.ts | 1 + .../web/view/ui/pages/customer-view-page copy | 333 ++++++++++++++++++ .../web/view/ui/pages/customer-view-page.tsx | 324 +++++++++++++++++ .../customers/src/web/view/ui/pages/index.ts | 1 + modules/customers/tsconfig.json | 2 +- 44 files changed, 918 insertions(+), 151 deletions(-) create mode 100644 modules/core/src/web/components/error-alert.tsx delete mode 100644 modules/customers/src/web/components/error-alert.tsx create mode 100644 modules/customers/src/web/list/api/api-types.ts create mode 100644 modules/customers/src/web/list/types/index.ts rename modules/customers/src/web/{types/customer-summary.web.schema.ts => list/types/types.ts} (55%) delete mode 100644 modules/customers/src/web/types/customer.api.schema.ts delete mode 100644 modules/customers/src/web/types/index.ts create mode 100644 modules/customers/src/web/view/adapters/customer-dto.adapter.ts create mode 100644 modules/customers/src/web/view/adapters/index.ts create mode 100644 modules/customers/src/web/view/api/api-types.ts create mode 100644 modules/customers/src/web/view/api/get-customer-by-ip.api.ts create mode 100644 modules/customers/src/web/view/api/index.ts create mode 100644 modules/customers/src/web/view/controllers/index.ts create mode 100644 modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts create mode 100644 modules/customers/src/web/view/controllers/use-customer-view.controller.ts create mode 100644 modules/customers/src/web/view/hooks/index.ts create mode 100644 modules/customers/src/web/view/hooks/use-customer-query.ts create mode 100644 modules/customers/src/web/view/index.ts create mode 100644 modules/customers/src/web/view/types/index.ts create mode 100644 modules/customers/src/web/view/types/types.ts rename modules/customers/src/web/{components/editor => view/ui/components}/customer-editor-skeleton.tsx (96%) create mode 100644 modules/customers/src/web/view/ui/components/index.ts create mode 100644 modules/customers/src/web/view/ui/index.ts create mode 100644 modules/customers/src/web/view/ui/pages/customer-view-page copy create mode 100644 modules/customers/src/web/view/ui/pages/customer-view-page.tsx create mode 100644 modules/customers/src/web/view/ui/pages/index.ts diff --git a/modules/core/src/web/components/error-alert.tsx b/modules/core/src/web/components/error-alert.tsx new file mode 100644 index 00000000..a1a6f8f4 --- /dev/null +++ b/modules/core/src/web/components/error-alert.tsx @@ -0,0 +1,15 @@ +interface ErrorAlertProps { + title: string; + message: string; +} + +export const ErrorAlert = ({ title, message }: ErrorAlertProps) => ( +
+

{title}

+

{message}

+
+); diff --git a/modules/core/src/web/components/index.ts b/modules/core/src/web/components/index.ts index 7ae52b84..84a45556 100644 --- a/modules/core/src/web/components/index.ts +++ b/modules/core/src/web/components/index.ts @@ -1,2 +1,3 @@ +export * from "./error-alert"; export * from "./form"; export * from "./page-header"; diff --git a/modules/customers/src/web/components/editor/index.ts b/modules/customers/src/web/components/editor/index.ts index 2ecb2b3d..a09d2728 100644 --- a/modules/customers/src/web/components/editor/index.ts +++ b/modules/customers/src/web/components/editor/index.ts @@ -1,2 +1,2 @@ -export * from "./customer-edit-form"; -export * from "./customer-editor-skeleton"; +//export * from "./customer-edit-form"; +export * from "../../view/ui/components/customer-editor-skeleton"; diff --git a/modules/customers/src/web/components/error-alert.tsx b/modules/customers/src/web/components/error-alert.tsx deleted file mode 100644 index 01098060..00000000 --- a/modules/customers/src/web/components/error-alert.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// components/ErrorAlert.tsx -interface ErrorAlertProps { - title: string; - message: string; -} - -export const ErrorAlert = ({ title, message }: ErrorAlertProps) => ( -
-

{title}

-

{message}

-
-); diff --git a/modules/customers/src/web/components/index.ts b/modules/customers/src/web/components/index.ts index f4cec8d0..bf45a03e 100644 --- a/modules/customers/src/web/components/index.ts +++ b/modules/customers/src/web/components/index.ts @@ -1,5 +1,5 @@ -export * from "./client-selector-modal"; -export * from "./customer-modal-selector"; -export * from "./editor"; -export * from "./error-alert"; -export * from "./not-found-card"; +//export * from "./client-selector-modal"; +//export * from "./customer-modal-selector"; +//export * from "./editor"; +export * from "../../../../core/src/web/components/error-alert"; +//export * from "./not-found-card"; diff --git a/modules/customers/src/web/customer-routes.tsx b/modules/customers/src/web/customer-routes.tsx index 0048d7c5..840d0f56 100644 --- a/modules/customers/src/web/customer-routes.tsx +++ b/modules/customers/src/web/customer-routes.tsx @@ -5,8 +5,8 @@ import { Outlet, type RouteObject } from "react-router-dom"; // Lazy load components const CustomerLayout = lazy(() => import("./ui").then((m) => ({ default: m.CustomerLayout }))); const CustomersList = lazy(() => import("./list").then((m) => ({ default: m.CustomerListPage }))); +const CustomerView = lazy(() => import("./view").then((m) => ({ default: m.CustomerViewPage }))); -//const CustomerView = lazy(() => import("./pages").then((m) => ({ default: m.CustomerViewPage }))); //const CustomerAdd = lazy(() => import("./pages").then((m) => ({ default: m.CustomerCreatePage }))); /*const CustomerUpdate = lazy(() => import("./pages").then((m) => ({ default: m.CustomerUpdatePage })) @@ -25,7 +25,7 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => { { path: "", index: true, element: }, // index { path: "list", element: }, //{ path: "create", element: }, - //{ path: ":id", element: }, + { path: ":id", element: }, //{ path: ":id/edit", element: }, // diff --git a/modules/customers/src/web/hooks/index.ts b/modules/customers/src/web/hooks/index.ts index 019eb746..65f6a1d0 100644 --- a/modules/customers/src/web/hooks/index.ts +++ b/modules/customers/src/web/hooks/index.ts @@ -1,4 +1,4 @@ -export * from "./use-create-customer-mutation"; -export * from "./use-customer-query"; -export * from "./use-customers-context"; -export * from "./use-update-customer-mutation"; +//export * from "./use-create-customer-mutation"; +//export * from "./use-customer-query"; +//export * from "./use-customers-context"; +//export * from "./use-update-customer-mutation"; diff --git a/modules/customers/src/web/hooks/use-create-customer-mutation.ts b/modules/customers/src/web/hooks/use-create-customer-mutation.ts index 23cc20c6..6768e5fe 100644 --- a/modules/customers/src/web/hooks/use-create-customer-mutation.ts +++ b/modules/customers/src/web/hooks/use-create-customer-mutation.ts @@ -1,9 +1,11 @@ import { useDataSource } from "@erp/core/hooks"; import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd"; -import { DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; -import { ZodError } from "zod/v4"; +import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; +import type { ZodError } from "zod/v4"; + import { CreateCustomerRequestSchema } from "../../common"; -import { Customer, CustomerFormData } from "../schemas"; +import type { Customer, CustomerFormData } from "../schemas"; + import { CUSTOMERS_LIST_KEY, invalidateCustomerListCache } from "./use-customer-list-query"; import { setCustomerDetailCache } from "./use-customer-query"; diff --git a/modules/customers/src/web/hooks/use-update-customer-mutation.ts b/modules/customers/src/web/hooks/use-update-customer-mutation.ts index 562bca2d..7325206c 100644 --- a/modules/customers/src/web/hooks/use-update-customer-mutation.ts +++ b/modules/customers/src/web/hooks/use-update-customer-mutation.ts @@ -1,8 +1,10 @@ import { useDataSource } from "@erp/core/hooks"; import { ValidationErrorCollection } from "@repo/rdx-ddd"; -import { DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; +import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; + import { UpdateCustomerByIdRequestSchema } from "../../common"; -import { Customer, CustomerFormData } from "../schemas"; +import type { Customer, CustomerFormData } from "../schemas"; + import { toValidationErrors } from "./use-create-customer-mutation"; import { invalidateCustomerListCache, diff --git a/modules/customers/src/web/list/adapters/customer-summary-dto.adapter.ts b/modules/customers/src/web/list/adapters/customer-summary-dto.adapter.ts index 6fe1747d..e98ba75a 100644 --- a/modules/customers/src/web/list/adapters/customer-summary-dto.adapter.ts +++ b/modules/customers/src/web/list/adapters/customer-summary-dto.adapter.ts @@ -1,4 +1,5 @@ -import type { CustomerSummaryPage, CustomerSummaryPageData } from "../../types"; +import type { CustomerSummaryPage } from "../api"; +import type { CustomerSummaryPageData } from "../types"; /** * Convierte el DTO completo de API a datos numéricos para el formulario. diff --git a/modules/customers/src/web/list/api/api-types.ts b/modules/customers/src/web/list/api/api-types.ts new file mode 100644 index 00000000..984beff5 --- /dev/null +++ b/modules/customers/src/web/list/api/api-types.ts @@ -0,0 +1,6 @@ +import type { ListCustomersResponseDTO } from "@erp/customers/common"; +import type { ArrayElement } from "@repo/rdx-utils"; + +// Resultado de consulta con criteria (paginado, etc.) +export type CustomerSummaryPage = Omit; +export type CustomerSummary = Omit, "metadata">; diff --git a/modules/customers/src/web/list/api/get-customer-list.api.ts b/modules/customers/src/web/list/api/get-customer-list.api.ts index 0f7d2540..20874765 100644 --- a/modules/customers/src/web/list/api/get-customer-list.api.ts +++ b/modules/customers/src/web/list/api/get-customer-list.api.ts @@ -1,7 +1,7 @@ import type { CriteriaDTO } from "@erp/core"; import type { IDataSource } from "@erp/core/client"; -import type { CustomerSummaryPage } from "../../types"; +import type { CustomerSummaryPage } from "./api-types"; export async function getCustomerListApi( dataSource: IDataSource, @@ -13,6 +13,5 @@ export async function getCustomerListApi( ...criteria, }); - //return mapIssuedInvoiceList(raw); return response; } diff --git a/modules/customers/src/web/list/api/index.ts b/modules/customers/src/web/list/api/index.ts index c4929307..5b8ede7c 100644 --- a/modules/customers/src/web/list/api/index.ts +++ b/modules/customers/src/web/list/api/index.ts @@ -1 +1,2 @@ +export * from "./api-types"; export * from "./get-customer-list.api"; diff --git a/modules/customers/src/web/list/hooks/use-customer-list-query.tsx b/modules/customers/src/web/list/hooks/use-customer-list-query.tsx index b880212a..f3d4c390 100644 --- a/modules/customers/src/web/list/hooks/use-customer-list-query.tsx +++ b/modules/customers/src/web/list/hooks/use-customer-list-query.tsx @@ -1,10 +1,14 @@ import type { CriteriaDTO } from "@erp/core"; import { useDataSource } from "@erp/core/hooks"; import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria"; -import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query"; +import { + type DefaultError, + type QueryKey, + type UseQueryResult, + useQuery, +} from "@tanstack/react-query"; -import type { CustomerSummaryPage } from "../../types"; -import { getCustomerListApi } from "../api"; +import { type CustomerSummaryPage, getCustomerListApi } from "../api"; export const CUSTOMERS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey => [ "customers", @@ -23,7 +27,9 @@ type CustomersQueryOptions = { criteria?: CriteriaDTO; }; -export const useCustomerListQuery = (options?: CustomersQueryOptions) => { +export const useCustomerListQuery = ( + options?: CustomersQueryOptions +): UseQueryResult => { const dataSource = useDataSource(); const enabled = options?.enabled ?? true; const criteria = options?.criteria ?? {}; @@ -37,7 +43,6 @@ export const useCustomerListQuery = (options?: CustomersQueryOptions) => { }; /* - export function cancelCustomerListQueries(qc: QueryClient) { return qc.cancelQueries({ queryKey: CUSTOMERS_LIST_KEY }); } diff --git a/modules/customers/src/web/list/types/index.ts b/modules/customers/src/web/list/types/index.ts new file mode 100644 index 00000000..eea524d6 --- /dev/null +++ b/modules/customers/src/web/list/types/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/modules/customers/src/web/types/customer-summary.web.schema.ts b/modules/customers/src/web/list/types/types.ts similarity index 55% rename from modules/customers/src/web/types/customer-summary.web.schema.ts rename to modules/customers/src/web/list/types/types.ts index 625e1ef6..2044f0b3 100644 --- a/modules/customers/src/web/types/customer-summary.web.schema.ts +++ b/modules/customers/src/web/list/types/types.ts @@ -1,6 +1,5 @@ -import type { CustomerSummary } from "../schemas"; - -import type { CustomerSummaryPage } from "./customer.api.schema"; +import type { CustomerSummary } from "../../schemas"; +import type { CustomerSummaryPage } from "../api"; export type CustomerSummaryData = CustomerSummary; diff --git a/modules/customers/src/web/list/ui/blocks/customers-grid/customers-grid.tsx b/modules/customers/src/web/list/ui/blocks/customers-grid/customers-grid.tsx index c3a12d19..9c1bcc2d 100644 --- a/modules/customers/src/web/list/ui/blocks/customers-grid/customers-grid.tsx +++ b/modules/customers/src/web/list/ui/blocks/customers-grid/customers-grid.tsx @@ -3,7 +3,7 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "../../../../i18n"; -import type { CustomerSummaryData, CustomerSummaryPageData } from "../../../../types"; +import type { CustomerSummaryData, CustomerSummaryPageData } from "../../../types"; interface CustomersGridProps { data: CustomerSummaryPageData; @@ -58,4 +58,4 @@ export const CustomersGrid = ({ totalItems={total_items} /> ); -} +}; diff --git a/modules/customers/src/web/list/ui/blocks/customers-grid/use-customer-grid-columns.tsx b/modules/customers/src/web/list/ui/blocks/customers-grid/use-customer-grid-columns.tsx index 9fdd3c32..fead1165 100644 --- a/modules/customers/src/web/list/ui/blocks/customers-grid/use-customer-grid-columns.tsx +++ b/modules/customers/src/web/list/ui/blocks/customers-grid/use-customer-grid-columns.tsx @@ -2,24 +2,21 @@ import { DataTableColumnHeader } from "@repo/rdx-ui/components"; import { Avatar, AvatarFallback, - Button, DropdownMenu, + Button, + DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, - DropdownMenuTrigger + DropdownMenuTrigger, } from "@repo/shadcn-ui/components"; import type { ColumnDef } from "@tanstack/react-table"; -import { - EyeIcon, MoreHorizontalIcon -} from "lucide-react"; +import { EyeIcon, MoreHorizontalIcon } from "lucide-react"; import * as React from "react"; import { useTranslation } from "../../../../i18n"; -import { - type CustomerSummaryData, -} from "../../../../types"; import { CustomerStatusBadge } from "../../../../ui"; +import type { CustomerSummaryData } from "../../../types"; import { AddressCell, ContactCell, Initials } from "../../components"; import { KindBadge } from "../../components/kind-badge"; import { Soft } from "../../components/soft"; @@ -86,12 +83,14 @@ export function useCustomersGridColumns( return (
- + + +
{" "} - {customer.name} + {customer.name} {customer.trade_name && ({customer.trade_name})}
@@ -105,22 +104,22 @@ export function useCustomersGridColumns( }, // Contacto (emails, teléfonos, web) - { - id: "contact", - header: ({ column }) => ( - - ), - accessorFn: (r) => `${r.email_primary} ${r.phone_primary} ${r.mobile_primary} ${r.website}`, - size: 140, - minSize: 120, - cell: ({ row }) => , - }, + { + id: "contact", + header: ({ column }) => ( + + ), + accessorFn: (r) => `${r.email_primary} ${r.phone_primary} ${r.mobile_primary} ${r.website}`, + size: 140, + minSize: 120, + cell: ({ row }) => , + }, - // Dirección (múltiples campos en bloque) + // Dirección (múltiples campos en bloque) { id: "address", header: t("pages.list.grid_columns.address"), @@ -159,37 +158,37 @@ export function useCustomersGridColumns( - {0 === false && ( - - - - - - Actions - - actionHandlers.onViewClick?.(customer)}>Open - actionHandlers.onEditClick?.(customer)}>Edit - - window.open(safeHttp(website), "_blank")}> - Visit website - - navigator.clipboard.writeText(email_primary)} - > - Copy email - - - actionHandlers.onDeleteClick?.(customer)} - > - Delete - - - - )} + + + + + + Actions + + actionHandlers.onViewClick?.(customer)}> + Open + + actionHandlers.onEditClick?.(customer)}> + Edit + + + window.open(safeHttp(website), "_blank")}> + Visit website + + navigator.clipboard.writeText(email_primary)}> + Copy email + + + actionHandlers.onDeleteClick?.(customer)} + > + Delete + + +
); diff --git a/modules/customers/src/web/list/ui/components/address-cell.tsx b/modules/customers/src/web/list/ui/components/address-cell.tsx index 8e713f17..9125ed2a 100644 --- a/modules/customers/src/web/list/ui/components/address-cell.tsx +++ b/modules/customers/src/web/list/ui/components/address-cell.tsx @@ -1,11 +1,8 @@ -import { - type CustomerSummaryData, -} from "../../../types"; +import type { CustomerSummaryData } from "../../types"; -import { Soft } from './soft'; +import { Soft } from "./soft"; - -export const AddressCell = ({ customer }: { customer: CustomerSummaryData; }) => { +export const AddressCell = ({ customer }: { customer: CustomerSummaryData }) => { const line1 = [customer.street, customer.street2].filter(Boolean).join(", "); const line2 = [customer.postal_code, customer.city].filter(Boolean).join(" "); const line3 = [customer.province, customer.country].filter(Boolean).join(", "); diff --git a/modules/customers/src/web/list/ui/components/contact-cell.tsx b/modules/customers/src/web/list/ui/components/contact-cell.tsx index 3c719a70..ae8e8627 100644 --- a/modules/customers/src/web/list/ui/components/contact-cell.tsx +++ b/modules/customers/src/web/list/ui/components/contact-cell.tsx @@ -1,9 +1,8 @@ -import { MailIcon, PhoneIcon } from 'lucide-react'; -import { Soft } from './soft'; -import { - type CustomerSummaryData, -} from "../../../types"; +import { MailIcon, PhoneIcon } from "lucide-react"; +import type { CustomerSummaryData } from "../../types"; + +import { Soft } from "./soft"; export const ContactCell = ({ customer }: { customer: CustomerSummaryData }) => (
@@ -32,4 +31,4 @@ export const ContactCell = ({ customer }: { customer: CustomerSummaryData }) =>
{false}
-); \ No newline at end of file +); diff --git a/modules/customers/src/web/list/ui/pages/customer-list-page.tsx b/modules/customers/src/web/list/ui/pages/customer-list-page.tsx index ae364c5e..9db16236 100644 --- a/modules/customers/src/web/list/ui/pages/customer-list-page.tsx +++ b/modules/customers/src/web/list/ui/pages/customer-list-page.tsx @@ -1,10 +1,13 @@ +import { PageHeader, SimpleSearchInput } from "@erp/core/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 { useCustomerListPageController } from '../../controllers'; -import { useCustomersGridColumns } from '../blocks'; -import { AppContent, BackHistoryButton } from '@repo/rdx-ui/components'; -import { ErrorAlert } from '../../../ui'; +import { ErrorAlert } from "../../../ui"; +import { useCustomerListPageController } from "../../controllers"; +import { CustomersGrid, useCustomersGridColumns } from "../blocks"; export const CustomerListPage = () => { const { t } = useTranslation(); @@ -12,9 +15,11 @@ export const CustomerListPage = () => { const { listCtrl } = useCustomerListPageController(); - const columns = useCustomersGridColumns({}); - - + const columns = useCustomersGridColumns({ + onEditClick: (customer) => navigate(`/customers/${customer.id}/edit`), + onViewClick: (customer) => navigate(`/customers/${customer.id}`), + onDeleteClick: (customer) => null, //confirmDelete(inv.id), + }); if (listCtrl.isError || !listCtrl.data) { return ( @@ -26,10 +31,8 @@ export const CustomerListPage = () => { ); - } + } - return

gika

; -/* return ( <> @@ -68,6 +71,5 @@ export const CustomerListPage = () => { /> - );*/ + ); }; - diff --git a/modules/customers/src/web/schemas/customer.api.schema.ts b/modules/customers/src/web/schemas/customer.api.schema.ts index 2bab9a9a..24ce1df9 100644 --- a/modules/customers/src/web/schemas/customer.api.schema.ts +++ b/modules/customers/src/web/schemas/customer.api.schema.ts @@ -1,7 +1,7 @@ -import { z } from "zod/v4"; +import type { PaginationSchema } from "@erp/core"; +import type { ArrayElement } from "@repo/rdx-utils"; +import type { z } from "zod/v4"; -import { PaginationSchema } from "@erp/core"; -import { ArrayElement } from "@repo/rdx-utils"; import { CreateCustomerRequestSchema, GetCustomerByIdResponseSchema, diff --git a/modules/customers/src/web/types/customer.api.schema.ts b/modules/customers/src/web/types/customer.api.schema.ts deleted file mode 100644 index 6eec62d6..00000000 --- a/modules/customers/src/web/types/customer.api.schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - GetCustomerByIdResponseSchema, - type ListCustomersResponseDTO, -} from "@erp/customers/common"; -import type { ArrayElement } from "@repo/rdx-utils"; -import type { z } from "zod/v4"; - -// IssuedInvoices -export const CustomerSchema = GetCustomerByIdResponseSchema.omit({ - metadata: true, -}); - -export type Customer = z.infer; - -// Resultado de consulta con criteria (paginado, etc.) -export type CustomerSummaryPage = Omit; -export type CustomerSummary = Omit, "metadata">; diff --git a/modules/customers/src/web/types/index.ts b/modules/customers/src/web/types/index.ts deleted file mode 100644 index 532ca68d..00000000 --- a/modules/customers/src/web/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./customer.api.schema"; -export * from "./customer-summary.web.schema"; diff --git a/modules/customers/src/web/view/adapters/customer-dto.adapter.ts b/modules/customers/src/web/view/adapters/customer-dto.adapter.ts new file mode 100644 index 00000000..13a938ee --- /dev/null +++ b/modules/customers/src/web/view/adapters/customer-dto.adapter.ts @@ -0,0 +1,13 @@ +import type { Customer } from "../api"; +import type { CustomerData } from "../types"; + +/** + * Convierte el DTO completo de API a datos numéricos para el formulario. + */ +export const CustomerDtoAdapter = { + fromDto(customerDto: Customer, context?: unknown): CustomerData { + return { + ...customerDto, + }; + }, +}; diff --git a/modules/customers/src/web/view/adapters/index.ts b/modules/customers/src/web/view/adapters/index.ts new file mode 100644 index 00000000..bc45e2cf --- /dev/null +++ b/modules/customers/src/web/view/adapters/index.ts @@ -0,0 +1 @@ +export * from "./customer-dto.adapter"; diff --git a/modules/customers/src/web/view/api/api-types.ts b/modules/customers/src/web/view/api/api-types.ts new file mode 100644 index 00000000..ae2c0977 --- /dev/null +++ b/modules/customers/src/web/view/api/api-types.ts @@ -0,0 +1,3 @@ +import type { GetCustomerByIdResponseDTO } from "@erp/customers/common"; + +export type Customer = Omit; diff --git a/modules/customers/src/web/view/api/get-customer-by-ip.api.ts b/modules/customers/src/web/view/api/get-customer-by-ip.api.ts new file mode 100644 index 00000000..a9b844c9 --- /dev/null +++ b/modules/customers/src/web/view/api/get-customer-by-ip.api.ts @@ -0,0 +1,9 @@ +import type { IDataSource } from "@erp/core/client"; + +import type { Customer } from "./api-types"; + +export async function getCustomerById(dataSource: IDataSource, signal: AbortSignal, id?: string) { + if (!id) throw new Error("customerId is required"); + const response = dataSource.getOne("customers", id, { signal }); + return response; +} diff --git a/modules/customers/src/web/view/api/index.ts b/modules/customers/src/web/view/api/index.ts new file mode 100644 index 00000000..83fe3b24 --- /dev/null +++ b/modules/customers/src/web/view/api/index.ts @@ -0,0 +1,2 @@ +export * from "./api-types"; +export * from "./get-customer-by-ip.api"; diff --git a/modules/customers/src/web/view/controllers/index.ts b/modules/customers/src/web/view/controllers/index.ts new file mode 100644 index 00000000..c493a2b7 --- /dev/null +++ b/modules/customers/src/web/view/controllers/index.ts @@ -0,0 +1,2 @@ +export * from "./use-customer-view.controller"; +export * from "./use-customer-view-page.controller"; diff --git a/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts b/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts new file mode 100644 index 00000000..a48a0acb --- /dev/null +++ b/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts @@ -0,0 +1,9 @@ +import { useCustomerViewController } from "./use-customer-view.controller"; + +export function useCustomerViewPageController() { + const viewCtrl = useCustomerViewController(); + + return { + viewCtrl, + }; +} diff --git a/modules/customers/src/web/view/controllers/use-customer-view.controller.ts b/modules/customers/src/web/view/controllers/use-customer-view.controller.ts new file mode 100644 index 00000000..ae9d1ad8 --- /dev/null +++ b/modules/customers/src/web/view/controllers/use-customer-view.controller.ts @@ -0,0 +1,21 @@ +import { useMemo, useState } from "react"; + +import { CustomerDtoAdapter } from "../adapters"; +import { useCustomerGetQuery } from "../hooks"; + +export const useCustomerViewController = () => { + const [customerId, setCustomerId] = useState(""); + + const query = useCustomerGetQuery(customerId); + const data = useMemo( + () => (query.data ? CustomerDtoAdapter.fromDto(query.data) : undefined), + [query.data] + ); + + return { + ...query, + data, + customerId, + setCustomerId, + }; +}; diff --git a/modules/customers/src/web/view/hooks/index.ts b/modules/customers/src/web/view/hooks/index.ts new file mode 100644 index 00000000..e75bf5cf --- /dev/null +++ b/modules/customers/src/web/view/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-customer-query"; diff --git a/modules/customers/src/web/view/hooks/use-customer-query.ts b/modules/customers/src/web/view/hooks/use-customer-query.ts new file mode 100644 index 00000000..816d5fa7 --- /dev/null +++ b/modules/customers/src/web/view/hooks/use-customer-query.ts @@ -0,0 +1,47 @@ +import { useDataSource } from "@erp/core/hooks"; +import { + type DefaultError, + type QueryKey, + type UseQueryResult, + useQuery, +} from "@tanstack/react-query"; + +import { type Customer, getCustomerById } from "../api"; + +export const CUSTOMER_QUERY_KEY = (customerId?: string): QueryKey => [ + "customers:detail", + { + customerId, + }, +]; + +type CustomerQueryOptions = { + enabled?: boolean; +}; + +export const useCustomerGetQuery = ( + customerId?: string, + options?: CustomerQueryOptions +): UseQueryResult => { + const dataSource = useDataSource(); + const enabled = options?.enabled ?? Boolean(customerId); + + return useQuery({ + queryKey: CUSTOMER_QUERY_KEY(customerId), + queryFn: async ({ signal }) => getCustomerById(dataSource, signal, customerId), + enabled, + placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`) + }); +}; + +/*export function invalidateCustomerDetailCache(qc: QueryClient, id: string) { + return qc.invalidateQueries({ + queryKey: getCustomerQueryKey(id ?? "unknown"), + exact: Boolean(id), + }); +} + +export function setCustomerDetailCache(qc: QueryClient, id: string, data: unknown) { + qc.setQueryData(getCustomerQueryKey(id), data); +} +*/ diff --git a/modules/customers/src/web/view/index.ts b/modules/customers/src/web/view/index.ts new file mode 100644 index 00000000..4aedf593 --- /dev/null +++ b/modules/customers/src/web/view/index.ts @@ -0,0 +1 @@ +export * from "./ui"; diff --git a/modules/customers/src/web/view/types/index.ts b/modules/customers/src/web/view/types/index.ts new file mode 100644 index 00000000..eea524d6 --- /dev/null +++ b/modules/customers/src/web/view/types/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/modules/customers/src/web/view/types/types.ts b/modules/customers/src/web/view/types/types.ts new file mode 100644 index 00000000..bb510873 --- /dev/null +++ b/modules/customers/src/web/view/types/types.ts @@ -0,0 +1,3 @@ +import type { Customer } from "../api"; + +export type CustomerData = Customer; diff --git a/modules/customers/src/web/components/editor/customer-editor-skeleton.tsx b/modules/customers/src/web/view/ui/components/customer-editor-skeleton.tsx similarity index 96% rename from modules/customers/src/web/components/editor/customer-editor-skeleton.tsx rename to modules/customers/src/web/view/ui/components/customer-editor-skeleton.tsx index 2c9df64f..e99da2ca 100644 --- a/modules/customers/src/web/components/editor/customer-editor-skeleton.tsx +++ b/modules/customers/src/web/view/ui/components/customer-editor-skeleton.tsx @@ -2,7 +2,7 @@ import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; import { Button } from "@repo/shadcn-ui/components"; -import { useTranslation } from "../../i18n"; +import { useTranslation } from "../../../i18n"; export const CustomerEditorSkeleton = () => { const { t } = useTranslation(); diff --git a/modules/customers/src/web/view/ui/components/index.ts b/modules/customers/src/web/view/ui/components/index.ts new file mode 100644 index 00000000..9e05e166 --- /dev/null +++ b/modules/customers/src/web/view/ui/components/index.ts @@ -0,0 +1 @@ +export * from "./customer-editor-skeleton"; diff --git a/modules/customers/src/web/view/ui/index.ts b/modules/customers/src/web/view/ui/index.ts new file mode 100644 index 00000000..c4e34b27 --- /dev/null +++ b/modules/customers/src/web/view/ui/index.ts @@ -0,0 +1 @@ +export * from "./pages"; diff --git a/modules/customers/src/web/view/ui/pages/customer-view-page copy b/modules/customers/src/web/view/ui/pages/customer-view-page copy new file mode 100644 index 00000000..bfbd3094 --- /dev/null +++ b/modules/customers/src/web/view/ui/pages/customer-view-page copy @@ -0,0 +1,333 @@ +import { PageHeader } from "@erp/core/components"; +import { useUrlParamId } from "@erp/core/hooks"; +import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; +import { + Badge, + Button, + Card, + CardContent, + CardHeader, + CardTitle, +} from "@repo/shadcn-ui/components"; +import { + Banknote, + EditIcon, + FileText, + Globe, + Languages, + Mail, + MapPin, + MoreVertical, + Phone, + Smartphone, +} from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +import { CustomerEditorSkeleton, ErrorAlert } from "../../../components"; +import { useTranslation } from "../../../i18n"; +import { useCustomerGetQuery } from "../../hooks"; + +export const CustomerViewPage2 = () => { + const customerId = useUrlParamId(); + const { t } = useTranslation(); + const navigate = useNavigate(); + + // 1) Estado de carga del cliente (query) + const { + data: customer, + isLoading: isLoadingCustomer, + isError: isLoadError, + error: loadError, + } = useCustomerGetQuery(customerId, { enabled: !!customerId }); + + if (isLoadingCustomer) { + return ; + } + + if (isLoadError) { + return ( + <> + + + +
+ +
+
+ + ); + } + + return ( + <> + + + + {customer?.tin} + + {customer?.is_company ? "Empresa" : "Persona"} + + } + rightSlot={ +
+ + +
+ } + title={ +
+ {customer?.name}{" "} + {customer?.trade_name && ( + ({customer.trade_name}) + )} +
+ } + /> +
+ + {/* Main Content Grid */} +
+ {/* Información Básica */} + + + + + Información Básica + + + +
+
Nombre
+
{customer?.name}
+
+
+
Referencia
+
{customer?.reference}
+
+
+
Registro Legal
+
{customer?.legal_record}
+
+
+
Impuestos por Defecto
+
+ {customer?.default_taxes.map((tax) => ( + + {tax} + + ))} +
+
+
+
+ + {/* Dirección */} + + + + + Dirección + + + +
+
Calle
+
+ {customer?.street} + {customer?.street2 && ( + <> +
+ {customer?.street2} + + )} +
+
+
+
+
Ciudad
+
{customer?.city}
+
+
+
Código Postal
+
{customer?.postal_code}
+
+
+
+
+
Provincia
+
{customer?.province}
+
+
+
País
+
{customer?.country}
+
+
+
+
+ + {/* Información de Contacto */} + + + + + Información de Contacto + + + +
+ {/* Contacto Principal */} +
+

Contacto Principal

+ {customer?.email_primary && ( +
+ +
+
Email
+
+ {customer?.email_primary} +
+
+
+ )} + {customer?.mobile_primary && ( +
+ +
+
Móvil
+
+ {customer?.mobile_primary} +
+
+
+ )} + {customer?.phone_primary && ( +
+ +
+
Teléfono
+
+ {customer?.phone_primary} +
+
+
+ )} +
+ + {/* Contacto Secundario */} +
+

Contacto Secundario

+ {customer?.email_secondary && ( +
+ +
+
Email
+
+ {customer?.email_secondary} +
+
+
+ )} + {customer?.mobile_secondary && ( +
+ +
+
Móvil
+
+ {customer?.mobile_secondary} +
+
+
+ )} + {customer?.phone_secondary && ( +
+ +
+
Teléfono
+
+ {customer?.phone_secondary} +
+
+
+ )} +
+ + {/* Otros Contactos */} + {(customer?.website || customer?.fax) && ( +
+

Otros

+
+ {customer?.website && ( +
+ +
+
Sitio Web
+
+ + {customer?.website} + +
+
+
+ )} + {customer?.fax && ( +
+ +
+
Fax
+
{customer?.fax}
+
+
+ )} +
+
+ )} +
+
+
+ + {/* Preferencias */} + + + + + Preferencias + + + +
+
+ +
+
Idioma Preferido
+
{customer?.language_code}
+
+
+
+ +
+
Moneda Preferida
+
{customer?.currency_code}
+
+
+
+
+
+
+
+ + ); +}; diff --git a/modules/customers/src/web/view/ui/pages/customer-view-page.tsx b/modules/customers/src/web/view/ui/pages/customer-view-page.tsx new file mode 100644 index 00000000..031f824d --- /dev/null +++ b/modules/customers/src/web/view/ui/pages/customer-view-page.tsx @@ -0,0 +1,324 @@ +import { ErrorAlert, PageHeader } from "@erp/core/components"; +import { useUrlParamId } from "@erp/core/hooks"; +import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; +import { + Badge, + Button, + Card, + CardContent, + CardHeader, + CardTitle, +} from "@repo/shadcn-ui/components"; +import { + Banknote, + EditIcon, + FileText, + Globe, + Languages, + Mail, + MapPin, + MoreVertical, + Phone, + Smartphone, +} from "lucide-react"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +import { useTranslation } from "../../../i18n"; +import { useCustomerViewPageController } from "../../controllers/use-customer-view-page.controller"; +import { CustomerEditorSkeleton } from "../components"; + +export const CustomerViewPage = () => { + const initialCustomerId = useUrlParamId(); + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { + viewCtrl: { setCustomerId, customerId, data, isError, error, isLoading }, + } = useCustomerViewPageController(); + + useEffect(() => { + if (initialCustomerId && customerId !== initialCustomerId) { + setCustomerId(initialCustomerId); + } + }, [initialCustomerId, customerId, setCustomerId]); + + if (isError) { + return ( + <> + + + +
+ +
+
+ + ); + } + + if (isLoading) { + return ; + } + + return ( + <> + + + + {data?.tin} + + {data?.is_company ? "Empresa" : "Persona"} + + } + rightSlot={ +
+ + +
+ } + title={ +
+ {data?.name}{" "} + {data?.trade_name && ( + ({data.trade_name}) + )} +
+ } + /> +
+ + {/* Main Content Grid */} +
+ {/* Información Básica */} + + + + + Información Básica + + + +
+
Nombre
+
{data?.name}
+
+
+
Referencia
+
{data?.reference}
+
+
+
Registro Legal
+
{data?.legal_record}
+
+
+
Impuestos por Defecto
+
+ {data?.default_taxes.map((tax) => ( + + {tax} + + ))} +
+
+
+
+ + {/* Dirección */} + + + + + Dirección + + + +
+
Calle
+
+ {data?.street} + {data?.street2 && ( + <> +
+ {data?.street2} + + )} +
+
+
+
+
Ciudad
+
{data?.city}
+
+
+
Código Postal
+
{data?.postal_code}
+
+
+
+
+
Provincia
+
{data?.province}
+
+
+
País
+
{data?.country}
+
+
+
+
+ + {/* Información de Contacto */} + + + + + Información de Contacto + + + +
+ {/* Contacto Principal */} +
+

Contacto Principal

+ {data?.email_primary && ( +
+ +
+
Email
+
{data?.email_primary}
+
+
+ )} + {data?.mobile_primary && ( +
+ +
+
Móvil
+
{data?.mobile_primary}
+
+
+ )} + {data?.phone_primary && ( +
+ +
+
Teléfono
+
{data?.phone_primary}
+
+
+ )} +
+ + {/* Contacto Secundario */} +
+

Contacto Secundario

+ {data?.email_secondary && ( +
+ +
+
Email
+
{data?.email_secondary}
+
+
+ )} + {data?.mobile_secondary && ( +
+ +
+
Móvil
+
{data?.mobile_secondary}
+
+
+ )} + {data?.phone_secondary && ( +
+ +
+
Teléfono
+
{data?.phone_secondary}
+
+
+ )} +
+ + {/* Otros Contactos */} + {(data?.website || data?.fax) && ( +
+

Otros

+
+ {data?.website && ( +
+ +
+
Sitio Web
+
+ + {data?.website} + +
+
+
+ )} + {data?.fax && ( +
+ +
+
Fax
+
{data?.fax}
+
+
+ )} +
+
+ )} +
+
+
+ + {/* Preferencias */} + + + + + Preferencias + + + +
+
+ +
+
Idioma Preferido
+
{data?.language_code}
+
+
+
+ +
+
Moneda Preferida
+
{data?.currency_code}
+
+
+
+
+
+
+
+ + ); +}; diff --git a/modules/customers/src/web/view/ui/pages/index.ts b/modules/customers/src/web/view/ui/pages/index.ts new file mode 100644 index 00000000..44ef4e26 --- /dev/null +++ b/modules/customers/src/web/view/ui/pages/index.ts @@ -0,0 +1 @@ +export * from "./customer-view-page"; diff --git a/modules/customers/tsconfig.json b/modules/customers/tsconfig.json index 4fea4749..60f73fec 100644 --- a/modules/customers/tsconfig.json +++ b/modules/customers/tsconfig.json @@ -28,6 +28,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"], + "include": ["src", "../core/src/web/components/error-alert.tsx"], "exclude": ["node_modules"] }