solana / src /components /dashboard /TradeLog.tsx
dronesplace's picture
Initial upload
2acde71 verified
Raw
History Blame Contribute Delete
9.6 kB
import React, { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Activity,
Download,
Search,
TrendingUp,
TrendingDown,
ExternalLink,
Clock
} from 'lucide-react';
// Mock trade data
const mockTrades = [
{
id: '1',
timestamp: new Date('2024-01-07T14:30:00Z'),
targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
pair: 'SOL/USDC',
type: 'BUY',
amount: 2.5,
price: 98.45,
pnl: 156.80,
status: 'completed',
signature: '2uJYB9jKzZi8YMFQWyJ8UvBdJLnVcP9q3HrXj6K4T7N8L9R1'
},
{
id: '2',
timestamp: new Date('2024-01-07T13:15:00Z'),
targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
pair: 'RAY/SOL',
type: 'SELL',
amount: 150,
price: 0.045,
pnl: -23.40,
status: 'completed',
signature: '3vKYC0jLzZi9YNGRXyK9VvCeKMoWdQ0r4IsYk7L5U8O9M0S2'
},
{
id: '3',
timestamp: new Date('2024-01-07T12:45:00Z'),
targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8',
pair: 'ORCA/USDC',
type: 'BUY',
amount: 85.2,
price: 1.23,
pnl: 45.60,
status: 'completed',
signature: '4wLYD1kMzZj0YOHSYyL0WwDfLNpXeR1s5JtZl8M6V9P0N1T3'
},
{
id: '4',
timestamp: new Date('2024-01-07T11:20:00Z'),
targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8',
pair: 'SOL/USDC',
type: 'BUY',
amount: 1.8,
price: 97.20,
pnl: 0,
status: 'pending',
signature: '5xMYE2lNzZk1YPITZyM1XxEgMOqYfS2t6KuAm9N7W0Q1O2U4'
},
{
id: '5',
timestamp: new Date('2024-01-07T10:05:00Z'),
targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
pair: 'BONK/SOL',
type: 'SELL',
amount: 1000000,
price: 0.0000089,
pnl: 78.90,
status: 'completed',
signature: '6yNYF3mOzZl2YQJUAyN2YyFhNPrZgT3u7LvBn0O8X1R2P3V5'
}
];
export const TradeLog: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const filteredTrades = mockTrades.filter(trade =>
trade.pair.toLowerCase().includes(searchTerm.toLowerCase()) ||
trade.targetWallet.toLowerCase().includes(searchTerm.toLowerCase()) ||
trade.type.toLowerCase().includes(searchTerm.toLowerCase())
);
const handleExportTrades = () => {
// Create CSV content
const csvContent = [
['Timestamp', 'Target Wallet', 'Pair', 'Type', 'Amount', 'Price', 'P&L', 'Status', 'Signature'],
...filteredTrades.map(trade => [
trade.timestamp.toISOString(),
trade.targetWallet,
trade.pair,
trade.type,
trade.amount.toString(),
trade.price.toString(),
trade.pnl.toString(),
trade.status,
trade.signature
])
].map(row => row.join(',')).join('\n');
// Create and download file
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `trade-log-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'completed':
return <Badge variant="default" className="bg-profit text-profit-foreground">Completed</Badge>;
case 'pending':
return <Badge variant="secondary" className="bg-warning text-warning-foreground">Pending</Badge>;
case 'failed':
return <Badge variant="destructive">Failed</Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
};
const getPnLDisplay = (pnl: number, status: string) => {
if (status === 'pending') {
return <span className="text-muted-foreground">--</span>;
}
return (
<span className={pnl >= 0 ? 'text-profit' : 'text-loss'}>
{pnl >= 0 ? '+' : ''}${pnl.toFixed(2)}
</span>
);
};
return (
<Card className="bg-gradient-card border-border/50">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Activity className="h-5 w-5 text-primary" />
Trade Log
</CardTitle>
<div className="flex items-center gap-2">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search trades..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 w-64"
/>
</div>
<Button onClick={handleExportTrades} variant="outline" className="gap-2">
<Download className="h-4 w-4" />
Export CSV
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Time</TableHead>
<TableHead>Target Wallet</TableHead>
<TableHead>Pair</TableHead>
<TableHead>Type</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Price</TableHead>
<TableHead>P&L</TableHead>
<TableHead>Status</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTrades.map((trade, index) => (
<TableRow
key={trade.id}
className="animate-fade-in"
style={{ animationDelay: `${index * 0.05}s` }}
>
<TableCell>
<div className="flex items-center gap-1 text-sm">
<Clock className="h-3 w-3 text-muted-foreground" />
{trade.timestamp.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
})}
</div>
<div className="text-xs text-muted-foreground">
{trade.timestamp.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})}
</div>
</TableCell>
<TableCell>
<div className="font-mono text-xs">
{trade.targetWallet.slice(0, 6)}...{trade.targetWallet.slice(-4)}
</div>
</TableCell>
<TableCell>
<span className="font-medium">{trade.pair}</span>
</TableCell>
<TableCell>
<Badge
variant={trade.type === 'BUY' ? 'default' : 'secondary'}
className={trade.type === 'BUY' ? 'bg-profit text-profit-foreground' : 'bg-loss text-loss-foreground'}
>
{trade.type === 'BUY' ? (
<TrendingUp className="h-3 w-3 mr-1" />
) : (
<TrendingDown className="h-3 w-3 mr-1" />
)}
{trade.type}
</Badge>
</TableCell>
<TableCell>
<span className="font-mono text-sm">{trade.amount.toLocaleString()}</span>
</TableCell>
<TableCell>
<span className="font-mono text-sm">${trade.price.toFixed(4)}</span>
</TableCell>
<TableCell className="font-bold">
{getPnLDisplay(trade.pnl, trade.status)}
</TableCell>
<TableCell>
{getStatusBadge(trade.status)}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="sm"
className="gap-1"
onClick={() => window.open(`https://explorer.solana.com/tx/${trade.signature}?cluster=devnet`, '_blank')}
>
<ExternalLink className="h-3 w-3" />
View
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{filteredTrades.length === 0 && (
<div className="text-center py-8 text-muted-foreground">
<Activity className="h-12 w-12 mx-auto mb-3 opacity-50" />
<p>No trades found</p>
<p className="text-sm">
{searchTerm ? 'Try adjusting your search terms' : 'Start trading to see your trade history'}
</p>
</div>
)}
</CardContent>
</Card>
);
};