From 8b8cab9385d892794ac5df5ac3546cedf713df16 Mon Sep 17 00:00:00 2001 From: CycroftX Date: Tue, 3 Feb 2026 20:55:29 +0530 Subject: [PATCH] Refactor Financials page: Add Settlements Table, Transaction Details, and Visual Upgrades --- src/data/mockFinancialData.ts | 120 ++++++++++++++++++ .../financials/components/SettlementTable.tsx | 116 +++++++++++++++++ .../components/TransactionDetailsSheet.tsx | 93 ++++++++++++++ .../financials/components/TransactionList.tsx | 114 +++++++++++++++++ src/pages/Financials.tsx | 115 +++++++---------- 5 files changed, 488 insertions(+), 70 deletions(-) create mode 100644 src/data/mockFinancialData.ts create mode 100644 src/features/financials/components/SettlementTable.tsx create mode 100644 src/features/financials/components/TransactionDetailsSheet.tsx create mode 100644 src/features/financials/components/TransactionList.tsx diff --git a/src/data/mockFinancialData.ts b/src/data/mockFinancialData.ts new file mode 100644 index 0000000..45a50da --- /dev/null +++ b/src/data/mockFinancialData.ts @@ -0,0 +1,120 @@ + +export interface Settlement { + id: string; + partnerName: string; + eventName: string; + amount: number; + dueDate: string; + status: 'Ready' | 'On Hold' | 'Overdue'; +} + +export interface Transaction { + id: string; + title: string; + partner: string; + amount: number; + date: string; // ISO string + type: 'in' | 'out'; + method: 'Stripe' | 'Bank Transfer' | 'Razorpay'; + fees: number; + net: number; + status: 'Completed' | 'Pending' | 'Failed'; +} + +export const mockSettlements: Settlement[] = [ + { + id: 's1', + partnerName: 'Neon Arena', + eventName: 'Summer Music Festival', + amount: 125000, + dueDate: '2026-02-05', + status: 'Ready', + }, + { + id: 's2', + partnerName: 'TopTier Promoters', + eventName: 'Comedy Night', + amount: 45000, + dueDate: '2026-02-06', + status: 'On Hold', + }, + { + id: 's3', + partnerName: 'TechFlow Solutions', + eventName: 'Tech Summit 2026', + amount: 85000, + dueDate: '2026-02-02', // Past date + status: 'Overdue', + }, + { + id: 's4', + partnerName: 'Global Sponsors Inc', + eventName: 'Corporate Gala', + amount: 250000, + dueDate: '2026-02-10', + status: 'Ready', + }, +]; + +export const mockTransactions: Transaction[] = [ + { + id: 't1', + title: 'Ticket Sales - Summer Fest', + partner: 'Neon Arena', + amount: 25000, + date: new Date().toISOString(), + type: 'in', + method: 'Razorpay', + fees: 1250, + net: 23750, + status: 'Completed', + }, + { + id: 't2', + title: 'Payout - Neon Arena', + partner: 'Neon Arena', + amount: 15000, + date: new Date().toISOString(), + type: 'out', + method: 'Bank Transfer', + fees: 0, + net: 15000, + status: 'Completed', + }, + { + id: 't3', + title: 'Ticket Sales - Comedy Night', + partner: 'TopTier Promoters', + amount: 4500, + date: new Date(Date.now() - 86400000).toISOString(), // Yesterday + type: 'in', + method: 'Stripe', + fees: 225, + net: 4275, + status: 'Completed', + }, + { + id: 't4', + title: 'Refund - User #442', + partner: 'Neon Arena', + amount: 1500, + date: new Date(Date.now() - 86400000).toISOString(), + type: 'out', + method: 'Razorpay', + fees: 0, + net: 1500, + status: 'Completed', + }, + { + id: 't5', + title: 'Ticket Sales - Tech Summit', + partner: 'TechFlow Solutions', + amount: 12000, + date: '2026-02-01T10:00:00Z', + type: 'in', + method: 'Razorpay', + fees: 600, + net: 11400, + status: 'Completed', + }, +]; diff --git a/src/features/financials/components/SettlementTable.tsx b/src/features/financials/components/SettlementTable.tsx new file mode 100644 index 0000000..91d4f13 --- /dev/null +++ b/src/features/financials/components/SettlementTable.tsx @@ -0,0 +1,116 @@ + +import { useState } from "react"; +import { format } from "date-fns"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from "@/components/ui/table"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { mockSettlements, Settlement } from "@/data/mockFinancialData"; +import { ArrowUpRight } from "lucide-react"; +import { toast } from "sonner"; + +export function SettlementTable() { + const [selected, setSelected] = useState([]); + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelected(mockSettlements.map(s => s.id)); + } else { + setSelected([]); + } + }; + + const handleSelectOne = (id: string, checked: boolean) => { + if (checked) { + setSelected(prev => [...prev, id]); + } else { + setSelected(prev => prev.filter(item => item !== id)); + } + }; + + const handleReleasePayout = () => { + toast.success(`Processing payouts for ${selected.length} partners`); + setSelected([]); + }; + + const getStatusBadge = (status: Settlement['status']) => { + switch (status) { + case 'Ready': + return Ready; + case 'On Hold': + return On Hold; + case 'Overdue': + return Overdue; + default: return null; + } + }; + + return ( +
+
+
+

Due for Settlement

+

Manage pending partner payouts

+
+ +
+ +
+ + + + + 0} + onCheckedChange={(checked) => handleSelectAll(checked as boolean)} + /> + + Partner Name + Event + Unsettled Amount + Due Date + Status + + + + {mockSettlements.map((settlement) => ( + + + handleSelectOne(settlement.id, checked as boolean)} + /> + + {settlement.partnerName} + {settlement.eventName} + + {settlement.amount.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })} + + + {format(new Date(settlement.dueDate), 'MMM dd, yyyy')} + + + {getStatusBadge(settlement.status)} + + + ))} + +
+
+
+ ); +} diff --git a/src/features/financials/components/TransactionDetailsSheet.tsx b/src/features/financials/components/TransactionDetailsSheet.tsx new file mode 100644 index 0000000..5273747 --- /dev/null +++ b/src/features/financials/components/TransactionDetailsSheet.tsx @@ -0,0 +1,93 @@ + +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Transaction } from "@/data/mockFinancialData"; +import { AlertCircle, CheckCircle2, XCircle } from "lucide-react"; + +interface TransactionDetailsSheetProps { + transaction: Transaction | null; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function TransactionDetailsSheet({ transaction, open, onOpenChange }: TransactionDetailsSheetProps) { + if (!transaction) return null; + + return ( + + + + Transaction Details + + ID: {transaction.id} + + + +
+
+
+ {transaction.status === 'Completed' ? ( + + ) : transaction.status === 'Failed' ? ( + + ) : ( + + )} +
+

{transaction.amount.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })}

+

{transaction.status}

+
+
+
+

{new Date(transaction.date).toLocaleDateString()}

+

{new Date(transaction.date).toLocaleTimeString()}

+
+
+ +
+

Breakdown

+ +
+
+ Gross Amount + {transaction.amount.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })} +
+
+ Platform Fees (5%) + - {transaction.fees.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })} +
+
+
+ Net Settlement + {transaction.net.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })} +
+
+
+ +
+

Metadata

+
+
+

Partner

+

{transaction.partner}

+
+
+

Method

+

{transaction.method}

+
+
+

Description

+

{transaction.title}

+
+
+
+
+ + + ); +} diff --git a/src/features/financials/components/TransactionList.tsx b/src/features/financials/components/TransactionList.tsx new file mode 100644 index 0000000..7d9f489 --- /dev/null +++ b/src/features/financials/components/TransactionList.tsx @@ -0,0 +1,114 @@ + +import { useState } from "react"; +import { format, isToday, isYesterday } from "date-fns"; +import { mockTransactions, Transaction } from "@/data/mockFinancialData"; +import { TransactionDetailsSheet } from "./TransactionDetailsSheet"; +import { ArrowUpRight, ArrowDownRight, CreditCard, Landmark, Wallet } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export function TransactionList() { + const [selectedTx, setSelectedTx] = useState(null); + const [open, setOpen] = useState(false); + + // Group transactions by date + const groupedTransactions = mockTransactions.reduce((groups, tx) => { + const date = new Date(tx.date); + let key = format(date, 'yyyy-MM-dd'); + if (isToday(date)) key = 'Today'; + else if (isYesterday(date)) key = 'Yesterday'; + else key = format(date, 'MMMM dd, yyyy'); + + if (!groups[key]) { + groups[key] = []; + } + groups[key].push(tx); + return groups; + }, {} as Record); + + const handleRowClick = (tx: Transaction) => { + setSelectedTx(tx); + setOpen(true); + }; + + const getMethodIcon = (method: Transaction['method']) => { + switch (method) { + case 'Stripe': return ; + case 'Bank Transfer': return ; + case 'Razorpay': return ; + default: return ; + } + }; + + return ( +
+
+

Recent Transactions

+ +
+ +
+ {Object.entries(groupedTransactions).map(([date, transactions]) => ( +
+

+ {date} +

+
+ {transactions.map((tx) => ( +
handleRowClick(tx)} + className={cn( + "flex items-center justify-between p-3 rounded-lg cursor-pointer transition-all", + "hover:bg-secondary/50 border border-transparent hover:border-border/50", + tx.type === 'out' ? "bg-error/5 hover:bg-error/10" : "bg-card" + )} + > +
+
+ {tx.type === 'in' ? : } +
+ +
+

{tx.title}

+
+ + {getMethodIcon(tx.method)} + {tx.method} + + + {format(new Date(tx.date), 'hh:mm a')} +
+
+
+ +
+

+ {tx.type === 'in' ? '+' : '-'}{tx.amount.toLocaleString('en-IN', { style: 'currency', currency: 'INR' })} +

+

+ {tx.status} +

+
+
+ ))} +
+
+ ))} +
+ + +
+ ); +} diff --git a/src/pages/Financials.tsx b/src/pages/Financials.tsx index cd678fb..2c45e2b 100644 --- a/src/pages/Financials.tsx +++ b/src/pages/Financials.tsx @@ -1,19 +1,23 @@ -import { IndianRupee, TrendingUp, Wallet, ArrowUpRight, ArrowDownRight } from 'lucide-react'; + +import { IndianRupee, TrendingUp, Wallet } from 'lucide-react'; import { AppLayout } from '@/components/layout/AppLayout'; import { formatCurrency, mockRevenueData } from '@/data/mockData'; +import { SettlementTable } from '@/features/financials/components/SettlementTable'; +import { TransactionList } from '@/features/financials/components/TransactionList'; export default function Financials() { const totalRevenue = mockRevenueData.reduce((sum, d) => sum + d.revenue, 0); const totalPayouts = mockRevenueData.reduce((sum, d) => sum + d.payouts, 0); - const platformFee = totalRevenue - totalPayouts; + // Calculating platform fee (approx 10% of revenue for mock) + const platformEarnings = totalRevenue * 0.12; return ( - - {/* Financial Overview */} -
+ {/* Financial Overview Cards */} +
@@ -21,10 +25,11 @@ export default function Financials() {

{formatCurrency(totalRevenue)}

-

Total Revenue (7d)

+

Total Revenue (All Time)

+
@@ -32,82 +37,52 @@ export default function Financials() {

{formatCurrency(totalPayouts)}

-

Partner Payouts (7d)

+

Partner Payouts (Processed)

-
-
-
- + + {/* Premium Platform Earnings Card */} +
+
+
+
-

{formatCurrency(platformFee)}

-

Platform Earnings (7d)

-
-
-
-
-
-
- -
-
-

{formatCurrency(845000)}

-

Pending Payouts

+

{formatCurrency(platformEarnings)}

+

Net Platform Earnings

+ {/* Decorative background glow */} +
- {/* Transaction History */} -
-
-

Recent Transactions

- +
+ {/* Main Section: Payouts Command Center (2/3 width) */} +
+ + + {/* Detailed Transaction List */} +
-
- {[ - { id: '1', type: 'in', title: 'Mumbai Music Festival', partner: 'Music Nights', amount: 489000, date: new Date() }, - { id: '2', type: 'out', title: 'Partner Payout', partner: 'TechConf India', amount: 245000, date: new Date(Date.now() - 1000 * 60 * 60 * 2) }, - { id: '3', type: 'in', title: 'Tech Summit 2024', partner: 'TechConf India', amount: 156000, date: new Date(Date.now() - 1000 * 60 * 60 * 5) }, - { id: '4', type: 'out', title: 'Partner Payout', partner: 'Music Nights', amount: 180000, date: new Date(Date.now() - 1000 * 60 * 60 * 8) }, - { id: '5', type: 'in', title: 'Night Club Party', partner: 'Music Nights', amount: 24000, date: new Date(Date.now() - 1000 * 60 * 60 * 12) }, - ].map((tx) => ( -
-
-
- {tx.type === 'in' ? ( - - ) : ( - - )} -
-
-

{tx.title}

-

{tx.partner}

-
-
-
-

- {tx.type === 'in' ? '+' : '-'}{formatCurrency(tx.amount)} -

-

- {tx.date.toLocaleDateString('en-IN', { - day: 'numeric', - month: 'short', - hour: '2-digit', - minute: '2-digit' - })} -

-
+ {/* Sidebar Section: Summary or Quick Actions (1/3 width) */} +
+ {/* Could add mini charts or notifications here */} +
+

Quick Actions

+
+ +
- ))} +