feat: api integration for account tab

fix: layout design
feat : add screen for account tab
This commit is contained in:
2025-07-13 19:21:53 +05:30
parent 1023646751
commit 26e6dea82b
7 changed files with 442 additions and 266 deletions

View File

@@ -0,0 +1,222 @@
"use client";
import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider } from "@mantine/core";
import { DateInput } from '@mantine/dates';
import { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { notifications } from "@mantine/notifications";
import dayjs from 'dayjs';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(isSameOrAfter);
dayjs.extend(isSameOrBefore);
dayjs.extend(customParseFormat);
export default function AccountStatementPage() {
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");
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");
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(-5).reverse();
setTransactions(last5);
}
})
.catch(() => {
notifications.show({
withBorder: true,
color: "red",
title: "Fetch Failed",
message: "Could not load recent transactions.",
autoClose: 5000,
});
});
}
}
}, [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") > 60) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Date range",
message: "End date must be within 60 days from start date",
autoClose: 4000,
});
return;
}
try {
const token = localStorage.getItem("access_token");
const response = await fetch(`/api/transactions/account/${selectedAccNo}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok && Array.isArray(data)) {
const filterData = data.filter((txn: any) => {
const txnDate = dayjs(txn.date, 'DD/MM/YYYY');
return txnDate.isSameOrAfter(start) && txnDate.isSameOrBefore(end);
});
setTransactions(filterData);
}
} catch {
notifications.show({
withBorder: true,
color: "red",
title: "Please try again later",
message: "Unable to Fetch Account Transaction, Please try again later",
autoClose: 5000,
});
}
};
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 Statement</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 Transaction Statement</Title>
<Text fw={500} fs="italic" >Account No : {selectedAccNo}</Text>
<Divider size="xs" />
<ScrollArea style={{ flex: 1 }}>
{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>
</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: "left", color: txn.type === "DR" ? "#e03131" : "#2f9e44" }}>
{parseFloat(txn.amount).toLocaleString("en-IN", {
minimumFractionDigits: 2,
})}
</td>
</tr>
))}
</tbody>
</Table>
</>
)}
</ScrollArea>
</Paper>
</Grid.Col >
</Grid >
);
}

View File

@@ -0,0 +1,28 @@
"use client";
import { Suspense, useEffect, useState } from "react";
import AccountStatementPage from "./accountStatement";
import { useRouter } from "next/navigation";
export default function AccountStatement() {
const [authorized, SetAuthorized] = useState<boolean | null>(null);
const router = useRouter();
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
SetAuthorized(false);
router.push("/login");
}
else {
SetAuthorized(true);
}
}, []);
if (authorized) {
return (
<Suspense fallback={<div>Loading...</div>}>
<AccountStatementPage />
</Suspense>
);
}
}

View File

@@ -2,55 +2,70 @@
import { Divider, Stack, Text } from '@mantine/core'; import { Divider, Stack, Text } from '@mantine/core';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
import React from 'react'; import React, { useEffect, useState } from 'react';
import { useRouter } from "next/navigation";
export default function Layout({ children }: { children: React.ReactNode }) { export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname(); // get current route const [authorized, SetAuthorized] = useState<boolean | null>(null);
const router = useRouter();
const pathname = usePathname();
const links = [ const links = [
{ label: "Account Summary", href: "/accounts" }, { label: "Account Summary", href: "/accounts" },
{ label: "Statement of Account", href: "/accounts/account_statement" }, { label: "Statement of Account", href: "/accounts/account_statement" },
]; ];
useEffect(() => {
return ( const token = localStorage.getItem("access_token");
<div style={{ display: "flex", height: '100%' }}> if (!token) {
<div SetAuthorized(false);
style={{ router.push("/login");
width: "15%", }
backgroundColor: '#c5e4f9', else {
borderRight: "1px solid #ccc", SetAuthorized(true);
}} }
> }, []);
<Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}>
<Text fw={700} fs="italic" c='white' style={{ textAlign: 'center', marginTop: '10px' }}> if (authorized) {
Accounts return (
</Text> <div style={{ display: "flex", height: '100%' }}>
</Stack> <div
style={{
<Stack gap="sm" justify="flex-start" style={{ padding: '1rem' }}> width: "16%",
{links.map(link => { backgroundColor: '#c5e4f9',
const isActive = pathname === link.href || pathname.startsWith(link.href + '/'); borderRight: "1px solid #ccc",
return ( }}
<Text >
key={link.href} <Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}>
component={Link} <Text fw={700} fs="italic" c='white' style={{ textAlign: 'center', marginTop: '10px' }}>
href={link.href} Accounts
c={isActive ? 'darkblue' : 'blue'} </Text>
style={{ </Stack>
textDecoration: isActive ? 'underline' : 'none',
fontWeight: isActive ? 600 : 400, <Stack gap="sm" justify="flex-start" style={{ padding: '1rem' }}>
}} {links.map(link => {
> const isActive = pathname === link.href;
{link.label} return (
</Text> <Text
); key={link.href}
})} component={Link}
</Stack> href={link.href}
c={isActive ? 'darkblue' : 'blue'}
style={{
textDecoration: isActive ? 'underline' : 'none',
fontWeight: isActive ? 600 : 400,
}}
>
{link.label}
</Text>
);
})}
</Stack>
</div>
<div style={{ flex: 1, padding: '1rem' }}>
{children}
</div>
</div> </div>
);
<div style={{ flex: 1, padding: '1rem' }}> }
{children}
</div>
</div>
);
} }

View File

@@ -12,7 +12,7 @@ interface accountData {
custname: string; custname: string;
} }
export default function SavingsAccount() { export default function AccountSummary() {
const router = useRouter(); const router = useRouter();
const [authorized, setAuthorized] = useState<boolean | null>(null); const [authorized, setAuthorized] = useState<boolean | null>(null);
const [accountData, setAccountData] = useState<accountData[]>([]); const [accountData, setAccountData] = useState<accountData[]>([]);
@@ -30,6 +30,7 @@ export default function SavingsAccount() {
const data = await response.json(); const data = await response.json();
if (response.ok && Array.isArray(data)) { if (response.ok && Array.isArray(data)) {
setAccountData(data); setAccountData(data);
sessionStorage.setItem('accountData',JSON.stringify(data))
} }
} catch { } catch {
notifications.show({ notifications.show({
@@ -67,7 +68,9 @@ export default function SavingsAccount() {
<tr key={index}> <tr key={index}>
<td style={{ ...cellStyle, textAlign: "left" }}>{acc.custname}</td> <td style={{ ...cellStyle, textAlign: "left" }}>{acc.custname}</td>
<td style={{ ...cellStyle, textAlign: "left" }}>{acc.stAccountType}</td> <td style={{ ...cellStyle, textAlign: "left" }}>{acc.stAccountType}</td>
<td style={{ ...cellStyle, textAlign: "right" }}>{acc.stAccountNo}</td> <td style={{ ...cellStyle, textAlign: "right", color: '#1c7ed6', cursor: "pointer" }}
onClick={() => router.push(`/accounts/account_statement?accNo=${acc.stAccountNo}`)}>
{acc.stAccountNo}</td>
<td style={{ ...cellStyle, textAlign: "right" }}> <td style={{ ...cellStyle, textAlign: "right" }}>
{parseFloat(acc.stAvailableBalance).toLocaleString("en-IN", { {parseFloat(acc.stAvailableBalance).toLocaleString("en-IN", {
minimumFractionDigits: 2, minimumFractionDigits: 2,

View File

@@ -7,7 +7,6 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Providers } from "../../providers"; import { Providers } from "../../providers";
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import StatementModel from './statementModel';
interface accountData { interface accountData {
stAccountNo: string; stAccountNo: string;
@@ -17,13 +16,6 @@ interface accountData {
activeAccounts: string; activeAccounts: string;
} }
interface statementData {
name: string;
date: string;
amount: string;
type: string;
}
export default function Home() { export default function Home() {
const [authorized, SetAuthorized] = useState<boolean | null>(null); const [authorized, SetAuthorized] = useState<boolean | null>(null);
const router = useRouter(); const router = useRouter();
@@ -35,9 +27,6 @@ export default function Home() {
const [selectedLN, setSelectedLN] = useState(loanAccounts[0]?.stAccountNo || ""); const [selectedLN, setSelectedLN] = useState(loanAccounts[0]?.stAccountNo || "");
const selectedLNData = loanAccounts.find(acc => acc.stAccountNo === selectedLN); const selectedLNData = loanAccounts.find(acc => acc.stAccountNo === selectedLN);
const [showBalance, setShowBalance] = useState(false); const [showBalance, setShowBalance] = useState(false);
const [openStatement, setOpenStatement] = useState(false);
const [statementData, setStatementData] = useState(null);
const [loading, setLoading] = useState(false);
async function handleFetchUserDetails() { async function handleFetchUserDetails() {
@@ -75,40 +64,9 @@ export default function Home() {
} }
async function handleGetAccountStatement(accountNo: string) { async function handleGetAccountStatement(accountNo: string) {
// e.preventDefault(); router.push(`/accounts/account_statement?accNo=${accountNo}`);
setOpenStatement(true);
setLoading(true);
try {
const token = localStorage.getItem("access_token");
const response = await fetch(`api//transactions/account/${accountNo}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
});
const data = await response.json();
if (response.ok) {
console.log(data);
setStatementData(data);
}
else { throw new Error(); }
}
catch {
notifications.show({
withBorder: true,
color: "red",
title: "Please try again later",
message: "Unable to Fetch the statement, Please try again later",
autoClose: 5000,
});
}
finally {
setLoading(false);
}
} }
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
if (!token) { if (!token) {
@@ -137,10 +95,10 @@ export default function Home() {
<Text fw={700} style={{ fontFamily: "inter", fontSize: '17px' }}>Show Balance </Text> <Text fw={700} style={{ fontFamily: "inter", fontSize: '17px' }}>Show Balance </Text>
<Switch size="md" onLabel="ON" offLabel="OFF" checked={showBalance} <Switch size="md" onLabel="ON" offLabel="OFF" checked={showBalance}
onChange={(event) => setShowBalance(event.currentTarget.checked)} /> onChange={(event) => setShowBalance(event.currentTarget.checked)} />
</Group> </Group>
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-start", marginLeft: '15px' }}> <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-start", marginLeft: '15px' }}>
<Group grow gap="xl"> <Group grow gap="xl">
<Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350}}> <Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350 }}>
<Group gap='xs'> <Group gap='xs'>
<IconBuildingBank size={25} /> <IconBuildingBank size={25} />
<Text fw={700}>Deposit Account</Text> <Text fw={700}>Deposit Account</Text>
@@ -168,13 +126,7 @@ export default function Home() {
<Title order={2} mt="md"> <Title order={2} mt="md">
{showBalance ? `${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} {showBalance ? `${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title> </Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedDA)}>Get Statement</Button> <Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedDA)}>Get Statement</Button>
<StatementModel
opened={openStatement}
onClose={() => setOpenStatement(false)}
loading={loading}
data={statementData} error={''} />
</Paper> </Paper>
<Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350 }}> <Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350 }}>
<Group gap='xs'> <Group gap='xs'>
@@ -205,11 +157,6 @@ export default function Home() {
{showBalance ? `${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} {showBalance ? `${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title> </Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedLN)}>Get Statement</Button> <Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedLN)}>Get Statement</Button>
<StatementModel
opened={openStatement}
onClose={() => setOpenStatement(false)}
loading={loading}
data={statementData} error={''} />
</Paper> </Paper>
<Paper p="md" radius="md" style={{ width: 300, backgroundColor: '#FFFFFF', marginLeft: '130px', border: '1px solid grey' }}> <Paper p="md" radius="md" style={{ width: 300, backgroundColor: '#FFFFFF', marginLeft: '130px', border: '1px solid grey' }}>
<Title order={5} mb="sm">Important Links</Title> <Title order={5} mb="sm">Important Links</Title>

View File

@@ -1,51 +0,0 @@
'use client';
import React from "react";
import { Modal, Text, Loader } from "@mantine/core";
type StatementItem = {
date: string;
type: string;
amount: number;
};
interface StatementModalProps {
opened: boolean;
onClose: () => void;
loading: boolean;
error: string;
data: StatementItem[] | null;
}
const StatementModal: React.FC<StatementModalProps> = ({
opened,
onClose,
loading,
error,
data,
}) => {
return (
<Modal opened={opened} onClose={onClose} title="Account Statement" size="lg">
{loading && <Loader />}
{error && <Text c="red">{error}</Text>}
{!loading && !error && data && data.length > 0 && (
<div>
{data.map((item, index) => (
<div key={index} style={{ marginBottom: 10 }}>
<Text>Date: {item.date}</Text>
<Text>Type: {item.type}</Text>
<Text>Amount: {item.amount}</Text>
</div>
))}
</div>
)}
{!loading && !error && data && data.length === 0 && (
<Text>No transactions found.</Text>
)}
</Modal>
);
};
export default StatementModal;

View File

@@ -8,18 +8,29 @@ import { Providers } from '../providers';
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo.jpg';
import NextImage from 'next/image'; import NextImage from 'next/image';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const [authorized, SetAuthorized] = useState<boolean | null>(null);
const [userLastLoginDetails, setUserLastLoginDetails] = useState(null); const [userLastLoginDetails, setUserLastLoginDetails] = useState(null);
async function handleLogout(e: React.FormEvent) { async function handleLogout(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("access_token"); localStorage.removeItem("access_token");
router.push("/login"); router.push("/login");
} }
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
SetAuthorized(false);
router.push("/login");
}
else {
SetAuthorized(true);
}
}, []);
async function handleFetchUserDetails(e: React.FormEvent) { async function handleFetchUserDetails(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
@@ -43,132 +54,133 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}); });
} }
} }
useEffect(() => { useEffect(() => {
const fetchLoginTime = async () => { const fetchLoginTime = async () => {
const result = await handleFetchUserDetails({ preventDefault: () => {} } as React.FormEvent); const result = await handleFetchUserDetails({ preventDefault: () => { } } as React.FormEvent);
if (result) { if (result) {
setUserLastLoginDetails(result.last_login); setUserLastLoginDetails(result.last_login);
} }
}; };
fetchLoginTime(); fetchLoginTime();
}, []); }, []);
const navItems = [ const navItems = [
{ href: "/home", label: "Home", icon: IconHome }, { href: "/home", label: "Home", icon: IconHome },
{ href: "/accounts", label: "Accounts", icon: IconBook }, { href: "/accounts", label: "Accounts", icon: IconBook },
{ href: "/funds_transfer", label: "Send Money", icon: IconCurrencyRupee }, { href: "/funds_transfer", label: "Send Money", icon: IconCurrencyRupee },
{ href: "/settings", label: "Settings", icon: IconSettings }, { href: "/settings", label: "Settings", icon: IconSettings },
]; ];
return ( if (authorized) {
<html lang="en"> return (
<body> <html lang="en">
<Providers> <body>
<div style={{ backgroundColor: "#e6ffff", height: "100vh", display: "flex", flexDirection: "column", padding: 0, margin: 0 }}> <Providers>
<Box <div style={{ backgroundColor: "#e6ffff", height: "100vh", display: "flex", flexDirection: "column", padding: 0, margin: 0 }}>
style={{ <Box
height: "60px",
position: 'relative',
width: '100%',
display: "flex",
justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}}
>
<Image
fit="cover"
src={logo}
component={NextImage}
alt="ebanking"
style={{ width: "100%", height: "100%" }}
/>
<Text
style={{ style={{
position: 'absolute', height: "60px",
top: '50%', position: 'relative',
left: '80%', width: '100%',
color: 'white', display: "flex",
textShadow: '1px 1px 2px black', justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}} }}
> >
<IconPhoneFilled size={20} /> Toll Free No : 1800-180-8008 <Image
</Text> fit="cover"
</Box> src={logo}
component={NextImage}
<div alt="ebanking"
style={{ style={{ width: "100%", height: "100%" }}
flexShrink: 0, />
padding: '0.5rem 1rem', <Text
display: "flex", style={{
justifyContent: 'space-between', position: 'absolute',
alignItems: "center", top: '50%',
}} left: '80%',
> color: 'white',
<Stack gap={0} align="flex-start"> textShadow: '1px 1px 2px black',
<Title order={4} style={{ fontFamily: "inter", fontSize: '22px' }}> }}
Welcome, Rajat Kumar Maharana >
</Title> <IconPhoneFilled size={20} /> Toll Free No : 1800-180-8008
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"}
</Text> </Text>
</Stack> </Box>
<Group mt="md" gap="sm"> <div
{navItems.map((item) => { style={{
const isActive = pathname === item.href; flexShrink: 0,
const Icon = item.icon; padding: '0.5rem 1rem',
return ( display: "flex",
<Link key={item.href} href={item.href}> justifyContent: 'space-between',
<Button alignItems: "center",
leftSection={<Icon size={20} />} }}
variant={isActive ? "light" : "subtle"} >
color={isActive ? "blue" : undefined} <Stack gap={0} align="flex-start">
> <Title order={4} style={{ fontFamily: "inter", fontSize: '22px' }}>
{item.label} Welcome, Rajat Kumar Maharana
</Button> </Title>
</Link> <Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
); Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"}
})} </Text>
<Button leftSection={<IconLogout size={20} />} variant="subtle" onClick={handleLogout}> </Stack>
Logout
</Button> <Group mt="md" gap="sm">
</Group> {navItems.map((item) => {
const isActive = pathname.startsWith(item.href);
const Icon = item.icon;
return (
<Link key={item.href} href={item.href}>
<Button
leftSection={<Icon size={20} />}
variant={isActive ? "light" : "subtle"}
color={isActive ? "blue" : undefined}
>
{item.label}
</Button>
</Link>
);
})}
<Button leftSection={<IconLogout size={20} />} variant="subtle" onClick={handleLogout}>
Logout
</Button>
</Group>
</div>
<Divider size="xs" color='#99c2ff' />
<div
style={{
flex: 1,
overflowY: "auto",
borderTop: '1px solid #ddd',
borderBottom: '1px solid #ddd',
}}
>
{children}
</div>
<Divider size="xs" color='blue' />
<Box
style={{
flexShrink: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f8f9fa",
marginTop: "0.5rem",
}}
>
<Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank
</Text>
</Box>
</div> </div>
</Providers>
<Divider size="xs" color='#99c2ff' /> </body>
</html>
<div );
style={{ }
flex: 1,
overflowY: "auto",
borderTop: '1px solid #ddd',
borderBottom: '1px solid #ddd',
}}
>
{children}
</div>
<Divider size="xs" color='blue' />
<Box
style={{
flexShrink: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f8f9fa",
marginTop: "0.5rem",
}}
>
<Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank
</Text>
</Box>
</div>
</Providers>
</body>
</html>
);
} }