erp-core-frontend-v2/src/features/partners/components/PartnerForm.tsx

323 lines
11 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 { Select } from '@components/organisms/Select';
import type { Partner, CreatePartnerDto, UpdatePartnerDto, PartnerType } from '../types';
const partnerSchema = z.object({
name: z.string().min(2, 'Mínimo 2 caracteres'),
legalName: z.string().optional(),
partnerType: z.enum(['person', 'company'] as const),
isCustomer: z.boolean(),
isSupplier: z.boolean(),
isEmployee: z.boolean(),
email: z.string().email('Email inválido').optional().or(z.literal('')),
phone: z.string().optional(),
mobile: z.string().optional(),
website: z.string().optional(),
taxId: z.string().optional(),
language: z.string().optional(),
notes: z.string().optional(),
internalNotes: z.string().optional(),
});
type FormData = z.infer<typeof partnerSchema>;
interface PartnerFormProps {
partner?: Partner;
onSubmit: (data: CreatePartnerDto | UpdatePartnerDto) => Promise<void>;
onCancel: () => void;
isLoading?: boolean;
}
const partnerTypeOptions = [
{ value: 'person', label: 'Persona física' },
{ value: 'company', label: 'Empresa' },
];
const languageOptions = [
{ value: 'es', label: 'Español' },
{ value: 'en', label: 'English' },
];
export function PartnerForm({ partner, onSubmit, onCancel, isLoading }: PartnerFormProps) {
const isEditing = !!partner;
const {
register,
handleSubmit,
setValue,
watch,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(partnerSchema),
defaultValues: partner
? {
name: partner.name,
legalName: partner.legalName || '',
partnerType: partner.partnerType,
isCustomer: partner.isCustomer,
isSupplier: partner.isSupplier,
isEmployee: partner.isEmployee,
email: partner.email || '',
phone: partner.phone || '',
mobile: partner.mobile || '',
website: partner.website || '',
taxId: partner.taxId || '',
language: partner.language || 'es',
notes: partner.notes || '',
internalNotes: partner.internalNotes || '',
}
: {
name: '',
legalName: '',
partnerType: 'company' as PartnerType,
isCustomer: true,
isSupplier: false,
isEmployee: false,
email: '',
phone: '',
mobile: '',
website: '',
taxId: '',
language: 'es',
notes: '',
internalNotes: '',
},
});
const selectedType = watch('partnerType');
const selectedLanguage = watch('language');
const handleFormSubmit = async (data: FormData) => {
const cleanData: CreatePartnerDto | UpdatePartnerDto = {
name: data.name,
partnerType: data.partnerType,
isCustomer: data.isCustomer,
isSupplier: data.isSupplier,
isEmployee: data.isEmployee,
isCompany: data.partnerType === 'company',
...(data.legalName && { legalName: data.legalName }),
...(data.email && { email: data.email }),
...(data.phone && { phone: data.phone }),
...(data.mobile && { mobile: data.mobile }),
...(data.website && { website: data.website }),
...(data.taxId && { taxId: data.taxId }),
...(data.language && { language: data.language }),
...(data.notes && { notes: data.notes }),
...(data.internalNotes && { internalNotes: data.internalNotes }),
};
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">Información básica</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
label="Nombre"
error={errors.name?.message}
required
>
<input
{...register('name')}
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 partner"
/>
</FormField>
<FormField
label="Tipo"
error={errors.partnerType?.message}
required
>
<Select
options={partnerTypeOptions}
value={selectedType}
onChange={(value) => setValue('partnerType', value as PartnerType)}
placeholder="Seleccionar tipo..."
/>
</FormField>
</div>
<FormField
label="Razón social"
error={errors.legalName?.message}
>
<input
{...register('legalName')}
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="Razón social (empresas)"
/>
</FormField>
{/* Partner Roles */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Tipo de relación
</label>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isCustomer')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Cliente</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isSupplier')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Proveedor</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
{...register('isEmployee')}
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Empleado</span>
</label>
</div>
</div>
</div>
{/* Contact Info */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Información 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="Teléfono"
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="Móvil"
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="Sitio web"
error={errors.website?.message}
>
<input
{...register('website')}
type="url"
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="https://www.ejemplo.com"
/>
</FormField>
</div>
</div>
{/* Fiscal Info */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Información fiscal</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
label="RFC / Tax ID"
error={errors.taxId?.message}
>
<input
{...register('taxId')}
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="ABC123456XYZ"
/>
</FormField>
<FormField
label="Idioma"
error={errors.language?.message}
>
<Select
options={languageOptions}
value={selectedLanguage || 'es'}
onChange={(value) => setValue('language', value as string)}
placeholder="Seleccionar idioma..."
/>
</FormField>
</div>
</div>
{/* Notes */}
<div className="space-y-4">
<h3 className="text-lg font-medium text-gray-900">Notas</h3>
<FormField
label="Notas públicas"
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 visibles para el partner..."
/>
</FormField>
<FormField
label="Notas internas"
error={errors.internalNotes?.message}
>
<textarea
{...register('internalNotes')}
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 internas (solo visibles para empleados)..."
/>
</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' : 'Crear partner'}
</Button>
</div>
</form>
);
}