From 3b69be758454989e47461423a7c80ff0f0446150 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Wed, 19 Aug 2026 18:18:33 +0100 Subject: [PATCH] Refactor document job download functionality and enhance error handling - Updated the download functionality in NewDocumentJob to use a unified downloadTemplate method, supporting multiple file formats (PDF, PNG, JPEG, SVG). - Improved error handling in ApiServerContext by adding getTemplateErrorFromResponse and triggerFileDownload functions for better response management. - Refactored fetchTemplatePDF to utilize fetchTemplateDownload for streamlined code and improved maintainability. - Cleaned up ObjectDisplay component by removing unnecessary line breaks for better readability. --- .../DocumentJobs/NewDocumentJob.jsx | 52 ++++- .../Dashboard/common/ObjectDisplay.jsx | 4 +- .../Dashboard/context/ApiServerContext.jsx | 191 ++++++++++++++---- 3 files changed, 196 insertions(+), 51 deletions(-) diff --git a/src/components/Dashboard/Management/DocumentJobs/NewDocumentJob.jsx b/src/components/Dashboard/Management/DocumentJobs/NewDocumentJob.jsx index 20fe4316..bf783c80 100644 --- a/src/components/Dashboard/Management/DocumentJobs/NewDocumentJob.jsx +++ b/src/components/Dashboard/Management/DocumentJobs/NewDocumentJob.jsx @@ -8,7 +8,7 @@ import { useContext, useRef, useState } from 'react' import dayjs from 'dayjs' const NewDocumentJob = ({ onOk, defaultValues = {} }) => { - const { sendObjectAction, downloadTemplatePDF, formatFileName } = + const { sendObjectAction, downloadTemplate, formatFileName } = useContext(ApiServerContext) const [downloading, setDownloading] = useState(false) @@ -92,11 +92,12 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => { key: 'pdf', onClick: () => { setDownloading(true) - downloadTemplatePDF( + downloadTemplate( objectData.documentTemplate._id, objectData.documentTemplate.content, objectData.object, fileName, + 'pdf', () => { setDownloading(false) } @@ -105,11 +106,54 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => { }, { label: 'PNG', - key: 'png' + key: 'png', + onClick: () => { + setDownloading(true) + downloadTemplate( + objectData.documentTemplate._id, + objectData.documentTemplate.content, + objectData.object, + fileName, + 'png', + () => { + setDownloading(false) + } + ) + } }, { label: 'JPEG', - key: 'jpeg' + key: 'jpeg', + onClick: () => { + setDownloading(true) + downloadTemplate( + objectData.documentTemplate._id, + objectData.documentTemplate.content, + objectData.object, + fileName, + 'jpeg', + () => { + setDownloading(false) + } + ) + } + }, + { + label: 'SVG', + key: 'svg', + onClick: () => { + setDownloading(true) + downloadTemplate( + objectData.documentTemplate._id, + objectData.documentTemplate.content, + objectData.object, + fileName, + 'svg', + () => { + setDownloading(false) + } + ) + } } ] } diff --git a/src/components/Dashboard/common/ObjectDisplay.jsx b/src/components/Dashboard/common/ObjectDisplay.jsx index 81f00a12..88a8e1a9 100644 --- a/src/components/Dashboard/common/ObjectDisplay.jsx +++ b/src/components/Dashboard/common/ObjectDisplay.jsx @@ -192,9 +192,7 @@ const ObjectDisplay = ({ if (!objectData?.name) return null const textElement = ( - - {objectData.name} - + {objectData.name} ) // If hyperlink is enabled diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx index 9c152c2a..8ddb068d 100644 --- a/src/components/Dashboard/context/ApiServerContext.jsx +++ b/src/components/Dashboard/context/ApiServerContext.jsx @@ -1750,6 +1750,30 @@ const ApiServerProvider = ({ children }) => { } } + const getTemplateErrorFromResponse = (err) => { + const data = err?.response?.data + if (data instanceof ArrayBuffer) { + try { + const parsed = JSON.parse(new TextDecoder().decode(data)) + return parsed?.error || err.message + } catch { + return err.message + } + } + return data?.error || err.message + } + + const triggerFileDownload = (blob, filename) => { + const fileUrl = URL.createObjectURL(blob) + const fileLink = document.createElement('a') + fileLink.href = fileUrl + fileLink.setAttribute('download', filename) + document.body.appendChild(fileLink) + fileLink.click() + fileLink.parentNode.removeChild(fileLink) + URL.revokeObjectURL(fileUrl) + } + const fetchTemplatePreview = async ( id, content, @@ -1758,38 +1782,138 @@ const ApiServerProvider = ({ children }) => { callback ) => { logger.debug('Fetching preview...') - if (socketRef.current && socketRef.current.connected) { - return socketRef.current.emit( - 'previewTemplate', + try { + const response = await axios.post( + `${config.backendUrl}/documenttemplates/${id}/preview`, { - _id: id, - content: content, - testObject: testObject, - scale: scale + content, + testObject, + scale }, - callback + { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}` + } + } ) + if (typeof callback === 'function') { + callback(response.data) + } + return response.data + } catch (err) { + const error = getTemplateErrorFromResponse(err) + logger.error('Error fetching template preview:', error) + if (typeof callback === 'function') { + callback({ error }) + } + return { error } } - if (typeof callback === 'function') { - callback({ error: 'Api Server disconnected' }) + } + + const fetchTemplateDownload = async (id, content, object, type = 'pdf') => { + logger.debug(`Fetching template download as ${type}...`) + const response = await axios.post( + `${config.backendUrl}/documenttemplates/${id}/download`, + { + content, + object + }, + { + params: { type }, + responseType: 'arraybuffer', + headers: { + Authorization: `Bearer ${token}` + } + } + ) + const contentType = response.headers['content-type'] || '' + if (contentType.includes('application/json')) { + return JSON.parse(new TextDecoder().decode(response.data)) + } + return { + type, + mime: contentType, + buffer: response.data } } const fetchTemplatePDF = async (id, content, testObject, callback) => { logger.debug('Fetching pdf template...') - if (socketRef.current && socketRef.current.connected) { - return socketRef.current.emit( - 'renderTemplatePDF', - { - _id: id, - content: content, - object: testObject - }, - callback + try { + const result = await fetchTemplateDownload( + id, + content, + testObject, + 'pdf' ) + const payload = result.error + ? result + : { pdf: result.buffer || result } + if (typeof callback === 'function') { + callback(payload) + } + return payload + } catch (err) { + const error = getTemplateErrorFromResponse(err) + logger.error('Error fetching template PDF:', error) + if (typeof callback === 'function') { + callback({ error }) + } + return { error } } - if (typeof callback === 'function') { - callback({ error: 'Api Server disconnected' }) + } + + const downloadTemplate = async ( + id, + content, + object, + filename, + type = 'pdf', + callback + ) => { + logger.debug(`Downloading template as ${type}...`) + try { + const result = await fetchTemplateDownload(id, content, object, type) + if (result?.error) { + if (callback) { + callback(result.error) + } + return result + } + + if (Array.isArray(result.images)) { + result.images.forEach((image, index) => { + const binary = atob(image) + const bytes = new Uint8Array(binary.length) + for (let i = 0; i < binary.length; i += 1) { + bytes[i] = binary.charCodeAt(i) + } + const blob = new Blob([bytes], { type: result.mime }) + const suffix = result.images.length > 1 ? `-${index + 1}` : '' + triggerFileDownload( + blob, + `${filename}${suffix}.${result.extension || type}` + ) + }) + } else { + const blob = new Blob([result.buffer], { + type: result.mime || 'application/octet-stream' + }) + const extension = type === 'jpeg' ? 'jpeg' : type + triggerFileDownload(blob, `${filename}.${extension}`) + } + + if (callback) { + callback() + } + } catch (err) { + const error = getTemplateErrorFromResponse(err) + logger.error('Error downloading template:', error) + if (callback) { + callback(error) + } + return { error } } } @@ -1800,29 +1924,7 @@ const ApiServerProvider = ({ children }) => { filename, callback ) => { - logger.debug('Downloading template PDF...') - - fetchTemplatePDF(id, content, object, (result) => { - logger.debug('Downloading template PDF result:', result) - if (result?.error) { - console.error(result.error) - if (callback) { - callback(result.error) - } - } else { - const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' }) - const pdfUrl = URL.createObjectURL(pdfBlob) - const fileLink = document.createElement('a') - fileLink.href = pdfUrl - fileLink.setAttribute('download', `${filename}.pdf`) - document.body.appendChild(fileLink) - fileLink.click() - fileLink.parentNode.removeChild(fileLink) - if (callback) { - callback() - } - } - }) + return downloadTemplate(id, content, object, filename, 'pdf', callback) } const fetchHostOTP = async (id, callback) => { @@ -2253,6 +2355,7 @@ const ApiServerProvider = ({ children }) => { fetchTemplatePreview, fetchTemplatePDF, fetchNotes, + downloadTemplate, downloadTemplatePDF, fetchHostOTP, sendObjectAction,