Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/components/CorporateTreasuryDashboard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-4">
<div className="flex items-center gap-2 mb-2">
<Building2 className="h-5 w-5 text-primary" />
<h2 className="text-xl font-bold">{orgName} — Treasury</h2>
</div>

{/* Overview */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Treasury Balance</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-end justify-between">
<span className="text-3xl font-bold">${totalBalance.toLocaleString()} USDC</span>
<span className={`flex items-center gap-1 text-sm font-medium ${isUp ? 'text-green-600' : 'text-red-500'}`}>
{isUp ? <TrendingUp className="h-4 w-4" /> : <TrendingDown className="h-4 w-4" />}
{isUp ? '+' : ''}{balanceTrend}% this month
</span>
</div>
</CardContent>
</Card>

{/* Wallet allocation breakdown */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Wallet Allocation</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{wallets.map((w) => (
<div key={w.name} className="space-y-1">
<div className="flex justify-between text-sm">
<span className="flex items-center gap-1">
<Wallet className="h-3 w-3 text-muted-foreground" />
{w.name}
</span>
<span className="font-medium">${w.balance.toLocaleString()} {w.currency}</span>
</div>
<div className="h-2 w-full rounded-full bg-muted overflow-hidden">
<div
className="h-full rounded-full bg-primary"
style={{ width: `${w.percentage}%` }}
/>
</div>
<span className="text-xs text-muted-foreground">{w.percentage}% of total</span>
</div>
))}
</CardContent>
</Card>

{/* Quick action */}
<button className="flex items-center gap-2 text-sm text-primary hover:underline">
<ArrowUpRight className="h-4 w-4" /> View full treasury report
</button>
</div>
);
}
103 changes: 103 additions & 0 deletions src/components/TeamAccountManagement.tsx
Original file line number Diff line number Diff line change
@@ -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<TeamRole, { label: string; icon: React.ReactNode; color: string }> = {
owner: { label: 'Owner', icon: <Crown className="h-3 w-3" />, color: 'text-yellow-600' },
admin: { label: 'Admin', icon: <Shield className="h-3 w-3" />, color: 'text-blue-600' },
operator: { label: 'Operator', icon: <Wrench className="h-3 w-3" />, 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 (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-primary" />
<h2 className="text-xl font-bold">Team Management</h2>
</div>
<button
onClick={onInvite}
className="flex items-center gap-1 text-sm bg-primary text-primary-foreground px-3 py-1.5 rounded-lg hover:bg-primary/90 transition-colors"
>
<UserPlus className="h-4 w-4" /> Invite
</button>
</div>

<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{members.length} member{members.length !== 1 ? 's' : ''}
</CardTitle>
</CardHeader>
<CardContent className="divide-y divide-border">
{members.map((m) => {
const meta = ROLE_META[m.role];
return (
<div key={m.id} className="flex items-center justify-between py-3 first:pt-0 last:pb-0">
<div>
<p className="text-sm font-medium">{m.name}</p>
<p className="text-xs text-muted-foreground">{m.email}</p>
</div>
<div className="flex items-center gap-3">
<span className={`flex items-center gap-1 text-xs font-medium ${meta.color}`}>
{meta.icon} {meta.label}
</span>
{m.role !== 'owner' && (
<div className="flex items-center gap-1">
{onChangeRole && (
<select
value={m.role}
onChange={(e) => onChangeRole(m.id, e.target.value as TeamRole)}
className="text-xs border rounded px-1 py-0.5 bg-background"
>
<option value="admin">Admin</option>
<option value="operator">Operator</option>
</select>
)}
{onRemove && (
<button
onClick={() => onRemove(m.id)}
className="text-xs text-destructive hover:underline ml-1"
>
Remove
</button>
)}
</div>
)}
</div>
</div>
);
})}
</CardContent>
</Card>
</div>
);
}
105 changes: 105 additions & 0 deletions src/components/TransactionVolumeForecasting.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-4">
<div className="flex items-center gap-2 mb-2">
<TrendingUp className="h-5 w-5 text-primary" />
<h2 className="text-xl font-bold">Volume Forecasting</h2>
</div>

{/* Summary cards */}
<div className="grid grid-cols-2 gap-3">
<Card>
<CardContent className="p-4">
<p className="text-xs text-muted-foreground flex items-center gap-1">
<Calendar className="h-3 w-3" /> Weekly forecast
</p>
<p className="text-xl font-bold mt-1">${weeklyForecast.toLocaleString()}</p>
<p className="text-xs text-muted-foreground">{currency}</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<p className="text-xs text-muted-foreground flex items-center gap-1">
<BarChart3 className="h-3 w-3" /> Daily avg (7-day)
</p>
<p className="text-xl font-bold mt-1">${dailyAvg.toLocaleString()}</p>
<p className="text-xs text-muted-foreground">{currency}</p>
</CardContent>
</Card>
</div>

{/* Bar chart */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Daily Volume — Actual vs Forecast</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-end gap-2 h-32">
{data.map((d) => (
<div key={d.label} className="flex-1 flex flex-col items-center gap-1">
<div className="w-full flex items-end gap-0.5 justify-center h-24">
{d.actual !== undefined && (
<div
className="flex-1 rounded-t bg-primary"
style={{ height: `${(d.actual / maxVal) * 100}%` }}
title={`Actual: $${d.actual.toLocaleString()}`}
/>
)}
<div
className="flex-1 rounded-t bg-primary/30 border border-primary/50"
style={{ height: `${(d.forecast / maxVal) * 100}%` }}
title={`Forecast: $${d.forecast.toLocaleString()}`}
/>
</div>
<span className="text-xs text-muted-foreground">{d.label}</span>
</div>
))}
</div>
<div className="flex items-center gap-4 mt-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1"><span className="inline-block h-2 w-3 rounded bg-primary" /> Actual</span>
<span className="flex items-center gap-1"><span className="inline-block h-2 w-3 rounded bg-primary/30 border border-primary/50" /> Forecast</span>
</div>
</CardContent>
</Card>
</div>
);
}
Loading
Loading