From bdc5637a81e67174dcbfc194760f287f2f4e502e Mon Sep 17 00:00:00 2001 From: david Date: Mon, 20 Oct 2025 10:02:08 +0200 Subject: [PATCH] Facturas de cliente --- .../recipient-modal-selector-field.tsx | 2 +- .../src/web/pages/list/invoices-list-page.tsx | 1 + .../src/web/schemas/invoices.api.schema.ts | 1 + .../response/list-customers.response.dto.ts | 1 + .../customer-modal-selector/customer-card.tsx | 144 +++++++++++------- .../customer-modal-selector.tsx | 8 +- 6 files changed, 101 insertions(+), 56 deletions(-) diff --git a/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx b/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx index abbca324..4ac1e55c 100644 --- a/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx +++ b/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx @@ -53,9 +53,9 @@ export function RecipientModalSelectorField({ {label && {label}} ; +export type CustomerInvoiceRecipient = CustomerInvoice["recipient"]; export type CustomerInvoiceItem = ArrayElement; export type CustomerInvoiceCreateInput = z.infer; // Cuerpo para crear diff --git a/modules/customers/src/common/dto/response/list-customers.response.dto.ts b/modules/customers/src/common/dto/response/list-customers.response.dto.ts index 681889e2..f9cd662b 100644 --- a/modules/customers/src/common/dto/response/list-customers.response.dto.ts +++ b/modules/customers/src/common/dto/response/list-customers.response.dto.ts @@ -14,6 +14,7 @@ export const ListCustomersResponseSchema = createPaginatedListSchema( tin: z.string(), street: z.string(), + street2: z.string(), city: z.string(), province: z.string(), postal_code: z.string(), diff --git a/modules/customers/src/web/components/customer-modal-selector/customer-card.tsx b/modules/customers/src/web/components/customer-modal-selector/customer-card.tsx index 970fac20..3b9be44e 100644 --- a/modules/customers/src/web/components/customer-modal-selector/customer-card.tsx +++ b/modules/customers/src/web/components/customer-modal-selector/customer-card.tsx @@ -7,21 +7,46 @@ import { } from "@repo/shadcn-ui/components"; import { EyeIcon, + MapPinIcon, RefreshCwIcon, UserPlusIcon } from "lucide-react"; +import React, { useMemo } from 'react'; import { CustomerSummary } from "../../schemas"; + interface CustomerCardProps { customer: CustomerSummary; - onViewCustomer?: () => void; onChangeCustomer?: () => void; onAddNewCustomer?: () => void; - className?: string; } +function buildAddress(customer: CustomerSummary) { + // Línea 1: calle(s) + const line1 = [customer.street, customer.street2].filter(Boolean).join(", "); + + // Línea 2: CP + ciudad con espacio no rompible entre CP y ciudad + const line2Parts: string[] = []; + if (customer.postal_code && customer.city) { + line2Parts.push(`${customer.postal_code}\u00A0${customer.city}`); // CP Ciudad + } else { + if (customer.postal_code) line2Parts.push(customer.postal_code); + if (customer.city) line2Parts.push(customer.city); + } + const line2 = line2Parts.join(" "); + + // Línea 3: provincia + país + const line3 = [customer.province, customer.country].filter(Boolean).join(", "); + + const stack = [line1, line2, line3].filter(Boolean); + const inline = stack.join(" · "); // separador compacto + const full = stack.join(", "); + + return { has: stack.length > 0, stack, inline, full }; +} + export const CustomerCard = ({ customer, onViewCustomer, @@ -29,79 +54,92 @@ export const CustomerCard = ({ onAddNewCustomer, className, }: CustomerCardProps) => { - const hasAddress = - customer.street || - customer.street2 || - customer.city || - customer.postal_code || - customer.province || - customer.country; + const address = useMemo(() => buildAddress(customer), [customer]); return ( - - {customer.name} + + {customer.name} - - {customer.tin && ({customer.tin})} - {/* Dirección con mejor estructura */} - {hasAddress && ( -
+ + {/* TIN en su propia línea si existe */} + {customer.tin && ( +
{customer.tin}
+ )} - {customer.street &&
{customer.street}
} - {customer.street2 &&
{customer.street2}
} -
- {customer.postal_code && {customer.postal_code}} - {customer.city && {customer.city}} + {/* Dirección */} + {address.has ? ( +
+ {/* Desktop/tablet: compacto en una línea (o dos por wrap natural) */} +
+ + {/* Partes con separadores reales para que el wrap ocurra en “ · ” */} + {address.stack.map((part, i) => ( + + {part} + {i < address.stack.length - 1 && ( + · + )} + + ))}
-
- {customer.province && {customer.province}} - {customer.country && {customer.country}} -
-
+ {/* Móvil: apilado (3 líneas máximo) */} +
+
+ +
+ {address.stack.map((line, i) => ( +
{line}
+ ))} +
+
+
+ + ) : ( + Sin dirección )}
- + {/* Footer con acciones */} + - + ); -}; +}; \ No newline at end of file diff --git a/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx b/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx index 5b0e6920..4953aef6 100644 --- a/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx +++ b/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx @@ -19,18 +19,18 @@ function useDebouncedValue(value: T, delay = 300) { interface CustomerModalSelectorProps { value?: string; onValueChange?: (id: string) => void; - initialCustomer?: CustomerSummary; disabled?: boolean; readOnly?: boolean; + initialCustomer?: CustomerSummary; className?: string; } export const CustomerModalSelector = ({ value, onValueChange, - initialCustomer, disabled = false, readOnly = false, + initialCustomer, className, }: CustomerModalSelectorProps) => { @@ -41,8 +41,10 @@ export const CustomerModalSelector = ({ // Cliente seleccionado y creación local optimista const [selected, setSelected] = useState(initialCustomer ?? null); + const [newClient, setNewClient] = useState>(defaultCustomerFormData); + const [localCreated, setLocalCreated] = useState([]); const { @@ -92,6 +94,8 @@ export const CustomerModalSelector = ({ className={className} customer={selected} onChangeCustomer={() => setShowSearch(true)} + onViewCustomer={() => null} + onAddNewCustomer={() => null} /> ) : (