Adding Export Functionality in Advance Payment and adding spinner on it.

This commit is contained in:
Kartik Sharma 2025-11-27 10:16:25 +05:30
parent 7b1ad80d78
commit 3072354ed2
5 changed files with 187 additions and 47 deletions

View File

@ -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 <p className="text-center py-4">Loading...</p>;
if (isLoading) {
return (
<div className="d-flex justify-content-center align-items-center py-4" style={{ height: "300px" }}>
<SpinnerLoader />
</div>
);
}
if (isError) return <p className="text-center py-4 text-danger">{error.message}</p>;
return (

View File

@ -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 (
<div className="table-responsive">
<table className="table align-middle">
<table className="table align-middle" ref={tableRef}>
<thead className="table_header_border">
<tr>
{columns.map((col) => (

View File

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

View File

@ -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 (
<AdvancePaymentContext.Provider value={{ setBalance }}>
<div className="container-fluid">
@ -73,28 +82,64 @@ const AdvancePaymentPageDetails = () => {
/>
<div className="card px-4 py-2 page-min-h ">
<div className="row py-1 justify-content-end">
<div className="col-md-8 d-flex align-items-center justify-content-end">
{balance ? (
<div className="row py-1 align-items-center">
{/* LEFT SIDE → EXPORT DROPDOWN */}
<div className="col-md-4 d-flex align-items-center">
<div className="dropdown">
<button
aria-label="Click me"
className="btn btn-sm btn-label-secondary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-export me-2 bx-sm"></i>Export
</button>
<ul className="dropdown-menu">
<li>
<button className="dropdown-item" onClick={() => handleExport("print")}>
<i className="bx bx-printer me-1"></i> Print
</button>
</li>
<li>
<button className="dropdown-item" onClick={() => handleExport("csv")}>
<i className="bx bx-file me-1"></i> CSV
</button>
</li>
<li>
<button className="dropdown-item" onClick={() => handleExport("excel")}>
<i className="bx bxs-file-export me-1"></i> Excel
</button>
</li>
<li>
<button className="dropdown-item" onClick={() => handleExport("pdf")}>
<i className="bx bxs-file-pdf me-1"></i> PDF
</button>
</li>
</ul>
</div>
</div>
{/* RIGHT SIDE → CURRENT BALANCE */}
<div className="col-md-8 d-flex justify-content-end align-items-center">
{balance !== null ? (
<>
<label className="fs-5 fw-semibold">Current Balance : </label>
<label className="fs-5 fw-semibold">Current Balance :</label>
<span
className={`${balance > 0 ? "text-success" : "text-danger"} fs-5 fw-bold ms-1`}
>
{balance > 0 && <i className="bx bx-plus b-sm"></i>}{" "}
{formatFigure(balance, {
type: "currency",
currency: "INR",
})}
{formatFigure(balance, { type: "currency", currency: "INR" })}
</span>
</>
) : (
<></>
)}
) : null}
</div>
</div>
<AdvancePaymentList employeeId={selectedEmployeeId} searchString={searchString} />
<AdvancePaymentList employeeId={selectedEmployeeId} searchString={searchString} tableRef={tableRef} />
</div>
</div>
</AdvancePaymentContext.Provider>

View File

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