feat: export resource tables to pdf

This commit is contained in:
Ahmed Bouhuolia
2024-05-23 14:23:49 +02:00
parent 1227111fae
commit fe41f7976d
10 changed files with 195 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import BaseController from '@/api/controllers/BaseController';
import { ServiceError } from '@/exceptions';
import { ExportApplication } from '@/services/Export/ExportApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { convertAcceptFormatToFormat } from './_utils';
@Service()
export class ExportController extends BaseController {
@@ -48,10 +49,12 @@ export class ExportController extends BaseController {
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
const applicationFormat = convertAcceptFormatToFormat(acceptType);
const data = await this.exportResourceApp.export(
tenantId,
query.resource,
acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv'
applicationFormat
);
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
@@ -69,6 +72,13 @@ export class ExportController extends BaseController {
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(data);
//
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
res.set({
'Content-Type': 'application/pdf',
'Content-Length': data.length,
});
res.send(data);
}
} catch (error) {
next(error);

View File

@@ -0,0 +1,13 @@
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { ExportFormat } from '@/services/Export/common';
export const convertAcceptFormatToFormat = (accept: string): ExportFormat => {
switch (accept) {
case ACCEPT_TYPE.APPLICATION_CSV:
return ExportFormat.Csv;
case ACCEPT_TYPE.APPLICATION_PDF:
return ExportFormat.Pdf;
case ACCEPT_TYPE.APPLICATION_XLSX:
return ExportFormat.Xlsx;
}
};

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi';
import { ExportResourceService } from './ExportService';
import { ExportFormat } from './common';
@Service()
export class ExportApplication {
@@ -9,9 +10,9 @@ export class ExportApplication {
/**
* Exports the given resource to csv, xlsx or pdf format.
* @param {string} reosurce
* @param {string} format
* @param {ExportFormat} format
*/
public export(tenantId: number, resource: string, format: string) {
public export(tenantId: number, resource: string, format: ExportFormat) {
return this.exportResource.export(tenantId, resource, format);
}
}

View File

@@ -0,0 +1,47 @@
import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable';
import { mapPdfRows } from './utils';
@Service()
export class ExportPdf {
@Inject()
private templateInjectable: TemplateInjectable;
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
/**
*
* @param tenantId
* @param columns
* @param data
* @param sheetTitle
* @param sheetDescription
* @returns
*/
public async pdf(
tenantId: number,
columns: { accessor: string },
data: Record<string, any>,
sheetTitle: string = '',
sheetDescription: string = ''
) {
const rows = mapPdfRows(columns, data);
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/export-resource-table',
{
table: { rows, columns },
sheetTitle,
sheetDescription,
}
);
// Convert the HTML content to PDF
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, {
margins: { top: 0, bottom: 0, left: 0, right: 0 },
landscape: true,
});
}
}

View File

@@ -6,9 +6,10 @@ import { sanitizeResourceName } from '../Import/_utils';
import ResourceService from '../Resource/ResourceService';
import { ExportableResources } from './ExportResources';
import { ServiceError } from '@/exceptions';
import { Errors } from './common';
import { Errors, ExportFormat } from './common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
import { flatDataCollections, getDataAccessor } from './utils';
import { ExportPdf } from './ExportPdf';
@Service()
export class ExportResourceService {
@@ -18,13 +19,20 @@ export class ExportResourceService {
@Inject()
private exportableResources: ExportableResources;
@Inject()
private exportPdf: ExportPdf;
/**
* Exports the given resource data through csv, xlsx or pdf.
* @param {number} tenantId - Tenant id.
* @param {string} resourceName - Resource name.
* @param {string} format - File format.
* @param {ExportFormat} format - File format.
*/
public async export(tenantId: number, resourceName: string, format: string = 'csv') {
public async export(
tenantId: number,
resourceName: string,
format: ExportFormat = ExportFormat.Csv
) {
const resource = sanitizeResourceName(resourceName);
const resourceMeta = this.getResourceMeta(tenantId, resource);
@@ -33,9 +41,21 @@ export class ExportResourceService {
const data = await this.getExportableData(tenantId, resource);
const transformed = this.transformExportedData(tenantId, resource, data);
const exportableColumns = this.getExportableColumns(resourceMeta);
const workbook = this.createWorkbook(transformed, exportableColumns);
return this.exportWorkbook(workbook, format);
// Returns the csv, xlsx format.
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
const workbook = this.createWorkbook(transformed, exportableColumns);
return this.exportWorkbook(workbook, format);
// Returns the pdf format.
} else if (format === ExportFormat.Pdf) {
return this.exportPdf.pdf(
tenantId,
exportableColumns,
transformed,
'Accounts'
);
}
}
/**
@@ -91,6 +111,7 @@ export class ExportResourceService {
private async getExportableData(tenantId: number, resource: string) {
const exportable =
this.exportableResources.registry.getExportable(resource);
return exportable.exportable(tenantId, {});
}

View File

@@ -1,3 +1,9 @@
export enum Errors {
RESOURCE_NOT_EXPORTABLE = 'RESOURCE_NOT_EXPORTABLE',
}
export enum ExportFormat {
Csv = 'csv',
Pdf = 'pdf',
Xlsx = 'xlsx',
}

View File

@@ -1,4 +1,4 @@
import { flatMap } from 'lodash';
import { flatMap, get } from 'lodash';
/**
* Flattens the data based on a specified attribute.
* @param data - The data to be flattened.
@@ -25,3 +25,18 @@ export const flatDataCollections = (
export const getDataAccessor = (col: any) => {
return col.group ? `${col.group}.${col.accessor}` : col.accessor;
};
/**
*
* @param columns
* @param data
* @returns
*/
export const mapPdfRows = (columns: any, data: Record<string, any>) => {
return data.map((item) => {
const cells = columns.map((column) => {
return { key: column.accessor, value: get(item, column.accessor) };
});
return { cells, classNames: '' };
});
};