Skip to content

Commit

Permalink
Merge pull request #48 from arifshariati/banking
Browse files Browse the repository at this point in the history
feat(banking): recent transactions
  • Loading branch information
arifshariati authored Aug 27, 2024
2 parents bafe741 + c815fd7 commit 1e32152
Show file tree
Hide file tree
Showing 10 changed files with 519 additions and 6 deletions.
16 changes: 10 additions & 6 deletions apps/banking/src/app/(root)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { getLoggedInUsers } from '../../actions/auth.actions';
import { getAccount, getAccounts } from '../../actions/bank.actions';
import PageHeader from '../../components/page-header';
import RecentTransactions from '../../components/recent-transactions';
import SummaryChart from '../../components/summary-chart';
import UserProfileRight from '../../components/user-profile-right';

type SearchParamProps = {
params: { [key: string]: string };
searchParams: { [key: string]: string | string[] | undefined };
};

const RootPage = async ({ searchParams: { id, page } }: SearchParamProps) => {
const currentPage = Number(page as string) || 1;
const userDetails = await getLoggedInUsers();
if (!userDetails) return;

Expand All @@ -23,21 +24,24 @@ const RootPage = async ({ searchParams: { id, page } }: SearchParamProps) => {

return (
<div className="flex gap-4">
<div className="flex flex-col">
<div className="flex flex-col flex-1">
<PageHeader
type="greeting"
title="Welcome"
subtext="Manage your transactions effeciently"
user={userDetails?.name}
/>
<SummaryChart
accounts={accounts?.data}
accounts={accountsData}
totalBanks={accounts?.totalBanks}
totalCurrentBalance={accounts?.totalCurrentBalance}
/>
</div>
<div className="ml-auto">
<UserProfileRight user={userDetails} />
<RecentTransactions
accounts={accountsData}
transactions={account?.transactions}
appwriteItemId={appwriteItemId}
page={currentPage}
/>
</div>
</div>
);
Expand Down
59 changes: 59 additions & 0 deletions apps/banking/src/components/bank-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import { useRouter, useSearchParams } from 'next/navigation';
import { Landmark } from 'lucide-react';
import { cn } from '@monor/utils/tailwind/cn';
import { formatAmount, formUrlQuery, getAccountTypeColors } from '../lib/utils';
import { Account, AccountTypes } from '../types/account';

type BankInfoProps = {
account: Account;
appwriteItemId?: string;
type: 'full' | 'card';
};

const BankInfo = ({ account, appwriteItemId, type }: BankInfoProps) => {
const router = useRouter();
const searchParams = useSearchParams();

const isActive = appwriteItemId === account?.appwriteItemId;

const handleBankChange = () => {
const newUrl = formUrlQuery({
params: searchParams.toString(),
key: 'id',
value: account?.appwriteItemId,
});
router.push(newUrl, { scroll: false });
};

const colors = getAccountTypeColors(account?.type as AccountTypes);

return (
<div
onClick={handleBankChange}
className="flex px-4 py-2 gap-4 bg-blue-50 rounded-lg"
>
<div className="flex items-center justify-center ">
<div className="flex items-center justify-center bg-blue-100 h-[50px] w-[50px] rounded-lg">
<Landmark />
</div>
</div>

<div className="flex flex-1 items-center justify-between">
<div className="flex flex-col">
<h2 className="text-xl font-semibold">{account.name}</h2>
{type === 'full' && (
<p className="italic text-sm">{account.subtype}</p>
)}
</div>

<p className="text-2xl font-semibold">
{formatAmount(account.currentBalance)}
</p>
</div>
</div>
);
};

export default BankInfo;
26 changes: 26 additions & 0 deletions apps/banking/src/components/bank-tab-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';
import { Account } from '../types/account';
import { useRouter, useSearchParams } from 'next/navigation';
import { formUrlQuery } from '../lib/utils';
import { cn } from '@monor/utils/tailwind/cn';

type BankTabItemProps = { account: Account; appwriteItemId?: string };

const BankTabItem = ({ account, appwriteItemId }: BankTabItemProps) => {
const searchParams = useSearchParams();
const router = useRouter();
const isActive = appwriteItemId === account.appwriteItemId;

const handleTabChange = () => {
const newUrl = formUrlQuery({
params: searchParams.toString(),
key: 'id',
value: account.appwriteItemId,
});
router.push(newUrl, { scroll: false });
};

return <div onClick={handleTabChange}>{account.name}</div>;
};

export default BankTabItem;
54 changes: 54 additions & 0 deletions apps/banking/src/components/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@monor/ui/shadcn/button';
import { formUrlQuery } from '../lib/utils';

type PaginationProps = { page: number; totalPages: number };

const Pagination = ({ page, totalPages }: PaginationProps) => {
const router = useRouter();
const searchParams = useSearchParams();

const handleNavigation = (type: 'prev' | 'next') => {
const pageNumber = type === 'prev' ? page - 1 : page + 1;

const newUrl = formUrlQuery({
params: searchParams.toString(),
key: 'page',
value: pageNumber.toString(),
});

router.push(newUrl, { scroll: false });
};

return (
<div className="flex justify-between gap-3">
<Button
size="lg"
variant="ghost"
className="p-0 hover:bg-transparent"
onClick={() => handleNavigation('prev')}
disabled={Number(page) <= 1}
>
<ChevronLeft />
Prev
</Button>
<p className="text-14 flex items-center px-2">
{page} / {totalPages}
</p>
<Button
size="lg"
variant="ghost"
className="p-0 hover:bg-transparent"
onClick={() => handleNavigation('next')}
disabled={Number(page) >= totalPages}
>
Next
<ChevronRight />
</Button>
</div>
);
};

export default Pagination;
90 changes: 90 additions & 0 deletions apps/banking/src/components/recent-transactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Link from 'next/link';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@monor/ui/shadcn/tabs';
import { Account } from '../types/account';
import { Transaction } from '../types/transaction';
import BankTabItem from './bank-tab-item';
import BankInfo from './bank-info';
import TransactionsTable from './transactions-table';
import Pagination from './pagination';
import { Button } from '@monor/ui/shadcn/button';

type RecentTransactionsProps = {
accounts: Account[];
transactions: Transaction[];
appwriteItemId: string;
page: number;
};

const RecentTransactions = ({
accounts,
transactions = [],
appwriteItemId,
page = 1,
}: RecentTransactionsProps) => {
const rowsPerPage = 10;
const totalPages = Math.ceil(transactions.length / rowsPerPage);

const indexOfLastTransaction = page * rowsPerPage;
const indexOfFirstTransaction = indexOfLastTransaction - rowsPerPage;

const currentTransactions = transactions.slice(
indexOfFirstTransaction,
indexOfLastTransaction
);

return (
<section className="flex w-full flex-col gap-6">
<header className="flex items-center justify-between">
<h2 className="text-20 md:text-24 font-semibold text-gray-900">
Recent transactions
</h2>
<Link href={`/transaction-history/?id=${appwriteItemId}`}>
<Button variant={'outline'}>View All</Button>
</Link>
</header>

<Tabs defaultValue={appwriteItemId} className="w-auto">
<TabsList className="flex items-center justify-center">
{accounts.map((account: Account) => (
<TabsTrigger key={account.id} value={account.appwriteItemId}>
<BankTabItem
key={account.id}
account={account}
appwriteItemId={appwriteItemId}
/>
</TabsTrigger>
))}
</TabsList>

{accounts.map((account: Account) => (
<TabsContent
value={account.appwriteItemId}
key={account.id}
className="space-y-2"
>
<BankInfo
account={account}
appwriteItemId={appwriteItemId}
type="full"
/>

<TransactionsTable transactions={currentTransactions} />

{totalPages > 1 && (
<div className="my-4 w-full">
<Pagination totalPages={totalPages} page={page} />
</div>
)}
</TabsContent>
))}
</Tabs>
</section>
);
};

export default RecentTransactions;
110 changes: 110 additions & 0 deletions apps/banking/src/components/transactions-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { cn } from '@monor/utils/tailwind/cn';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@monor/ui/shadcn/table';
import { Transaction } from '../types/transaction';
import { transactionCategoryStyles } from '../constants/transaction-table';
import {
formatAmount,
formatDateTime,
getTransactionStatus,
removeSpecialCharacters,
} from '../lib/utils';

const CategoryBadge = ({ category }: { category: string }) => {
const { borderColor, backgroundColor, textColor, chipBackgroundColor } =
transactionCategoryStyles[
category as keyof typeof transactionCategoryStyles
] || transactionCategoryStyles.default;

return (
<div
className={cn(
'flex items-center justify-center truncate w-fit gap-1 rounded-2xl border-[1.5px] py-[2px] pl-1.5 pr-2',
borderColor,
chipBackgroundColor
)}
>
<div className={cn('size-2 rounded-full', backgroundColor)} />
<p className={cn('text-[12px] font-medium', textColor)}>{category}</p>
</div>
);
};

type TransactionTablesProps = { transactions: Transaction[] };

const TransactionTables = ({ transactions }: TransactionTablesProps) => {
return (
<Table>
<TableHeader className="bg-[#f9fafb]">
<TableRow>
<TableHead className="px-2">Transaction</TableHead>
<TableHead className="px-2">Amount</TableHead>
<TableHead className="px-2">Status</TableHead>
<TableHead className="px-2">Date</TableHead>
<TableHead className="px-2 max-md:hidden">Channel</TableHead>
<TableHead className="px-2 max-md:hidden">Category</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{transactions.map((t: Transaction) => {
const status = getTransactionStatus(new Date(t.date));
const amount = formatAmount(t.amount);

const isDebit = t.type === 'debit';
const isCredit = t.type === 'credit';

return (
<TableRow
key={t.id}
className={`${
isDebit || amount[0] === '-' ? 'bg-[#FFFBFA]' : 'bg-[#F6FEF9]'
} !over:bg-none !border-b-DEFAULT`}
>
<TableCell className="max-w-[250px] pl-2 pr-10">
<div className="flex items-center gap-3">
<h1 className="text-14 truncate font-semibold text-[#344054]">
{removeSpecialCharacters(t.name)}
</h1>
</div>
</TableCell>

<TableCell
className={`pl-2 pr-10 font-semibold ${
isDebit || amount[0] === '-'
? 'text-[#f04438]'
: 'text-[#039855]'
}`}
>
{isDebit ? `-${amount}` : isCredit ? amount : amount}
</TableCell>

<TableCell className="pl-2 pr-10">
<CategoryBadge category={status} />
</TableCell>

<TableCell className="min-w-32 pl-2 pr-10">
{formatDateTime(new Date(t.date)).dateTime}
</TableCell>

<TableCell className="pl-2 pr-10 capitalize min-w-24">
{t.paymentChannel}
</TableCell>

<TableCell className="pl-2 pr-10 max-md:hidden">
<CategoryBadge category={t.category} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
};

export default TransactionTables;
Loading

0 comments on commit 1e32152

Please sign in to comment.