Compare commits
15 Commits
cc2a82e3f0
...
798ea24088
Author | SHA1 | Date | |
---|---|---|---|
798ea24088 | |||
8460460caf | |||
c8273070ac | |||
dc4e48ad3b | |||
20b508bebc | |||
05c01d1d34 | |||
073897156e | |||
3a2fcf71ee | |||
f0c6aea55d | |||
278a5651fe | |||
6fc3e674e5 | |||
9f4d82eb06 | |||
4ba0c823c0 | |||
acf6a28191 | |||
98c90f2a9b |
@ -223,8 +223,20 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center text-muted"
|
||||
style={{ height: "200px" }}
|
||||
>
|
||||
{searchTerm
|
||||
? "No results found for your search."
|
||||
: attendanceList.length === 0
|
||||
? "No employees assigned to the project."
|
||||
: "No pending records available."}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||
@ -268,20 +280,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
||||
</nav>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center text-muted"
|
||||
style={{ height: "200px" }}
|
||||
>
|
||||
{searchTerm
|
||||
? "No results found for your search."
|
||||
: attendanceList.length === 0
|
||||
? "No employees assigned to the project."
|
||||
: "No pending records available."}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -15,6 +15,7 @@ import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||
import { queryClient } from "../../layouts/AuthLayout";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const usePagination = (data, itemsPerPage) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@ -44,6 +45,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPending, setShowPending] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
@ -172,7 +174,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
||||
className="dataTables_length text-start py-2 d-flex flex-wrap justify-content-between"
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
<div className="d-flex flex-wrap align-items-center gap-2 gap-md-3 my-0">
|
||||
@ -202,6 +204,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="table-responsive text-nowrap"
|
||||
style={{ minHeight: "200px" }}
|
||||
@ -268,7 +271,12 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
lastName={attendance.lastName}
|
||||
/>
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-heading text-truncate">
|
||||
<a
|
||||
onClick={() =>
|
||||
navigate(`/employee/${attendance.employeeId}?for=attendance`)
|
||||
}
|
||||
className="text-heading text-truncate cursor-pointer"
|
||||
>
|
||||
<span className="fw-normal">
|
||||
{attendance.firstName} {attendance.lastName}
|
||||
</span>
|
||||
@ -305,8 +313,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
) : (
|
||||
<div className="my-12">
|
||||
<span className="text-secondary">
|
||||
No data available for the selected date range. Please Select
|
||||
another date.
|
||||
No attendance record found in selected date range.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
@ -62,9 +62,13 @@ const InfraPlanning = () => {
|
||||
|
||||
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<p className="my-3">No Result Found</p>
|
||||
<div
|
||||
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>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from "../../slices/apiDataManager";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import Pagination from "../../components/common/Pagination";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const Regularization = ({
|
||||
handleRequest,
|
||||
@ -26,6 +27,7 @@ const Regularization = ({
|
||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const selectedProject = useSelectedProject();
|
||||
const [regularizesList, setregularizedList] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
const { regularizes, loading, error, refetch } = useRegularizationRequests(
|
||||
selectedProject,
|
||||
organizationId,
|
||||
@ -102,8 +104,9 @@ const Regularization = ({
|
||||
}, [employeeHandler]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="table-responsive text-nowrap pb-4"
|
||||
className="table-responsive pt-3 text-nowrap pb-4"
|
||||
style={{ minHeight: "200px" }}
|
||||
>
|
||||
{loading ? (
|
||||
@ -141,8 +144,11 @@ const Regularization = ({
|
||||
<td colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Avatar firstName={att.firstName} lastName={att.lastName} />
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-heading text-truncate">
|
||||
<div className="d-flex flex-column"> <a
|
||||
onClick={() =>
|
||||
navigate(`/employee/${att.employeeId}?for=attendance`)
|
||||
}
|
||||
className="text-heading text-truncate cursor-pointer" >
|
||||
<span className="fw-normal">
|
||||
{att.firstName} {att.lastName}
|
||||
</span>
|
||||
@ -197,46 +203,7 @@ const Regularization = ({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* {!loading && totalPages > 1 && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||
<button
|
||||
className="page-link btn-xs"
|
||||
onClick={() => paginate(currentPage - 1)}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
</li>
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
onClick={() => paginate(index + 1)}
|
||||
>
|
||||
{index + 1}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
onClick={() => paginate(currentPage + 1)}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)} */}
|
||||
|
||||
</div>
|
||||
{totalPages > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
@ -244,6 +211,7 @@ const Regularization = ({
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -192,6 +192,7 @@ const TaskReportList = () => {
|
||||
if (isLoading) return <TaskReportListSkeleton />;
|
||||
if (isError) return <div>Loading....</div>;
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-2 table-responsive text-nowrap">
|
||||
<table className="table">
|
||||
<thead>
|
||||
@ -287,13 +288,17 @@ const TaskReportList = () => {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{data?.data?.length > 0 && (
|
||||
|
||||
</div>
|
||||
{
|
||||
data?.data?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
@ -160,8 +160,7 @@ const ListViewContact = ({ data, Pagination }) => {
|
||||
</div>
|
||||
) : (
|
||||
<i
|
||||
className={`bx ${
|
||||
isPending && activeContact === row.id
|
||||
className={`bx ${isPending && activeContact === row.id
|
||||
? "bx-loader-alt bx-spin"
|
||||
: "bx-recycle"
|
||||
} me-1 text-primary cursor-pointer`}
|
||||
@ -188,14 +187,14 @@ const ListViewContact = ({ data, Pagination }) => {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{Pagination && (
|
||||
<div className="d-flex justify-content-start">
|
||||
{Pagination}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
84
src/components/Employee/handleEmployeeExport.jsx
Normal file
84
src/components/Employee/handleEmployeeExport.jsx
Normal 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;
|
@ -141,6 +141,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
getValue: (e) =>
|
||||
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A",
|
||||
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A",
|
||||
customRender: (e) => (
|
||||
<div className="d-flex align-items-center cursor-pointer"
|
||||
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}>
|
||||
@ -153,6 +155,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
<span className="text-truncate">
|
||||
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||
}`.trim() || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
@ -178,6 +182,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
<span
|
||||
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
>
|
||||
{e.status?.name || "Unknown"}
|
||||
</span>
|
||||
|
@ -30,12 +30,20 @@ const ImageGalleryListView = ({filter}) => {
|
||||
|
||||
if (!data?.data?.length && !isLoading) {
|
||||
return (
|
||||
<p className="text-center text-muted mt-5">
|
||||
{selectedProject ? " No images match the selected filters.":"Please Select Project!"}
|
||||
</p>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center text-muted"
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<span style={{ fontSize: "0.9rem" }}>
|
||||
{selectedProject
|
||||
? "No images match the selected filters."
|
||||
: "Please Select Project!"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="page-min-h d-flex justify-content-center align-items-center">
|
||||
|
@ -179,10 +179,9 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
|
||||
{/* Search + Organization filter */}
|
||||
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
|
||||
{/* Organization Dropdown */}
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6 mb-2 mb-sm-0">
|
||||
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto">
|
||||
<div className="row g-2">
|
||||
<div className="col-12 col-sm-6">
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
value={appliedFilters.selectedOrganization}
|
||||
@ -203,7 +202,6 @@ const AttendancePage = () => {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
{/* Search Input */}
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
@ -213,10 +211,8 @@ const AttendancePage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -224,7 +220,7 @@ const AttendancePage = () => {
|
||||
{selectedProject ? (
|
||||
<>
|
||||
{activeTab === "all" && (
|
||||
<div className="tab-pane fade show active py-0 mx-5">
|
||||
<div className="tab-pane fade show active py-0 mx-2">
|
||||
<Attendance
|
||||
handleModalData={handleModalData}
|
||||
getRole={getRole}
|
||||
@ -234,7 +230,7 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "logs" && (
|
||||
<div className="tab-pane fade show active py-0">
|
||||
<div className="tab-pane fade p-3 show active py-0">
|
||||
<AttendanceLog
|
||||
handleModalData={handleModalData}
|
||||
searchTerm={searchTerm}
|
||||
@ -243,7 +239,7 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "regularization" && DoRegularized && (
|
||||
<div className="tab-pane fade show active py-0">
|
||||
<div className="tab-pane fade p-3 show active py-0">
|
||||
<Regularization
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
|
@ -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">
|
||||
{data?.length === 0 ? (
|
||||
<p className="badge bg-label-secondary m-0">Service not assigned</p>
|
||||
|
@ -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,26 +135,11 @@ const EmployeeList = () => {
|
||||
|
||||
const tableRef = useRef(null);
|
||||
const handleExport = (type) => {
|
||||
if (!currentItems || currentItems.length === 0) return;
|
||||
|
||||
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;
|
||||
}
|
||||
handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleAllEmployeesToggle = (e) => {
|
||||
const isChecked = e.target.checked;
|
||||
setShowInactive(false);
|
||||
@ -664,7 +650,8 @@ const EmployeeList = () => {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{displayData?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
@ -673,8 +660,6 @@ const EmployeeList = () => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="card">
|
||||
<div className="text-center">
|
||||
|
@ -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
|
||||
// Default options
|
||||
const {
|
||||
columnWidths = [], // array of widths per column
|
||||
fontSizeHeader = 12,
|
||||
fontSizeRow = 10,
|
||||
rowHeight = 25,
|
||||
} = options;
|
||||
|
||||
// Calculate column widths dynamically based on data content
|
||||
const headers = Object.keys(data[0]);
|
||||
const rows = data.map(item => headers.map(header => item[header] || ''));
|
||||
const pageWidth = 1000;
|
||||
const pageHeight = 600;
|
||||
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||
const margin = 30;
|
||||
let y = pageHeight - margin;
|
||||
|
||||
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 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 });
|
||||
});
|
||||
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),
|
||||
// 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 });
|
||||
});
|
||||
xPosition += columnWidths[index] * scaleFactor; // Adjust X position based on scaling
|
||||
});
|
||||
tableY -= rowHeight; // Move down after adding headers
|
||||
};
|
||||
y -= rowHeight;
|
||||
|
||||
// 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("<html><head><title>Print Table</title>");
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
@ -173,14 +213,12 @@ export const printTable = (table) => {
|
||||
newWindow.document.head.appendChild(style);
|
||||
|
||||
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.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();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user