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"; import type { DashboardStats, RecentInvoice } from "./types"; // Hero section with clean mono design // Enhanced stats cards with better visuals function DashboardStats({ stats }: { stats: DashboardStats }) { // TODO: Import RouterOutput type const formatTrend = (value: number, isCount = false) => { if (isCount) { return value > 0 ? `+${value}` : value.toString(); } return value > 0 ? `+${value.toFixed(1)}%` : `${value.toFixed(1)}%`; }; const statCards = [ { title: "Total Revenue", value: `$${stats.totalRevenue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`, numericValue: stats.totalRevenue, isCurrency: true, change: formatTrend(stats.revenueChange), trend: stats.revenueChange >= 0 ? ("up" as const) : ("down" as const), iconName: "DollarSign" as const, description: "Total collected revenue", }, { title: "Pending Amount", value: `$${stats.pendingAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })}`, numericValue: stats.pendingAmount, isCurrency: true, change: "0%", // TODO: Calculate pending change if needed trend: "neutral" as const, iconName: "Clock" as const, description: "Invoices awaiting payment", }, { title: "Active Clients", value: stats.totalClients.toString(), numericValue: stats.totalClients, isCurrency: false, change: "0", // TODO: Calculate client change if needed trend: "neutral" as const, iconName: "Users" as const, description: "Total registered clients", }, { title: "Overdue Invoices", value: stats.overdueCount.toString(), numericValue: stats.overdueCount, isCurrency: false, change: "0", // TODO: Calculate overdue change if needed trend: "neutral" as const, iconName: "TrendingDown" as const, description: "Invoices past due date", }, ]; return (
{statCards.map((stat, index) => ( ))}
); } // Charts section async function ChartsSection({ stats }: { stats: DashboardStats }) { // We still fetch all invoices for the status chart for now, or we could aggregate that too. // For now, let's keep status chart as is (fetching all) but use aggregated for revenue. // Actually, let's fetch invoices here for the status chart to keep it working. 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({ recentInvoices }: { recentInvoices: RecentInvoice[] }) { // Use passed recentInvoices instead of fetching all 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 (
); } import { DashboardPageHeader } from "~/components/layout/page-header"; // ... imports export default async function DashboardPage() { const session = await auth.api.getSession({ headers: await headers(), }); const firstName = session?.user?.name?.split(" ")[0] ?? "User"; // Fetch stats centrally const stats = await api.dashboard.getStats(); return (
}> }>
}>
}>
); }