This commit is contained in:
David Arranz 2026-04-12 21:58:11 +02:00
parent 723f58293a
commit 1167f5bc14
14 changed files with 49 additions and 183 deletions

View File

@ -1,4 +1,3 @@
export * from "./items"; export * from "./items";
export * from "./proforma-basic-info-fields"; export * from "./proforma-basic-info-fields";
export * from "./proforma-totals"; export * from "./proforma-totals";
export * from "./recipient";

View File

@ -1,2 +0,0 @@
export * from "./proforma-recipient";
export * from "./proforma-recipient-modal-selector-field";

View File

@ -1,73 +0,0 @@
import { CustomerModalSelector } from "@erp/customers/components";
import { Field, FieldLabel } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import type { CustomerSummary } from "node_modules/@erp/customers/src/web/schemas";
import { type Control, Controller, type FieldPath, type FieldValues } from "react-hook-form";
type RecipientModalSelectorFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label?: string;
description?: string;
orientation?: "vertical" | "horizontal" | "responsive";
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
initialRecipient?: unknown;
};
export function RecipientModalSelectorField<TFormValues extends FieldValues>({
control,
name,
label,
description,
orientation = "vertical",
disabled = false,
required = false,
readOnly = false,
className,
initialRecipient = {},
}: RecipientModalSelectorFieldProps<TFormValues>) {
const isDisabled = disabled;
const isReadOnly = readOnly && !disabled;
return (
<Controller
control={control}
name={name}
render={({ field, fieldState }) => {
const { name, value, onChange, onBlur, ref } = field;
return (
<Field
className={cn("gap-1", className)}
data-invalid={fieldState.invalid}
orientation={orientation}
>
{label && (
<FieldLabel className="text-xs text-muted-foreground text-nowrap" htmlFor={name}>
{label}
</FieldLabel>
)}
<CustomerModalSelector
disabled={isDisabled}
initialCustomer={{
...(initialRecipient as CustomerSummary),
}}
onValueChange={onChange}
readOnly={isReadOnly}
value={value}
/>
</Field>
);
}}
/>
);
}

View File

@ -1,34 +0,0 @@
import { FieldDescription, FieldGroup, FieldLegend, FieldSet } from "@repo/shadcn-ui/components";
import type { ComponentProps } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "../../../../../../i18n";
import { RecipientModalSelectorField } from "./proforma-recipient-modal-selector-field";
export const ProformaRecipient = (props: ComponentProps<"fieldset">) => {
const { t } = useTranslation();
const { control, getValues } = useFormContext();
const recipient = getValues("recipient");
return (
<FieldSet {...props}>
<FieldLegend className="hidden text-foreground" variant="label">
{t("form_groups.recipient.title")}
</FieldLegend>
<FieldDescription className="hidden">
{t("form_groups.recipient.description")}
</FieldDescription>
<FieldGroup className="flex flex-row flex-wrap gap-6 xl:flex-nowrap">
<RecipientModalSelectorField
control={control}
initialRecipient={recipient}
label={t("form_groups.customer.title")}
name="customer_id"
/>
</FieldGroup>
</FieldSet>
);
};

View File

@ -3,4 +3,4 @@ export * from "./proforma-form-field-shell";
export * from "./proforma-header-fields-card"; export * from "./proforma-header-fields-card";
export * from "./proforma-header-form-grid"; export * from "./proforma-header-form-grid";
export * from "./proforma-section-card"; export * from "./proforma-section-card";
export * from "./selected-recipient-summary"; export * from "./selected-recipient";

View File

@ -5,7 +5,7 @@ import { cn } from "@repo/shadcn-ui/lib/utils";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
interface ProformaSectionCardProps { interface ProformaSectionCardProps {
title: string; title?: string;
description?: string; description?: string;
children: ReactNode; children: ReactNode;
className?: string; className?: string;
@ -20,14 +20,16 @@ export const ProformaSectionCard = ({
return ( return (
<section className={cn("rounded-xl border bg-background p-4 md:p-6", className)}> <section className={cn("rounded-xl border bg-background p-4 md:p-6", className)}>
<FieldSet> <FieldSet>
<FieldLegend className="mb-4 space-y-1 md:mb-6"> {title || description ? (
<h2 className="text-base font-semibold tracking-tight">{title}</h2> <FieldLegend className="mb-4 space-y-1 md:mb-6">
{description ? ( {title ? <h2 className="text-base font-semibold tracking-tight">{title}</h2> : null}
<FieldDescription className="text-sm text-muted-foreground"> {description ? (
{description} <FieldDescription className="text-sm text-muted-foreground">
</FieldDescription> {description}
) : null} </FieldDescription>
</FieldLegend> ) : null}
</FieldLegend>
) : null}
{children} {children}
</FieldSet> </FieldSet>

View File

@ -3,7 +3,7 @@ import { cn } from "@repo/shadcn-ui/lib/utils";
import { EyeIcon, MapPinIcon, RefreshCwIcon, UserPlusIcon } from "lucide-react"; import { EyeIcon, MapPinIcon, RefreshCwIcon, UserPlusIcon } from "lucide-react";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import type { CustomerSummary } from "../../schemas"; import type { CustomerSummary } from "../../../../../../../../customers/src/web/_archived/schemas";
interface CustomerCardProps { interface CustomerCardProps {
customer: CustomerSummary; customer: CustomerSummary;

View File

@ -0,0 +1 @@
export * from "./selected-recipient-summary";

View File

@ -1,15 +1,21 @@
import type { CustomerSelectionOption } from "@erp/customers"; import type { CustomerSelectionOption } from "@erp/customers";
import { Button } from "@repo/shadcn-ui/components"; import { Button } from "@repo/shadcn-ui/components";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../../i18n";
type SelectedRecipientSummaryProps = { type SelectedRecipientSummaryProps = {
disabled?: boolean;
readOnly?: boolean;
recipient?: CustomerSelectionOption | null; recipient?: CustomerSelectionOption | null;
onChangeClick: () => void; onChangeClick: () => void;
onCreateClick?: () => void; onCreateClick?: () => void;
}; };
export const SelectedRecipientSummary = ({ export const SelectedRecipientSummary = ({
disabled = false,
readOnly = false,
recipient, recipient,
onChangeClick, onChangeClick,
onCreateClick, onCreateClick,
@ -17,11 +23,9 @@ export const SelectedRecipientSummary = ({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="rounded-lg border p-4"> <div>
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"> <div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="min-w-0"> <div className="min-w-0">
<p className="text-sm font-medium">{t("customers.selected_customer.title", "Cliente")}</p>
{recipient ? ( {recipient ? (
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
<p className="truncate font-medium">{recipient.name}</p> <p className="truncate font-medium">{recipient.name}</p>
@ -37,17 +41,19 @@ export const SelectedRecipientSummary = ({
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{onCreateClick ? ( {onCreateClick && !readOnly && !disabled && (
<Button onClick={onCreateClick} type="button" variant="outline"> <Button onClick={onCreateClick} type="button" variant="outline">
{t("customers.selected_customer.new_customer", "Nuevo cliente")} {t("customers.selected_customer.new_customer", "Nuevo cliente")}
</Button> </Button>
) : null} )}
<Button onClick={onChangeClick} type="button" variant="outline"> {onChangeClick && !readOnly && !disabled && (
{recipient <Button onClick={onChangeClick} type="button" variant="secondary">
? t("customers.selected_customer.change", "Cambiar cliente") {recipient
: t("customers.selected_customer.select", "Seleccionar cliente")} ? t("customers.selected_customer.change", "Cambiar cliente")
</Button> : t("customers.selected_customer.select", "Seleccionar cliente")}
</Button>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,9 +1,7 @@
import type { CustomerSelectionOption } from "@erp/customers"; import type { CustomerSelectionOption } from "@erp/customers";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import type { ProformaUpdateForm } from "../../entities"; import { ProformaSectionCard, SelectedRecipientSummary } from "../blocks";
import { ProformaHeaderFormGrid, ProformaSectionCard, SelectedRecipientSummary } from "../blocks";
interface ProformaUpdateRecipientEditorProps { interface ProformaUpdateRecipientEditorProps {
disabled?: boolean; disabled?: boolean;
@ -24,26 +22,15 @@ export const ProformaUpdateRecipientEditor = ({
}: ProformaUpdateRecipientEditorProps) => { }: ProformaUpdateRecipientEditorProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const {
register,
control,
formState: { errors },
} = useFormContext<ProformaUpdateForm>();
const isFieldLocked = disabled || readOnly;
return ( return (
<ProformaSectionCard <ProformaSectionCard title={t("form_groups.proformas.customer.title")}>
description={t("form_groups.proformas.customer.description")} <SelectedRecipientSummary
title={t("form_groups.proformas.customer.title")} disabled={disabled}
> onChangeClick={onChangeCustomerClick}
<ProformaHeaderFormGrid> onCreateClick={onCreateCustomerClick}
<SelectedRecipientSummary readOnly={readOnly}
onChangeClick={onChangeCustomerClick} recipient={selectedCustomer}
onCreateClick={onCreateCustomerClick} />
recipient={selectedCustomer}
/>
</ProformaHeaderFormGrid>
</ProformaSectionCard> </ProformaSectionCard>
); );
}; };

View File

@ -1,27 +0,0 @@
import { UserSearchIcon } from "lucide-react";
export const CustomerEmptyCard = (props: React.ComponentProps<"button">) => {
return (
<button
aria-label="Seleccionar cliente"
className="group w-full cursor-pointer rounded-lg border border-border bg-card p-4 transition hover:bg-accent/50 hover:border-primary"
tabIndex={0}
type="button"
{...props}
>
<div className="flex items-center gap-4 group-hover:text-primary">
<div className="flex size-12 items-center justify-center rounded-full bg-muted group-hover:bg-primary/15">
<UserSearchIcon className="size-6 text-muted-foreground group-hover:text-primary" />
</div>
<div className="flex-1 text-left">
<h3 className="font-medium text-muted-foreground mb-1 group-hover:text-primary">
Seleccionar Cliente
</h3>
<p className="text-sm text-muted-foreground/70 group-hover:text-primary">
Haz clic para buscar un cliente existente o crear uno nuevo
</p>
</div>
</div>
</button>
);
};

View File

@ -1,5 +1,6 @@
import { useEffect, useId, useMemo, useState } from "react"; import { useEffect, useId, useMemo, useState } from "react";
import { CustomerEmptyCard } from "../../../../../../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/selected-recipient-empty-card";
import { useCustomerListQuery } from "../../hooks"; import { useCustomerListQuery } from "../../hooks";
import { import {
type CustomerFormData, type CustomerFormData,
@ -7,9 +8,10 @@ import {
defaultCustomerFormData, defaultCustomerFormData,
} from "../../schemas"; } from "../../schemas";
../../../../../../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/customer-card
import { CustomerCard } from "./customer-card"; import { CustomerCard } from "./customer-card";
import { CustomerCreateModal } from "./customer-create-modal"; import { CustomerCreateModal } from "./customer-create-modal";
import { CustomerEmptyCard } from "./customer-empty-card";
import { CustomerSearchDialog } from "./customer-search-dialog"; import { CustomerSearchDialog } from "./customer-search-dialog";
import { CustomerViewDialog } from "./customer-view-dialog"; import { CustomerViewDialog } from "./customer-view-dialog";

View File

@ -28,6 +28,11 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["src"], "include": [
"src",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/customer-view-dialog.tsx",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/customer-card.tsx",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/selected-recipient-empty-card.tsx"
],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }