290 lines
14 KiB
TypeScript
290 lines
14 KiB
TypeScript
"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 >
|
||
);
|
||
}
|
||
|
||
|