Compare commits

..

10 Commits

8 changed files with 255 additions and 136 deletions

View File

@ -305,8 +305,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
) : ( ) : (
<div className="my-12"> <div className="my-12">
<span className="text-secondary"> <span className="text-secondary">
No data available for the selected date range. Please Select No attendance record found in selected date range.
another date.
</span> </span>
</div> </div>
)} )}

View File

@ -33,7 +33,7 @@ const InfraPlanning = () => {
const selectedService = useCurrentService(); const selectedService = useCurrentService();
const { projectInfra, isLoading, isError, error, isFetched } = const { projectInfra, isLoading, isError, error, isFetched } =
useProjectInfra(selectedProject, selectedService || "" ); useProjectInfra(selectedProject, selectedService || "");
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const canApproveTask = useHasUserPermission(APPROVE_TASK); const canApproveTask = useHasUserPermission(APPROVE_TASK);
@ -62,9 +62,13 @@ const InfraPlanning = () => {
if (isFetched && (!projectInfra || projectInfra.length === 0)) { if (isFetched && (!projectInfra || projectInfra.length === 0)) {
return ( return (
<div className="text-center"> <div
<p className="my-3">No Result Found</p> className="text-center d-flex justify-content-center align-items-center text-muted"
style={{ minHeight: "40vh", fontSize: "0.9rem" }}
>
<p className="my-3 m-0">No Result Found</p>
</div> </div>
); );
} }

View File

@ -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;

View File

@ -104,7 +104,7 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
</span> </span>
</div> </div>
<div className="col-2"> <div className="col-6 col-md-2">
<ProgressBar <ProgressBar
completedWork={formatNumber(workArea?.completedWork)} completedWork={formatNumber(workArea?.completedWork)}
plannedWork={formatNumber(workArea?.plannedWork)} plannedWork={formatNumber(workArea?.plannedWork)}

View File

@ -28,13 +28,21 @@ const ImageGalleryListView = ({filter}) => {
} }
}; };
if (!data?.data?.length && !isLoading) { if (!data?.data?.length && !isLoading) {
return ( return (
<p className="text-center text-muted mt-5"> <div
{selectedProject ? " No images match the selected filters.":"Please Select Project!"} className="d-flex justify-content-center align-items-center text-muted"
</p> style={{ minHeight: "50vh" }}
); >
} <span style={{ fontSize: "0.9rem" }}>
{selectedProject
? "No images match the selected filters."
: "Please Select Project!"}
</span>
</div>
);
}
if (isLoading) { if (isLoading) {
return ( return (

View File

@ -38,7 +38,7 @@ const TaskPlanning = () => {
]} ]}
/> />
<div className="card py-2 "> <div className="card py-2 page-min-h">
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start"> <div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
{data?.length === 0 ? ( {data?.length === 0 ? (
<p className="badge bg-label-secondary m-0">Service not assigned</p> <p className="badge bg-label-secondary m-0">Service not assigned</p>

View File

@ -38,6 +38,7 @@ import usePagination from "../../hooks/usePagination";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../../components/common/Pagination"; import Pagination from "../../components/common/Pagination";
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(
@ -134,26 +135,11 @@ const EmployeeList = () => {
const tableRef = useRef(null); const tableRef = useRef(null);
const handleExport = (type) => { const handleExport = (type) => {
if (!currentItems || currentItems.length === 0) return; handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef);
switch (type) {
case "csv":
exportToCSV(currentItems, "employees");
break;
case "excel":
exportToExcel(currentItems, "employees");
break;
case "pdf":
exportToPDF(currentItems, "employees");
break;
case "print":
printTable(tableRef.current);
break;
default:
break;
}
}; };
const handleAllEmployeesToggle = (e) => { const handleAllEmployeesToggle = (e) => {
const isChecked = e.target.checked; const isChecked = e.target.checked;
setShowInactive(false); setShowInactive(false);

View File

@ -40,112 +40,57 @@ export const exportToExcel = (data, fileName = "data") => {
* @param {Array} data - Array of objects to export * @param {Array} data - Array of objects to export
* @param {string} fileName - File name for the PDF (optional) * @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; if (!data || data.length === 0) return;
// Create a new PDF document
const pdfDoc = await PDFDocument.create(); const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
// Set up the font // Default options
const font = await pdfDoc.embedFont(StandardFonts.Helvetica); // Use Helvetica font const {
columnWidths = [], // array of widths per column
// Calculate column widths dynamically based on data content fontSizeHeader = 12,
const headers = Object.keys(data[0]); fontSizeRow = 10,
const rows = data.map(item => headers.map(header => item[header] || '')); rowHeight = 25,
} = options;
const getMaxColumnWidth = (columnIndex) => { const pageWidth = 1000;
let maxWidth = font.widthOfTextAtSize(headers[columnIndex], 12); const pageHeight = 600;
rows.forEach(row => { let page = pdfDoc.addPage([pageWidth, pageHeight]);
const cellText = row[columnIndex].toString(); const margin = 30;
maxWidth = Math.max(maxWidth, font.widthOfTextAtSize(cellText, 10)); 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)); if (y < margin) {
const tableX = 30; // X-coordinate for the table start page = pdfDoc.addPage([pageWidth, pageHeight]);
const rowHeight = 20; // Height of each row (can be adjusted) y = pageHeight - margin;
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
} }
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(); const pdfBytes = await pdfDoc.save();
// Trigger a download of the PDF
const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
@ -153,15 +98,110 @@ export const exportToPDF = async (data, fileName = "data") => {
link.click(); 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. * Print the HTML table by accepting the table element or a reference.
* @param {HTMLElement} table - The table element (or ref) to print * @param {HTMLElement} table - The table element (or ref) to print
*/ */
export const printTable = (table) => { export const printTable = (table) => {
if (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("<html><head><title>Print Table</title>"); newWindow.document.write("<html><head><title>Print Table</title>");
const style = document.createElement('style'); const style = document.createElement('style');
style.innerHTML = ` style.innerHTML = `
@ -171,16 +211,14 @@ export const printTable = (table) => {
th { background-color: #f2f2f2; } th { background-color: #f2f2f2; }
`; `;
newWindow.document.head.appendChild(style); newWindow.document.head.appendChild(style);
newWindow.document.write("</head><body>"); newWindow.document.write("</head><body>");
newWindow.document.write(table.outerHTML); // Write the table HTML to the new window newWindow.document.write(clone.outerHTML);
newWindow.document.write("</body></html>"); newWindow.document.write("</body></html>");
newWindow.document.close();
newWindow.document.close(); // Close the document stream
// Wait for the document to load before triggering print
newWindow.onload = () => { newWindow.onload = () => {
newWindow.print(); // Trigger the print dialog after the content is loaded newWindow.print();
}; };
} }
}; };