Skip to content

Commit

Permalink
Merge pull request #143 from bcgov/TICDI-324_Preview
Browse files Browse the repository at this point in the history
Preview the template doc
  • Loading branch information
mgtennant authored Mar 29, 2024
2 parents c3621bf + 6ee35df commit 7b77d26
Show file tree
Hide file tree
Showing 8 changed files with 1,470 additions and 63 deletions.
1,279 changes: 1,221 additions & 58 deletions backend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,13 @@
"csvtojson": "^2.0.10",
"dotenv": "^16.0.3",
"express-session": "^1.17.3",
"html-pdf": "^3.0.1",
"joi": "^17.6.0",
"jwt-decode": "^3.1.2",
"mammoth": "^1.7.1",
"multer": "^1.4.5-lts.1",
"nestjs-session": "^3.0.1",
"pdfkit": "^0.15.0",
"pg": "^8.8.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.7",
Expand Down
21 changes: 21 additions & 0 deletions backend/src/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ export class AdminController {
});
}



@Get('preview-template/:id')
async previewTemplate(@Param('id') id: number, @Res() res) {
try {
const dtObject = await this.adminService.downloadTemplate(id);
const base64Data = dtObject.the_file;
const pdfBuffer = await this.adminService.convertDocxToPdfBuffer(base64Data);
const streamableFile = new stream.PassThrough();
streamableFile.end(pdfBuffer);
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename=file.pdf',
});
streamableFile.pipe(res);
} catch (error) {
console.error('Error:', error);
res.status(500).send('Internal Server Error');
}
}

@Get('download-template/:id')
async downloadTemplate(@Param('id') id: number, @Res() res) {
const dtObject = await this.adminService.downloadTemplate(id);
Expand Down
38 changes: 38 additions & 0 deletions backend/src/admin/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { DocumentTemplateService } from 'src/document_template/document_template
import { ProvisionService } from 'src/provision/provision.service';
import { DocumentTypeService } from 'src/document_type/document_type.service';
import { DocumentType } from 'src/document_type/entities/document_type.entity';
import * as fs from 'fs';
import * as mammoth from 'mammoth';
import * as PDFDocument from 'pdfkit';
import * as pdf from 'html-pdf';


const axios = require('axios');
const FormData = require('form-data');

Expand Down Expand Up @@ -357,4 +363,36 @@ export class AdminService {

return header + csvRows.join('\n');
}

async convertDocxToPdfBuffer(base64Data: string): Promise<Buffer> {
// Decode base64 string
const buffer = Buffer.from(base64Data, 'base64');

// Write buffer to temporary file
const tempFilePath = './temp.docx';
fs.writeFileSync(tempFilePath, buffer);

// Convert DOCX to HTML
const { value } = await mammoth.convertToHtml({ path: tempFilePath });
const htmlContent = value;

// Set options for html-pdf
const options: pdf.CreateOptions = {
format: 'Letter', // Set the PDF format (e.g., 'A4', 'Letter', etc.)
base: `file://${__dirname}/`, // Set the base path for local file references
};

return new Promise<Buffer>((resolve, reject) => {
// Convert HTML to PDF
pdf.create(htmlContent, options).toBuffer((err, buffer) => {
if (err) {
reject(err);
} else {
// Remove temporary file
fs.unlinkSync(tempFilePath);
resolve(buffer);
}
});
});
}
}
25 changes: 25 additions & 0 deletions frontend/src/app/common/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,31 @@ const fileDownloadPost = <T, M = {}>(parameters: ApiRequestParameters<M>): Promi
});
};

/**
* Used for file Preview through get request
*
* @param url
* @param filename
*/
export const handleFilePreviewGet = async (url: string, filename: string): Promise<Blob | null> => {
try {
const getParameters = generateApiParameters(url);
return new Promise<Blob>((resolve, reject) => {
fileDownloadGet<Blob>(getParameters)
.then(blob => {
resolve(blob);
})
.catch(error => {
console.error('Preview error:', error);
reject(error);
});
});
} catch (error) {
console.error('Preview error:', error);
return null;
}
};

/**
* Used for file downloads through get request
*
Expand Down
58 changes: 57 additions & 1 deletion frontend/src/app/common/manage-templates.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as api from './api';
import config from '../../config';
import { TemplateInfo } from '../types/types';
import { GroupMax, Provision, TemplateInfo, Variable } from '../types/types';

export const getTemplatesInfo = async (document_type_id: number): Promise<TemplateInfo[]> => {
const url = `${config.API_BASE_URL}/admin/get-templates/${document_type_id}`;
Expand All @@ -11,6 +11,62 @@ export const getTemplatesInfo = async (document_type_id: number): Promise<Templa
return response;
};

export const getGroupMax = async (): Promise<GroupMax[]> => {
const url = `${config.API_BASE_URL}/admin/get-group-max`;
const getParameters = api.generateApiParameters(url);
const response: GroupMax[] = await api.get<GroupMax[]>(getParameters);
console.log('getGroupMax response');
console.log(response);
return response;
};

export const getProvisions = async (): Promise<Provision[]> => {
const url = `${config.API_BASE_URL}/admin/provisions`;
const getParameters = api.generateApiParameters(url);
const response: Provision[] = await api.get<Provision[]>(getParameters);
console.log('getProvisions response');
console.log(response);
return response;
};

export const getVariables = async (): Promise<Variable[]> => {
const url = `${config.API_BASE_URL}/admin/document-variables`;
const getParameters = api.generateApiParameters(url);
const response: Variable[] = await api.get<Variable[]>(getParameters);
console.log('getVariables response');
console.log(response);
return response;
};

export const enableProvision = async (provisionId: number): Promise<void> => {
const url = `${config.API_BASE_URL}/admin/enable-provision/${provisionId}`;
const getParameters = api.generateApiParameters(url);
const response = await api.get<void>(getParameters);
console.log('enableProvision response');
console.log(response);
};

export const disableProvision = async (provisionId: number): Promise<void> => {
const url = `${config.API_BASE_URL}/admin/disable-provision/${provisionId}`;
const getParameters = api.generateApiParameters(url);
const response = await api.get<void>(getParameters);
console.log('disableProvision response');
console.log(response);
};

export const previewTemplate = async (id: number, fileName: string): Promise<Blob | null>=> {
const url = `${config.API_BASE_URL}/admin/preview-template/${id}`;
const response = await api.handleFilePreviewGet(url, fileName);
if (response) {
console.log('Preview response:');
console.log(response);
return response;
} else {
console.log('Preview response was null or there was an error.');
return null;
}
};

export const downloadTemplate = async (id: number, fileName: string): Promise<void> => {
const url = `${config.API_BASE_URL}/admin/download-template/${id}`;
const response = await api.handleFileDownloadGet(url, fileName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import Button from 'react-bootstrap/Button';

interface PreviewTemplateModalProps {
isOpen: boolean;
toggleModal: () => void;
iframeSrcBlob: Blob | null;
}

const PreviewTemplateModal: React.FC<PreviewTemplateModalProps> = ({ isOpen, toggleModal, iframeSrcBlob }) => {
const iframeSrc = iframeSrcBlob ? window.URL.createObjectURL(iframeSrcBlob) : '';

const modalOverlayStyle: React.CSSProperties = {
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 9999,
display: isOpen ? 'flex' : 'none',
alignItems: 'center',
justifyContent: 'center',
};

const modalContentStyle: React.CSSProperties = {
width: '80%',
height: '80%',
backgroundColor: '#fff',
borderRadius: '8px',
overflow: 'hidden',
position: 'relative',
display: 'flex',
flexDirection: 'column',
};

const iframeContainerStyle: React.CSSProperties = {
flex: 1,
overflow: 'auto',
padding: '20px',
};

const buttonContainerStyle: React.CSSProperties = {
textAlign: 'right', // Align button to the right
padding: '20px',
};

return (
<div style={modalOverlayStyle} onClick={toggleModal}>
<div style={modalContentStyle} onClick={(e) => e.stopPropagation()}>
<div style={iframeContainerStyle}>
<h2>Preview Document</h2>
{iframeSrc && (
<iframe
src={iframeSrc}
title="Example Iframe"
style={{ border: 'none', width: '100%', height: '100%' }}
allowFullScreen
/>
)}
</div>
<div style={buttonContainerStyle}>
<Button variant="secondary" onClick={toggleModal}>
Close
</Button>
</div>
</div>
</div>
);
};

export default PreviewTemplateModal;
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { DataTable } from '../common/DataTable';
import { ColumnDef, createColumnHelper } from '@tanstack/react-table';
import { activateTemplate, downloadTemplate, getTemplatesInfo } from '../../../common/manage-templates';
import { activateTemplate, downloadTemplate, getTemplatesInfo, previewTemplate } from '../../../common/manage-templates';
import { DocType, TemplateInfo } from '../../../types/types';
import { Button } from 'react-bootstrap';
import PreviewTemplateModal from '../../modal/admin/manage-templates/PreviewTemplateModal';

interface TemplateInfoTableProps {
documentType: DocType;
Expand All @@ -15,6 +16,8 @@ const TemplateInfoTable: React.FC<TemplateInfoTableProps> = ({ documentType, ref
const [templateData, setTemplateData] = useState<TemplateInfo[]>([]);
const [currentlyActive, setCurrentlyActive] = useState<number>();
const [loading, setLoading] = useState<boolean>(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [iframeSrcBlob, setIframeSrcBlob] = useState<Blob | null>(null);

useEffect(() => {
const fetchData = async () => {
Expand Down Expand Up @@ -55,6 +58,27 @@ const TemplateInfoTable: React.FC<TemplateInfoTableProps> = ({ documentType, ref
}
};

const toggleModal = () => {
setIsOpen(!isOpen);
};

const handlePreviewTemplate = async (id: number, fileName: string) => {
try {
setLoading(true);
const responce = await previewTemplate(id, fileName);
if (responce) {
setIframeSrcBlob(responce);
setIsOpen(true);
setLoading(false);
}
} catch (error) {
console.log('Error downloading template');
console.log(error);
} finally {
setLoading(false);
}
};

// this opens a modal which is handled on the ManageTemplatesPage
const handleRemoveButton = (id: number) => {
handleRemove(id);
Expand Down Expand Up @@ -103,7 +127,7 @@ const TemplateInfoTable: React.FC<TemplateInfoTableProps> = ({ documentType, ref
columnHelper.accessor('preview', {
id: 'preview',
cell: (info) => (
<Button variant="success" onClick={() => console.log('')}>
<Button variant="success" onClick={() => handlePreviewTemplate(info.row.original.id, info.row.original.file_name)}>
Preview
</Button>
),
Expand Down Expand Up @@ -145,14 +169,19 @@ const TemplateInfoTable: React.FC<TemplateInfoTableProps> = ({ documentType, ref
}),
];

return (



return <>
{isOpen && <PreviewTemplateModal isOpen={isOpen} toggleModal={toggleModal} iframeSrcBlob={iframeSrcBlob} />}
<DataTable
columns={columns}
data={templateData}
enableSorting={true}
initialSorting={[{ id: 'template_version', desc: false }]}
/>
);
</>;

};

export default TemplateInfoTable;

0 comments on commit 7b77d26

Please sign in to comment.