'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@repo/shadcn/button'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@repo/shadcn/form'; import { Input } from '@repo/shadcn/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@repo/shadcn/select'; import { Textarea } from '@repo/shadcn/textarea'; import { useForm, useWatch } from 'react-hook-form'; import { ACTIVIDAD_CENTRAL_MAP, ACTIVIDAD_PRINCIPAL_MAP, ACTIVIDAD_PRODUCTIVA_MAP, SECTOR_ECONOMICO_OPTIONS, SECTOR_PRODUCTIVO_MAP, } from '../constants/osp-data'; import { useCreateTraining, useUpdateTraining } from '../hooks/use-training'; import { TrainingSchema, trainingSchema } from '../schemas/training'; import { EquipmentList } from './equipment-list'; import { ProductActivityList } from './product-activity-list'; import { ProductionList } from './production-list'; import { useMunicipalityQuery, useParishQuery, useStateQuery, } from '@/feactures/location/hooks/use-query-location'; import { Card, CardContent, CardHeader, CardTitle, } from '@repo/shadcn/components/ui/card'; import { SelectSearchable } from '@repo/shadcn/components/ui/select-searchable'; import React from 'react'; const OSP_TYPES = ['EPSIC', 'EPSDC', 'UPF', 'OTROS', 'COOPERATIVA']; const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA']; const CIVIL_STATE_OPTIONS = ['Soltero', 'Casado']; interface CreateTrainingFormProps { onSuccess?: () => void; onCancel?: () => void; defaultValues?: Partial; } export function CreateTrainingForm({ onSuccess, onCancel, defaultValues, }: CreateTrainingFormProps) { const { mutate: createTraining, isPending: isCreating } = useCreateTraining(); const { mutate: updateTraining, isPending: isUpdating } = useUpdateTraining(); const isSaving = isCreating || isUpdating; const [state, setState] = React.useState(0); const [municipality, setMunicipality] = React.useState(0); const [disabledMunicipality, setDisabledMunicipality] = React.useState(true); const [disabledParish, setDisabledParish] = React.useState(true); const [coorState, setcoorState] = React.useState(0); const [coorMunicipality, setcoorMunicipality] = React.useState(0); const [disabledCoorMunicipality, setDisabledCoorMunicipality] = React.useState(true); const [disabledCoorParish, setDisabledCoorParish] = React.useState(true); const [selectedFiles, setSelectedFiles] = React.useState([]); const { data: dataState } = useStateQuery(); const { data: dataMunicipality } = useMunicipalityQuery(state); const { data: dataParish } = useParishQuery(municipality); const formatToLocalISO = (dateStr?: string | Date) => { const date = dateStr ? new Date(dateStr) : new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day}T${hours}:${minutes}`; }; const form = useForm({ resolver: zodResolver(trainingSchema), defaultValues: { firstname: defaultValues?.firstname || '', lastname: defaultValues?.lastname || '', coorState: defaultValues?.coorState || undefined, coorMunicipality: defaultValues?.coorMunicipality || undefined, coorParish: defaultValues?.coorParish || undefined, coorPhone: defaultValues?.coorPhone || '', visitDate: formatToLocalISO(defaultValues?.visitDate), productiveActivity: defaultValues?.productiveActivity || '', ecoSector: defaultValues?.ecoSector || undefined, productiveSector: defaultValues?.productiveSector || undefined, centralProductiveActivity: defaultValues?.centralProductiveActivity || '', mainProductiveActivity: defaultValues?.mainProductiveActivity || '', photo1: defaultValues?.photo1 || '', photo2: defaultValues?.photo2 || '', photo3: defaultValues?.photo3 || '', siturCodeCommune: defaultValues?.siturCodeCommune || '', communeName: defaultValues?.communeName || '', communeRif: defaultValues?.communeRif || '', communeSpokespersonName: defaultValues?.communeSpokespersonName || '', communeSpokespersonCedula: defaultValues?.communeSpokespersonCedula || '', communeSpokespersonRif: defaultValues?.communeSpokespersonRif || '', communeSpokespersonPhone: defaultValues?.communeSpokespersonPhone || '', communeEmail: defaultValues?.communeEmail || '', communalCouncil: defaultValues?.communalCouncil || '', siturCodeCommunalCouncil: defaultValues?.siturCodeCommunalCouncil || '', communalCouncilRif: defaultValues?.communalCouncilRif || '', communalCouncilSpokespersonName: defaultValues?.communalCouncilSpokespersonName || '', communalCouncilSpokespersonCedula: defaultValues?.communalCouncilSpokespersonCedula || '', communalCouncilSpokespersonRif: defaultValues?.communalCouncilSpokespersonRif || '', communalCouncilSpokespersonPhone: defaultValues?.communalCouncilSpokespersonPhone || '', communalCouncilEmail: defaultValues?.communalCouncilEmail || '', ospGoogleMapsLink: defaultValues?.ospGoogleMapsLink || '', ospName: defaultValues?.ospName || '', ospAddress: defaultValues?.ospAddress || '', ospRif: defaultValues?.ospRif || '', ospType: defaultValues?.ospType || '', currentStatus: defaultValues?.currentStatus || 'ACTIVA', companyConstitutionYear: defaultValues?.companyConstitutionYear || new Date().getFullYear(), ospResponsibleFullname: defaultValues?.ospResponsibleFullname || '', ospResponsibleCedula: defaultValues?.ospResponsibleCedula || '', ospResponsibleRif: defaultValues?.ospResponsibleRif || '', ospResponsiblePhone: defaultValues?.ospResponsiblePhone || '', civilState: defaultValues?.civilState || '', familyBurden: defaultValues?.familyBurden || 0, numberOfChildren: defaultValues?.numberOfChildren || 0, generalObservations: defaultValues?.generalObservations || '', ospResponsibleEmail: defaultValues?.ospResponsibleEmail || '', paralysisReason: defaultValues?.paralysisReason || '', infrastructureMt2: defaultValues?.infrastructureMt2 || '', hasTransport: defaultValues?.hasTransport || false, structureType: defaultValues?.structureType || '', isOpenSpace: defaultValues?.isOpenSpace || false, equipmentList: defaultValues?.equipmentList || [], productionList: defaultValues?.productionList || [], productList: defaultValues?.productList || [], state: defaultValues?.state || undefined, municipality: defaultValues?.municipality || undefined, parish: defaultValues?.parish || undefined, }, mode: 'onChange', }); // Cascading Select Logic const ecoSector = useWatch({ control: form.control, name: 'ecoSector' }); const productiveSector = useWatch({ control: form.control, name: 'productiveSector', }); const centralProductiveActivity = useWatch({ control: form.control, name: 'centralProductiveActivity', }); const mainProductiveActivity = useWatch({ control: form.control, name: 'mainProductiveActivity', }); const productiveSectorOptions = ecoSector ? SECTOR_PRODUCTIVO_MAP[ecoSector] || [] : []; const centralProductiveActivityOptions = productiveSector ? ACTIVIDAD_CENTRAL_MAP[productiveSector] || [] : []; const mainProductiveActivityOptions = centralProductiveActivity ? ACTIVIDAD_PRINCIPAL_MAP[centralProductiveActivity] || [] : []; const productiveActivityOptions = mainProductiveActivity ? ACTIVIDAD_PRODUCTIVA_MAP[mainProductiveActivity] || [] : []; // Reset dependent fields when parent changes React.useEffect(() => { // This effect handles shifts in options, but react-hook-form values persist // You might want to clear values if they are no longer valid, // but for now we rely on the user making a valid selection. }, [ ecoSector, productiveSector, centralProductiveActivity, mainProductiveActivity, ]); const { data: dataCoorState } = useStateQuery(); const { data: dataCoorMunicipality } = useMunicipalityQuery(coorState); const { data: dataCoorParish } = useParishQuery(coorMunicipality); const coorStateOptions = dataCoorState?.data || [ { id: 0, name: 'Sin estados' }, ]; const coorMunicipalityOptions = Array.isArray(dataCoorMunicipality?.data) && dataCoorMunicipality.data.length > 0 ? dataCoorMunicipality.data : [{ id: 0, stateId: 0, name: 'Sin Municipios' }]; const coorParishOptions = Array.isArray(dataCoorParish?.data) && dataCoorParish?.data.length > 0 ? dataCoorParish.data : [{ id: 0, stateId: 0, name: 'Sin Parroquias' }]; const stateOptions = dataState?.data || [{ id: 0, name: 'Sin estados' }]; const municipalityOptions = Array.isArray(dataMunicipality?.data) && dataMunicipality.data.length > 0 ? dataMunicipality.data : [{ id: 0, stateId: 0, name: 'Sin Municipios' }]; const parishOptions = Array.isArray(dataParish?.data) && dataParish.data.length > 0 ? dataParish.data : [{ id: 0, stateId: 0, name: 'Sin Parroquias' }]; // No local state needed for existing photos, we use form values React.useEffect(() => { if (defaultValues) { if (defaultValues.state) { setState(Number(defaultValues.state)); setDisabledMunicipality(false); } if (defaultValues.municipality) { setMunicipality(Number(defaultValues.municipality)); setDisabledParish(false); } if (defaultValues.coorState) { setcoorState(Number(defaultValues.coorState)); setDisabledCoorMunicipality(false); } if (defaultValues.coorMunicipality) { setcoorMunicipality(Number(defaultValues.coorMunicipality)); setDisabledCoorParish(false); } } }, [defaultValues]); const onSubmit = async (formData: TrainingSchema) => { const data = new FormData(); // 1. Definimos las claves que NO queremos enviar en el bucle general // 'files' se procesa aparte. // 'photo1/2/3' son strings (urls viejas) que no queremos reenviar como texto. const excludedKeys = [ 'files', 'photo1', 'photo2', 'photo3', 'coorState', 'coorMunicipality', 'coorParish', ]; Object.entries(formData).forEach(([key, value]) => { // 2. Condición actualizada: Si la key está en la lista de excluidos, la saltamos if (excludedKeys.includes(key) || value === undefined || value === null) { return; } // 3. Lógica de conversión (Igual que tenías) if ( Array.isArray(value) || (typeof value === 'object' && !(value instanceof Date)) ) { data.append(key, JSON.stringify(value)); } else { data.append(key, value.toString()); } }); // 2. Mapeo manual y conversión a numérico // if (formData.state) { // data.append('state', Number(formData.state).toString()); // } // if (formData.municipality) { // data.append('municipality', Number(formData.municipality).toString()); // } // if (formData.parish) { // data.append('parish', Number(formData.parish).toString()); // } if (defaultValues?.id) { data.append('id', defaultValues.id.toString()); } // 4. Aquí se agregan las NUEVAS fotos (binary) si el usuario seleccionó alguna selectedFiles.forEach((file) => { data.append('files', file); }); const mutation = defaultValues?.id ? updateTraining : createTraining; mutation(data as any, { onSuccess: () => { form.reset(); setSelectedFiles([]); onSuccess?.(); }, onError: (e) => { console.error(e); form.setError('root', { type: 'manual', message: 'Error al guardar el registro', }); }, }); }; return ( <>

Formulario de Registro de Organizaciones Socioproductivas

Complete el Formulario para guardar la organización

{form.formState.errors.root && (
{form.formState.errors.root.message}
)} {/* 1. Datos de la visita */} 1. Datos de la visita ( Nombre del Coordinador Estadal )} /> ( Apellido del Coordinador Estadal )} /> ( Teléfono )} /> ( Fecha y Hora de la visita )} /> ( Estado ({ value: item.id.toString(), label: item.name, }))} onValueChange={(value) => { field.onChange(Number(value)); setState(Number(value)); setDisabledMunicipality(false); setDisabledParish(true); }} placeholder="Selecciona un estado" defaultValue={field.value?.toString()} /> )} /> ( Municipio ({ value: item.id.toString(), label: item.name, }))} onValueChange={(value) => { field.onChange(Number(value)); setMunicipality(Number(value)); setDisabledParish(false); }} placeholder="Selecciona un municipio" disabled={disabledMunicipality} defaultValue={field.value?.toString()} /> )} /> ( Parroquia ({ value: item.id.toString(), label: item.name, }))} onValueChange={(value) => field.onChange(Number(value))} placeholder="Selecciona una parroquia" disabled={disabledParish} defaultValue={field.value?.toString()} /> )} /> {/* 2. Datos de la OSP */} 2. Datos de la Organización Socioproductiva (OSP) {/* CAMBIO 1: lg:grid-cols-2 para evitar columnas estrechas en tablets */} ( /* CAMBIO 2: flex flex-col y space-y-3 para forzar separación vertical interna */ Tipo de Organización )} /> ( Sector Económico )} /> ( Sector Productivo )} /> ( Actividad Central Productiva )} /> ( Actividad Productiva Principal )} /> ( Actividad Productiva )} /> ( RIF de la organización (opcional) )} /> ( Nombre de la organización )} /> ( Año de constitución )} /> ( Estatus )} /> ( infraestrutura (MT2) )} /> ( ¿Posee Transporte? )} /> ( Tipo Estructura )} /> ( ¿Es un Espacio Abierto? )} /> ( /* CAMBIO: Se mantiene col-span-2 para que ocupe todo el ancho si hay espacio */ Razones de paralización (opcional)