diff --git a/src/components/CorporateTreasuryDashboard.tsx b/src/components/CorporateTreasuryDashboard.tsx new file mode 100644 index 0000000..5fff49d --- /dev/null +++ b/src/components/CorporateTreasuryDashboard.tsx @@ -0,0 +1,88 @@ +import { Building2, Wallet, TrendingUp, TrendingDown, ArrowUpRight } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +interface WalletAllocation { + name: string; + balance: number; + currency: string; + percentage: number; +} + +interface CorporateTreasuryDashboardProps { + orgName?: string; + totalBalance?: number; + wallets?: WalletAllocation[]; + balanceTrend?: number; // % change +} + +const DEFAULT_WALLETS: WalletAllocation[] = [ + { name: 'Operations', balance: 42500, currency: 'USDC', percentage: 55 }, + { name: 'Payroll', balance: 18300, currency: 'USDC', percentage: 24 }, + { name: 'Reserve', balance: 16200, currency: 'USDC', percentage: 21 }, +]; + +export function CorporateTreasuryDashboard({ + orgName = 'My Organization', + totalBalance = 77000, + wallets = DEFAULT_WALLETS, + balanceTrend = 4.2, +}: CorporateTreasuryDashboardProps) { + const isUp = balanceTrend >= 0; + + return ( +
+
+ +

{orgName} — Treasury

+
+ + {/* Overview */} + + + Total Treasury Balance + + +
+ ${totalBalance.toLocaleString()} USDC + + {isUp ? : } + {isUp ? '+' : ''}{balanceTrend}% this month + +
+
+
+ + {/* Wallet allocation breakdown */} + + + Wallet Allocation + + + {wallets.map((w) => ( +
+
+ + + {w.name} + + ${w.balance.toLocaleString()} {w.currency} +
+
+
+
+ {w.percentage}% of total +
+ ))} + + + + {/* Quick action */} + +
+ ); +} diff --git a/src/components/TeamAccountManagement.tsx b/src/components/TeamAccountManagement.tsx new file mode 100644 index 0000000..9edf6ab --- /dev/null +++ b/src/components/TeamAccountManagement.tsx @@ -0,0 +1,103 @@ +import { Users, UserPlus, Shield, Crown, Wrench } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +type TeamRole = 'owner' | 'admin' | 'operator'; + +interface TeamMember { + id: string; + name: string; + email: string; + role: TeamRole; + joinedAt: string; +} + +interface TeamAccountManagementProps { + members?: TeamMember[]; + onInvite?: () => void; + onChangeRole?: (id: string, role: TeamRole) => void; + onRemove?: (id: string) => void; +} + +const ROLE_META: Record = { + owner: { label: 'Owner', icon: , color: 'text-yellow-600' }, + admin: { label: 'Admin', icon: , color: 'text-blue-600' }, + operator: { label: 'Operator', icon: , color: 'text-green-600' }, +}; + +const DEFAULT_MEMBERS: TeamMember[] = [ + { id: '1', name: 'Alice Johnson', email: 'alice@corp.com', role: 'owner', joinedAt: '2024-01' }, + { id: '2', name: 'Bob Smith', email: 'bob@corp.com', role: 'admin', joinedAt: '2024-03' }, + { id: '3', name: 'Carol White', email: 'carol@corp.com', role: 'operator', joinedAt: '2024-06' }, +]; + +export function TeamAccountManagement({ + members = DEFAULT_MEMBERS, + onInvite, + onChangeRole, + onRemove, +}: TeamAccountManagementProps) { + return ( +
+
+
+ +

Team Management

+
+ +
+ + + + + {members.length} member{members.length !== 1 ? 's' : ''} + + + + {members.map((m) => { + const meta = ROLE_META[m.role]; + return ( +
+
+

{m.name}

+

{m.email}

+
+
+ + {meta.icon} {meta.label} + + {m.role !== 'owner' && ( +
+ {onChangeRole && ( + + )} + {onRemove && ( + + )} +
+ )} +
+
+ ); + })} +
+
+
+ ); +} diff --git a/src/components/TransactionVolumeForecasting.tsx b/src/components/TransactionVolumeForecasting.tsx new file mode 100644 index 0000000..fa5369d --- /dev/null +++ b/src/components/TransactionVolumeForecasting.tsx @@ -0,0 +1,105 @@ +import { TrendingUp, BarChart3, Calendar } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +interface ForecastPoint { + label: string; + actual?: number; + forecast: number; +} + +interface TransactionVolumeForecastingProps { + data?: ForecastPoint[]; + currency?: string; +} + +// Simple 7-day rolling average forecast from historical data +function computeForecast(history: number[]): number { + if (history.length === 0) return 0; + const window = history.slice(-7); + return Math.round(window.reduce((a, b) => a + b, 0) / window.length); +} + +const DEFAULT_DATA: ForecastPoint[] = [ + { label: 'Mon', actual: 12400, forecast: 11800 }, + { label: 'Tue', actual: 9800, forecast: 10200 }, + { label: 'Wed', actual: 15600, forecast: 13000 }, + { label: 'Thu', actual: 11200, forecast: 12100 }, + { label: 'Fri', actual: 18900, forecast: 15400 }, + { label: 'Sat', forecast: 13200 }, + { label: 'Sun', forecast: 11900 }, +]; + +export function TransactionVolumeForecasting({ + data = DEFAULT_DATA, + currency = 'USDC', +}: TransactionVolumeForecastingProps) { + const maxVal = Math.max(...data.map((d) => Math.max(d.actual ?? 0, d.forecast))); + const weeklyForecast = data.reduce((s, d) => s + (d.forecast ?? 0), 0); + const historicalVals = data.filter((d) => d.actual !== undefined).map((d) => d.actual!); + const dailyAvg = computeForecast(historicalVals); + + return ( +
+
+ +

Volume Forecasting

+
+ + {/* Summary cards */} +
+ + +

+ Weekly forecast +

+

${weeklyForecast.toLocaleString()}

+

{currency}

+
+
+ + +

+ Daily avg (7-day) +

+

${dailyAvg.toLocaleString()}

+

{currency}

+
+
+
+ + {/* Bar chart */} + + + Daily Volume — Actual vs Forecast + + +
+ {data.map((d) => ( +
+
+ {d.actual !== undefined && ( +
+ )} +
+
+ {d.label} +
+ ))} +
+
+ Actual + Forecast +
+ + +
+ ); +} diff --git a/src/components/TransferSchedulingCalendar.tsx b/src/components/TransferSchedulingCalendar.tsx new file mode 100644 index 0000000..9528db9 --- /dev/null +++ b/src/components/TransferSchedulingCalendar.tsx @@ -0,0 +1,106 @@ +import { CalendarDays, Clock, Pencil, Trash2 } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +interface ScheduledTransfer { + id: string; + recipientName: string; + amount: number; + currency: string; + scheduledDate: string; // ISO date string + status: 'pending' | 'processing' | 'completed' | 'cancelled'; +} + +interface TransferSchedulingCalendarProps { + transfers?: ScheduledTransfer[]; + onEdit?: (id: string) => void; + onCancel?: (id: string) => void; +} + +const STATUS_STYLES: Record = { + pending: 'bg-yellow-100 text-yellow-700', + processing: 'bg-blue-100 text-blue-700', + completed: 'bg-green-100 text-green-700', + cancelled: 'bg-gray-100 text-gray-500', +}; + +const DEFAULT_TRANSFERS: ScheduledTransfer[] = [ + { id: '1', recipientName: 'Maria Garcia', amount: 500, currency: 'USDC', scheduledDate: '2026-06-02', status: 'pending' }, + { id: '2', recipientName: 'John Doe', amount: 1200, currency: 'USDC', scheduledDate: '2026-06-05', status: 'pending' }, + { id: '3', recipientName: 'Acme Corp', amount: 3000, currency: 'USDC', scheduledDate: '2026-06-10', status: 'processing' }, + { id: '4', recipientName: 'Sara Ahmed', amount: 250, currency: 'USDC', scheduledDate: '2026-05-28', status: 'completed' }, +]; + +function groupByDate(transfers: ScheduledTransfer[]): Map { + const map = new Map(); + for (const t of transfers) { + const key = t.scheduledDate; + map.set(key, [...(map.get(key) ?? []), t]); + } + return new Map([...map.entries()].sort()); +} + +export function TransferSchedulingCalendar({ + transfers = DEFAULT_TRANSFERS, + onEdit, + onCancel, +}: TransferSchedulingCalendarProps) { + const grouped = groupByDate(transfers); + const upcoming = transfers.filter((t) => t.status === 'pending' || t.status === 'processing'); + + return ( +
+
+ +

Scheduled Transfers

+
+ + + +

+ + {upcoming.length} upcoming transfer{upcoming.length !== 1 ? 's' : ''} +

+
+
+ + {[...grouped.entries()].map(([date, items]) => ( +
+

+ {new Date(date).toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })} +

+
+ {items.map((t) => ( + + +
+ {t.recipientName} + + {t.status} + +
+
+ + {t.amount.toLocaleString()} {t.currency} + {(t.status === 'pending') && ( +
+ {onEdit && ( + + )} + {onCancel && ( + + )} +
+ )} +
+
+ ))} +
+
+ ))} +
+ ); +}