import {
Activity,
ArrowUpRight,
BarChart3,
Calendar,
Edit,
Eye,
FileText,
Plus,
Users,
} from "lucide-react";
import Link from "next/link";
import { Suspense } from "react";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Skeleton } from "~/components/ui/skeleton";
import { getEffectiveInvoiceStatus } from "~/lib/invoice-status";
import { auth } from "~/lib/auth";
import { headers } from "next/headers";
import { HydrateClient, api } from "~/trpc/server";
import type { StoredInvoiceStatus } from "~/types/invoice";
import { RevenueChart } from "~/app/dashboard/_components/revenue-chart";
import { InvoiceStatusChart } from "~/app/dashboard/_components/invoice-status-chart";
import { MonthlyMetricsChart } from "~/app/dashboard/_components/monthly-metrics-chart";
import { AnimatedStatsCard } from "~/app/dashboard/_components/animated-stats-card";
// Hero section with clean mono design
function DashboardHero({ firstName }: { firstName: string }) {
return (
Welcome back, {firstName}!
Here's what's happening with your business today
);
}
// Enhanced stats cards with better visuals
async function DashboardStats() {
const [clients, invoices] = await Promise.all([
api.clients.getAll(),
api.invoices.getAll(),
]);
const totalClients = clients.length;
const paidInvoices = invoices.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "paid",
);
const totalRevenue = paidInvoices.reduce(
(sum, invoice) => sum + invoice.totalAmount,
0,
);
const pendingInvoices = invoices.filter((invoice) => {
const effectiveStatus = getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
);
return effectiveStatus === "sent" || effectiveStatus === "overdue";
});
const pendingAmount = pendingInvoices.reduce(
(sum, invoice) => sum + invoice.totalAmount,
0,
);
const overdueInvoices = invoices.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "overdue",
);
// Calculate month-over-month trends
const now = new Date();
const currentMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
// Current month data
const currentMonthInvoices = invoices.filter(
(invoice) => new Date(invoice.issueDate) >= currentMonth,
);
const currentMonthRevenue = currentMonthInvoices
.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "paid",
)
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
// Last month data
const lastMonthInvoices = invoices.filter((invoice) => {
const date = new Date(invoice.issueDate);
return date >= lastMonth && date < currentMonth;
});
const lastMonthRevenue = lastMonthInvoices
.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "paid",
)
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
// Previous month data for clients
const prevMonthClients = clients.filter(
(client) => new Date(client.createdAt) < currentMonth,
).length;
// Calculate trends
const revenueChange =
lastMonthRevenue > 0
? ((currentMonthRevenue - lastMonthRevenue) / lastMonthRevenue) * 100
: currentMonthRevenue > 0
? 100
: 0;
const pendingChange =
lastMonthInvoices.length > 0
? ((pendingInvoices.length -
lastMonthInvoices.filter((invoice) => {
const status = getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
);
return status === "sent" || status === "overdue";
}).length) /
lastMonthInvoices.length) *
100
: pendingInvoices.length > 0
? 100
: 0;
const clientChange = totalClients - prevMonthClients;
const lastMonthOverdue = lastMonthInvoices.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "overdue",
).length;
const overdueChange = overdueInvoices.length - lastMonthOverdue;
const formatTrend = (value: number, isCount = false) => {
if (isCount) {
return value > 0 ? `+${value}` : value.toString();
}
return value > 0 ? `+${value.toFixed(1)}%` : `${value.toFixed(1)}%`;
};
// Debug logging to see actual values
console.log("Dashboard Stats Debug:", {
totalRevenue,
pendingAmount,
totalClients,
overdueInvoices: overdueInvoices.length,
revenueChange,
pendingChange,
clientChange,
overdueChange,
paidInvoicesCount: paidInvoices.length,
pendingInvoicesCount: pendingInvoices.length,
});
const stats = [
{
title: "Total Revenue",
value: `$${totalRevenue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
numericValue: totalRevenue,
isCurrency: true,
change: formatTrend(revenueChange),
trend: revenueChange >= 0 ? ("up" as const) : ("down" as const),
iconName: "DollarSign" as const,
description: `From ${paidInvoices.length} paid invoices`,
},
{
title: "Pending Amount",
value: `$${pendingAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
numericValue: pendingAmount,
isCurrency: true,
change: formatTrend(pendingChange),
trend: pendingChange >= 0 ? ("up" as const) : ("down" as const),
iconName: "Clock" as const,
description: `${pendingInvoices.length} invoices awaiting payment`,
},
{
title: "Active Clients",
value: totalClients.toString(),
numericValue: totalClients,
isCurrency: false,
change: formatTrend(clientChange, true),
trend: clientChange >= 0 ? ("up" as const) : ("down" as const),
iconName: "Users" as const,
description: "Total registered clients",
},
{
title: "Overdue Invoices",
value: overdueInvoices.length.toString(),
numericValue: overdueInvoices.length,
isCurrency: false,
change: formatTrend(overdueChange, true),
trend: overdueChange <= 0 ? ("up" as const) : ("down" as const),
iconName: "TrendingDown" as const,
description: "Invoices past due date",
},
];
return (
{stats.map((stat, index) => (
))}
);
}
// Charts section
async function ChartsSection() {
const invoices = await api.invoices.getAll();
return (
{/* Revenue Trend Chart */}
Revenue Over Time
{/* Invoice Status Breakdown */}
Invoice Status
{/* Monthly Metrics */}
Monthly Metrics
);
}
// Enhanced Quick Actions
function QuickActions() {
const actions = [
{
title: "Create Invoice",
description: "Start a new invoice for a client",
href: "/dashboard/invoices/new",
icon: FileText,
featured: true,
},
{
title: "Add Client",
description: "Register a new client",
href: "/dashboard/clients/new",
icon: Users,
featured: false,
},
{
title: "View All Invoices",
description: "Manage your invoice pipeline",
href: "/dashboard/invoices",
icon: BarChart3,
featured: false,
},
];
return (
Quick Actions
{actions.map((action) => {
const Icon = action.icon;
return (
{action.title}
{action.description}
);
})}
);
}
// Current work section with enhanced design
async function CurrentWork() {
const invoices = await api.invoices.getAll();
const draftInvoices = invoices.filter(
(invoice) =>
getEffectiveInvoiceStatus(
invoice.status as StoredInvoiceStatus,
invoice.dueDate,
) === "draft",
);
const currentInvoice = draftInvoices[0];
if (!currentInvoice) {
return (
Current Work
No active drafts
Create a new invoice to get started
);
}
const totalHours =
currentInvoice.items?.reduce((sum, item) => sum + item.hours, 0) ?? 0;
return (
Current Work
In Progress
#{currentInvoice.invoiceNumber}
${currentInvoice.totalAmount.toFixed(2)}
{currentInvoice.client?.name}
{totalHours.toFixed(1)} hours logged
);
}
// Enhanced recent activity
async function RecentActivity() {
const invoices = await api.invoices.getAll();
const recentInvoices = invoices
.sort(
(a, b) =>
new Date(b.issueDate).getTime() - new Date(a.issueDate).getTime(),
)
.slice(0, 5);
const getStatusStyle = (status: string) => {
switch (status) {
case "paid":
return {
backgroundColor: "oklch(var(--chart-2) / 0.1)",
borderColor: "oklch(var(--chart-2) / 0.3)",
color: "oklch(var(--chart-2))",
};
case "sent":
return {
backgroundColor: "oklch(var(--chart-1) / 0.1)",
borderColor: "oklch(var(--chart-1) / 0.3)",
color: "oklch(var(--chart-1))",
};
case "overdue":
return {
backgroundColor: "oklch(var(--chart-3) / 0.1)",
borderColor: "oklch(var(--chart-3) / 0.3)",
color: "oklch(var(--chart-3))",
};
default:
return {
backgroundColor: "hsl(var(--muted))",
borderColor: "hsl(var(--border))",
color: "hsl(var(--muted-foreground))",
};
}
};
return (
Recent Activity
{recentInvoices.length === 0 ? (
No invoices yet
Create your first invoice to get started
) : (
{recentInvoices.map((invoice, _index) => (
#{invoice.invoiceNumber}
{invoice.client?.name}
{invoice.status}
${invoice.totalAmount.toFixed(2)}
{new Date(invoice.issueDate).toLocaleDateString()}
))}
)}
);
}
// Loading skeletons
function StatsSkeleton() {
return (
{Array.from({ length: 4 }).map((_, i) => (
))}
);
}
function ChartsSkeleton() {
return (
);
}
function CardSkeleton() {
return (
);
}
export default async function DashboardPage() {
const session = await auth.api.getSession({
headers: await headers(),
});
const firstName = session?.user?.name?.split(" ")[0] ?? "User";
return (
);
}