diff --git a/src/components/AdvancePayment/AdvancePaymentList.jsx b/src/components/AdvancePayment/AdvancePaymentList.jsx index 54031c43..8d2d131d 100644 --- a/src/components/AdvancePayment/AdvancePaymentList.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList.jsx @@ -3,6 +3,7 @@ import Avatar from "../common/Avatar"; // <-- ADD THIS import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; import { useNavigate } from 'react-router-dom'; import { formatFigure } from '../../utils/appUtils'; +import { SpinnerLoader } from '../common/Loader'; const AdvancePaymentList = ({ searchString }) => { @@ -53,7 +54,14 @@ const AdvancePaymentList = ({ searchString }) => { }, ]; - if (isLoading) return

Loading...

; + if (isLoading) { + return ( +
+ +
+ ); + } + if (isError) return

{error.message}

; return ( diff --git a/src/components/AdvancePayment/AdvancePaymentListDetails.jsx b/src/components/AdvancePayment/AdvancePaymentListDetails.jsx index f6af63f7..c3800beb 100644 --- a/src/components/AdvancePayment/AdvancePaymentListDetails.jsx +++ b/src/components/AdvancePayment/AdvancePaymentListDetails.jsx @@ -8,10 +8,10 @@ import { useForm, useFormContext } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { employee } from "../../data/masters"; -import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPageDetails"; // All data +import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPageDetails"; import { formatFigure } from "../../utils/appUtils"; -const AdvancePaymentListDetails = ({ employeeId, searchString }) => { +const AdvancePaymentListDetails = ({ employeeId, searchString,tableRef }) => { const { setBalance } = useAdvancePaymentContext(); const { data, isError, isLoading, error, isFetching } = useExpenseTransactions(employeeId, { enabled: !!employeeId }); @@ -142,7 +142,7 @@ const AdvancePaymentListDetails = ({ employeeId, searchString }) => { return (
- +
{columns.map((col) => ( diff --git a/src/components/AdvancePayment/handleAdvancePaymentExport.jsx b/src/components/AdvancePayment/handleAdvancePaymentExport.jsx new file mode 100644 index 00000000..13be8501 --- /dev/null +++ b/src/components/AdvancePayment/handleAdvancePaymentExport.jsx @@ -0,0 +1,76 @@ +import moment from "moment"; +import { exportToCSV, exportToExcel, exportToPDF, printTable } from "../../utils/tableExportUtils"; + +const handleAdvancePaymentExport = (type, data, tableRef) => { + if (!data || data.length === 0) return; + + let currentBalance = 0; + const exportData = data.map((item) => { + const credit = item.amount > 0 ? item.amount : 0; + const debit = item.amount < 0 ? Math.abs(item.amount) : 0; + currentBalance += credit - debit; + + return { + Date: item.createdAt ? moment(item.createdAt).format("DD-MMM-YYYY") : "", + Description: item.title || "-", // used only for CSV/Excel + Project: item.project?.name || "-", + Credit: credit || "", + Debit: debit || "", + "Finance ID": item.financeUId || "-", + Balance: currentBalance, + }; + }); + + // Final row + exportData.push({ + Date: "", + Description: "Final Balance", + Project: "", + Credit: "", + Debit: "", + "Finance ID": "", + Balance: currentBalance, + }); + + switch (type) { + case "csv": + exportToCSV(exportData, "advance-payments"); + break; + + case "excel": + exportToExcel(exportData, "advance-payments"); + break; + + case "pdf": + // Create a copy of data ONLY for PDF (without Description) + const pdfData = exportData.map((row, index) => { + // Detect final row + const isFinal = index === exportData.length - 1; + + return { + Date: isFinal ? "" : row.Date, + Project: isFinal ? "Final Balance" : row.Project, + Credit: row.Credit, + Debit: row.Debit, + "Finance ID": row["Finance ID"], + Balance: row.Balance, + }; + }); + + exportToPDF( + pdfData, + "advance-payments", + ["Date", "Project", "Credit", "Debit", "Finance ID", "Balance"] + ); + break; + + case "print": + if (tableRef?.current) printTable(tableRef.current); + break; + + default: + break; + } +}; + +export default handleAdvancePaymentExport; diff --git a/src/pages/AdvancePayment/AdvancePaymentPageDetails.jsx b/src/pages/AdvancePayment/AdvancePaymentPageDetails.jsx index 6f6d3240..6e2918dd 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPageDetails.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPageDetails.jsx @@ -3,6 +3,7 @@ import React, { useContext, useEffect, useMemo, + useRef, useState, } from "react"; import Breadcrumb from "../../components/common/Breadcrumb"; @@ -15,6 +16,7 @@ import { employee } from "../../data/masters"; import { formatFigure } from "../../utils/appUtils"; import { useParams } from "react-router-dom"; import { useExpenseTransactions } from "../../hooks/useExpense"; +import handleAdvancePaymentExport from "../../components/AdvancePayment/handleAdvancePaymentExport"; export const AdvancePaymentContext = createContext(); export const useAdvancePaymentContext = () => { @@ -60,6 +62,13 @@ const AdvancePaymentPageDetails = () => { }); }, [reset]); + + const tableRef = useRef(null); + + const handleExport = (type) => { + handleAdvancePaymentExport(type, transactionData, tableRef); + }; + return (
@@ -73,28 +82,64 @@ const AdvancePaymentPageDetails = () => { />
-
-
- {balance ? ( +
+ + {/* LEFT SIDE → EXPORT DROPDOWN */} +
+
+ + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + {/* RIGHT SIDE → CURRENT BALANCE */} +
+ {balance !== null ? ( <> - + 0 ? "text-success" : "text-danger"} fs-5 fw-bold ms-1`} > {balance > 0 && }{" "} - {formatFigure(balance, { - type: "currency", - currency: "INR", - })} + {formatFigure(balance, { type: "currency", currency: "INR" })} - ) : ( - <> - )} - + ) : null}
+
- + +
diff --git a/src/utils/tableExportUtils.jsx b/src/utils/tableExportUtils.jsx index 768ff5ac..e7dfe072 100644 --- a/src/utils/tableExportUtils.jsx +++ b/src/utils/tableExportUtils.jsx @@ -46,62 +46,73 @@ const sanitizeText = (text) => { return text.replace(/[^\x00-\x7F]/g, "?"); }; -export const exportToPDF = async (data, fileName = "data", columns = null, options = {}) => { +export const exportToPDF = async (data, fileName = "data", columns = null) => { if (!data || data.length === 0) return; const pdfDoc = await PDFDocument.create(); const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - // Default options - const { - columnWidths = [], // array of widths per column - fontSizeHeader = 12, - fontSizeRow = 10, - rowHeight = 25, - } = options; - - const pageWidth = 1000; + const pageWidth = 900; const pageHeight = 600; - let page = pdfDoc.addPage([pageWidth, pageHeight]); const margin = 30; + const rowHeight = 20; + + let page = pdfDoc.addPage([pageWidth, pageHeight]); 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 }); + const sanitize = (value) => { + if (value === null || value === undefined) return ""; + return String(value).replace(/[^\x00-\x7F]/g, ""); // remove unicode + }; + + // ---- Draw Header Row ---- + headers.forEach((header, index) => { + const x = margin + index * 120; + page.drawText(sanitize(header), { + x, + y, + size: 12, + font, + }); }); + 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 }); - }); - y -= rowHeight; - + // ---- Draw Table Rows ---- + data.forEach((row) => { if (y < margin) { + // Create a new page page = pdfDoc.addPage([pageWidth, pageHeight]); y = pageHeight - margin; } + + headers.forEach((header, index) => { + const x = margin + index * 120; + const text = sanitize(row[header]); + + page.drawText(text, { + x, + y, + size: 10, + font, + }); + }); + + y -= rowHeight; }); const pdfBytes = await pdfDoc.save(); - const blob = new Blob([pdfBytes], { type: 'application/pdf' }); - const link = document.createElement('a'); + + // Download + const blob = new Blob([pdfBytes], { type: "application/pdf" }); + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = `${fileName}.pdf`; link.click(); }; - - - - /** * Export JSON data to PDF in a card-style format * @param {Array} data - Array of objects to export