@@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from 'react';
22import './WeeklyProjectSummary.css' ;
33import { useDispatch , useSelector } from 'react-redux' ;
44import { v4 as uuidv4 } from 'uuid' ;
5+ import html2canvas from 'html2canvas' ;
6+ import { jsPDF } from 'jspdf' ;
57import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader' ;
68import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions' ;
79import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed' ;
@@ -268,9 +270,97 @@ export default function WeeklyProjectSummary() {
268270 [ quantityOfMaterialsUsedData ] ,
269271 ) ;
270272
273+ const handleSaveAsPDF = async ( ) => {
274+ const currentOpenSections = { ...openSections } ;
275+
276+ try {
277+ const allSectionsOpen = { } ;
278+ sections . forEach ( section => {
279+ allSectionsOpen [ section . key ] = true ;
280+ } ) ;
281+ setOpenSections ( allSectionsOpen ) ;
282+
283+ // eslint-disable-next-line no-promise-executor-return
284+ await new Promise ( resolve => setTimeout ( resolve , 500 ) ) ;
285+
286+ const contentElement = document . querySelector ( '.weekly-project-summary-container' ) ;
287+ if ( ! contentElement ) throw new Error ( 'Weekly project summary container not found.' ) ;
288+
289+ const pdfContainer = document . createElement ( 'div' ) ;
290+ pdfContainer . id = 'pdf-export-container' ;
291+ Object . assign ( pdfContainer . style , {
292+ width : '420mm' ,
293+ padding : '10mm' ,
294+ backgroundColor : '#fff' ,
295+ position : 'absolute' ,
296+ left : '-9999px' ,
297+ boxSizing : 'border-box' ,
298+ } ) ;
299+
300+ const clonedContent = contentElement . cloneNode ( true ) ;
301+
302+ // Remove buttons and controls not needed in PDF
303+ clonedContent
304+ . querySelectorAll (
305+ 'button, .weekly-project-summary-dropdown-icon, .no-print, .weekly-summary-header-controls' ,
306+ )
307+ . forEach ( el => el . parentNode ?. removeChild ( el ) ) ;
308+
309+ const styleElem = document . createElement ( 'style' ) ;
310+ styleElem . textContent = `
311+ img, svg {
312+ height: auto !important;
313+ page-break-inside: avoid !important;
314+ }
315+ ` ;
316+
317+ clonedContent . prepend ( styleElem ) ;
318+ pdfContainer . appendChild ( clonedContent ) ;
319+ document . body . appendChild ( pdfContainer ) ;
320+
321+ const canvas = await html2canvas ( pdfContainer , {
322+ scale : 2 ,
323+ useCORS : true ,
324+ backgroundColor : '#fff' ,
325+ windowWidth : pdfContainer . scrollWidth ,
326+ windowHeight : pdfContainer . scrollHeight ,
327+ logging : false ,
328+ } ) ;
329+
330+ if ( ! canvas ) throw new Error ( 'Failed to capture content as image.' ) ;
331+
332+ const imgData = canvas . toDataURL ( 'image/jpeg' , 0.95 ) ;
333+
334+ const pdfWidth = 210 ;
335+ const imgHeight = ( canvas . height * pdfWidth ) / canvas . width ;
336+
337+ // eslint-disable-next-line new-cap
338+ const pdf = new jsPDF ( {
339+ orientation : 'portrait' ,
340+ unit : 'mm' ,
341+ format : [ pdfWidth , imgHeight ] ,
342+ } ) ;
343+
344+ pdf . addImage ( imgData , 'JPEG' , 0 , 0 , pdfWidth , imgHeight ) ;
345+
346+ const now = new Date ( ) ;
347+ const fileName = `weekly-project-summary-${ now . toISOString ( ) . slice ( 0 , 10 ) } .pdf` ;
348+
349+ // Save the PDF
350+ pdf . save ( fileName ) ;
351+
352+ document . body . removeChild ( pdfContainer ) ;
353+ } catch ( err ) {
354+ // eslint-disable-next-line no-console
355+ console . error ( 'PDF generation failed:' , err ) ;
356+ } finally {
357+ setOpenSections ( currentOpenSections ) ;
358+ }
359+ } ;
360+
271361 return (
272362 < div className = { `weekly-project-summary-container ${ darkMode ? 'dark-mode' : '' } ` } >
273- < WeeklyProjectSummaryHeader />
363+ < WeeklyProjectSummaryHeader handleSaveAsPDF = { handleSaveAsPDF } />
274364 < div className = "weekly-project-summary-dashboard-container" >
275365 < div className = "weekly-project-summary-dashboard-grid" >
276366 { sections . map ( ( { title, key, className, content } ) => (
0 commit comments