'use client';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { ChevronDown, ChevronUp, Filter, RefreshCw, Plus, Edit2 } from 'lucide-react';
import PageShortcuts from '@/components/PageShortcuts';
import { equityLinks } from '@/lib/links';
import { VirtualTradeList } from '@/components/VirtualTradeList';
interface Account {
id: number;
name: string;
}
interface Trade {
id: number;
symbol: string;
trade_date: string;
trade_type: 'buy' | 'sell';
quantity: number;
price: number;
exchange: string;
order_id: string;
}
interface TradeGroup {
symbol: string;
accountId: number;
accountName: string;
totalBuyQuantity: number;
totalSellQuantity: number;
netQuantity: number;
totalBuyValue: number;
totalSellValue: number;
avgBuyPrice: number;
avgSellPrice: number;
currentPrice: number;
currentValue: number;
realizedPnL: number;
realizedPnLPercent: number;
unrealizedPnL: number;
unrealizedPnLPercent: number;
totalPnL: number;
status: 'active' | 'sold';
xirr: number | null;
trades: Trade[];
firstTradeDate: string;
lastTradeDate: string;
// For sold positions only
currentValueIfHeld?: number;
opportunityCost?: number;
opportunityCostPercent?: number;
}
interface TradebookData {
groups: TradeGroup[];
summary: {
totalStocks: number;
activeStocks: number;
soldStocks: number;
totalBuyValue: number;
totalSellValue: number;
totalRealizedPnL: number;
totalUnrealizedPnL: number;
totalInvestment: number;
totalCurrentValue: number;
// Opportunity cost metrics for sold positions
potentialLosses: number; // Total missed gains (sold too early)
potentialProfits: number; // Total avoided losses (good timing)
soldEarlyCount: number; // Number of stocks sold too early
soldWellCount: number; // Number of stocks sold at good timing
soldEarlyInvestment: number; // Original cost of stocks sold too early
soldWellInvestment: number; // Original cost of stocks sold at good timing
};
}
export default function TradebookPage() {
const [accounts, setAccounts] = useState<Account[]>([]);
const [selectedAccount, setSelectedAccount] = useState<string>('consolidated');
const [fromDate, setFromDate] = useState<string>('');
const [toDate, setToDate] = useState<string>('');
const [statusFilter, setStatusFilter] = useState<string>('active');
const [sortBy, setSortBy] = useState<string>('currentValue');
const [sortOrder, setSortOrder] = useState<string>('desc');
const [data, setData] = useState<TradebookData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
const [editingTrade, setEditingTrade] = useState<number | null>(null);
const [editValues, setEditValues] = useState<{ symbol?: string; quantity?: string; price?: string }>({});
const [bulkEditSymbol, setBulkEditSymbol] = useState<string>('');
const [newSymbolName, setNewSymbolName] = useState<string>('');
const [showBulkEdit, setShowBulkEdit] = useState(false);
const [showAddTrade, setShowAddTrade] = useState(false);
const [newTrade, setNewTrade] = useState({
accountId: '',
symbol: '',
tradeDate: new Date().toISOString().split('T')[0],
tradeType: 'buy' as 'buy' | 'sell',
quantity: '',
price: '',
exchange: 'NSE',
segment: 'EQ',
series: 'EQ',
});
useEffect(() => {
fetchAccounts();
fetchTradebook();
}, []);
useEffect(() => {
fetchTradebook();
}, [selectedAccount, fromDate, toDate, statusFilter, sortBy, sortOrder]);
const fetchAccounts = async () => {
try {
const response = await fetch('/api/accounts');
const result = await response.json();
if (result.success) {
setAccounts(result.accounts);
}
} catch (err) {
console.error('Failed to fetch accounts:', err);
}
};
const fetchTradebook = async () => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams();
if (selectedAccount && selectedAccount !== 'consolidated') {
params.append('accountId', selectedAccount);
}
if (fromDate) params.append('fromDate', fromDate);
if (toDate) params.append('toDate', toDate);
if (statusFilter) params.append('status', statusFilter);
if (sortBy) params.append('sortBy', sortBy);
if (sortOrder) params.append('sortOrder', sortOrder);
const response = await fetch(`/api/tradebook?${params.toString()}`);
const result = await response.json();
if (result.success) {
setData(result.data);
} else {
setError(result.error);
}
} catch (err: any) {
setError('Failed to fetch tradebook data');
} finally {
setLoading(false);
}
};
// Memoized callback functions for better performance
const toggleGroup = useCallback((key: string) => {
setExpandedGroups(prev => {
const newExpanded = new Set(prev);
if (newExpanded.has(key)) {
newExpanded.delete(key);
} else {
newExpanded.add(key);
}
return newExpanded;
});
}, []);
const formatDate = useCallback((dateString: string) => {
return new Date(dateString).toLocaleDateString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric',
});
}, []);
const formatCurrency = useCallback((amount: number) => {
return `₹${amount.toLocaleString('en-IN', { maximumFractionDigits: 2 })}`;
}, []);
const startEditingTrade = useCallback((trade: Trade) => {
setEditingTrade(trade.id);
setEditValues({
symbol: trade.symbol || '',
quantity: parseFloat(trade.quantity.toString()).toString(),
price: parseFloat(trade.price.toString()).toString(),
});
}, []);
const cancelEditing = useCallback(() => {
setEditingTrade(null);
setEditValues({});
}, []);
const saveTrade = async (tradeId: number) => {
try {
const response = await fetch(`/api/trades/${tradeId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(editValues),
});
const result = await response.json();
if (result.success) {
setEditingTrade(null);
setEditValues({});
fetchTradebook(); // Refresh data
} else {
alert('Error updating trade: ' + result.error);
}
} catch (err) {
console.error('Error saving trade:', err);
alert('Failed to save trade');
}
};
const handleBulkRename = async () => {
if (!bulkEditSymbol || !newSymbolName) {
alert('Please enter both old and new symbol names');
return;
}
if (bulkEditSymbol === newSymbolName) {
alert('Old and new symbol names must be different');
return;
}
const confirm = window.confirm(
`Are you sure you want to rename all "${bulkEditSymbol}" trades to "${newSymbolName}"?`
);
if (!confirm) return;
try {
const params: any = {
action: 'rename_symbol',
oldSymbol: bulkEditSymbol,
newSymbol: newSymbolName,
};
if (selectedAccount && selectedAccount !== 'consolidated') {
params.accountId = selectedAccount;
}
const response = await fetch('/api/trades/bulk-update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
const result = await response.json();
if (result.success) {
alert(`Successfully updated ${result.affectedRows} trade(s)`);
setBulkEditSymbol('');
setNewSymbolName('');
setShowBulkEdit(false);
fetchTradebook(); // Refresh data
} else {
alert('Error: ' + result.error);
}
} catch (err) {
console.error('Error in bulk rename:', err);
alert('Failed to rename symbol');
}
};
const handleAddTrade = async () => {
// Validate required fields
if (!newTrade.accountId || !newTrade.symbol || !newTrade.tradeDate ||
!newTrade.quantity || !newTrade.price) {
alert('Please fill in all required fields');
return;
}
try {
const response = await fetch('/api/trades', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTrade),
});
const result = await response.json();
if (result.success) {
alert('Trade added successfully!');
// Reset form
setNewTrade({
accountId: newTrade.accountId, // Keep account selected
symbol: '',
tradeDate: new Date().toISOString().split('T')[0],
tradeType: 'buy',
quantity: '',
price: '',
exchange: 'NSE',
segment: 'EQ',
series: 'EQ',
});
setShowAddTrade(false);
fetchTradebook(); // Refresh data
} else {
alert('Error adding trade: ' + result.error);
}
} catch (err) {
console.error('Error adding trade:', err);
alert('Failed to add trade');
}
};
return (
<div className="min-h-screen bg-gray-50">
<PageShortcuts links={equityLinks} title="Equity" />
<div className="p-8">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Tradebook</h1>
<p className="text-gray-700">View all imported trades grouped by script with P&L analysis</p>
</div>
{/* Filters */}
<div className="bg-white rounded-lg shadow p-6 mb-6">
<div className="flex items-center gap-2 mb-4">
<Filter className="h-5 w-5 text-gray-700" />
<h2 className="text-lg font-semibold text-gray-900">Filters & Sorting</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
{/* Account Filter */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Account
</label>
<select
value={selectedAccount}
onChange={(e) => setSelectedAccount(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
>
<option value="consolidated">All Accounts</option>
{accounts.map((account) => (
<option key={account.id} value={account.id}>
{account.name}
</option>
))}
</select>
</div>
{/* From Date */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
From Date
</label>
<input
type="date"
value={fromDate}
onChange={(e) => setFromDate(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
{/* To Date */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
To Date
</label>
<input
type="date"
value={toDate}
onChange={(e) => setToDate(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Status Filter */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Position Status
</label>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
>
<option value="all">All Positions</option>
<option value="active">Active Holdings</option>
<option value="sold">Sold Holdings</option>
</select>
</div>
{/* Sort By */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Sort By
</label>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
>
<option value="currentValue">Current Value (Active) / Value If Held (Sold)</option>
<option value="totalPnL">Total P&L</option>
<option value="xirr">XIRR %</option>
<option value="opportunityCost">Opportunity Cost</option>
<option value="symbol">Symbol (A-Z)</option>
</select>
</div>
{/* Sort Order */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Sort Order
</label>
<select
value={sortOrder}
onChange={(e) => setSortOrder(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
>
<option value="desc">Highest to Lowest</option>
<option value="asc">Lowest to Highest</option>
</select>
</div>
</div>
</div>
{/* Bulk Edit Tool */}
<div className="bg-white rounded-lg shadow p-4 mb-6">
<div className="flex items-center justify-between">
<button
onClick={() => setShowBulkEdit(!showBulkEdit)}
className="flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium"
>
<Edit2 className="h-4 w-4" />
<span>Bulk Rename Symbol</span>
{showBulkEdit ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</button>
</div>
{showBulkEdit && (
<div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 items-end">
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Old Symbol Name
</label>
<input
type="text"
value={bulkEditSymbol}
onChange={(e) => setBulkEditSymbol(e.target.value.toUpperCase())}
placeholder="e.g., HDFCBANK"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
New Symbol Name
</label>
<input
type="text"
value={newSymbolName}
onChange={(e) => setNewSymbolName(e.target.value.toUpperCase())}
placeholder="e.g., HDFCBANK_NEW"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
<div>
<button
onClick={handleBulkRename}
disabled={!bulkEditSymbol || !newSymbolName}
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
<RefreshCw className="h-4 w-4" />
<span>Rename All Trades</span>
</button>
</div>
<div className="col-span-3 text-sm text-gray-700">
<strong>Note:</strong> This will rename all trades with symbol "{bulkEditSymbol}"
{selectedAccount !== 'consolidated' && ` in the selected account`} to "{newSymbolName}".
Use this when a stock symbol changes (e.g., merger, name change).
</div>
</div>
)}
</div>
{/* Manual Trade Entry */}
<div className="bg-white rounded-lg shadow p-4 mb-6">
<div className="flex items-center justify-between">
<button
onClick={() => setShowAddTrade(!showAddTrade)}
className="flex items-center gap-2 text-green-600 hover:text-green-700 font-medium"
>
<Plus className="h-4 w-4" />
<span>Add New Trade</span>
{showAddTrade ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</button>
</div>
{showAddTrade && (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Account */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Account <span className="text-red-600">*</span>
</label>
<select
value={newTrade.accountId}
onChange={(e) => setNewTrade({ ...newTrade, accountId: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
>
<option value="">Select Account</option>
{accounts.map((account) => (
<option key={account.id} value={account.id}>
{account.name}
</option>
))}
</select>
</div>
{/* Symbol */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Symbol <span className="text-red-600">*</span>
</label>
<input
type="text"
value={newTrade.symbol}
onChange={(e) => setNewTrade({ ...newTrade, symbol: e.target.value.toUpperCase() })}
placeholder="e.g., RELIANCE"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
/>
</div>
{/* Trade Date */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Trade Date <span className="text-red-600">*</span>
</label>
<input
type="date"
value={newTrade.tradeDate}
onChange={(e) => setNewTrade({ ...newTrade, tradeDate: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
/>
</div>
{/* Trade Type */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Trade Type <span className="text-red-600">*</span>
</label>
<select
value={newTrade.tradeType}
onChange={(e) => setNewTrade({ ...newTrade, tradeType: e.target.value as 'buy' | 'sell' })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
>
<option value="buy">BUY</option>
<option value="sell">SELL</option>
</select>
</div>
{/* Quantity */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Quantity <span className="text-red-600">*</span>
</label>
<input
type="number"
step="0.01"
value={newTrade.quantity}
onChange={(e) => setNewTrade({ ...newTrade, quantity: e.target.value })}
placeholder="e.g., 10"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
/>
</div>
{/* Price */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Price <span className="text-red-600">*</span>
</label>
<input
type="number"
step="0.01"
value={newTrade.price}
onChange={(e) => setNewTrade({ ...newTrade, price: e.target.value })}
placeholder="e.g., 2500.50"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
required
/>
</div>
{/* Exchange */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Exchange
</label>
<select
value={newTrade.exchange}
onChange={(e) => setNewTrade({ ...newTrade, exchange: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
>
<option value="NSE">NSE</option>
<option value="BSE">BSE</option>
<option value="NFO">NFO</option>
<option value="MCX">MCX</option>
</select>
</div>
{/* Segment */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Segment
</label>
<input
type="text"
value={newTrade.segment}
onChange={(e) => setNewTrade({ ...newTrade, segment: e.target.value })}
placeholder="e.g., EQ"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
{/* Series */}
<div>
<label className="block text-sm font-medium text-gray-800 mb-2">
Series
</label>
<input
type="text"
value={newTrade.series}
onChange={(e) => setNewTrade({ ...newTrade, series: e.target.value })}
placeholder="e.g., EQ"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 text-gray-900 placeholder:text-gray-500"
/>
</div>
</div>
{/* Trade Value Preview */}
{newTrade.quantity && newTrade.price && (
<div className="p-4 bg-blue-50 rounded-lg">
<div className="text-sm text-gray-700">Trade Value:</div>
<div className="text-2xl font-bold text-gray-900">
₹{(parseFloat(newTrade.quantity) * parseFloat(newTrade.price)).toLocaleString('en-IN', { maximumFractionDigits: 2 })}
</div>
</div>
)}
{/* Action Buttons */}
<div className="flex gap-3">
<button
onClick={handleAddTrade}
disabled={!newTrade.accountId || !newTrade.symbol || !newTrade.quantity || !newTrade.price}
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:bg-gray-300 disabled:cursor-not-allowed flex items-center gap-2"
>
<Plus className="h-4 w-4" />
<span>Add Trade</span>
</button>
<button
onClick={() => {
setShowAddTrade(false);
setNewTrade({
accountId: '',
symbol: '',
tradeDate: new Date().toISOString().split('T')[0],
tradeType: 'buy',
quantity: '',
price: '',
exchange: 'NSE',
segment: 'EQ',
series: 'EQ',
});
}}
className="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300"
>
Cancel
</button>
</div>
<div className="text-sm text-gray-700">
<strong>Note:</strong> Fields marked with <span className="text-red-600">*</span> are required.
</div>
</div>
)}
</div>
{/* Summary Cards */}
{data && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
{/* Active Stocks - Hide when viewing only sold positions */}
{statusFilter !== 'sold' && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-green-500">
<div className="text-sm text-gray-600">Active Holdings</div>
<div className="text-2xl font-bold text-green-600">{data.summary.activeStocks}</div>
</div>
)}
{/* Sold Stocks - Hide when viewing only active positions */}
{statusFilter !== 'active' && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-gray-400">
<div className="text-sm text-gray-600">Sold Positions</div>
<div className="text-2xl font-bold text-gray-600">{data.summary.soldStocks}</div>
</div>
)}
{/* Total Investment - Show only for active or all */}
{statusFilter !== 'sold' && data.summary.totalInvestment > 0 && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-blue-500">
<div className="text-sm text-gray-600">Total Investment</div>
<div className="text-lg font-bold text-gray-900">
{formatCurrency(data.summary.totalInvestment)}
</div>
</div>
)}
{/* Current Value - Show only for active or all */}
{statusFilter !== 'sold' && data.summary.totalCurrentValue > 0 && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-purple-500">
<div className="text-sm text-gray-600">Current Value</div>
<div className="text-lg font-bold text-gray-900">
{formatCurrency(data.summary.totalCurrentValue)}
</div>
</div>
)}
{/* Realized P&L - Always visible */}
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-orange-500">
<div className="text-sm text-gray-600">Realized P&L</div>
<div className={`text-lg font-bold ${data.summary.totalRealizedPnL >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{formatCurrency(data.summary.totalRealizedPnL)}
</div>
{data.summary.totalBuyValue > 0 && (
<div className="text-xs text-gray-500 mt-1">
{((data.summary.totalRealizedPnL / data.summary.totalBuyValue) * 100).toFixed(2)}% of investment
</div>
)}
</div>
{/* Unrealized P&L - Show only for active or all */}
{statusFilter !== 'sold' && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-teal-500">
<div className="text-sm text-gray-600">Unrealized P&L</div>
<div className={`text-lg font-bold ${data.summary.totalUnrealizedPnL >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{formatCurrency(data.summary.totalUnrealizedPnL)}
</div>
{data.summary.totalInvestment > 0 && (
<div className="text-xs text-gray-500 mt-1">
{((data.summary.totalUnrealizedPnL / data.summary.totalInvestment) * 100).toFixed(2)}% of investment
</div>
)}
</div>
)}
{/* Total P&L - Show when both are relevant */}
{statusFilter !== 'sold' && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-blue-500">
<div className="text-sm text-gray-600">Total P&L</div>
<div className={`text-lg font-bold ${(data.summary.totalRealizedPnL + data.summary.totalUnrealizedPnL) >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{formatCurrency(data.summary.totalRealizedPnL + data.summary.totalUnrealizedPnL)}
</div>
<div className="text-xs text-gray-500 mt-1">
Realized + Unrealized
</div>
</div>
)}
{/* Potential Losses (Sold Too Early) - Show only for sold or all */}
{statusFilter !== 'active' && data.summary.potentialLosses > 0 && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-orange-500">
<div className="text-sm text-gray-600 flex items-center gap-1">
<span>⚠️ Missed Gains</span>
</div>
<div className="text-lg font-bold text-orange-600">
-{formatCurrency(data.summary.potentialLosses)}
</div>
<div className="text-xs text-gray-500 mt-1">
{data.summary.soldEarlyCount} stock{data.summary.soldEarlyCount !== 1 ? 's' : ''} sold too early
</div>
<div className="text-xs text-gray-700 mt-1 pt-1 border-t border-gray-200">
Original Cost: {formatCurrency(data.summary.soldEarlyInvestment)}
</div>
</div>
)}
{/* Potential Profits (Good Timing) - Show only for sold or all */}
{statusFilter !== 'active' && data.summary.potentialProfits > 0 && (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-green-500">
<div className="text-sm text-gray-600 flex items-center gap-1">
<span>✅ Avoided Losses</span>
</div>
<div className="text-lg font-bold text-green-600">
+{formatCurrency(data.summary.potentialProfits)}
</div>
<div className="text-xs text-gray-500 mt-1">
{data.summary.soldWellCount} stock{data.summary.soldWellCount !== 1 ? 's' : ''} sold at good timing
</div>
<div className="text-xs text-gray-700 mt-1 pt-1 border-t border-gray-200">
Original Cost: {formatCurrency(data.summary.soldWellInvestment)}
</div>
</div>
)}
</div>
)}
{/* Loading State */}
{loading && (
<div className="flex items-center justify-center py-12 bg-white rounded-lg shadow">
<div className="text-gray-700">Loading tradebook data...</div>
</div>
)}
{/* Error State */}
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 mb-6">
{error}
</div>
)}
{/* Tradebook Groups - Optimized with Virtual Scrolling */}
{!loading && data && data.groups.length > 0 && (
<VirtualTradeList
groups={data.groups}
expandedGroups={expandedGroups}
editingTrade={editingTrade}
editValues={editValues}
onToggleGroup={toggleGroup}
onStartEdit={startEditingTrade}
onSave={saveTrade}
onCancelEdit={cancelEditing}
onEditChange={setEditValues}
formatDate={formatDate}
formatCurrency={formatCurrency}
/>
)}
{/* Empty State */}
{!loading && data && data.groups.length === 0 && (
<div className="text-center py-12 bg-white rounded-lg shadow">
<p className="text-gray-700 mb-4">No trades found with the selected filters</p>
<p className="text-sm text-gray-700">
Try adjusting your filters or import tradebook data
</p>
</div>
)}
</div>
</div>
</div>
);
}