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.
166 lines
10 KiB
TypeScript
166 lines
10 KiB
TypeScript
import { auth } from "@/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import {
|
|
DollarSign,
|
|
Receipt,
|
|
Clock,
|
|
Wallet,
|
|
ArrowUpRight,
|
|
TrendingUp,
|
|
CreditCard,
|
|
Zap
|
|
} from "lucide-react";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { formatCurrency } from "@/lib/utils";
|
|
import Link from "next/link";
|
|
|
|
export default async function WorkerDashboard() {
|
|
const session = await auth();
|
|
const userId = (session?.user as any)?.id;
|
|
|
|
const [reimbursements, overtimes] = await Promise.all([
|
|
prisma.reimbursement.findMany({
|
|
where: { userId },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 5
|
|
}),
|
|
prisma.overtime.findMany({
|
|
where: { userId },
|
|
take: 20
|
|
})
|
|
]);
|
|
|
|
const pendingClaimsCount = await prisma.reimbursement.count({
|
|
where: { userId, status: 'PENDING' }
|
|
});
|
|
|
|
const totalSpendingAgg = await prisma.reimbursement.aggregate({
|
|
where: { userId, status: 'PAID' },
|
|
_sum: { amount: true }
|
|
});
|
|
|
|
const overtimeHours = overtimes.reduce((acc, curr) => acc + curr.hours, 0);
|
|
|
|
return (
|
|
<div className="space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<div>
|
|
<h2 className="text-4xl font-extrabold tracking-tight text-neutral-900 dark:text-white">
|
|
Welcome back, <span className="text-indigo-600">{(session?.user?.name || 'User').split(' ')[0]}</span>
|
|
</h2>
|
|
<p className="text-neutral-500 mt-2 font-medium text-lg">Your financial activity and pending requests at a glance.</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Button variant="outline" className="rounded-2xl border-neutral-200 h-14 px-6 gap-2 font-bold hover:bg-white hover:shadow-xl transition-all">
|
|
Download Report
|
|
</Button>
|
|
<Link href="/me/reimbursements">
|
|
<Button className="bg-indigo-600 hover:bg-indigo-700 text-white rounded-2xl h-14 px-8 font-black shadow-2xl shadow-indigo-200 dark:shadow-none transition-all active:scale-95 flex items-center gap-2">
|
|
<Zap className="w-5 h-5 fill-white" />
|
|
New Request
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
|
{[
|
|
{ label: 'Total Spending', value: formatCurrency(totalSpendingAgg._sum.amount?.toString() || '0'), icon: DollarSign, color: 'indigo', trend: '+12% this month' },
|
|
{ label: 'Pending Claims', value: pendingClaimsCount.toString(), icon: Receipt, color: 'amber', trend: 'Wait time: ~2 days' },
|
|
{ label: 'Overtime Hours', value: `${overtimeHours}h`, icon: Clock, color: 'violet', trend: 'YTD tracking' },
|
|
{ label: 'Next Payout', value: formatCurrency(2450), icon: Wallet, color: 'emerald', trend: 'Due in 12 days' },
|
|
].map((stat, i) => (
|
|
<Card key={i} className="group border-none shadow-2xl shadow-black/5 bg-white dark:bg-neutral-900 rounded-[2.5rem] p-8 hover:-translate-y-2 transition-all duration-500 overflow-hidden relative">
|
|
<div className="relative z-10">
|
|
<div className={`w-14 h-14 rounded-2xl bg-indigo-50 dark:bg-indigo-900/20 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-500`}>
|
|
<stat.icon className={`w-7 h-7 text-indigo-600 dark:text-indigo-400`} />
|
|
</div>
|
|
<p className="text-xs font-black text-neutral-400 uppercase tracking-widest mb-1">{stat.label}</p>
|
|
<div className="flex items-baseline gap-2">
|
|
<h3 className="text-3xl font-black text-neutral-900 dark:text-neutral-100">{stat.value}</h3>
|
|
</div>
|
|
<p className="mt-4 text-[10px] font-bold text-neutral-500 flex items-center gap-1">
|
|
<TrendingUp className="w-3 h-3 text-emerald-500" />
|
|
{stat.trend}
|
|
</p>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
|
|
<Card className="xl:col-span-2 border-none shadow-2xl shadow-black/5 bg-white dark:bg-neutral-900 rounded-[3rem] p-10">
|
|
<div className="flex items-center justify-between mb-10">
|
|
<h3 className="text-2xl font-black tracking-tight">Recent Activity</h3>
|
|
<Button variant="ghost" className="text-indigo-600 font-bold hover:bg-indigo-50 rounded-xl px-4 py-2">View All</Button>
|
|
</div>
|
|
<div className="space-y-6">
|
|
{reimbursements.map((item) => (
|
|
<div key={item.id} className="flex items-center justify-between p-6 rounded-[2rem] hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-all group cursor-pointer border border-transparent hover:border-neutral-100 dark:hover:border-neutral-800">
|
|
<div className="flex items-center gap-6">
|
|
<div className="w-14 h-14 rounded-2xl bg-neutral-100 dark:bg-neutral-800 flex items-center justify-center group-hover:bg-white dark:group-hover:bg-neutral-700 transition-colors shadow-sm">
|
|
<CreditCard className="w-6 h-6 text-neutral-400 group-hover:text-indigo-600 transition-colors" />
|
|
</div>
|
|
<div>
|
|
<p className="font-bold text-lg text-neutral-900 dark:text-neutral-100">{item.description}</p>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<span className="text-[10px] font-black uppercase text-neutral-400 tracking-wider bg-neutral-100 dark:bg-neutral-800 px-2 py-0.5 rounded-md">{item.category}</span>
|
|
<span className="text-xs text-neutral-400 font-medium">{new Date(item.createdAt).toLocaleDateString()}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="font-black text-xl text-neutral-900 dark:text-neutral-100">{formatCurrency(item.amount.toString())}</p>
|
|
<span className={`text-[10px] font-black uppercase ${item.status === 'PAID' ? 'text-emerald-500' : 'text-amber-500'
|
|
}`}>{item.status}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
<div className="space-y-8">
|
|
<Card className="border-none shadow-2xl shadow-indigo-500/10 bg-gradient-to-br from-indigo-700 to-violet-800 rounded-[3rem] p-10 text-white relative overflow-hidden group">
|
|
<div className="relative z-10">
|
|
<h3 className="text-xl font-bold mb-6 flex items-center gap-2">
|
|
<ArrowUpRight className="w-5 h-5" />
|
|
Quick Wallet Tip
|
|
</h3>
|
|
<p className="text-indigo-100 font-medium leading-relaxed mb-8">
|
|
You have <span className="text-white font-bold underline decoration-indigo-400 underline-offset-4">{pendingClaimsCount} pending reimbursement requests</span>. Claims are usually processed within 48 hours.
|
|
</p>
|
|
<Link href="/me/reimbursements">
|
|
<Button className="w-full bg-white text-indigo-700 hover:bg-indigo-50 rounded-2xl h-14 font-black shadow-xl transition-all active:scale-95">
|
|
Submit New Claim
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</Card>
|
|
|
|
<Card className="border-none shadow-2xl shadow-black/5 bg-white dark:bg-neutral-900 rounded-[3rem] p-10">
|
|
<h3 className="text-xl font-black mb-8">Spending Snapshot</h3>
|
|
<div className="space-y-6">
|
|
{[
|
|
{ label: 'Travel', value: 45, color: 'bg-indigo-500' },
|
|
{ label: 'Dining', value: 30, color: 'bg-emerald-500' },
|
|
{ label: 'Supplies', value: 25, color: 'bg-amber-500' },
|
|
].map((item) => (
|
|
<div key={item.label} className="space-y-2">
|
|
<div className="flex justify-between text-sm font-bold">
|
|
<span className="text-neutral-500">{item.label}</span>
|
|
<span>{item.value}%</span>
|
|
</div>
|
|
<div className="w-full h-2 bg-neutral-100 dark:bg-neutral-800 rounded-full overflow-hidden">
|
|
<div className={`h-full ${item.color} rounded-full transition-all duration-1000`} style={{ width: `${item.value}%` }} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|