You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
8.9 KiB
TypeScript

import { prisma } from "@/lib/prisma";
import {
Plus,
Search,
Filter,
ArrowUpRight,
ArrowDownRight,
Download,
Calendar,
Wallet,
ArrowRightLeft
} from "lucide-react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { formatCurrency } from "@/lib/utils";
export default async function LedgerPage() {
const transactions = await prisma.companyTransaction.findMany({
orderBy: { date: 'desc' },
include: {
category: true,
account: true
}
});
return (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h2 className="text-3xl font-bold tracking-tight">Company Ledger</h2>
<p className="text-neutral-500 mt-1">Full transaction history and cash flow tracking.</p>
</div>
<div className="flex items-center gap-3">
<Button variant="outline" className="rounded-xl border-neutral-200 gap-2 h-12 px-6 font-bold shadow-sm hover:bg-neutral-50 transition-all">
<Download className="w-4 h-4" />
Export CSV
</Button>
<Button className="bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl gap-2 h-12 px-8 font-bold shadow-lg shadow-emerald-100 dark:shadow-none transition-all active:scale-95">
<Plus className="w-4 h-4" />
New Entry
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{ label: 'Total Inflow', amount: 452000, color: 'emerald', icon: ArrowUpRight },
{ label: 'Total Outflow', amount: 128400, color: 'rose', icon: ArrowDownRight },
{ label: 'Net Balance', amount: 323600, color: 'indigo', icon: Wallet },
].map((stat, i) => (
<Card key={i} className="border-none shadow-xl shadow-black/5 bg-white dark:bg-neutral-900 rounded-3xl p-8 relative overflow-hidden group">
<div className="relative z-10">
<div className={`w-12 h-12 rounded-2xl bg-${stat.color}-50 dark:bg-${stat.color}-900/20 flex items-center justify-center mb-4`}>
<stat.icon className={`w-6 h-6 text-${stat.color}-600 dark:text-${stat.color}-400`} />
</div>
<p className="text-xs font-bold text-neutral-400 uppercase tracking-widest mb-1">{stat.label}</p>
<h3 className="text-3xl font-black text-neutral-900 dark:text-neutral-100">{formatCurrency(stat.amount.toString())}</h3>
</div>
</Card>
))}
</div>
<Card className="border-none shadow-2xl shadow-black/5 bg-white dark:bg-neutral-900 rounded-[2.5rem] overflow-hidden">
<div className="p-8 border-b border-neutral-100 dark:border-neutral-800 flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400" />
<Input placeholder="Search transactions..." className="pl-10 h-12 rounded-xl bg-neutral-50 dark:bg-neutral-800 border-none shadow-none" />
</div>
<div className="flex items-center gap-3">
<Button variant="outline" className="rounded-xl border-neutral-200 h-12 flex items-center gap-2 font-bold transition-all hover:bg-neutral-50">
<Filter className="w-4 h-4" />
All Accounts
</Button>
<Button variant="outline" className="rounded-xl border-neutral-200 h-12 flex items-center gap-2 font-bold transition-all hover:bg-neutral-50">
<Calendar className="w-4 h-4" />
This Month
</Button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="bg-neutral-50/50 dark:bg-neutral-800/50 border-b border-neutral-100 dark:border-neutral-800">
<th className="p-6 px-8 text-xs font-black uppercase tracking-[0.15em] text-neutral-400">Transaction</th>
<th className="p-6 text-xs font-black uppercase tracking-[0.15em] text-neutral-400">Account</th>
<th className="p-6 text-xs font-black uppercase tracking-[0.15em] text-neutral-400">Category</th>
<th className="p-6 text-xs font-black uppercase tracking-[0.15em] text-neutral-400">Amount</th>
<th className="p-6 px-8 text-xs font-black uppercase tracking-[0.15em] text-neutral-400 text-right">Date</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{transactions.map((tx) => (
<tr key={tx.id} className="hover:bg-neutral-50/50 transition-all group">
<td className="p-6 px-8">
<div className="flex items-center gap-4">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${tx.type === 'DEBIT' ? 'bg-rose-50 text-rose-600' : 'bg-emerald-50 text-emerald-600'
}`}>
{tx.type === 'DEBIT' ? <ArrowDownRight className="w-5 h-5" /> : <ArrowUpRight className="w-5 h-5" />}
</div>
<div>
<p className="font-bold text-neutral-900 dark:text-neutral-100">{tx.description}</p>
<p className="text-[10px] text-neutral-400 font-bold uppercase tracking-tight">TX-{tx.id.substring(0, 8).toUpperCase()}</p>
</div>
</div>
</td>
<td className="p-6">
<span className="text-sm font-semibold text-neutral-600 dark:text-neutral-400">{tx.account.name}</span>
</td>
<td className="p-6">
<span className="px-2.5 py-1 rounded-lg bg-neutral-100 dark:bg-neutral-800 text-[10px] font-black uppercase text-neutral-500">
{tx.category?.name || 'Uncategorized'}
</span>
</td>
<td className="p-6">
<span className={`text-lg font-black ${tx.type === 'DEBIT' ? 'text-rose-600' : 'text-emerald-600'}`}>
{tx.type === 'DEBIT' ? '-' : '+'}{formatCurrency(tx.amount.toString())}
</span>
</td>
<td className="p-6 px-8 text-right">
<p className="text-sm font-bold text-neutral-900 dark:text-neutral-100">{new Date(tx.date).toLocaleDateString()}</p>
<p className="text-[10px] text-neutral-400 uppercase tracking-tighter">14:20 PM</p>
</td>
</tr>
))}
</tbody>
</table>
{transactions.length === 0 && (
<div className="p-20 text-center">
<div className="w-20 h-20 bg-neutral-100 dark:bg-neutral-800 rounded-full flex items-center justify-center mx-auto mb-6">
<ArrowRightLeft className="w-10 h-10 text-neutral-300" />
</div>
<h3 className="text-xl font-black mb-2">No Transactions Found</h3>
<p className="text-neutral-500 font-medium">Start recording financial activities to see them here.</p>
</div>
)}
</div>
</Card>
</div>
);
}