fix: Create report for pdf and excel

feat: Change design the Application
This commit is contained in:
2025-11-28 17:14:59 +05:30
parent c1d0519c09
commit cf9faf2e82
17 changed files with 2252 additions and 482 deletions

934
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"exceljs": "^4.4.0",
"html2pdf.js": "^0.12.0", "html2pdf.js": "^0.12.0",
"ib": "file:", "ib": "file:",
"IB": "file:", "IB": "file:",

BIN
public/kccb_watermark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -7,7 +7,7 @@ import { notifications } from "@mantine/notifications";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react"; import { IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react";
import { generatePDF } from "@/app/_components/statement_download/PdfGenerator"; import { generatePDF } from "@/app/_components/statement_download/PdfGenerator";
import { generateCSV } from "@/app/_components/statement_download/CsvGenerator"; import { generateExcel } from "@/app/_components/statement_download/CsvGenerator";
import { useMediaQuery } from "@mantine/hooks"; import { useMediaQuery } from "@mantine/hooks";
export default function AccountStatementPage() { export default function AccountStatementPage() {
@@ -223,17 +223,34 @@ export default function AccountStatementPage() {
size={22} size={22}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => onClick={() =>
generatePDF(selectedAccNo || "", availableBalance || "0", transactions, generatePDF(
selectedAccNo || "",
availableBalance || "0",
transactions,
localStorage.getItem("remitter_name") || "", localStorage.getItem("remitter_name") || "",
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "", startDate ? dayjs(startDate).format("DD/MM/YYYY") : "",
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "") endDate ? dayjs(endDate).format("DD/MM/YYYY") : "",
localStorage.getItem("remitter_branch_no") || "",
localStorage.getItem("remitter_cif_no") || "",
localStorage.getItem("remitter_address") || ""
)
} }
/> />
<IconFileSpreadsheet <IconFileSpreadsheet
size={22} size={22}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => onClick={() =>
generateCSV(selectedAccNo || "NA", availableBalance || "0.00", transactions) generateExcel(
selectedAccNo || "",
availableBalance || "0",
transactions,
localStorage.getItem("remitter_name") || "",
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "",
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "",
localStorage.getItem("remitter_branch_no") || "",
localStorage.getItem("remitter_cif_no") || "",
localStorage.getItem("remitter_address") || ""
)
} }
/> />

View File

@@ -135,7 +135,7 @@ export default function AddBeneficiaryOthers() {
}, [ifsccode]); }, [ifsccode]);
const validateAndSendOtp = async () => { const validateAndSendOtp = async () => {
if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType) { if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType || !nickName) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
@@ -430,6 +430,16 @@ export default function AddBeneficiaryOthers() {
withAsterisk withAsterisk
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
required
onChange={(e) => setNickName(e.currentTarget.value)}
/>
</Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextInput <TextInput
label="Beneficiary Name" label="Beneficiary Name"
@@ -439,19 +449,10 @@ export default function AddBeneficiaryOthers() {
required required
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
onChange={(e) => setNickName(e.currentTarget.value)}
/>
</Grid.Col>
{!otpSent && ( {!otpSent && (
<Grid.Col > <Grid.Col >
<Group gap="sm"> <Group gap="sm">
<Button onClick={validateAndSendOtp} disabled={loading}>Validate</Button> <Button onClick={validateAndSendOtp} disabled={loading}>Validate</Button>
{loading && ( {loading && (
<> <>
<Loader size='sm' type="bars"></Loader> <Loader size='sm' type="bars"></Loader>
@@ -493,9 +494,9 @@ export default function AddBeneficiaryOthers() {
</Grid.Col > </Grid.Col >
<Grid.Col > <Grid.Col >
{!otpVerified ? ( {!otpVerified ? (
<Button onClick={verify_otp}>Validate OTP</Button> <Button onClick={verify_otp}>Validate OTP</Button>
) : ( ) : (
<Button size="sm" onClick={AddBen}> <Button size="sm" onClick={AddBen}>
Add Add
</Button> </Button>
)} )}

View File

@@ -0,0 +1,322 @@
"use client";
import React, { useEffect, useState } from 'react';
import {TextInput,Button,Select,Title,Paper,Grid,Group,Radio,Text,PasswordInput} from '@mantine/core';
import { useRouter } from "next/navigation";
import { notifications } from '@mantine/notifications';
import AddBeneficiaryOthers from '@/app/(main)/beneficiary/add_beneficiary/addBeneficiaryOthers';
const bankOptions = [
{ value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank Ltd.' },
];
const AddBeneficiary: React.FC = () => {
const router = useRouter();
const [bankName, setBankName] = useState('');
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [bankType, setBankType] = useState('own');
const [accountNo, setAccountNo] = useState('');
const [confirmAccountNo, setConfirmAccountNo] = useState('');
const [nickName, setNickName] = useState('');
const [otp, setOtp] = useState('');
const [generatedOtp, setGeneratedOtp] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [otpVerified, setOtpVerified] = useState(false);
const [validationStatus, setValidationStatus] = useState<'success' | 'error' | null>(null);
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [isVisibilityLocked, setIsVisibilityLocked] = useState(false);
const [showPayeeAcc, setShowPayeeAcc] = useState(true);
const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); };
const handleGenerateOtp = async () => {
const value = "123456"; // Or generate a random OTP
setGeneratedOtp(value);
return value;
};
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
// const isValidBankName = (name: string) => {
// const regex = /^[A-Za-z\s]{3,5}$/;
// return regex.test(name.trim());
// };
// const isValidIfscCode = (code: string) => {
// return /^[A-Z]{4}0[0-9]{6}$/.test(code);
// };
const validateAndSendOtp = async () => {
if (!bankName || !accountNo || !confirmAccountNo) {
notifications.show({
withBorder: true,
color: "red",
title: "Missing Field",
message: "All fields must be completed.",
autoClose: 5000,
});
return;
}
// if (bankType === "outside" && !/^[A-Za-z ]{1,5}$/.test(bankName)) {
// notifications.show({
// color: "red",
// title: "Invalid Bank Name",
// message: "Use only alphabets/spaces, max 100 characters.",
// });
// return;
// }
// const trimmedIfsc = ifsccode.trim().toUpperCase();
// if (!isValidIfscCode(trimmedIfsc)) {
// notifications.show({
// title: "Invalid IFSC Code",
// message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
// color: "red",
// });
// return;
// }
if (accountNo.length < 10 || accountNo.length > 17) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Account Number",
message: "Enter a valid account number (1017 digits).",
autoClose: 5000,
});
return;
}
if (accountNo !== confirmAccountNo) {
notifications.show({
withBorder: true,
color: "red",
title: "Mismatch",
message: "Account numbers do not match.",
autoClose: 5000,
});
return;
}
try {
const token = localStorage.getItem("access_token");
const response = await fetch(`/api/beneficiary/validate/within-bank?accountNumber=${accountNo}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok && data?.name) {
setBeneficiaryName(data.name);
setValidationStatus("success");
setIsVisibilityLocked(true);
setOtpSent(true);
await handleGenerateOtp();
notifications.show({
withBorder: true,
color: "green",
title: "OTP Sent",
message: "OTP has been sent to your registered mobile number.",
autoClose: 5000,
});
} else {
setBeneficiaryName("Invalid beneficiary account number");
setValidationStatus("error");
setAccountNo("");
setConfirmAccountNo("");
}
} catch (error) {
setBeneficiaryName("Invalid beneficiary account number");
setValidationStatus("error");
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Could not validate account number.",
autoClose: 5000,
});
}
};
const verifyOtp = () => {
if (otp === generatedOtp) {
setOtpVerified(true);
notifications.show({
withBorder: true,
color: "green",
title: "OTP Verified",
message: "OTP validated successfully.",
autoClose: 5000,
});
} else {
notifications.show({
withBorder: true,
color: "red",
title: "OTP Error",
message: "OTP does not match.",
autoClose: 5000,
});
}
};
const AddBen = () => {
notifications.show({
withBorder: true,
color: "green",
title: "Beneficiary Added",
message: "Beneficiary added successfully.",
autoClose: 5000,
});
};
if (!authorized) return null;
return (
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md">Add Beneficiary</Title>
{/* <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
<Group justify="center">
<Radio value="own" label="Own Bank" />
<Radio value="outside" label="Outside Bank" />
</Group>
</Radio.Group> */}
{/* {bankType === "own" ? (
<Grid gutter="md">
<Grid.Col span={4}>
<Select
label="Bank Name"
placeholder="Select bank"
data={bankOptions}
value={bankName}
onChange={(value) => setBankName(value || '')}
required
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Beneficiary Account Number"
placeholder="Enter account number"
value={showPayeeAcc ? accountNo : getFullMaskedAccount(accountNo)}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setAccountNo(value);
setShowPayeeAcc(true);
}
}}
onBlur={() => setShowPayeeAcc(false)}
onFocus={() => setShowPayeeAcc(true)}
readOnly={isVisibilityLocked}
maxLength={17}
// disabled={isVisibilityLocked}
required
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Confirm Beneficiary Account Number"
placeholder="Re-enter account number"
value={confirmAccountNo}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setConfirmAccountNo(value);
}
}}
maxLength={17}
// disabled={isVisibilityLocked}
readOnly={isVisibilityLocked}
required
onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()}
/>
{validationStatus === "error" && (
<Text c="red" size="sm" style={{ marginLeft: '1rem', whiteSpace: 'nowrap' }}>
{beneficiaryName}
</Text>
)}
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
onChange={(e) => setNickName(e.currentTarget.value)}
/>
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label="Beneficiary Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
disabled
maxLength={100}
required
/>
</Grid.Col>
{!otpSent && (
<Grid.Col>
<Button onClick={validateAndSendOtp}>Validate</Button>
</Grid.Col>
)}
{otpSent && (
<>
<Grid.Col span={3}>
<PasswordInput
label="Enter OTP"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
maxLength={6}
disabled={otpVerified} // ✅ Disable after verified
/>
</Grid.Col >
<Grid.Col >
{!otpVerified ? (
<Button onClick={verifyOtp}>Validate OTP</Button>
) : (
<Button color="blue" size="sm" onClick={AddBen}>
Add
</Button>
)}
</Grid.Col>
</>
)}
</Grid>
) : ( */}
<Grid gutter="md">
<AddBeneficiaryOthers />
</Grid>
{/* )} */}
</Paper>
);
};
export default AddBeneficiary;

View File

@@ -27,8 +27,8 @@ export default function Layout({ children }: { children: React.ReactNode }) {
/* Beneficiary Options */ /* Beneficiary Options */
const links = [ const links = [
{ label: "Add Beneficiary", href: "/beneficiary" }, { label: "View Beneficiary", href: "/beneficiary" },
{ label: "View Beneficiary", href: "/beneficiary/view_beneficiary" }, { label: "Add Beneficiary", href: "/beneficiary/add_beneficiary" },
]; ];
useEffect(() => { useEffect(() => {

View File

@@ -1,322 +1,12 @@
"use client"; "use client";
import React, { useEffect, useState } from 'react'; import React from "react";
import {TextInput,Button,Select,Title,Paper,Grid,Group,Radio,Text,PasswordInput} from '@mantine/core'; import ViewBeneficiary from "./view_beneficiary/page";
import { useRouter } from "next/navigation";
import { notifications } from '@mantine/notifications';
import AddBeneficiaryOthers from '@/app/(main)/beneficiary/add_beneficiary/addBeneficiaryOthers';
const bankOptions = [ export default function Beneficiary() {
{ value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank Ltd.' },
];
const AddBeneficiary: React.FC = () => {
const router = useRouter();
const [bankName, setBankName] = useState('');
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [bankType, setBankType] = useState('own');
const [accountNo, setAccountNo] = useState('');
const [confirmAccountNo, setConfirmAccountNo] = useState('');
const [nickName, setNickName] = useState('');
const [otp, setOtp] = useState('');
const [generatedOtp, setGeneratedOtp] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [otpVerified, setOtpVerified] = useState(false);
const [validationStatus, setValidationStatus] = useState<'success' | 'error' | null>(null);
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [isVisibilityLocked, setIsVisibilityLocked] = useState(false);
const [showPayeeAcc, setShowPayeeAcc] = useState(true);
const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); };
const handleGenerateOtp = async () => {
const value = "123456"; // Or generate a random OTP
setGeneratedOtp(value);
return value;
};
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
// const isValidBankName = (name: string) => {
// const regex = /^[A-Za-z\s]{3,5}$/;
// return regex.test(name.trim());
// };
// const isValidIfscCode = (code: string) => {
// return /^[A-Z]{4}0[0-9]{6}$/.test(code);
// };
const validateAndSendOtp = async () => {
if (!bankName || !accountNo || !confirmAccountNo) {
notifications.show({
withBorder: true,
color: "red",
title: "Missing Field",
message: "All fields must be completed.",
autoClose: 5000,
});
return;
}
// if (bankType === "outside" && !/^[A-Za-z ]{1,5}$/.test(bankName)) {
// notifications.show({
// color: "red",
// title: "Invalid Bank Name",
// message: "Use only alphabets/spaces, max 100 characters.",
// });
// return;
// }
// const trimmedIfsc = ifsccode.trim().toUpperCase();
// if (!isValidIfscCode(trimmedIfsc)) {
// notifications.show({
// title: "Invalid IFSC Code",
// message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
// color: "red",
// });
// return;
// }
if (accountNo.length < 10 || accountNo.length > 17) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Account Number",
message: "Enter a valid account number (1017 digits).",
autoClose: 5000,
});
return;
}
if (accountNo !== confirmAccountNo) {
notifications.show({
withBorder: true,
color: "red",
title: "Mismatch",
message: "Account numbers do not match.",
autoClose: 5000,
});
return;
}
try {
const token = localStorage.getItem("access_token");
const response = await fetch(`/api/beneficiary/validate/within-bank?accountNumber=${accountNo}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok && data?.name) {
setBeneficiaryName(data.name);
setValidationStatus("success");
setIsVisibilityLocked(true);
setOtpSent(true);
await handleGenerateOtp();
notifications.show({
withBorder: true,
color: "green",
title: "OTP Sent",
message: "OTP has been sent to your registered mobile number.",
autoClose: 5000,
});
} else {
setBeneficiaryName("Invalid beneficiary account number");
setValidationStatus("error");
setAccountNo("");
setConfirmAccountNo("");
}
} catch (error) {
setBeneficiaryName("Invalid beneficiary account number");
setValidationStatus("error");
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Could not validate account number.",
autoClose: 5000,
});
}
};
const verifyOtp = () => {
if (otp === generatedOtp) {
setOtpVerified(true);
notifications.show({
withBorder: true,
color: "green",
title: "OTP Verified",
message: "OTP validated successfully.",
autoClose: 5000,
});
} else {
notifications.show({
withBorder: true,
color: "red",
title: "OTP Error",
message: "OTP does not match.",
autoClose: 5000,
});
}
};
const AddBen = () => {
notifications.show({
withBorder: true,
color: "green",
title: "Beneficiary Added",
message: "Beneficiary added successfully.",
autoClose: 5000,
});
};
if (!authorized) return null;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <ViewBeneficiary />
<Title order={3} mb="md">Add Beneficiary</Title>
{/* <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
<Group justify="center">
<Radio value="own" label="Own Bank" />
<Radio value="outside" label="Outside Bank" />
</Group>
</Radio.Group> */}
{/* {bankType === "own" ? (
<Grid gutter="md">
<Grid.Col span={4}>
<Select
label="Bank Name"
placeholder="Select bank"
data={bankOptions}
value={bankName}
onChange={(value) => setBankName(value || '')}
required
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Beneficiary Account Number"
placeholder="Enter account number"
value={showPayeeAcc ? accountNo : getFullMaskedAccount(accountNo)}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setAccountNo(value);
setShowPayeeAcc(true);
}
}}
onBlur={() => setShowPayeeAcc(false)}
onFocus={() => setShowPayeeAcc(true)}
readOnly={isVisibilityLocked}
maxLength={17}
// disabled={isVisibilityLocked}
required
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Confirm Beneficiary Account Number"
placeholder="Re-enter account number"
value={confirmAccountNo}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setConfirmAccountNo(value);
}
}}
maxLength={17}
// disabled={isVisibilityLocked}
readOnly={isVisibilityLocked}
required
onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()}
/>
{validationStatus === "error" && (
<Text c="red" size="sm" style={{ marginLeft: '1rem', whiteSpace: 'nowrap' }}>
{beneficiaryName}
</Text>
)}
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
onChange={(e) => setNickName(e.currentTarget.value)}
/>
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label="Beneficiary Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
disabled
maxLength={100}
required
/>
</Grid.Col>
{!otpSent && (
<Grid.Col>
<Button onClick={validateAndSendOtp}>Validate</Button>
</Grid.Col>
)}
{otpSent && (
<>
<Grid.Col span={3}>
<PasswordInput
label="Enter OTP"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
maxLength={6}
disabled={otpVerified} // ✅ Disable after verified
/>
</Grid.Col >
<Grid.Col >
{!otpVerified ? (
<Button onClick={verifyOtp}>Validate OTP</Button>
) : (
<Button color="blue" size="sm" onClick={AddBen}>
Add
</Button>
)}
</Grid.Col>
</>
)}
</Grid>
) : ( */}
<Grid gutter="md">
<AddBeneficiaryOthers />
</Grid>
{/* )} */}
</Paper>
); );
}; }
export default AddBeneficiary;

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, Modal } from "@mantine/core"; import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, Modal, PasswordInput } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
@@ -215,7 +215,7 @@ export default function ViewBeneficiary() {
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Group justify="space-between" align="center" mb="md" wrap="wrap"> <Group justify="space-between" align="center" mb="md" wrap="wrap">
<Title order={3}>My Beneficiaries</Title> <Title order={3}>My Beneficiaries</Title>
@@ -223,8 +223,7 @@ export default function ViewBeneficiary() {
color="green" color="green"
variant="filled" variant="filled"
size="sm" size="sm"
component="a" onClick={() => router.push("/beneficiary/add_beneficiary")}
href="/beneficiary"
style={{ minWidth: "150px" }} style={{ minWidth: "150px" }}
> >
+ Add Beneficiary + Add Beneficiary
@@ -235,7 +234,7 @@ export default function ViewBeneficiary() {
<Text>No beneficiaries found.</Text> <Text>No beneficiaries found.</Text>
) : ( ) : (
<> <>
<ScrollArea h={300} type="always"> <ScrollArea h={400} type="always">
<Table highlightOnHover withTableBorder stickyHeader style={{ borderCollapse: "collapse", width: "100%" }}> <Table highlightOnHover withTableBorder stickyHeader style={{ borderCollapse: "collapse", width: "100%" }}>
<thead style={{ <thead style={{
background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)", background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
@@ -314,7 +313,7 @@ export default function ViewBeneficiary() {
) : ( ) : (
<> <>
<Text mb="sm">Enter OTP sent to your registered number:</Text> <Text mb="sm">Enter OTP sent to your registered number:</Text>
<TextInput <PasswordInput
value={otp} value={otp}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
placeholder="Enter OTP" placeholder="Enter OTP"

View File

@@ -395,10 +395,8 @@ export default function SendToBeneficiaryOwn() {
</Group> </Group>
</Modal> </Modal>
{/* main content */} {/* main content */}
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md"> <Title order={3} mb="md">
Send To Beneficiary Send To Beneficiary
</Title> </Title>

View File

@@ -546,7 +546,7 @@ export default function SendToBeneficiaryOthers() {
{/* main content */} {/* main content */}
{!showIntroModal && ( {!showIntroModal && (
<div style={{ maxHeight: "290px", overflowY: "auto" }}> <div style={{ maxHeight: "350px", overflowY: "auto" }}>
<Stack gap={5} justify="flex-start"> <Stack gap={5} justify="flex-start">
<Group grow gap='xs' > <Group grow gap='xs' >
<Select <Select

View File

@@ -493,7 +493,7 @@ export default function Home() {
<Box mt="md"> <Box mt="md">
<Stack> <Stack>
<Text fw={700}> ** Book Balance includes uncleared effect.</Text> <Text fw={700}> ** Book Balance includes uncleared effect.</Text>
<Text fw={700}> ** Click "Show Balance" to display account balances.</Text> <Text fw={700}> ** Click &quot;Show Balance&quot; to display account balances.</Text>
<Text fw={400} c="red"> <Text fw={400} c="red">
** Your Password will expire in {PassExpiryRemains} days. ** Your Password will expire in {PassExpiryRemains} days.
</Text> </Text>

View File

@@ -1,60 +1,619 @@
"use client"; "use client";
import dayjs from "dayjs";
interface CsvGeneratorProps { export const generateExcel = (
accountNo: string;
balance: string;
txns: any[];
}
export const generateCSV = (
accountNo: string, accountNo: string,
balance: string, balance: string,
txns: any[] txns: any[],
customerName: string,
periodFrom: string,
periodTo: string,
branchCode: string,
cifNumber: string,
address: string
) => { ) => {
// CSV Header // Import ExcelJS dynamically
let csv = `Bank Statement\n`; const ExcelJS = require("exceljs");
csv += `Account No:,${accountNo}\n`;
csv += `Available Balance:,${balance}\n`;
csv += `Generated:,${new Date().toLocaleString()}\n\n`;
// Column headers // Create a new workbook and worksheet
csv += "Date,Name,Type,Amount\n"; const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Account Statement", {
// Rows pageSetup: {
txns.forEach((txn) => { paperSize: 9, // A4
csv += `${txn.date},${txn.name},${txn.type.toLowerCase()},${txn.amount}\n`; orientation: "portrait",
fitToPage: true,
fitToWidth: 1,
margins: {
left: 0.5,
right: 0.5,
top: 0.75,
bottom: 0.75,
header: 0.3,
footer: 0.3,
},
},
views: [{ showGridLines: false }],
}); });
// Trigger browser download let currentRow = 1;
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a"); /* ---------------------------------------------------------
link.setAttribute("href", url); * 1) HEADER SECTION
link.setAttribute("download", `statement_${accountNo}.csv`); * --------------------------------------------------------- */
document.body.appendChild(link);
link.click(); // Bank Name
document.body.removeChild(link); const headerRow = worksheet.getRow(currentRow);
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const headerCell = worksheet.getCell(`A${currentRow}`);
headerCell.value = "THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.";
headerCell.font = {
name: "Times New Roman",
size: 16,
bold: true,
color: { argb: "FF1a5f3a" },
};
headerCell.alignment = { vertical: "middle", horizontal: "center" };
headerRow.height = 25;
currentRow++;
// Bank Address
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const addressCell = worksheet.getCell(`A${currentRow}`);
addressCell.value = "Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215";
addressCell.font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
addressCell.alignment = { vertical: "middle", horizontal: "center" };
currentRow++;
// e-Statement Service & Date
worksheet.mergeCells(`A${currentRow}:B${currentRow}`);
const eStatementCell = worksheet.getCell(`A${currentRow}`);
eStatementCell.value = "e-Statement Service";
eStatementCell.font = {
name: "Times New Roman",
size: 10,
bold: true,
color: { argb: "FF1a5f3a" },
};
eStatementCell.alignment = { vertical: "middle", horizontal: "left" };
worksheet.mergeCells(`C${currentRow}:D${currentRow}`);
const dateCell = worksheet.getCell(`C${currentRow}`);
dateCell.value = `Generated: ${dayjs().format("DD/MM/YYYY HH:mm")}`;
dateCell.font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
dateCell.alignment = { vertical: "middle", horizontal: "right" };
currentRow++;
// Border line
currentRow++;
/* ---------------------------------------------------------
* 2) ACCOUNT DETAILS SECTION
* --------------------------------------------------------- */
// Account Details Header
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const detailsHeaderCell = worksheet.getCell(`A${currentRow}`);
detailsHeaderCell.value = "ACCOUNT DETAILS";
detailsHeaderCell.font = {
name: "Times New Roman",
size: 11,
bold: true,
color: { argb: "FF1a5f3a" },
};
detailsHeaderCell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
detailsHeaderCell.alignment = { vertical: "middle", horizontal: "center" };
detailsHeaderCell.border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
currentRow++;
// Account Name
worksheet.getCell(`A${currentRow}`).value = "Account Name";
worksheet.getCell(`A${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`A${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`B${currentRow}`).value = customerName;
worksheet.getCell(`B${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`B${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`C${currentRow}`).value = "Branch Name";
worksheet.getCell(`C${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`C${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`D${currentRow}`).value = branchCode;
worksheet.getCell(`D${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`D${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
// Apply borders
["A", "B", "C", "D"].forEach((col) => {
worksheet.getCell(`${col}${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
});
currentRow++;
// Account Number
worksheet.getCell(`A${currentRow}`).value = "Account Number";
worksheet.getCell(`A${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`A${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`B${currentRow}`).value = accountNo;
worksheet.getCell(`B${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`B${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`C${currentRow}`).value = "Branch Code";
worksheet.getCell(`C${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`C${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`D${currentRow}`).value = branchCode;
worksheet.getCell(`D${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`D${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
["A", "B", "C", "D"].forEach((col) => {
worksheet.getCell(`${col}${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
});
currentRow++;
// CIF Number
worksheet.getCell(`A${currentRow}`).value = "CIF Number";
worksheet.getCell(`A${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`A${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`B${currentRow}`).value = cifNumber;
worksheet.getCell(`B${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`B${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`C${currentRow}`).value = "Branch Address";
worksheet.getCell(`C${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`C${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.getCell(`D${currentRow}`).value = "DHARAMSHALA, KANGRA";
worksheet.getCell(`D${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`D${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
["A", "B", "C", "D"].forEach((col) => {
worksheet.getCell(`${col}${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
});
currentRow++;
// Address
worksheet.getCell(`A${currentRow}`).value = "Address";
worksheet.getCell(`A${currentRow}`).font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF666666" },
};
worksheet.getCell(`A${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
worksheet.mergeCells(`B${currentRow}:D${currentRow}`);
worksheet.getCell(`B${currentRow}`).value = address;
worksheet.getCell(`B${currentRow}`).font = {
name: "Times New Roman",
size: 10,
bold: true,
};
worksheet.getCell(`B${currentRow}`).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF9F9F9" },
};
["A", "B", "C", "D"].forEach((col) => {
worksheet.getCell(`${col}${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
});
currentRow++;
currentRow++; // Empty row
/* ---------------------------------------------------------
* 3) WARNING SECTION
* --------------------------------------------------------- */
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const warningCell = worksheet.getCell(`A${currentRow}`);
warningCell.value =
"⚠ NEVER SHARE your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized access to your account.";
warningCell.font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF721c24" },
};
warningCell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF8D7DA" },
};
warningCell.border = {
top: { style: "thin", color: { argb: "FFF5C6CB" } },
bottom: { style: "thin", color: { argb: "FFF5C6CB" } },
left: { style: "medium", color: { argb: "FFD32F2F" } },
right: { style: "thin", color: { argb: "FFF5C6CB" } },
};
warningCell.alignment = { vertical: "middle", horizontal: "left", wrapText: true };
worksheet.getRow(currentRow).height = 40;
currentRow++;
currentRow++; // Empty row
/* ---------------------------------------------------------
* 4) STATEMENT PERIOD
* --------------------------------------------------------- */
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const periodCell = worksheet.getCell(`A${currentRow}`);
periodCell.value = `Account statement from ${periodFrom} to ${periodTo}`;
periodCell.font = {
name: "Times New Roman",
size: 11,
bold: true,
color: { argb: "FF1a5f3a" },
};
periodCell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF5F5F5" },
};
periodCell.alignment = { vertical: "middle", horizontal: "center" };
periodCell.border = {
top: { style: "medium", color: { argb: "FF1a5f3a" } },
bottom: { style: "medium", color: { argb: "FF1a5f3a" } },
};
worksheet.getRow(currentRow).height = 22;
currentRow++;
currentRow++; // Empty row
/* ---------------------------------------------------------
* 5) TRANSACTION TABLE
* --------------------------------------------------------- */
// Table Headers
const headerRowNum = currentRow;
worksheet.getCell(`A${currentRow}`).value = "Date";
worksheet.getCell(`B${currentRow}`).value = "Mode / Particulars";
worksheet.getCell(`C${currentRow}`).value = "Withdrawals / Deposits";
worksheet.getCell(`D${currentRow}`).value = "Balance";
["A", "B", "C", "D"].forEach((col) => {
const cell = worksheet.getCell(`${col}${currentRow}`);
cell.font = {
name: "Times New Roman",
size: 10,
bold: true,
color: { argb: "FFFFFFFF" },
};
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FF2e7d32" },
};
cell.alignment = { vertical: "middle", horizontal: "center" };
cell.border = {
top: { style: "thin", color: { argb: "FF1a5f3a" } },
bottom: { style: "thin", color: { argb: "FF1a5f3a" } },
left: { style: "thin", color: { argb: "FF1a5f3a" } },
right: { style: "thin", color: { argb: "FF1a5f3a" } },
};
});
worksheet.getRow(currentRow).height = 20;
currentRow++;
// Transaction Rows
txns.forEach((t: any) => {
worksheet.getCell(`A${currentRow}`).value = t.date;
worksheet.getCell(`A${currentRow}`).font = {
name: "Times New Roman",
size: 9,
};
worksheet.getCell(`A${currentRow}`).alignment = {
vertical: "middle",
horizontal: "center",
};
worksheet.getCell(`A${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
worksheet.getCell(`B${currentRow}`).value = t.name;
worksheet.getCell(`B${currentRow}`).font = {
name: "Times New Roman",
size: 9,
};
worksheet.getCell(`B${currentRow}`).alignment = {
vertical: "middle",
horizontal: "left",
};
worksheet.getCell(`B${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
const amount = parseFloat(t.amount).toLocaleString("en-IN", {
minimumFractionDigits: 2,
});
worksheet.getCell(`C${currentRow}`).value = `${amount} ${t.type}`;
worksheet.getCell(`C${currentRow}`).font = {
name: "Times New Roman",
size: 9,
bold: true,
color: { argb: t.type === "DR" ? "FFD32F2F" : "FF2E7D32" },
};
worksheet.getCell(`C${currentRow}`).alignment = {
vertical: "middle",
horizontal: "right",
};
worksheet.getCell(`C${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
worksheet.getCell(`D${currentRow}`).value = t.balance;
worksheet.getCell(`D${currentRow}`).font = {
name: "Times New Roman",
size: 9,
bold: true,
};
worksheet.getCell(`D${currentRow}`).alignment = {
vertical: "middle",
horizontal: "right",
};
worksheet.getCell(`D${currentRow}`).border = {
top: { style: "thin", color: { argb: "FFD0D0D0" } },
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
left: { style: "thin", color: { argb: "FFD0D0D0" } },
right: { style: "thin", color: { argb: "FFD0D0D0" } },
};
currentRow++;
});
currentRow++; // Empty row
/* ---------------------------------------------------------
* 6) END OF STATEMENT
* --------------------------------------------------------- */
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const endCell = worksheet.getCell(`A${currentRow}`);
endCell.value = "*** END OF STATEMENT ***";
endCell.font = {
name: "Times New Roman",
size: 11,
bold: true,
color: { argb: "FF1a5f3a" },
};
endCell.alignment = { vertical: "middle", horizontal: "center" };
endCell.border = {
top: { style: "medium", color: { argb: "FF1a5f3a" } },
};
currentRow++;
currentRow += 2; // Empty rows
/* ---------------------------------------------------------
* 7) IMPORTANT INFORMATION
* --------------------------------------------------------- */
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const infoHeaderCell = worksheet.getCell(`A${currentRow}`);
infoHeaderCell.value = "IMPORTANT INFORMATION:";
infoHeaderCell.font = {
name: "Times New Roman",
size: 11,
bold: true,
color: { argb: "FF1a5f3a" },
};
infoHeaderCell.alignment = { vertical: "middle", horizontal: "left" };
infoHeaderCell.border = {
bottom: { style: "medium", color: { argb: "FF1a5f3a" } },
};
currentRow++;
const importantPoints = [
"The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.",
"Always stay vigilant to suspicious emails. Do not open any suspicious emails.",
"Always stay vigilant when giving out sensitive personal or account information.",
"Beware of messages that instill a sense of urgency (e.g., account will expire unless you \"verify\" your information). Contact the Bank directly if unsure.",
"Always log out of secondary devices and reset your passwords frequently.",
"Use strong passwords: Create strong passwords that are difficult for hackers to guess.",
"Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.",
"Back up your data regularly to a secure, encrypted, off-site location.",
"Follow corporate security policies: Adhere to your company's security guidelines.",
"Assess third-party app permissions carefully before granting access.",
"Lock your computer and mobile phone when not in use.",
"Don't leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.",
"Don't leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.",
];
importantPoints.forEach((point) => {
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const pointCell = worksheet.getCell(`A${currentRow}`);
pointCell.value = `${point}`;
pointCell.font = {
name: "Times New Roman",
size: 9,
color: { argb: "FF333333" },
};
pointCell.alignment = {
vertical: "top",
horizontal: "left",
wrapText: true,
};
worksheet.getRow(currentRow).height = 25;
currentRow++;
});
currentRow += 2; // Empty rows
/* ---------------------------------------------------------
* 8) FOOTER NOTE
* --------------------------------------------------------- */
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
const footerCell = worksheet.getCell(`A${currentRow}`);
footerCell.value = "** This is only for information purpose and not for legal use **";
footerCell.font = {
name: "Times New Roman",
size: 9,
italic: true,
color: { argb: "FF666666" },
};
footerCell.alignment = { vertical: "middle", horizontal: "center" };
/* ---------------------------------------------------------
* 9) COLUMN WIDTHS
* --------------------------------------------------------- */
worksheet.getColumn(1).width = 15; // Date
worksheet.getColumn(2).width = 45; // Particulars
worksheet.getColumn(3).width = 25; // Withdrawals/Deposits
worksheet.getColumn(4).width = 20; // Balance
/* ---------------------------------------------------------
* 10) SAVE FILE
* --------------------------------------------------------- */
workbook.xlsx.writeBuffer().then((buffer: any) => {
const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `Statement_${accountNo}_${dayjs().format("DDMMYYYY_HHmm")}.xlsx`;
a.click();
window.URL.revokeObjectURL(url);
});
}; };
export default function CsvGenerator({
accountNo,
balance,
txns,
}: CsvGeneratorProps) {
return (
<button
onClick={() => generateCSV(accountNo, balance, txns)}
style={{
padding: "6px 12px",
borderRadius: "6px",
background: "#02a355",
color: "white",
border: "none",
cursor: "pointer",
}}
>
Download CSV
</button>
);
}

View File

@@ -7,107 +7,369 @@ export const generatePDF = (
txns: any[], txns: any[],
customerName: string, customerName: string,
periodFrom: string, periodFrom: string,
periodTo: string periodTo: string,
// branchName: string,
branchCode: string,
cifNumber: string,
address: string
) => { ) => {
const html2pdf = require("html2pdf.js"); const html2pdf = require("html2pdf.js");
// Build rows /* ---------------------------------------------------------
const rows = txns.map( * 1) BUILD TRANSACTION ROWS
(t: any) => ` * --------------------------------------------------------- */
<tr style="page-break-inside: avoid;">
<td style="border:1px solid #ccc;padding:6px;text-align:center;">${t.date}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:left;">${t.name}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:right;color:${t.type === "DR" ? "red" : "green"
}">
${parseFloat(t.amount).toLocaleString("en-IN", {
minimumFractionDigits: 2,
})} ${t.type === "DR" ? "dr." : "cr."}
</td>
<td style="border:1px solid #ccc; padding:6px; text-align:right; color: blue;">${t.balance}</td>
</tr>
`
);
// Content for first page const rows = txns
const content = ` .map((t: any) => {
<div style="font-family:Arial, sans-serif;"> return `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;"> <tr class="txn-row">
<div style="display:flex;align-items:center;gap:10px;"> <td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:center;font-size:11px;">${t.date}</td>
<img src="/logo.jpg" alt="Bank Logo" style="height:50px;" /> <td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:left;font-size:11px;">${t.name}</td>
<h2 style="margin:0;">The Kangra Central Co-Operative Bank Ltd.</h2> <td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:right;font-size:11px;color:${t.type === "DR" ? "#d32f2f" : "#2e7d32"};font-weight:500;">
</div> ${parseFloat(t.amount).toLocaleString("en-IN", {
<div style="font-size:12px;color:#555;"> minimumFractionDigits: 2,
Report generated: ${dayjs().format("DD/MM/YYYY HH:mm")} })} ${t.type}
</td>
<td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:right;font-size:11px;font-weight:500;">${t.balance}</td>
</tr>
`;
})
.join("");
/* ---------------------------------------------------------
* 2) MAIN PAGE CONTENT
* --------------------------------------------------------- */
const firstPage = `
<div style="font-family:'Times New Roman', serif; font-size:12px;line-height:1.5;">
<style>
@page {
margin: 15mm 10mm 20mm 10mm;
}
body {
margin: 0;
padding: 0;
}
.txn-row {
page-break-inside: avoid;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
</style>
<!-- HEADER -->
<div style="
display:flex;
align-items:center;
gap:12px;
border-bottom:2px solid #1a5f3a;
padding-bottom:12px;
margin-bottom:15px;
">
<img src="/logo.jpg" style="height:55px;width:auto;" />
<div style="flex:1;">
<h2 style="margin:0 0 3px 0;font-size:18px;color:#1a5f3a;letter-spacing:0.3px;">
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</h2>
<p style="margin:0;font-size:10px;color:#666;">Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215</p>
</div>
<div style="text-align:right;">
<div style="font-size:11px;font-weight:bold;color:#1a5f3a;margin-bottom:2px;">e-Statement Service</div>
<div style="font-size:9px;color:#666;">
Generated: ${dayjs().format("DD/MM/YYYY HH:mm")}
</div> </div>
</div> </div>
<hr/>
<h3 style="text-align:center;">Account Statement</h3>
<p><strong>Account No:</strong> ${accountNo}</p>
<p><strong>Account Holder:</strong> ${customerName}</p>
<p><strong>Statement Period:</strong> ${periodFrom} to ${periodTo}</p>
<p><strong>Available Balance:</strong> ₹ ${parseFloat(balance).toLocaleString(
"en-IN",
{ minimumFractionDigits: 2 }
)}</p>
<table style="width:100%;border-collapse:collapse;font-size:12px;margin-top:10px;page-break-inside:auto;">
<thead style="background:#f0f0f0;">
<tr>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Date</th>
<th style="border:1px solid #ccc;padding:6px;text-align:left;">Description</th>
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Amount (₹)</th>
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Available Amount (₹)</th>
</tr>
</thead>
<tbody>
${rows.join("")}
</tbody>
</table>
</div> </div>
<!-- ACCOUNT DETAILS -->
<table style="
width:100%;
margin-bottom:12px;
border:1px solid #d0d0d0;
background:#f9f9f9;
border-collapse:collapse;
">
<tr>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;width:50%;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Account Name</span>
<strong style="font-size:11px;color:#000;">${customerName}</strong>
</td>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;width:50%;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Name</span>
<strong style="font-size:11px;color:#000;">${branchCode}</strong>
</td>
</tr>
<tr>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Account Number</span>
<strong style="font-size:11px;color:#000;">${accountNo}</strong>
</td>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Code</span>
<strong style="font-size:11px;color:#000;">${branchCode}</strong>
</td>
</tr>
<tr>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">CIF Number</span>
<strong style="font-size:11px;color:#000;">${cifNumber}</strong>
</td>
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Address</span>
<strong style="font-size:11px;color:#000;">DHARAMSHALA, KANGRA</strong>
</td>
</tr>
<tr>
<td colspan="2" style="padding:8px 12px;">
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Address</span>
<strong style="font-size:11px;color:#000;">${address}</strong>
</td>
</tr>
</table>
<!-- RED WARNING -->
<div style="
margin-bottom:15px;
border:1px solid #f5c6cb;
background:#f8d7da;
padding:10px 12px;
border-radius:4px;
border-left:4px solid #d32f2f;
">
<div style="display:flex;gap:10px;align-items:flex-start;">
<div style="font-size:16px;color:#d32f2f;line-height:1;margin-top:1px;">&#9888;</div>
<div style="font-size:10px;line-height:1.5;color:#721c24;">
<strong>NEVER SHARE</strong> your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with
anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized
access to your account.
</div>
</div>
</div>
<!-- PERIOD -->
<div style="
text-align:center;
margin:15px 0 12px 0;
padding:10px;
background:#f5f5f5;
border-top:2px solid #1a5f3a;
border-bottom:2px solid #1a5f3a;
">
<h3 style="margin:0;font-size:13px;color:#1a5f3a;font-weight:600;">
Account statement from <strong>${periodFrom}</strong> to <strong>${periodTo}</strong>
</h3>
</div>
<!-- TABLE -->
<table style="
width:100%;
border-collapse:collapse;
font-size:11px;
margin-bottom:15px;
border:1px solid #d0d0d0;
">
<thead style="display:table-header-group;">
<tr style="background:#2e7d32;color:white;">
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:15%;">Date</th>
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:left;font-weight:600;font-size:11px;width:40%;">Mode / Particulars</th>
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:22.5%;">Withdrawals / Deposits</th>
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:22.5%;">Balance</th>
</tr>
</thead>
<tbody style="background:white;">${rows}</tbody>
</table>
<!-- END OF STATEMENT -->
<div style="
text-align:center;
margin-top:20px;
padding:15px;
border-top:2px solid #1a5f3a;
page-break-before:auto;
page-break-after:always;
">
<p style="font-weight:bold;font-size:12px;color:#1a5f3a;margin:0;letter-spacing:1px;">
*** END OF STATEMENT ***
</p>
</div>
</div>
`; `;
/* ---------------------------------------------------------
* 3) LAST PAGE
* --------------------------------------------------------- */
const lastPage = `
<div style="font-family:'Times New Roman', serif; font-size:11px;line-height:1.6;margin-top:0;padding-top:20px;">
<div style="
border:2px solid #1a5f3a;
padding:20px 25px;
margin:0 20px;
background:#fafafa;
border-radius:4px;
">
<h3 style="
font-weight:bold;
margin:0 0 15px 0;
font-size:14px;
color:#1a5f3a;
border-bottom:2px solid #1a5f3a;
padding-bottom:8px;
">IMPORTANT INFORMATION:</h3>
<div style="color:#333;">
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Always stay vigilant to suspicious emails. Do not open any suspicious emails.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Always stay vigilant when giving out sensitive personal or account information.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Beware of messages that instill a sense of urgency (e.g., account will expire unless you "verify" your information). Contact the Bank directly if unsure.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Always log out of secondary devices and reset your passwords frequently.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Use strong passwords: Create strong passwords that are difficult for hackers to guess.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Back up your data regularly to a secure, encrypted, off-site location.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Follow corporate security policies: Adhere to your company's security guidelines.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Assess third-party app permissions carefully before granting access.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Lock your computer and mobile phone when not in use.
</p>
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Don't leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.
</p>
<p style="margin:0 0 0 0;padding-left:15px;position:relative;">
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
Don't leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.
</p>
</div>
</div>
</div>
`;
/* ---------------------------------------------------------
* 4) PDF GENERATION
* --------------------------------------------------------- */
const opt = { const opt = {
margin: [20, 10, 20, 10], margin: [15, 10, 20, 10],
filename: `AccountStatement_${accountNo}_${dayjs().format("DD/MM/YYYY HH:mm")}.pdf`, filename: `Statement_${accountNo}_${dayjs().format("DDMMYYYY_HHmm")}.pdf`,
image: { type: "jpeg", quality: 0.98 }, image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2 }, html2canvas: {
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, scale: 2,
pagebreak: { mode: ["avoid-all", "css", "legacy"] }, useCORS: true,
letterRendering: true,
},
jsPDF: {
unit: "mm",
format: "a4",
orientation: "portrait",
compress: true,
},
pagebreak: {
mode: ["css", "legacy"],
before: '.page-break-before',
after: '.page-break-after',
avoid: ['tr', '.txn-row']
},
}; };
html2pdf() html2pdf()
.set(opt) .set(opt)
.from(content) .from(firstPage + lastPage)
.toPdf() .toPdf()
.get("pdf") .get("pdf")
.then((pdf: any) => { .then((pdf: any) => {
const totalPages = pdf.internal.getNumberOfPages(); const total = pdf.internal.getNumberOfPages();
const pageWidth = pdf.internal.pageSize.getWidth(); const w = pdf.internal.pageSize.getWidth();
const h = pdf.internal.pageSize.getHeight();
for (let i = 2; i <= totalPages; i++) { const hasGState =
typeof pdf.GState === "function" && typeof pdf.setGState === "function";
for (let i = 1; i <= total; i++) {
pdf.setPage(i); pdf.setPage(i);
pdf.setFontSize(10);
// ✅ Left side Account No /* LIGHT WATERMARK */
pdf.setFont("helvetica", "bold"); try {
// pdf.text(`Account No: ${accountNo}`, 15, 18); if (hasGState) {
const light = pdf.GState({ opacity: 0.03 });
pdf.setGState(light);
pdf.addImage("/kccb_watermark.png", "PNG", 35, 70, 140, 140);
pdf.setGState(pdf.GState({ opacity: 1 }));
} else {
pdf.addImage("/kccb_watermark.png", "PNG", 35, 70, 140, 140);
}
} catch { }
// ✅ Centered Statement Period /* FOOTER */
pdf.setFont("times", "italic");
pdf.setFontSize(9);
pdf.setTextColor(100, 100, 100);
pdf.text( pdf.text(
`Statement Period: ${periodFrom} to ${periodTo}`, "** This is only for information purpose and not for legal use **",
pageWidth / 2, w / 2,
18, h - 12,
{ align: "center" } { align: "center" }
); );
// Footer page numbers /* PAGE NUMBER */
pdf.setFont("times", "normal");
pdf.setFontSize(9); pdf.setFontSize(9);
pdf.text( pdf.setTextColor(0, 0, 0);
`Page ${i} of ${totalPages}`, pdf.text(`Page ${i} of ${total}`, w - 15, h - 12, {
pageWidth - 40, align: "right",
pdf.internal.pageSize.getHeight() - 10 });
);
} }
}) })
.save(); .save();

View File

@@ -8,7 +8,7 @@ export async function fetchAndStoreUserName(token: string) {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Login-Type": "IB", "X-Login-Type": "IB",
"Authorization": `Bearer ${token}`, "Authorization": `Bearer ${token}`,
}, },
}); });
@@ -28,14 +28,20 @@ export async function fetchAndStoreUserName(token: string) {
const data = await response.json(); const data = await response.json();
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
const { custname, mobileno } = data[0]; const { custname, mobileno, cifNumber, stBranchNo,custaddress } = data[0];
localStorage.setItem("remitter_name", custname); localStorage.setItem("remitter_name", custname);
localStorage.setItem("remitter_mobile_no", mobileno); localStorage.setItem("remitter_mobile_no", mobileno);
localStorage.setItem("remitter_cif_no", cifNumber);
localStorage.setItem("remitter_branch_no", stBranchNo);
localStorage.setItem("remitter_address", custaddress);
} }
return true; return true;
} catch (error: any) { } catch (error: any) {
localStorage.removeItem("remitter_name"); localStorage.removeItem("remitter_name");
localStorage.removeItem("remitter_mobile_no"); localStorage.removeItem("remitter_mobile_no");
localStorage.removeItem("remitter_cif_no");
localStorage.removeItem("remitter_branch_no");
localStorage.removeItem("remitter_address");
console.error('Error sending OTP:', error.response?.data || error.message); console.error('Error sending OTP:', error.response?.data || error.message);
notifications.show({ notifications.show({
withBorder: true, withBorder: true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -8,7 +8,7 @@ import { sendOtp, verifyLoginOtp } from '@/app/_util/otp';
import NextImage from "next/image"; import NextImage from "next/image";
import styles from './page.module.css'; import styles from './page.module.css';
import logo from '@/app/image/logo1.jpg'; import logo from '@/app/image/logo1.jpg';
import frontPage from '@/app/image/ib_front_1.jpg'; import frontPage from '@/app/image/ib_front_2.jpg';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { generateCaptcha } from '@/app/captcha'; import { generateCaptcha } from '@/app/captcha';
import { IconRefresh, IconShieldLockFilled } from "@tabler/icons-react"; import { IconRefresh, IconShieldLockFilled } from "@tabler/icons-react";
@@ -78,6 +78,7 @@ export default function Login() {
title: `${err.message}`, title: `${err.message}`,
message: 'OTP verification failed. Please try again later.', message: 'OTP verification failed. Please try again later.',
color: 'red', color: 'red',
autoClose: 700,
}); });
return false; return false;
} }