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 { useExpenseAllTransactionsList } from '../../hooks/useExpense';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { formatFigure } from '../../utils/appUtils';
|
import { formatFigure } from '../../utils/appUtils';
|
||||||
|
import { SpinnerLoader } from '../common/Loader';
|
||||||
|
|
||||||
const AdvancePaymentList = ({ searchString }) => {
|
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>;
|
if (isError) return <p className="text-center py-4 text-danger">{error.message}</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import { useForm, useFormContext } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { employee } from "../../data/masters";
|
import { employee } from "../../data/masters";
|
||||||
import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPageDetails"; // All data
|
import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPageDetails";
|
||||||
import { formatFigure } from "../../utils/appUtils";
|
import { formatFigure } from "../../utils/appUtils";
|
||||||
|
|
||||||
const AdvancePaymentListDetails = ({ employeeId, searchString }) => {
|
const AdvancePaymentListDetails = ({ employeeId, searchString,tableRef }) => {
|
||||||
const { setBalance } = useAdvancePaymentContext();
|
const { setBalance } = useAdvancePaymentContext();
|
||||||
const { data, isError, isLoading, error, isFetching } =
|
const { data, isError, isLoading, error, isFetching } =
|
||||||
useExpenseTransactions(employeeId, { enabled: !!employeeId });
|
useExpenseTransactions(employeeId, { enabled: !!employeeId });
|
||||||
@ -142,7 +142,7 @@ const AdvancePaymentListDetails = ({ employeeId, searchString }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table align-middle">
|
<table className="table align-middle" ref={tableRef}>
|
||||||
<thead className="table_header_border">
|
<thead className="table_header_border">
|
||||||
<tr>
|
<tr>
|
||||||
{columns.map((col) => (
|
{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,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
@ -15,6 +16,7 @@ import { employee } from "../../data/masters";
|
|||||||
import { formatFigure } from "../../utils/appUtils";
|
import { formatFigure } from "../../utils/appUtils";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useExpenseTransactions } from "../../hooks/useExpense";
|
import { useExpenseTransactions } from "../../hooks/useExpense";
|
||||||
|
import handleAdvancePaymentExport from "../../components/AdvancePayment/handleAdvancePaymentExport";
|
||||||
|
|
||||||
export const AdvancePaymentContext = createContext();
|
export const AdvancePaymentContext = createContext();
|
||||||
export const useAdvancePaymentContext = () => {
|
export const useAdvancePaymentContext = () => {
|
||||||
@ -60,6 +62,13 @@ const AdvancePaymentPageDetails = () => {
|
|||||||
});
|
});
|
||||||
}, [reset]);
|
}, [reset]);
|
||||||
|
|
||||||
|
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const handleExport = (type) => {
|
||||||
|
handleAdvancePaymentExport(type, transactionData, tableRef);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdvancePaymentContext.Provider value={{ setBalance }}>
|
<AdvancePaymentContext.Provider value={{ setBalance }}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -73,28 +82,64 @@ const AdvancePaymentPageDetails = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="card px-4 py-2 page-min-h ">
|
<div className="card px-4 py-2 page-min-h ">
|
||||||
<div className="row py-1 justify-content-end">
|
<div className="row py-1 align-items-center">
|
||||||
<div className="col-md-8 d-flex align-items-center justify-content-end">
|
|
||||||
{balance ? (
|
{/* 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
|
<span
|
||||||
className={`${balance > 0 ? "text-success" : "text-danger"} fs-5 fw-bold ms-1`}
|
className={`${balance > 0 ? "text-success" : "text-danger"} fs-5 fw-bold ms-1`}
|
||||||
>
|
>
|
||||||
{balance > 0 && <i className="bx bx-plus b-sm"></i>}{" "}
|
{balance > 0 && <i className="bx bx-plus b-sm"></i>}{" "}
|
||||||
{formatFigure(balance, {
|
{formatFigure(balance, { type: "currency", currency: "INR" })}
|
||||||
type: "currency",
|
|
||||||
currency: "INR",
|
|
||||||
})}
|
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : null}
|
||||||
<></>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<AdvancePaymentList employeeId={selectedEmployeeId} searchString={searchString} />
|
<AdvancePaymentList employeeId={selectedEmployeeId} searchString={searchString} tableRef={tableRef} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AdvancePaymentContext.Provider>
|
</AdvancePaymentContext.Provider>
|
||||||
|
|||||||
@ -46,62 +46,73 @@ const sanitizeText = (text) => {
|
|||||||
return text.replace(/[^\x00-\x7F]/g, "?");
|
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;
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
const pdfDoc = await PDFDocument.create();
|
const pdfDoc = await PDFDocument.create();
|
||||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
|
||||||
// Default options
|
const pageWidth = 900;
|
||||||
const {
|
|
||||||
columnWidths = [], // array of widths per column
|
|
||||||
fontSizeHeader = 12,
|
|
||||||
fontSizeRow = 10,
|
|
||||||
rowHeight = 25,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const pageWidth = 1000;
|
|
||||||
const pageHeight = 600;
|
const pageHeight = 600;
|
||||||
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
||||||
const margin = 30;
|
const margin = 30;
|
||||||
|
const rowHeight = 20;
|
||||||
|
|
||||||
|
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
let y = pageHeight - margin;
|
let y = pageHeight - margin;
|
||||||
|
|
||||||
const headers = columns || Object.keys(data[0]);
|
const headers = columns || Object.keys(data[0]);
|
||||||
|
|
||||||
// Draw headers
|
const sanitize = (value) => {
|
||||||
headers.forEach((header, i) => {
|
if (value === null || value === undefined) return "";
|
||||||
const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
return String(value).replace(/[^\x00-\x7F]/g, ""); // remove unicode
|
||||||
page.drawText(header, { x, y, font, size: fontSizeHeader });
|
};
|
||||||
});
|
|
||||||
y -= rowHeight;
|
// ---- Draw Header Row ----
|
||||||
|
headers.forEach((header, index) => {
|
||||||
// Draw rows
|
const x = margin + index * 120;
|
||||||
data.forEach(row => {
|
page.drawText(sanitize(header), {
|
||||||
headers.forEach((header, i) => {
|
x,
|
||||||
const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
y,
|
||||||
const text = row[header] || '';
|
size: 12,
|
||||||
page.drawText(text, { x, y, font, size: fontSizeRow });
|
font,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
y -= rowHeight;
|
y -= rowHeight;
|
||||||
|
|
||||||
|
// ---- Draw Table Rows ----
|
||||||
|
data.forEach((row) => {
|
||||||
if (y < margin) {
|
if (y < margin) {
|
||||||
|
// Create a new page
|
||||||
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
y = pageHeight - margin;
|
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 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.href = URL.createObjectURL(blob);
|
||||||
link.download = `${fileName}.pdf`;
|
link.download = `${fileName}.pdf`;
|
||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export JSON data to PDF in a card-style format
|
* Export JSON data to PDF in a card-style format
|
||||||
* @param {Array} data - Array of objects to export
|
* @param {Array} data - Array of objects to export
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user