Adding Export Functionality in Advance Payment and adding spinner on it.
This commit is contained in:
parent
7b1ad80d78
commit
3072354ed2
@ -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 (
|
||||
|
||||
@ -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) => (
|
||||
|
||||
76
src/components/AdvancePayment/handleAdvancePaymentExport.jsx
Normal file
76
src/components/AdvancePayment/handleAdvancePaymentExport.jsx
Normal 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;
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user