Files
IB/src/app/(main)/accounts/account_statement/accountStatement.tsx

290 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider, Center, Loader, Stack, Group } from "@mantine/core";
import { DateInput } from '@mantine/dates';
import { useEffect, useRef, useState } from "react";
import { useSearchParams } from "next/navigation";
import { notifications } from "@mantine/notifications";
import dayjs from 'dayjs';
import { IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react";
import { generatePDF } from "@/app/_components/statement_download/PdfGenerator";
import { generateCSV } from "@/app/_components/statement_download/CsvGenerator";
export default function AccountStatementPage() {
const pdfRef = useRef<HTMLDivElement>(null);
const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]);
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [transactions, setTransactions] = useState<any[]>([]);
const searchParams = useSearchParams();
const passedAccNo = searchParams.get("accNo");
const [loading, setLoading] = useState(false);
const [availableBalance, setAvailableBalance] = useState<string | null>(null);
useEffect(() => {
const saved = sessionStorage.getItem("accountData");
if (saved) {
const parsed = JSON.parse(saved);
const options = parsed.map((acc: any) => ({
label: `${acc.stAccountNo} - ${acc.stAccountType}`,
value: acc.stAccountNo,
}));
setAccountOptions(options);
if (passedAccNo) {
setSelectedAccNo(passedAccNo);
//Automatically fetch last 5 transactions if accNo is passed
const token = localStorage.getItem("access_token");
setLoading(true);
fetch(`/api/transactions/account/${passedAccNo}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) {
const last5 = data.slice(0, 5);
setTransactions(last5);
// Reuse balance logic
const saved = sessionStorage.getItem("accountData");
if (saved) {
const parsed = JSON.parse(saved);
const acc = parsed.find((a: any) => a.stAccountNo === passedAccNo);
if (acc) {
setAvailableBalance(acc.stAvailableBalance);
}
}
}
})
.catch(() => {
notifications.show({
withBorder: true,
color: "red",
title: "Fetch Failed",
message: "Could not load recent transactions.",
autoClose: 5000,
});
})
.finally(() => setLoading(false));
}
}
}, [passedAccNo]);
const handleAccountTransaction = async () => {
if (!selectedAccNo || !startDate || !endDate) {
notifications.show({
withBorder: true,
color: "red",
title: "Missing field",
message: "Please select Account number,Start date and End date",
autoClose: 5000,
});
return;
}
const start = dayjs(startDate);
const end = dayjs(endDate);
const today = dayjs().startOf('day');
if (end.isAfter(today)) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid End Date",
message: "End date can not be the future date.",
autoClose: 4000,
});
return;
}
if (start.isAfter(end)) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Start Date",
message: "Start date can not be less than end date",
autoClose: 4000,
});
return;
}
if (end.diff(start, "day") > 90) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Date range",
message: "End date must be within 90 days from start date",
autoClose: 4000,
});
return;
}
try {
const token = localStorage.getItem("access_token");
setLoading(true);
const response = await fetch(`/api/transactions/account/${selectedAccNo}?fromDate=${dayjs(start).format('DDMMYYYY')}&toDate=${dayjs(end).format('DDMMYYYY')}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok && Array.isArray(data)) {
setTransactions(data.reverse());
// SHowing Available balance
const saved = sessionStorage.getItem("accountData");
if (saved) {
const parsed = JSON.parse(saved);
const acc = parsed.find((a: any) => a.stAccountNo === selectedAccNo);
if (acc) {
setAvailableBalance(acc.stAvailableBalance);
}
}
}
} catch {
notifications.show({
withBorder: true,
color: "red",
title: "Please try again later",
message: "Unable to Fetch Account Transaction, Please try again later",
autoClose: 5000,
});
}
finally {
setLoading(false);
}
};
const cellStyle = {
border: "1px solid #ccc",
padding: "8px",
};
return (
<Grid gutter="md">
{/* Left side form */}
<Grid.Col span={{ base: 12, md: 4 }}>
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
<Title order={4} mb="sm">Account Transactions</Title>
<Select
label="Select Account Number"
placeholder="Choose account number"
data={accountOptions}
value={selectedAccNo}
onChange={setSelectedAccNo}
searchable
/>
<DateInput
label="Start Date"
value={startDate}
onChange={setStartDate}
placeholder="Enter start date"
/>
<DateInput
label="End Date"
value={endDate}
onChange={setEndDate}
placeholder="Enter end date"
/>
<Button fullWidth mt="md" onClick={handleAccountTransaction}>
Proceed
</Button>
</Paper>
</Grid.Col>
{/* Right side transaction list */}
<Grid.Col span={{ base: 12, md: 8 }}>
<Paper shadow="sm" radius="md" p="md" withBorder h={400} style={{ display: 'flex', flexDirection: 'column' }}>
<Title order={5} mb="xs">Account Transactions</Title>
<Group justify="space-between" align="center" mt="sm">
<div>
<Text fw={500} ><strong>Account No :</strong> {selectedAccNo}</Text>
{availableBalance && (
<Text fw={500} >
<strong>Available Balance :</strong> {parseFloat(availableBalance).toLocaleString("en-IN", {
minimumFractionDigits: 2,
})}
</Text>
)}
</div>
<Group gap="xs">
<Text>Download :</Text>
{/* <IconFileTypePdf size={22} style={{ cursor: "pointer" }} onClick={() => console.log("Download PDF")} /> */}
<IconFileTypePdf
size={22}
style={{ cursor: "pointer" }}
onClick={() =>
generatePDF(selectedAccNo || "", availableBalance || "0", transactions,
localStorage.getItem("remitter_name") || "",
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "",
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "")
}
/>
<IconFileSpreadsheet
size={22}
style={{ cursor: "pointer" }}
onClick={() =>
generateCSV(selectedAccNo || "NA", availableBalance || "0.00", transactions)
}
/>
</Group>
</Group>
<Divider size="xs" />
<ScrollArea style={{ flex: 1 }}>
{loading ? (
<Center h="100%">
<Stack align="center" gap="sm">
<Loader size="lg" color="cyan" type="bars" />
<Text>Please wait, details are being fetched .....</Text>
</Stack>
</Center>
) : transactions.length === 0 ? (
<p>No transactions found.</p>
) : (
<>
<Text fs="italic" c='#228be6' ta='center'>
{!startDate && !endDate ? 'Last 5 Transactions'
: startDate && endDate ? `Transactions from ${dayjs(startDate).format("DD/MM/YYYY")} to ${dayjs(endDate).format("DD/MM/YYYY")}`
: ""}
</Text>
<Table style={{ borderCollapse: "collapse", width: '100%' }}>
<thead style={{ backgroundColor: "#3385ff" }}>
{/* <tr>
<th style={{ ...cellStyle, position: 'sticky', textAlign: "left" }}>Name</th>
<th style={{ ...cellStyle, position: 'sticky', textAlign: "left" }}>Date</th>
<th style={{ ...cellStyle, position: 'sticky', textAlign: "left" }}>Type</th>
<th style={{ ...cellStyle, position: 'sticky', textAlign: "left" }}>Amount(₹)</th>
<th style={{ ...cellStyle, position: 'sticky', textAlign: "left" }}>Available Balance(₹)</th>
</tr> */}
</thead>
<tbody style={{ maxHeight: '250px', overflowY: 'auto', width: '100%' }}>
{transactions.map((txn, i) => (
<tr key={i}>
<td style={{ ...cellStyle, textAlign: "left" }}> {txn.name || "—"}</td>
<td style={{ ...cellStyle, textAlign: "left" }}>{txn.date || "—"}</td>
{/* <td style={{ ...cellStyle, textAlign: "left" }}>{txn.type}</td> */}
<td style={{ ...cellStyle, textAlign: "right", color: txn.type === "DR" ? "#e03131" : "#2f9e44" }}>
{parseFloat(txn.amount).toLocaleString("en-IN", {
minimumFractionDigits: 2,
})} <span style={{ fontSize: '10px' }}>{txn.type === "DR" ? "Dr." : "Cr."}</span>
</td>
<td style={{ ...cellStyle, textAlign: "right", color: "blue", fontSize: '12px' }}>{txn.balance || "—"}</td>
</tr>
))}
</tbody>
</Table>
</>
)}
</ScrollArea>
</Paper>
</Grid.Col >
</Grid >
);
}