| 1 | import { Button } from "@/components/ui/button"; |
| 2 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; |
| 3 | import { ArrowLeft, TrendingUp, DollarSign, Users, CreditCard, AlertCircle, ArrowUpRight } from "lucide-react"; |
| 4 | import { Link } from "react-router-dom"; |
| 5 | import { ThemeToggle } from "@/components/ThemeToggle"; |
| 6 | import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts"; |
| 7 | |
| 8 | const monthlySpendData = [ |
| 9 | { month: "Jan", spend: 125000, budget: 150000 }, |
| 10 | { month: "Feb", spend: 142000, budget: 150000 }, |
| 11 | { month: "Mar", spend: 138000, budget: 150000 }, |
| 12 | { month: "Apr", spend: 156000, budget: 160000 }, |
| 13 | { month: "May", spend: 149000, budget: 160000 }, |
| 14 | { month: "Jun", spend: 167000, budget: 170000 }, |
| 15 | ]; |
| 16 | |
| 17 | const departmentSpend = [ |
| 18 | { name: "Engineering", spend: 85000, color: "#9fe870" }, |
| 19 | { name: "Marketing", spend: 45000, color: "#163300" }, |
| 20 | { name: "Sales", spend: 38000, color: "#37d505" }, |
| 21 | { name: "Operations", spend: 29000, color: "#1a1f2c" }, |
| 22 | { name: "HR", spend: 18000, color: "#6bd147" }, |
| 23 | ]; |
| 24 | |
| 25 | const recentTransactions = [ |
| 26 | { id: 1, vendor: "AWS Cloud Services", amount: 12450, category: "Infrastructure", date: "Nov 26, 2025", status: "completed" }, |
| 27 | { id: 2, vendor: "Figma Enterprise", amount: 2400, category: "Software", date: "Nov 25, 2025", status: "completed" }, |
| 28 | { id: 3, vendor: "Delta Airlines", amount: 3200, category: "Travel", date: "Nov 24, 2025", status: "pending" }, |
| 29 | { id: 4, vendor: "WeWork Office Space", amount: 8500, category: "Facilities", date: "Nov 23, 2025", status: "completed" }, |
| 30 | { id: 5, vendor: "HubSpot CRM", amount: 1800, category: "Software", date: "Nov 22, 2025", status: "completed" }, |
| 31 | ]; |
| 32 | |
| 33 | const CFOOverview = () => { |
| 34 | const totalSpend = 167000; |
| 35 | const budgetTotal = 200000; |
| 36 | const budgetUsedPercent = (totalSpend / budgetTotal) * 100; |
| 37 | |
| 38 | return ( |
| 39 | <div className="min-h-screen bg-muted/30"> |
| 40 | {/* Header */} |
| 41 | <header className="bg-card border-b border-border sticky top-0 z-50"> |
| 42 | <div className="container flex items-center justify-between h-16"> |
| 43 | <div className="flex items-center gap-4"> |
| 44 | <Link to="/"> |
| 45 | <Button variant="ghost" size="sm" className="gap-2"> |
| 46 | <ArrowLeft className="w-4 h-4" /> |
| 47 | Back |
| 48 | </Button> |
| 49 | </Link> |
| 50 | <div className="h-6 w-px bg-border" /> |
| 51 | <h1 className="text-xl font-semibold text-foreground">CFO Dashboard</h1> |
| 52 | </div> |
| 53 | <div className="flex items-center gap-3"> |
| 54 | <ThemeToggle /> |
| 55 | <Button variant="outline" size="sm"> |
| 56 | Export Report |
| 57 | </Button> |
| 58 | <Button size="sm" className="bg-primary text-primary-foreground hover:bg-lime-dark"> |
| 59 | Add Expense |
| 60 | </Button> |
| 61 | </div> |
| 62 | </div> |
| 63 | </header> |
| 64 | |
| 65 | <main className="container py-8"> |
| 66 | {/* Key Metrics */} |
| 67 | <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> |
| 68 | <Card className="animate-fade-up"> |
| 69 | <CardHeader className="flex flex-row items-center justify-between pb-2"> |
| 70 | <CardTitle className="text-sm font-medium text-muted-foreground">Total Spend (Nov)</CardTitle> |
| 71 | <div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center"> |
| 72 | <DollarSign className="w-4 h-4 text-lime-dark" /> |
| 73 | </div> |
| 74 | </CardHeader> |
| 75 | <CardContent> |
| 76 | <div className="text-3xl font-bold text-foreground">${totalSpend.toLocaleString()}</div> |
| 77 | <div className="flex items-center gap-1 mt-2"> |
| 78 | <div className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-destructive/10"> |
| 79 | <TrendingUp className="w-3 h-3 text-destructive" /> |
| 80 | <span className="text-xs text-destructive">+12.5%</span> |
| 81 | </div> |
| 82 | <span className="text-xs text-muted-foreground">from last month</span> |
| 83 | </div> |
| 84 | </CardContent> |
| 85 | </Card> |
| 86 | |
| 87 | <Card className="animate-fade-up animation-delay-100"> |
| 88 | <CardHeader className="flex flex-row items-center justify-between pb-2"> |
| 89 | <CardTitle className="text-sm font-medium text-muted-foreground">Budget Remaining</CardTitle> |
| 90 | <div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center"> |
| 91 | <CreditCard className="w-4 h-4 text-lime-dark" /> |
| 92 | </div> |
| 93 | </CardHeader> |
| 94 | <CardContent> |
| 95 | <div className="text-3xl font-bold text-foreground">${(budgetTotal - totalSpend).toLocaleString()}</div> |
| 96 | <div className="w-full bg-muted rounded-full h-2 mt-3"> |
| 97 | <div |
| 98 | className="bg-primary h-2 rounded-full transition-all" |
| 99 | style={{ width: `${budgetUsedPercent}%` }} |
| 100 | /> |
| 101 | </div> |
| 102 | <span className="text-xs text-muted-foreground mt-1 block">{budgetUsedPercent.toFixed(0)}% of budget used</span> |
| 103 | </CardContent> |
| 104 | </Card> |
| 105 | |
| 106 | <Card className="animate-fade-up animation-delay-200"> |
| 107 | <CardHeader className="flex flex-row items-center justify-between pb-2"> |
| 108 | <CardTitle className="text-sm font-medium text-muted-foreground">Pending Approvals</CardTitle> |
| 109 | <div className="w-8 h-8 rounded-full bg-amber-500/20 flex items-center justify-center"> |
| 110 | <AlertCircle className="w-4 h-4 text-amber-600" /> |
| 111 | </div> |
| 112 | </CardHeader> |
| 113 | <CardContent> |
| 114 | <div className="text-3xl font-bold text-foreground">8</div> |
| 115 | <div className="flex items-center gap-1 mt-2"> |
| 116 | <span className="text-xs text-muted-foreground">$24,500 awaiting review</span> |
| 117 | </div> |
| 118 | </CardContent> |
| 119 | </Card> |
| 120 | |
| 121 | <Card className="animate-fade-up animation-delay-300"> |
| 122 | <CardHeader className="flex flex-row items-center justify-between pb-2"> |
| 123 | <CardTitle className="text-sm font-medium text-muted-foreground">Active Cardholders</CardTitle> |
| 124 | <div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center"> |
| 125 | <Users className="w-4 h-4 text-lime-dark" /> |
| 126 | </div> |
| 127 | </CardHeader> |
| 128 | <CardContent> |
| 129 | <div className="text-3xl font-bold text-foreground">47</div> |
| 130 | <div className="flex items-center gap-1 mt-2"> |
| 131 | <div className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-primary/10"> |
| 132 | <TrendingUp className="w-3 h-3 text-lime-dark" /> |
| 133 | <span className="text-xs text-lime-dark">+3</span> |
| 134 | </div> |
| 135 | <span className="text-xs text-muted-foreground">this month</span> |
| 136 | </div> |
| 137 | </CardContent> |
| 138 | </Card> |
| 139 | </div> |
| 140 | |
| 141 | {/* Charts Row */} |
| 142 | <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> |
| 143 | {/* Monthly Spend Trend */} |
| 144 | <Card className="lg:col-span-2 animate-fade-up animation-delay-200"> |
| 145 | <CardHeader> |
| 146 | <CardTitle className="text-lg">Monthly Spend vs Budget</CardTitle> |
| 147 | <CardDescription>Track your spending against monthly budgets</CardDescription> |
| 148 | </CardHeader> |
| 149 | <CardContent> |
| 150 | <div className="h-[300px]"> |
| 151 | <ResponsiveContainer width="100%" height="100%"> |
| 152 | <AreaChart data={monthlySpendData}> |
| 153 | <defs> |
| 154 | <linearGradient id="spendGradient" x1="0" y1="0" x2="0" y2="1"> |
| 155 | <stop offset="5%" stopColor="#9fe870" stopOpacity={0.4} /> |
| 156 | <stop offset="95%" stopColor="#9fe870" stopOpacity={0.05} /> |
| 157 | </linearGradient> |
| 158 | </defs> |
| 159 | <CartesianGrid strokeDasharray="3 3" stroke="hsl(220, 13%, 91%)" /> |
| 160 | <XAxis dataKey="month" stroke="hsl(220, 13%, 46%)" fontSize={12} /> |
| 161 | <YAxis stroke="hsl(220, 13%, 46%)" fontSize={12} tickFormatter={(value) => `$${value / 1000}k`} /> |
| 162 | <Tooltip |
| 163 | contentStyle={{ |
| 164 | backgroundColor: "hsl(0, 0%, 100%)", |
| 165 | border: "1px solid hsl(220, 13%, 91%)", |
| 166 | borderRadius: "8px", |
| 167 | color: "hsl(220, 26%, 14%)" |
| 168 | }} |
| 169 | formatter={(value: number) => [`$${value.toLocaleString()}`, '']} |
| 170 | /> |
| 171 | <Area |
| 172 | type="monotone" |
| 173 | dataKey="spend" |
| 174 | stroke="#37d505" |
| 175 | strokeWidth={3} |
| 176 | fill="url(#spendGradient)" |
| 177 | name="Spend" |
| 178 | /> |
| 179 | <Area |
| 180 | type="monotone" |
| 181 | dataKey="budget" |
| 182 | stroke="#1a1f2c" |
| 183 | strokeWidth={2} |
| 184 | strokeDasharray="5 5" |
| 185 | fill="transparent" |
| 186 | name="Budget" |
| 187 | /> |
| 188 | </AreaChart> |
| 189 | </ResponsiveContainer> |
| 190 | </div> |
| 191 | </CardContent> |
| 192 | </Card> |
| 193 | |
| 194 | {/* Department Breakdown */} |
| 195 | <Card className="animate-fade-up animation-delay-300"> |
| 196 | <CardHeader> |
| 197 | <CardTitle className="text-lg">Spend by Department</CardTitle> |
| 198 | <CardDescription>November breakdown</CardDescription> |
| 199 | </CardHeader> |
| 200 | <CardContent> |
| 201 | <div className="h-[180px] mb-4"> |
| 202 | <ResponsiveContainer width="100%" height="100%"> |
| 203 | <PieChart> |
| 204 | <Pie |
| 205 | data={departmentSpend} |
| 206 | cx="50%" |
| 207 | cy="50%" |
| 208 | innerRadius={50} |
| 209 | outerRadius={80} |
| 210 | paddingAngle={2} |
| 211 | dataKey="spend" |
| 212 | > |
| 213 | {departmentSpend.map((entry, index) => ( |
| 214 | <Cell key={`cell-${index}`} fill={entry.color} stroke="transparent" /> |
| 215 | ))} |
| 216 | </Pie> |
| 217 | <Tooltip |
| 218 | formatter={(value: number) => `$${value.toLocaleString()}`} |
| 219 | contentStyle={{ |
| 220 | backgroundColor: "hsl(0, 0%, 100%)", |
| 221 | border: "1px solid hsl(220, 13%, 91%)", |
| 222 | borderRadius: "8px" |
| 223 | }} |
| 224 | /> |
| 225 | </PieChart> |
| 226 | </ResponsiveContainer> |
| 227 | </div> |
| 228 | <div className="space-y-3"> |
| 229 | {departmentSpend.map((dept) => ( |
| 230 | <div key={dept.name} className="flex items-center justify-between text-sm"> |
| 231 | <div className="flex items-center gap-2"> |
| 232 | <div className="w-3 h-3 rounded-full" style={{ backgroundColor: dept.color }} /> |
| 233 | <span className="text-muted-foreground">{dept.name}</span> |
| 234 | </div> |
| 235 | <span className="font-medium text-foreground">${dept.spend.toLocaleString()}</span> |
| 236 | </div> |
| 237 | ))} |
| 238 | </div> |
| 239 | </CardContent> |
| 240 | </Card> |
| 241 | </div> |
| 242 | |
| 243 | {/* Recent Transactions */} |
| 244 | <Card className="animate-fade-up animation-delay-400"> |
| 245 | <CardHeader className="flex flex-row items-center justify-between"> |
| 246 | <div> |
| 247 | <CardTitle className="text-lg">Recent Transactions</CardTitle> |
| 248 | <CardDescription>Latest company expenses</CardDescription> |
| 249 | </div> |
| 250 | <Button variant="ghost" size="sm" className="text-lime-dark hover:bg-primary/10"> |
| 251 | View All |
| 252 | <ArrowUpRight className="w-4 h-4 ml-1" /> |
| 253 | </Button> |
| 254 | </CardHeader> |
| 255 | <CardContent> |
| 256 | <div className="overflow-x-auto"> |
| 257 | <table className="w-full"> |
| 258 | <thead> |
| 259 | <tr className="border-b border-border"> |
| 260 | <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">Vendor</th> |
| 261 | <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">Category</th> |
| 262 | <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">Date</th> |
| 263 | <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">Status</th> |
| 264 | <th className="text-right py-3 px-4 text-sm font-medium text-muted-foreground">Amount</th> |
| 265 | </tr> |
| 266 | </thead> |
| 267 | <tbody> |
| 268 | {recentTransactions.map((tx) => ( |
| 269 | <tr key={tx.id} className="border-b border-border last:border-0 hover:bg-muted/50 transition-colors"> |
| 270 | <td className="py-4 px-4"> |
| 271 | <span className="font-medium text-foreground">{tx.vendor}</span> |
| 272 | </td> |
| 273 | <td className="py-4 px-4"> |
| 274 | <span className="text-sm text-muted-foreground">{tx.category}</span> |
| 275 | </td> |
| 276 | <td className="py-4 px-4"> |
| 277 | <span className="text-sm text-muted-foreground">{tx.date}</span> |
| 278 | </td> |
| 279 | <td className="py-4 px-4"> |
| 280 | <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ |
| 281 | tx.status === "completed" |
| 282 | ? "bg-primary/20 text-lime-dark" |
| 283 | : "bg-amber-500/20 text-amber-600" |
| 284 | }`}> |
| 285 | {tx.status} |
| 286 | </span> |
| 287 | </td> |
| 288 | <td className="py-4 px-4 text-right"> |
| 289 | <span className="font-semibold text-foreground">${tx.amount.toLocaleString()}</span> |
| 290 | </td> |
| 291 | </tr> |
| 292 | ))} |
| 293 | </tbody> |
| 294 | </table> |
| 295 | </div> |
| 296 | </CardContent> |
| 297 | </Card> |
| 298 | </main> |
| 299 | </div> |
| 300 | ); |
| 301 | }; |
| 302 | |
| 303 | export default CFOOverview; |