From 6fc3e674e54a8ed72a09648a89331478d3a7a7db Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 16:40:58 +0530 Subject: [PATCH 1/3] Export to PDF button not functioning --- src/pages/employee/EmployeeList.jsx | 43 ++++- src/utils/tableExportUtils.jsx | 246 ++++++++++++++++------------ 2 files changed, 182 insertions(+), 107 deletions(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 2018afb2..27dd2436 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -136,16 +136,53 @@ const EmployeeList = () => { const handleExport = (type) => { if (!currentItems || currentItems.length === 0) return; + // Map and format employee data for export + const exportData = currentItems.map((item) => ({ + "First Name": item.firstName || "", + "Middle Name": item.middleName || "", + "Last Name": item.lastName || "", + "Email": item.email || "", + "Gender": item.gender || "", + "Birth Date": item.birthdate + ? moment(item.birthdate).format("DD-MMM-YYYY") + : "", + "Joining Date": item.joiningDate + ? moment(item.joiningDate).format("DD-MMM-YYYY") + : "", + "Permanent Address": item.permanentAddress || "", + "Current Address": item.currentAddress || "", + "Phone Number": item.phoneNumber || "", + "Emergency Phone Number": item.emergencyPhoneNumber || "", + "Emergency Contact Person": item.emergencyContactPerson || "", + "Is Active": item.isActive ? "Active" : "Inactive", + "Job Role": item.jobRole || "", + })); + switch (type) { case "csv": - exportToCSV(currentItems, "employees"); + exportToCSV(exportData, "employees"); break; case "excel": - exportToExcel(currentItems, "employees"); + exportToExcel(exportData, "employees"); break; case "pdf": - exportToPDF(currentItems, "employees"); + exportToPDF( + currentItems.map((item) => ({ + Name: `${item.firstName || ""} ${item.lastName || ""}`.trim(), + Email: item.email || "", + "Phone Number": item.phoneNumber || "", + "Job Role": item.jobRole || "", + "Joining Date": item.joiningDate + ? moment(item.joiningDate).format("DD-MMM-YYYY") + : "", + "Gender": item.gender || "", + Status: item.isActive ? "Active" : "Inactive", + })), + "employees", + ["Name", "Email", "Phone Number", "Job Role", "Joining Date", "Gender", "Status"] + ); break; + case "print": printTable(tableRef.current); break; diff --git a/src/utils/tableExportUtils.jsx b/src/utils/tableExportUtils.jsx index 90a1306a..768ff5ac 100644 --- a/src/utils/tableExportUtils.jsx +++ b/src/utils/tableExportUtils.jsx @@ -40,112 +40,57 @@ export const exportToExcel = (data, fileName = "data") => { * @param {Array} data - Array of objects to export * @param {string} fileName - File name for the PDF (optional) */ -export const exportToPDF = async (data, fileName = "data") => { +const sanitizeText = (text) => { + if (!text) return ""; + // Replace all non-ASCII characters with "?" or remove them + return text.replace(/[^\x00-\x7F]/g, "?"); +}; + +export const exportToPDF = async (data, fileName = "data", columns = null, options = {}) => { if (!data || data.length === 0) return; - // Create a new PDF document const pdfDoc = await PDFDocument.create(); + const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - // Set up the font - const font = await pdfDoc.embedFont(StandardFonts.Helvetica); // Use Helvetica font - - // Calculate column widths dynamically based on data content - const headers = Object.keys(data[0]); - const rows = data.map(item => headers.map(header => item[header] || '')); + // Default options + const { + columnWidths = [], // array of widths per column + fontSizeHeader = 12, + fontSizeRow = 10, + rowHeight = 25, + } = options; - const getMaxColumnWidth = (columnIndex) => { - let maxWidth = font.widthOfTextAtSize(headers[columnIndex], 12); - rows.forEach(row => { - const cellText = row[columnIndex].toString(); - maxWidth = Math.max(maxWidth, font.widthOfTextAtSize(cellText, 10)); + const pageWidth = 1000; + const pageHeight = 600; + let page = pdfDoc.addPage([pageWidth, pageHeight]); + const margin = 30; + let y = pageHeight - margin; + + const headers = columns || Object.keys(data[0]); + + // Draw headers + headers.forEach((header, i) => { + const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150); + page.drawText(header, { x, y, font, size: fontSizeHeader }); + }); + y -= rowHeight; + + // Draw rows + data.forEach(row => { + headers.forEach((header, i) => { + const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150); + const text = row[header] || ''; + page.drawText(text, { x, y, font, size: fontSizeRow }); }); - return maxWidth + 10; // Padding for better spacing - }; + y -= rowHeight; - const columnWidths = headers.map((_, index) => getMaxColumnWidth(index)); - const tableX = 30; // X-coordinate for the table start - const rowHeight = 20; // Height of each row (can be adjusted) - const maxPageHeight = 750; // Max available height for content (before a new page is added) - const pageMargin = 30; // Margin from the top of the page - - let tableY = maxPageHeight; // Start Y position for the table - const maxPageWidth = 600; // Max available width for content (before a new page is added) - - // Add the headers and rows to the page - const addHeadersToPage = (page, scaleFactor) => { - let xPosition = tableX; - headers.forEach((header, index) => { - page.drawText(header, { - x: xPosition, - y: tableY, - font, - size: 12 * scaleFactor, // Scale the header font size - color: rgb(0, 0, 0), - }); - xPosition += columnWidths[index] * scaleFactor; // Adjust X position based on scaling - }); - tableY -= rowHeight; // Move down after adding headers - }; - - // Add a new page and reset the table position - const addNewPage = (scaleFactor) => { - const page = pdfDoc.addPage([600, 800]); - tableY = maxPageHeight; // Reset Y position for the new page - addHeadersToPage(page, scaleFactor); // Re-add headers to the new page - return page; - }; - - // Create the first page and add headers - let page = pdfDoc.addPage([600, 800]); - - // Check if the content fits within the page width, scale if necessary - const checkPageWidth = (row) => { - let totalWidth = columnWidths.reduce((acc, width) => acc + width, 0); - let scaleFactor = 1; - if (totalWidth > maxPageWidth) { - scaleFactor = maxPageWidth / totalWidth; // Scale down if necessary + if (y < margin) { + page = pdfDoc.addPage([pageWidth, pageHeight]); + y = pageHeight - margin; } - - return scaleFactor; - }; - - // Function to check for page breaks when adding a new row - const checkPageBreak = () => { - if (tableY - rowHeight < pageMargin) { - page = addNewPage(scaleFactor); // Add a new page if there is no space for the next row - } - }; - - // Add rows to the PDF with pagination and horizontal scaling - rows.forEach(row => { - checkPageBreak(); // Check for page break before adding each row - - const scaleFactor = checkPageWidth(row); // Get the scaling factor for the row - - // Add headers to the first page and each new page with the same scale factor - if (tableY === maxPageHeight) { - addHeadersToPage(page, scaleFactor); // Add headers only on the first page - } - - let xPosition = tableX; - row.forEach((value, index) => { - page.drawText(value.toString(), { - x: xPosition, - y: tableY, - font, - size: 10 * scaleFactor, // Scale the font size - color: rgb(0, 0, 0), - }); - xPosition += columnWidths[index] * scaleFactor; // Adjust X position based on scaling - }); - - tableY -= rowHeight; // Move down to the next row position }); - // Serialize the document to bytes const pdfBytes = await pdfDoc.save(); - - // Trigger a download of the PDF const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); @@ -153,15 +98,110 @@ export const exportToPDF = async (data, fileName = "data") => { link.click(); }; + + + + +/** + * Export JSON data to PDF in a card-style format + * @param {Array} data - Array of objects to export + * @param {string} fileName - File name for the PDF (optional) + */ +export const exportToPDF1 = async (data, fileName = "data") => { + if (!data || data.length === 0) return; + + const pdfDoc = await PDFDocument.create(); + const font = await pdfDoc.embedFont(StandardFonts.Helvetica); + const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold); + + const pageWidth = 600; + const pageHeight = 800; + const margin = 30; + const cardSpacing = 20; + const cardPadding = 10; + let page = pdfDoc.addPage([pageWidth, pageHeight]); + let y = pageHeight - margin; + + for (const item of data) { + const title = item.ContactName || ""; + const subtitle = `by ${item.CreatedBy || ""} on ${item.CreatedAt || ""}`; + const body = item.Note || ""; + + const cardHeight = 80 + (body.length / 60) * 14; // approximate height for body text + + if (y - cardHeight < margin) { + page = pdfDoc.addPage([pageWidth, pageHeight]); + y = pageHeight - margin; + } + + // Draw card border + page.drawRectangle({ + x: margin, + y: y - cardHeight, + width: pageWidth - 2 * margin, + height: cardHeight, + borderColor: rgb(0.7, 0.7, 0.7), + borderWidth: 1, + color: rgb(1, 1, 1), + }); + + // Draw title + page.drawText(title, { + x: margin + cardPadding, + y: y - 20, + font: boldFont, + size: 12, + color: rgb(0.1, 0.1, 0.1), + }); + + // Draw subtitle + page.drawText(subtitle, { + x: margin + cardPadding, + y: y - 35, + font, + size: 10, + color: rgb(0.4, 0.4, 0.4), + }); + + // Draw body text (wrap manually) + const lines = body.match(/(.|[\r\n]){1,80}/g) || []; + lines.forEach((line, i) => { + page.drawText(line, { + x: margin + cardPadding, + y: y - 50 - i * 12, + font, + size: 10, + color: rgb(0.2, 0.2, 0.2), + }); + }); + + y -= cardHeight + cardSpacing; + } + + const pdfBytes = await pdfDoc.save(); + const blob = new Blob([pdfBytes], { type: 'application/pdf' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = `${fileName}.pdf`; + link.click(); +}; + + + + /** * Print the HTML table by accepting the table element or a reference. * @param {HTMLElement} table - The table element (or ref) to print */ export const printTable = (table) => { if (table) { - const newWindow = window.open("", "", "width=600,height=600"); // Open a new window + const clone = table.cloneNode(true); + // Remove last column (Actions) from all rows + clone.querySelectorAll("tr").forEach((row) => { + row.removeChild(row.lastElementChild); + }); - // Inject styles for the table and body + const newWindow = window.open("", "", "width=600,height=600"); newWindow.document.write("Print Table"); const style = document.createElement('style'); style.innerHTML = ` @@ -171,16 +211,14 @@ export const printTable = (table) => { th { background-color: #f2f2f2; } `; newWindow.document.head.appendChild(style); - + newWindow.document.write(""); - newWindow.document.write(table.outerHTML); // Write the table HTML to the new window + newWindow.document.write(clone.outerHTML); newWindow.document.write(""); - - newWindow.document.close(); // Close the document stream - - // Wait for the document to load before triggering print + newWindow.document.close(); newWindow.onload = () => { - newWindow.print(); // Trigger the print dialog after the content is loaded + newWindow.print(); }; } }; + From 278a5651fe5dd61584cbb90a191931b769694a72 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 17:48:58 +0530 Subject: [PATCH 2/3] Changes in excel import. --- src/pages/employee/EmployeeList.jsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 27dd2436..0460f017 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -134,10 +134,13 @@ const EmployeeList = () => { const tableRef = useRef(null); const handleExport = (type) => { - if (!currentItems || currentItems.length === 0) return; + // Export full list (filtered if search applied) + const dataToExport = searchText ? filteredData : employeeList; + + if (!dataToExport || dataToExport.length === 0) return; // Map and format employee data for export - const exportData = currentItems.map((item) => ({ + const exportData = dataToExport.map((item) => ({ "First Name": item.firstName || "", "Middle Name": item.middleName || "", "Last Name": item.lastName || "", @@ -162,12 +165,14 @@ const EmployeeList = () => { case "csv": exportToCSV(exportData, "employees"); break; + case "excel": exportToExcel(exportData, "employees"); break; + case "pdf": exportToPDF( - currentItems.map((item) => ({ + dataToExport.map((item) => ({ Name: `${item.firstName || ""} ${item.lastName || ""}`.trim(), Email: item.email || "", "Phone Number": item.phoneNumber || "", @@ -175,22 +180,32 @@ const EmployeeList = () => { "Joining Date": item.joiningDate ? moment(item.joiningDate).format("DD-MMM-YYYY") : "", - "Gender": item.gender || "", + Gender: item.gender || "", Status: item.isActive ? "Active" : "Inactive", })), "employees", - ["Name", "Email", "Phone Number", "Job Role", "Joining Date", "Gender", "Status"] + [ + "Name", + "Email", + "Phone Number", + "Job Role", + "Joining Date", + "Gender", + "Status", + ] ); break; case "print": printTable(tableRef.current); break; + default: break; } }; + const handleAllEmployeesToggle = (e) => { const isChecked = e.target.checked; setShowInactive(false); From f0c6aea55da9bb572385e9cd528f484c00896906 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 18:02:36 +0530 Subject: [PATCH 3/3] Create a seprate file for export functionality in employee. --- .../Employee/handleEmployeeExport.jsx | 84 +++++++++++++++++++ src/pages/employee/EmployeeList.jsx | 72 +--------------- 2 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 src/components/Employee/handleEmployeeExport.jsx diff --git a/src/components/Employee/handleEmployeeExport.jsx b/src/components/Employee/handleEmployeeExport.jsx new file mode 100644 index 00000000..a4883d4f --- /dev/null +++ b/src/components/Employee/handleEmployeeExport.jsx @@ -0,0 +1,84 @@ +import moment from "moment"; +import { exportToExcel, exportToCSV, exportToPDF, printTable } from "../../utils/tableExportUtils"; + +/** + * Handles export operations for employee data. + * @param {string} type - Export type: 'csv', 'excel', 'pdf', or 'print' + * @param {Array} employeeList - Full employee data array + * @param {Array} filteredData - Filtered employee data (if search applied) + * @param {string} searchText - Current search text (used to decide dataset) + * @param {RefObject} tableRef - Table reference (used for print) + */ +const handleEmployeeExport = (type, employeeList, filteredData, searchText, tableRef) => { + // Export full list (filtered if search applied) + const dataToExport = searchText ? filteredData : employeeList; + + if (!dataToExport || dataToExport.length === 0) return; + + // Map and format employee data for export + const exportData = dataToExport.map((item) => ({ + "First Name": item.firstName || "", + "Middle Name": item.middleName || "", + "Last Name": item.lastName || "", + "Email": item.email || "", + "Gender": item.gender || "", + "Birth Date": item.birthdate + ? moment(item.birthdate).format("DD-MMM-YYYY") + : "", + "Joining Date": item.joiningDate + ? moment(item.joiningDate).format("DD-MMM-YYYY") + : "", + "Permanent Address": item.permanentAddress || "", + "Current Address": item.currentAddress || "", + "Phone Number": item.phoneNumber || "", + "Emergency Phone Number": item.emergencyPhoneNumber || "", + "Emergency Contact Person": item.emergencyContactPerson || "", + "Is Active": item.isActive ? "Active" : "Inactive", + "Job Role": item.jobRole || "", + })); + + switch (type) { + case "csv": + exportToCSV(exportData, "employees"); + break; + + case "excel": + exportToExcel(exportData, "employees"); + break; + + case "pdf": + exportToPDF( + dataToExport.map((item) => ({ + Name: `${item.firstName || ""} ${item.lastName || ""}`.trim(), + Email: item.email || "", + "Phone Number": item.phoneNumber || "", + "Job Role": item.jobRole || "", + "Joining Date": item.joiningDate + ? moment(item.joiningDate).format("DD-MMM-YYYY") + : "", + Gender: item.gender || "", + Status: item.isActive ? "Active" : "Inactive", + })), + "employees", + [ + "Name", + "Email", + "Phone Number", + "Job Role", + "Joining Date", + "Gender", + "Status", + ] + ); + break; + + case "print": + printTable(tableRef.current); + break; + + default: + break; + } +}; + +export default handleEmployeeExport; diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 0460f017..4d6d2f0c 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -38,6 +38,7 @@ import usePagination from "../../hooks/usePagination"; import { setProjectId } from "../../slices/localVariablesSlice"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import Pagination from "../../components/common/Pagination"; +import handleEmployeeExport from "../../components/Employee/handleEmployeeExport"; const EmployeeList = () => { const selectedProjectId = useSelector( @@ -134,78 +135,11 @@ const EmployeeList = () => { const tableRef = useRef(null); const handleExport = (type) => { - // Export full list (filtered if search applied) - const dataToExport = searchText ? filteredData : employeeList; - - if (!dataToExport || dataToExport.length === 0) return; - - // Map and format employee data for export - const exportData = dataToExport.map((item) => ({ - "First Name": item.firstName || "", - "Middle Name": item.middleName || "", - "Last Name": item.lastName || "", - "Email": item.email || "", - "Gender": item.gender || "", - "Birth Date": item.birthdate - ? moment(item.birthdate).format("DD-MMM-YYYY") - : "", - "Joining Date": item.joiningDate - ? moment(item.joiningDate).format("DD-MMM-YYYY") - : "", - "Permanent Address": item.permanentAddress || "", - "Current Address": item.currentAddress || "", - "Phone Number": item.phoneNumber || "", - "Emergency Phone Number": item.emergencyPhoneNumber || "", - "Emergency Contact Person": item.emergencyContactPerson || "", - "Is Active": item.isActive ? "Active" : "Inactive", - "Job Role": item.jobRole || "", - })); - - switch (type) { - case "csv": - exportToCSV(exportData, "employees"); - break; - - case "excel": - exportToExcel(exportData, "employees"); - break; - - case "pdf": - exportToPDF( - dataToExport.map((item) => ({ - Name: `${item.firstName || ""} ${item.lastName || ""}`.trim(), - Email: item.email || "", - "Phone Number": item.phoneNumber || "", - "Job Role": item.jobRole || "", - "Joining Date": item.joiningDate - ? moment(item.joiningDate).format("DD-MMM-YYYY") - : "", - Gender: item.gender || "", - Status: item.isActive ? "Active" : "Inactive", - })), - "employees", - [ - "Name", - "Email", - "Phone Number", - "Job Role", - "Joining Date", - "Gender", - "Status", - ] - ); - break; - - case "print": - printTable(tableRef.current); - break; - - default: - break; - } + handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef); }; + const handleAllEmployeesToggle = (e) => { const isChecked = e.target.checked; setShowInactive(false);