Exportar datos de osp de la db en un excel
This commit is contained in:
@@ -3,11 +3,13 @@ import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Header,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
StreamableFile,
|
||||
UploadedFiles,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
@@ -24,6 +26,7 @@ import { CreateTrainingDto } from './dto/create-training.dto';
|
||||
import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto';
|
||||
import { UpdateTrainingDto } from './dto/update-training.dto';
|
||||
import { TrainingService } from './training.service';
|
||||
import { Public } from '@/common/decorators';
|
||||
|
||||
@ApiTags('training')
|
||||
@Controller('training')
|
||||
@@ -76,6 +79,38 @@ export class TrainingController {
|
||||
const data = await this.trainingService.getStatistics(filterDto);
|
||||
return { message: 'Training statistics fetched successfully', data };
|
||||
}
|
||||
// ========== //
|
||||
// @Get('export/all')
|
||||
// @ApiOperation({ summary: 'Export all training records to Excel' })
|
||||
// @ApiResponse({
|
||||
// status: 200,
|
||||
// description: 'Return training records Excel.',
|
||||
// })
|
||||
// async exportAll(@Query() filterDto: TrainingStatisticsFilterDto) {
|
||||
// const data = await this.trainingService.exportAll(filterDto);
|
||||
// return new StreamableFile(data, {
|
||||
// type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
// disposition: 'attachment; filename=training_surveys.xlsx',
|
||||
// });
|
||||
// }
|
||||
|
||||
@Public()
|
||||
@Get('export/all')
|
||||
@ApiOperation({ summary: 'Export all training records to Excel' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Return training template.',
|
||||
content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } }
|
||||
})
|
||||
@Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
@Header('Content-Disposition', 'attachment; filename=export_osp.xlsx')
|
||||
async exportAll(@Query() filterDto: TrainingStatisticsFilterDto) {
|
||||
const data = await this.trainingService.exportAll(filterDto);
|
||||
return new StreamableFile(data, {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
disposition: 'attachment; filename=training_surveys.xlsx',
|
||||
});
|
||||
}
|
||||
|
||||
// ========== //
|
||||
@Get(':id')
|
||||
|
||||
@@ -5,6 +5,11 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
|
||||
import * as schema from 'src/database/index';
|
||||
import { municipalities, parishes, states, trainingSurveys } from 'src/database/index';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
// @ts-ignore
|
||||
import XlsxPopulate from 'xlsx-populate';
|
||||
|
||||
|
||||
import { PaginationDto } from '../../common/dto/pagination.dto';
|
||||
import { CreateTrainingDto } from './dto/create-training.dto';
|
||||
@@ -98,7 +103,7 @@ export class TrainingService {
|
||||
if (municipalityId)
|
||||
filters.push(eq(trainingSurveys.municipality, municipalityId));
|
||||
if (parishId) filters.push(eq(trainingSurveys.parish, parishId));
|
||||
if (ospType) filters.push(eq(trainingSurveys.ospType, ospType));
|
||||
if (ospType && ospType !== 'all') filters.push(eq(trainingSurveys.ospType, ospType));
|
||||
|
||||
const whereCondition = filters.length > 0 ? and(...filters) : undefined;
|
||||
|
||||
@@ -864,4 +869,178 @@ export class TrainingService {
|
||||
|
||||
// return book.outputAsync();
|
||||
// }
|
||||
async exportAll(filterDto: TrainingStatisticsFilterDto) {
|
||||
try {
|
||||
const { startDate, endDate, stateId, municipalityId, parishId, ospType } =
|
||||
filterDto;
|
||||
|
||||
const filters: SQL[] = [];
|
||||
|
||||
if (startDate)
|
||||
filters.push(gte(trainingSurveys.visitDate, new Date(startDate)));
|
||||
if (endDate)
|
||||
filters.push(lte(trainingSurveys.visitDate, new Date(endDate)));
|
||||
if (stateId) filters.push(eq(trainingSurveys.state, stateId));
|
||||
if (municipalityId)
|
||||
filters.push(eq(trainingSurveys.municipality, municipalityId));
|
||||
if (parishId) filters.push(eq(trainingSurveys.parish, parishId));
|
||||
if (ospType && ospType !== 'all')
|
||||
filters.push(eq(trainingSurveys.ospType, ospType));
|
||||
|
||||
const whereCondition = filters.length > 0 ? and(...filters) : undefined;
|
||||
|
||||
const records = await this.drizzle
|
||||
.select({
|
||||
coorFullName: trainingSurveys.coorFullName,
|
||||
visitDate: trainingSurveys.visitDate,
|
||||
stateName: states.name,
|
||||
municipalityName: municipalities.name,
|
||||
parishName: parishes.name,
|
||||
communeName: trainingSurveys.communeName,
|
||||
siturCodeCommune: trainingSurveys.siturCodeCommune,
|
||||
communalCouncil: trainingSurveys.communalCouncil,
|
||||
siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil,
|
||||
productiveActivity: trainingSurveys.productiveActivity,
|
||||
ospName: trainingSurveys.ospName,
|
||||
ospAddress: trainingSurveys.ospAddress,
|
||||
ospRif: trainingSurveys.ospRif,
|
||||
ospType: trainingSurveys.ospType,
|
||||
currentStatus: trainingSurveys.currentStatus,
|
||||
companyConstitutionYear: trainingSurveys.companyConstitutionYear,
|
||||
ospResponsibleFullname: trainingSurveys.ospResponsibleFullname,
|
||||
ospResponsibleCedula: trainingSurveys.ospResponsibleCedula,
|
||||
ospResponsibleRif: trainingSurveys.ospResponsibleRif,
|
||||
ospResponsiblePhone: trainingSurveys.ospResponsiblePhone,
|
||||
ospResponsibleEmail: trainingSurveys.ospResponsibleEmail,
|
||||
civilState: trainingSurveys.civilState,
|
||||
familyBurden: trainingSurveys.familyBurden,
|
||||
numberOfChildren: trainingSurveys.numberOfChildren,
|
||||
generalObservations: trainingSurveys.generalObservations,
|
||||
paralysisReason: trainingSurveys.paralysisReason,
|
||||
productList: trainingSurveys.productList,
|
||||
infrastructureMt2: trainingSurveys.infrastructureMt2,
|
||||
photo1: trainingSurveys.photo1,
|
||||
photo2: trainingSurveys.photo2,
|
||||
photo3: trainingSurveys.photo3,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.leftJoin(states, eq(trainingSurveys.state, states.id))
|
||||
.leftJoin(
|
||||
municipalities,
|
||||
eq(trainingSurveys.municipality, municipalities.id),
|
||||
)
|
||||
.leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
|
||||
.where(whereCondition)
|
||||
.execute();
|
||||
|
||||
const workbook: any = await XlsxPopulate.fromBlankAsync();
|
||||
const sheet = workbook.sheet(0);
|
||||
|
||||
const headers = [
|
||||
'Coordinador',
|
||||
'Fecha',
|
||||
'Hora',
|
||||
'Estado',
|
||||
'Municipio',
|
||||
'Parroquia',
|
||||
'Comuna',
|
||||
'Código SITUR Comuna',
|
||||
'Consejo Comunal',
|
||||
'Código SITUR C.C.',
|
||||
'Actividad Productiva',
|
||||
'Nombre OSP',
|
||||
'Dirección OSP',
|
||||
'RIF OSP',
|
||||
'Tipo OSP',
|
||||
'Estatus Actual',
|
||||
'Año Constitución',
|
||||
'Total Productores',
|
||||
'Productos',
|
||||
'Infraestructura (mt2)',
|
||||
'Motivo Paralización',
|
||||
'Responsable',
|
||||
'Cédula',
|
||||
'RIF Responsable',
|
||||
'Teléfono',
|
||||
'Email',
|
||||
'Estado Civil',
|
||||
'Carga Familiar',
|
||||
'Nro Hijos',
|
||||
'Observaciones',
|
||||
// 'Foto 1',
|
||||
// 'Foto 2',
|
||||
// 'Foto 3',
|
||||
];
|
||||
|
||||
// Configurar encabezados
|
||||
sheet.range('A1:AG1').value([headers]).style({
|
||||
bold: true,
|
||||
fill: 'BFBFBF',
|
||||
});
|
||||
|
||||
let currentRow = 2;
|
||||
|
||||
for (const record of records) {
|
||||
const date = new Date(record.visitDate);
|
||||
const dateStr = date.toLocaleDateString('es-VE');
|
||||
const timeStr = date.toLocaleTimeString('es-VE');
|
||||
|
||||
const products = (record.productList as any[]) || [];
|
||||
const totalProducers = products.reduce(
|
||||
(sum, p) =>
|
||||
sum + (Number(p.menCount) || 0) + (Number(p.womenCount) || 0),
|
||||
0,
|
||||
);
|
||||
const productsDesc = products.map((p) => p.name).join(', ');
|
||||
|
||||
const rowData = [
|
||||
record.coorFullName,
|
||||
dateStr,
|
||||
timeStr,
|
||||
record.stateName || '',
|
||||
record.municipalityName || '',
|
||||
record.parishName || '',
|
||||
record.communeName,
|
||||
record.siturCodeCommune,
|
||||
record.communalCouncil,
|
||||
record.siturCodeCommunalCouncil,
|
||||
record.productiveActivity,
|
||||
record.ospName,
|
||||
record.ospAddress,
|
||||
record.ospRif,
|
||||
record.ospType,
|
||||
record.currentStatus,
|
||||
record.companyConstitutionYear,
|
||||
totalProducers,
|
||||
productsDesc,
|
||||
record.infrastructureMt2,
|
||||
record.paralysisReason || '',
|
||||
record.ospResponsibleFullname,
|
||||
record.ospResponsibleCedula,
|
||||
record.ospResponsibleRif,
|
||||
record.ospResponsiblePhone,
|
||||
record.ospResponsibleEmail,
|
||||
record.civilState,
|
||||
record.familyBurden,
|
||||
record.numberOfChildren,
|
||||
record.generalObservations || '',
|
||||
// record.photo1 || '',
|
||||
// record.photo2 || '',
|
||||
// record.photo3 || '',
|
||||
];
|
||||
|
||||
sheet.cell(`A${currentRow}`).value([rowData]);
|
||||
currentRow++;
|
||||
}
|
||||
|
||||
return await workbook.outputAsync();
|
||||
} catch (error: any) {
|
||||
console.error('Export Error:', error);
|
||||
throw new HttpException(
|
||||
'Error al generar el archivo Excel: ' + error.message,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TrainingSchema,
|
||||
trainingApiResponseSchema,
|
||||
} from '../schemas/training';
|
||||
import z from 'zod';
|
||||
|
||||
export const getTrainingStatisticsAction = async (
|
||||
params: {
|
||||
@@ -159,3 +160,39 @@ export const getTrainingByIdAction = async (id: number) => {
|
||||
|
||||
return response?.data;
|
||||
};
|
||||
|
||||
export const exportTrainingAction = async (
|
||||
params: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
stateId?: number;
|
||||
municipalityId?: number;
|
||||
parishId?: number;
|
||||
ospType?: string;
|
||||
} = {},
|
||||
) => {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params.startDate) searchParams.append('startDate', params.startDate);
|
||||
if (params.endDate) searchParams.append('endDate', params.endDate);
|
||||
if (params.stateId) searchParams.append('stateId', params.stateId.toString());
|
||||
if (params.municipalityId)
|
||||
searchParams.append('municipalityId', params.municipalityId.toString());
|
||||
if (params.parishId)
|
||||
searchParams.append('parishId', params.parishId.toString());
|
||||
if (params.ospType) searchParams.append('ospType', params.ospType);
|
||||
|
||||
|
||||
const [error, response] = await safeFetchApi(
|
||||
z.any(), //Schema
|
||||
`/training/export/all?${searchParams.toString()}`,
|
||||
'GET',
|
||||
undefined,
|
||||
{ responseType: 'arraybuffer' },
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message || 'Error al exportar los datos');
|
||||
}
|
||||
|
||||
return Array.from(new Uint8Array(response));
|
||||
};
|
||||
|
||||
@@ -37,6 +37,8 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import { useTrainingStatsQuery } from '../hooks/use-training-statistics';
|
||||
import { exportTrainingAction } from '../actions/training-actions';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
const OSP_TYPES = [
|
||||
'EPSD',
|
||||
@@ -89,6 +91,38 @@ export function TrainingStatistics() {
|
||||
setOspType('');
|
||||
};
|
||||
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
setIsExporting(true);
|
||||
const bytes = await exportTrainingAction({
|
||||
startDate: startDate || undefined,
|
||||
endDate: endDate || undefined,
|
||||
stateId: stateId || undefined,
|
||||
municipalityId: municipalityId || undefined,
|
||||
parishId: parishId || undefined,
|
||||
ospType: ospType || undefined,
|
||||
});
|
||||
|
||||
const blob = new Blob([new Uint8Array(bytes)], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `entrenamientos_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.error('Error exporting:', error);
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center p-8">Cargando estadísticas...</div>
|
||||
@@ -216,10 +250,19 @@ export function TrainingStatistics() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<div className="flex items-end gap-2">
|
||||
<Button variant="outline" onClick={handleClearFilters}>
|
||||
Limpiar Filtros
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleExport}
|
||||
disabled={isExporting}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
{isExporting ? 'Exportando...' : 'Exportar Excel'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use server';
|
||||
import { env } from '@/lib/env';
|
||||
import axios, { InternalAxiosRequestConfig } from 'axios';
|
||||
import axios, { AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Crear instancia de Axios con la URL base validada
|
||||
@@ -32,6 +32,7 @@ export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||
url: string,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<
|
||||
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
||||
> => {
|
||||
@@ -40,6 +41,7 @@ export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||
method,
|
||||
url,
|
||||
data,
|
||||
...config,
|
||||
});
|
||||
|
||||
const parsed = schema.safeParse(response.data);
|
||||
|
||||
Reference in New Issue
Block a user