Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview the template doc #143

Merged
merged 3 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Loading