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>
239 lines
8.8 KiB
TypeScript
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>
|
|
);
|
|
}
|