import { useState, useEffect } from "react"; import type { ChangeEvent, FormEvent } from "react"; import { useSearchParams, useNavigate } from "react-router-dom"; import { API_URL, apiFetch } from "../utils/api"; import FieldTooltip from "./ui/FieldTooltip"; interface BPAddress { AddressName: string; Street: string; ZipCode: string; City: string; County: string; Country: string; GlobalLocationNumber: string; AddressType: "bo_ShipTo" | "bo_BillTo"; } interface BusinessPartnerFormData { CardCode: string; CardName: string; CardType: "cCustomer" | "cSupplier"; FederalTaxID: string; Phone1: string; EmailAddress: string; BPAddresses: BPAddress[]; [extraField: string]: unknown; } export interface FieldOption { value: string; label: string; } export interface ExtraFieldConfig { /** Nombre del campo tal como lo espera SAP (ej. "U_EXX_TIPODOCU"). */ name: string; label: string; type?: "text" | "email" | "select"; defaultValue?: string; fullWidth?: boolean; /** Opciones fijas para type="select". Ignoradas si se pasa loadOptions. */ options?: FieldOption[]; /** Opciones cargadas desde el backend para type="select". */ loadOptions?: () => Promise; } const DEFAULT_ADDRESSES: BPAddress[] = [ { AddressName: "Facturación", Street: "", ZipCode: "", City: "", County: "", Country: "PE", AddressType: "bo_BillTo", GlobalLocationNumber: "", }, { AddressName: "Despacho", Street: "", ZipCode: "", City: "", County: "", Country: "PE", AddressType: "bo_ShipTo", GlobalLocationNumber: "", }, ]; interface BusinessPartnerFormProps { cardType: "cCustomer" | "cSupplier"; entityLabel: string; listPath: string; codePlaceholder: string; /** Campos exclusivos de este tipo de socio de negocio (ver ExtraFieldConfig). */ extraFields?: ExtraFieldConfig[]; } export default function BusinessPartnerForm({ cardType, entityLabel, listPath, codePlaceholder, extraFields = [], }: BusinessPartnerFormProps) { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const cardCodeParam = searchParams.get("code"); const isEditMode = !!cardCodeParam; const entityLabelLower = entityLabel.toLowerCase(); const [activeTab, setActiveTab] = useState<"general" | "direcciones">( "general", ); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [dynamicOptions, setDynamicOptions] = useState< Record >({}); const [formData, setFormData] = useState({ CardCode: "", CardName: "", CardType: cardType, FederalTaxID: "", Phone1: "", EmailAddress: "", BPAddresses: DEFAULT_ADDRESSES, ...Object.fromEntries(extraFields.map((f) => [f.name, f.defaultValue ?? ""])), }); useEffect(() => { extraFields.forEach((f) => { if (!f.loadOptions) return; f.loadOptions() .then((opts) => setDynamicOptions((prev) => ({ ...prev, [f.name]: opts }))) .catch(() => {}); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!isEditMode) return; const fetchDetail = async () => { setLoading(true); try { const url = `${API_URL}/api/BusinessPartners/${cardCodeParam}`; const response = await apiFetch(url); if (!response.ok) throw new Error( `No se pudieron cargar los datos del ${entityLabelLower}`, ); const data = await response.json(); const addresses = data.BPAddresses?.length ? data.BPAddresses : DEFAULT_ADDRESSES; setFormData({ CardCode: data.CardCode || "", CardName: data.CardName || "", CardType: cardType, FederalTaxID: data.FederalTaxID || "", Phone1: data.Phone1 || "", EmailAddress: data.EmailAddress || "", BPAddresses: addresses, ...Object.fromEntries( extraFields.map((f) => [f.name, data[f.name] ?? f.defaultValue ?? ""]), ), }); } catch (err: any) { setError(err.message); } finally { setLoading(false); } }; fetchDetail(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [cardCodeParam, isEditMode]); const handleInputChange = ( e: ChangeEvent, ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); }; const handleAddressChange = ( index: number, field: keyof BPAddress, value: string, ) => { setFormData((prev) => { const updatedAddresses = [...prev.BPAddresses]; updatedAddresses[index] = { ...updatedAddresses[index], [field]: value }; return { ...prev, BPAddresses: updatedAddresses }; }); }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setLoading(true); setError(""); setSuccess(""); try { const url = isEditMode ? `${API_URL}/api/BusinessPartners/${formData.CardCode}` : `${API_URL}/api/BusinessPartners`; const response = await apiFetch(url, { method: isEditMode ? "PATCH" : "POST", body: JSON.stringify(formData), }); if (!response.ok) throw new Error("Error al procesar la solicitud en SAP Service Layer"); setSuccess( isEditMode ? `${entityLabel} actualizado correctamente` : `${entityLabel} creado exitosamente`, ); if (!isEditMode) { setTimeout(() => navigate(listPath), 1500); } } catch (err: any) { setError(err.message || "Ocurrió un error inesperado"); } finally { setLoading(false); } }; return (
{/* Cabecera del Formulario */}

{isEditMode ? `Ficha del ${entityLabel}: ${formData.CardCode}` : `Nuevo ${entityLabel}`}

Mantenimiento maestro conectado a Service Layer

{/* Alertas */} {error && (
{error}
)} {success && (
{success}
)} {/* PESTAÑAS (Estilo Navegación SAP Flat) */}
{/* CUERPO DEL FORMULARIO */}
{/* Contenido Pestaña: General */} {activeTab === "general" && (
{extraFields.map((f) => (
{f.type === "select" ? ( ) : ( )}
))}
)} {/* Contenido Pestaña: Direcciones */} {activeTab === "direcciones" && (
{formData.BPAddresses.map((address, idx) => (

Dirección de {address.AddressName} ( {address.AddressType === "bo_BillTo" ? "Facturación" : "Envío"} )

handleAddressChange(idx, "Street", e.target.value) } className="w-full px-2 py-1 border border-[var(--color-input-border)] bg-[var(--color-surface)] rounded-none outline-none focus:border-[var(--color-input-focus)]" />
handleAddressChange(idx, "City", e.target.value) } className="w-full px-2 py-1 border border-[var(--color-input-border)] bg-[var(--color-surface)] rounded-none outline-none focus:border-[var(--color-input-focus)]" />
handleAddressChange(idx, "County", e.target.value) } className="w-full px-2 py-1 border border-[var(--color-input-border)] bg-[var(--color-surface)] rounded-none outline-none focus:border-[var(--color-input-focus)]" />
handleAddressChange( idx, "GlobalLocationNumber", e.target.value, ) } className="w-full px-2 py-1 border border-[var(--color-input-border)] bg-[var(--color-surface)] rounded-none outline-none focus:border-[var(--color-input-focus)]" />
))}
)} {/* BOTÓN DE ACCIÓN FIJO EN PIE DE PÁGINA */}
); }