corrreciones al formulario de las osp

This commit is contained in:
2026-02-03 14:19:57 -04:00
parent 26fb849fa3
commit 63c39e399e
6 changed files with 157 additions and 26 deletions

View File

@@ -213,6 +213,8 @@ export class CreateTrainingDto {
@IsString() @IsString()
communalCouncilEmail: string; communalCouncilEmail: string;
// === 6. LISTAS (Arrays JSON) === // === 6. LISTAS (Arrays JSON) ===
// Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real // Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real
@@ -260,4 +262,19 @@ export class CreateTrainingDto {
return value; return value;
}) })
productList?: any[]; productList?: any[];
//ubicacion
@ApiProperty()
@IsString()
state: string;
@ApiProperty()
@IsString()
municipality: string;
@ApiProperty()
@IsString()
parish: string;
} }

View File

@@ -271,7 +271,7 @@ export class TrainingService {
// 2. Extraer solo visitDate para formatearlo. // 2. Extraer solo visitDate para formatearlo.
// Ya NO extraemos state, municipality, etc. porque no vienen en el DTO. // Ya NO extraemos state, municipality, etc. porque no vienen en el DTO.
const { visitDate, ...rest } = createTrainingDto; const { visitDate, state, municipality, parish, ...rest } = createTrainingDto;
const [newRecord] = await this.drizzle const [newRecord] = await this.drizzle
.insert(trainingSurveys) .insert(trainingSurveys)
@@ -286,6 +286,9 @@ export class TrainingService {
photo1: photoPaths[0] ?? null, photo1: photoPaths[0] ?? null,
photo2: photoPaths[1] ?? null, photo2: photoPaths[1] ?? null,
photo3: photoPaths[2] ?? null, photo3: photoPaths[2] ?? null,
state: Number(state) ?? null,
municipality: Number(municipality) ?? null,
parish: Number(parish) ?? null,
}) })
.returning(); .returning();

View File

@@ -42,15 +42,17 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</div> </div>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<GeneralMain titleGroup={'General'} items={GeneralItems} role={userRole} /> {AdministrationItems[0]?.role?.includes(userRole) &&
<AdministrationMain titleGroup={'Administracion'} items={AdministrationItems} role={userRole} />
}
{StatisticsItems[0]?.role?.includes(userRole) && {StatisticsItems[0]?.role?.includes(userRole) &&
<StatisticsMain titleGroup={'Estadisticas'} items={StatisticsItems} role={userRole} /> <StatisticsMain titleGroup={'Estadisticas'} items={StatisticsItems} role={userRole} />
} }
{AdministrationItems[0]?.role?.includes(userRole) && <GeneralMain titleGroup={'General'} items={GeneralItems} role={userRole} />
<AdministrationMain titleGroup={'Administracion'} items={AdministrationItems} role={userRole} />
}
{/* <NavProjects projects={data.projects} /> */} {/* <NavProjects projects={data.projects} /> */}
</SidebarContent> </SidebarContent>
<SidebarRail /> <SidebarRail />

View File

@@ -45,6 +45,7 @@ import {
CardTitle, CardTitle,
} from '@repo/shadcn/components/ui/card'; } from '@repo/shadcn/components/ui/card';
import React from 'react'; import React from 'react';
import { SelectSearchable } from '@repo/shadcn/components/ui/select-searchable';
const OSP_TYPES = ['EPSIC', 'EPSDC', 'UPF', 'OTROS', 'COOPERATIVA']; const OSP_TYPES = ['EPSIC', 'EPSDC', 'UPF', 'OTROS', 'COOPERATIVA'];
const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA']; const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA'];
@@ -158,10 +159,14 @@ export function CreateTrainingForm({
equipmentList: defaultValues?.equipmentList || [], equipmentList: defaultValues?.equipmentList || [],
productionList: defaultValues?.productionList || [], productionList: defaultValues?.productionList || [],
productList: defaultValues?.productList || [], productList: defaultValues?.productList || [],
state: defaultValues?.state || undefined,
municipality: defaultValues?.municipality || undefined,
parish: defaultValues?.parish || undefined,
}, },
mode: 'onChange', mode: 'onChange',
}); });
// Cascading Select Logic // Cascading Select Logic
const ecoSector = useWatch({ control: form.control, name: 'ecoSector' }); const ecoSector = useWatch({ control: form.control, name: 'ecoSector' });
const productiveSector = useWatch({ const productiveSector = useWatch({
@@ -217,7 +222,7 @@ export function CreateTrainingForm({
: [{ id: 0, stateId: 0, name: 'Sin Municipios' }]; : [{ id: 0, stateId: 0, name: 'Sin Municipios' }];
const coorParishOptions = const coorParishOptions =
Array.isArray(dataCoorParish?.data) && dataCoorParish.data.length > 0 Array.isArray(dataCoorParish?.data) && dataCoorParish?.data.length > 0
? dataCoorParish.data ? dataCoorParish.data
: [{ id: 0, stateId: 0, name: 'Sin Parroquias' }]; : [{ id: 0, stateId: 0, name: 'Sin Parroquias' }];
@@ -263,7 +268,7 @@ export function CreateTrainingForm({
// 1. Definimos las claves que NO queremos enviar en el bucle general // 1. Definimos las claves que NO queremos enviar en el bucle general
// 'files' se procesa aparte. // 'files' se procesa aparte.
// 'photo1/2/3' son strings (urls viejas) que no queremos reenviar como texto. // 'photo1/2/3' son strings (urls viejas) que no queremos reenviar como texto.
const excludedKeys = ['files', 'photo1', 'photo2', 'photo3']; const excludedKeys = ['files', 'photo1', 'photo2', 'photo3', 'coorState', 'coorMunicipality', 'coorParish'];
Object.entries(formData).forEach(([key, value]) => { Object.entries(formData).forEach(([key, value]) => {
// 2. Condición actualizada: Si la key está en la lista de excluidos, la saltamos // 2. Condición actualizada: Si la key está en la lista de excluidos, la saltamos
@@ -282,6 +287,17 @@ export function CreateTrainingForm({
} }
}); });
// 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) { if (defaultValues?.id) {
data.append('id', defaultValues.id.toString()); data.append('id', defaultValues.id.toString());
} }
@@ -289,24 +305,24 @@ export function CreateTrainingForm({
// 4. Aquí se agregan las NUEVAS fotos (binary) si el usuario seleccionó alguna // 4. Aquí se agregan las NUEVAS fotos (binary) si el usuario seleccionó alguna
selectedFiles.forEach((file) => { selectedFiles.forEach((file) => {
data.append('files', file); data.append('files', file);
}); });
console.log(data);
const mutation = defaultValues?.id ? updateTraining : createTraining; const mutation = defaultValues?.id ? updateTraining : createTraining;
mutation(data as any, { // mutation(data as any, {
onSuccess: () => { // onSuccess: () => {
form.reset(); // form.reset();
setSelectedFiles([]); // setSelectedFiles([]);
onSuccess?.(); // onSuccess?.();
}, // },
onError: (e) => { // onError: (e) => {
console.error(e); // console.error(e);
form.setError('root', { // form.setError('root', {
type: 'manual', // type: 'manual',
message: 'Error al guardar el registro', // message: 'Error al guardar el registro',
}); // });
}, // },
}); // });
}; };
return ( return (
@@ -382,6 +398,8 @@ export function CreateTrainingForm({
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="visitDate" name="visitDate"
@@ -399,6 +417,76 @@ export function CreateTrainingForm({
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="state"
render={({ field }) => (
<FormItem>
<FormLabel>Estado</FormLabel>
<SelectSearchable
options={stateOptions.map((item) => ({
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()}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="municipality"
render={({ field }) => (
<FormItem>
<FormLabel>Municipio</FormLabel>
<SelectSearchable
options={municipalityOptions.map((item) => ({
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()}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="parish"
render={({ field }) => (
<FormItem>
<FormLabel>Parroquia</FormLabel>
<SelectSearchable
options={parishOptions.map((item) => ({
value: item.id.toString(),
label: item.name,
}))}
onValueChange={(value) => field.onChange(Number(value))}
placeholder="Selecciona una parroquia"
disabled={disabledParish}
defaultValue={field.value?.toString()}
/>
<FormMessage />
</FormItem>
)}
/>
</CardContent> </CardContent>
</Card> </Card>
@@ -833,6 +921,7 @@ export function CreateTrainingForm({
<CardTitle>3. Detalles de la ubicación</CardTitle> <CardTitle>3. Detalles de la ubicación</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start"> <CardContent className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
<FormField <FormField
control={form.control} control={form.control}
name="ospAddress" name="ospAddress"

View File

@@ -77,7 +77,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
<TooltipProvider> {/* <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
@@ -92,7 +92,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
<p>Exportar Excel</p> <p>Exportar Excel</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider> */}
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>

View File

@@ -28,6 +28,11 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { TrainingSchema } from '../schemas/training'; import { TrainingSchema } from '../schemas/training';
import {
useMunicipalityQuery,
useParishQuery,
useStateQuery,
} from '@/feactures/location/hooks/use-query-location';
interface TrainingViewModalProps { interface TrainingViewModalProps {
data: TrainingSchema | null; data: TrainingSchema | null;
@@ -41,9 +46,21 @@ export function TrainingViewModal({
onClose, onClose,
}: TrainingViewModalProps) { }: TrainingViewModalProps) {
const [selectedImage, setSelectedImage] = useState<string | null>(null); const [selectedImage, setSelectedImage] = useState<string | null>(null);
const { data: statesData } = useStateQuery();
const { data: municipalitiesData } = useMunicipalityQuery(data?.state || 0);
const { data: parishesData } = useParishQuery(data?.municipality || 0);
if (!data) return null; if (!data) return null;
const stateName = statesData?.data?.find((s: any) => s.id === data.state)?.name;
const municipalityName = municipalitiesData?.data?.find(
(m: any) => m.id === data.municipality,
)?.name;
const parishName = parishesData?.data?.find(
(p: any) => p.id === data.parish,
)?.name;
const DetailItem = ({ label, value }: { label: string; value: any }) => ( const DetailItem = ({ label, value }: { label: string; value: any }) => (
<div className="space-y-1"> <div className="space-y-1">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider"> <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
@@ -123,6 +140,9 @@ export function TrainingViewModal({
: 'N/A' : 'N/A'
} }
/> />
<DetailItem label="Estado" value={stateName} />
<DetailItem label="Municipio" value={municipalityName} />
<DetailItem label="Parroquia" value={parishName} />
</Section> </Section>
{/* 2. Sectores y Actividad */} {/* 2. Sectores y Actividad */}