erp-core-frontend-v2/src/features/partners/components/ContactForm.tsx
Adrian Flores Cortes b5cffbff5f [TASK-MASTER] feat: FE-003 partners + FE-004 companies extensions
FE-003: Extended partners feature with:
- addresses API (CRUD endpoints)
- contacts API (CRUD endpoints)
- bankAccounts API (CRUD endpoints)
- AddressForm, AddressList, ContactForm, ContactList components
- usePartnerAddresses, usePartnerContacts, usePartnerBankAccounts hooks

FE-004: Extended companies feature with:
- settings endpoints (GET/PUT company settings)
- branches endpoints (CRUD branches)
- CompanySettingsForm, BranchesList components
- useCompanySettings, useCompanyBranches hooks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 00:29:50 -06:00

239 lines
8.8 KiB
TypeScript

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '@components/atoms/Button';
import { FormField } from '@components/molecules/FormField';
import type { PartnerContact, CreatePartnerContactDto, UpdatePartnerContactDto } from '../types';
const contactSchema = z.object({
fullName: z.string().min(2, 'Minimo 2 caracteres').max(200),
position: z.string().max(100).optional(),
department: z.string().max(100).optional(),
email: z.string().email('Email invalido').optional().or(z.literal('')),
phone: z.string().max(30).optional(),
mobile: z.string().max(30).optional(),
extension: z.string().max(30).optional(),
isPrimary: z.boolean(),
isBillingContact: z.boolean(),
isShippingContact: z.boolean(),
receivesNotifications: z.boolean(),
notes: z.string().optional(),
});
type FormData = z.infer<typeof contactSchema>;
interface ContactFormProps {
contact?: PartnerContact;
onSubmit: (data: CreatePartnerContactDto | UpdatePartnerContactDto) => Promise<void>;
onCancel: () => void;
isLoading?: boolean;
}
export function ContactForm({ contact, onSubmit, onCancel, isLoading }: ContactFormProps) {
const isEditing = !!contact;
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(contactSchema),
defaultValues: contact
? {
fullName: contact.fullName,
position: contact.position || '',
department: contact.department || '',
email: contact.email || '',
phone: contact.phone || '',
mobile: contact.mobile || '',
extension: contact.extension || '',
isPrimary: contact.isPrimary,
isBillingContact: contact.isBillingContact,
isShippingContact: contact.isShippingContact,
receivesNotifications: contact.receivesNotifications,
notes: contact.notes || '',
}
: {
fullName: '',
position: '',
department: '',
email: '',
phone: '',
mobile: '',
extension: '',
isPrimary: false,
isBillingContact: false,
isShippingContact: false,
receivesNotifications: true,
notes: '',
},
});
const handleFormSubmit = async (data: FormData) => {
const cleanData: CreatePartnerContactDto | UpdatePartnerContactDto = {
fullName: data.fullName,
isPrimary: data.isPrimary,
isBillingContact: data.isBillingContact,
isShippingContact: data.isShippingContact,
receivesNotifications: data.receivesNotifications,
...(data.position && { position: data.position }),
...(data.department && { department: data.department }),
...(data.email && { email: data.email }),
...(data.phone && { phone: data.phone }),
...(data.mobile && { mobile: data.mobile }),
...(data.extension && { extension: data.extension }),
...(data.notes && { notes: data.notes }),
};
await onSubmit(cleanData);
};
return (
<form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-6">
{/* Basic Info */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Informacion basica</h3>
<FormField label="Nombre completo" error={errors.fullName?.message} required>
<input
{...register('fullName')}
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="Nombre del contacto"
/>
</FormField>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Puesto" error={errors.position?.message}>
<input
{...register('position')}
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="Ej: Gerente de compras"
/>
</FormField>
<FormField label="Departamento" error={errors.department?.message}>
<input
{...register('department')}
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="Ej: Compras"
/>
</FormField>
</div>
</div>
{/* Contact Info */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Informacion de contacto</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Email" error={errors.email?.message}>
<input
{...register('email')}
type="email"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="email@ejemplo.com"
/>
</FormField>
<FormField label="Telefono" error={errors.phone?.message}>
<input
{...register('phone')}
type="tel"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="+52 55 1234 5678"
/>
</FormField>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Movil" error={errors.mobile?.message}>
<input
{...register('mobile')}
type="tel"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="+52 55 8765 4321"
/>
</FormField>
<FormField label="Extension" error={errors.extension?.message}>
<input
{...register('extension')}
type="text"
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="123"
/>
</FormField>
</div>
</div>
{/* Roles */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Roles del contacto</h3>
<div className="space-y-3">
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isPrimary')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Contacto principal</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isBillingContact')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Contacto de facturacion</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isShippingContact')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Contacto de envios</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('receivesNotifications')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Recibe notificaciones</span>
</label>
</div>
</div>
{/* Notes */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Notas</h3>
<FormField label="Notas" error={errors.notes?.message}>
<textarea
{...register('notes')}
rows={3}
className="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
placeholder="Notas adicionales sobre el contacto..."
/>
</FormField>
</div>
<div className="flex justify-end gap-3 pt-4 border-t">
<Button type="button" variant="outline" onClick={onCancel}>
Cancelar
</Button>
<Button type="submit" isLoading={isLoading}>
{isEditing ? 'Guardar cambios' : 'Agregar contacto'}
</Button>
</div>
</form>
);
}