/home/devscfvi/AblePro/js/import_ablePro.js
// ============================================================
// IMPORT VIEW — AblePro Bootstrap 5 UI override
// Replaces renderImport() + renderImportPreview() only.
// All parsers (parseBinanceCSV, parseIBKRTLGFile, etc.) are
// unchanged in import.js and still run as normal.
// ============================================================
const IMPORT_FORMATS = [
{
id: 'ibkr-tlg',
emoji: '🔶',
label: 'IBKR — .tlg File',
badge: 'Recommended',
badgeCls: 'bg-success',
sub: 'Reports → Activity → Trades → Export as .tlg (TraderVue format)',
color: 'warning',
},
{
id: 'ibkr',
emoji: '🟠',
label: 'IBKR — Transaction History CSV',
badge: null,
sub: 'Reports → Activity → Transaction History → Download CSV',
color: 'warning',
},
{
id: 'binance',
emoji: '🟡',
label: 'Binance Trade History',
badge: null,
sub: 'Orders → Trade History → Export → CSV',
color: 'warning',
},
{
id: 'coinbase',
emoji: '🔵',
label: 'Coinbase Transaction History',
badge: null,
sub: 'Reports → Generate Report → CSV',
color: 'primary',
},
{
id: 'app',
emoji: '📊',
label: 'App Export CSV',
badge: null,
sub: 'Re-import trades previously exported from TradeJournal',
color: 'secondary',
},
];
// ── Main render ───────────────────────────────────────────────
window.renderImport = function() {
return `
<div class="row justify-content-center">
<div class="col-xl-8 col-lg-10">
<!-- ── Step 1: Format ── -->
<div class="card mb-4">
<div class="card-header d-flex align-items-center gap-2 py-3">
<span class="avtar avtar-xs bg-light-primary text-primary fw-bold rounded-circle f-13">1</span>
<h5 class="mb-0">Choose Import Format</h5>
</div>
<div class="card-body p-3">
<div class="d-flex flex-column gap-2">
${IMPORT_FORMATS.map(fmt => {
const active = importState.format === fmt.id;
return `
<div class="import-fmt-card ${active ? 'active' : ''}"
onclick="selectImportFormat('${fmt.id}')"
style="cursor:pointer; border:2px solid ${active ? 'var(--bs-primary)' : 'var(--bs-border-color)'}; border-radius:12px; padding:.85rem 1rem; background:${active ? 'rgba(70,128,255,.05)' : '#fff'}; transition:all .18s">
<div class="d-flex align-items-center gap-3">
<span style="font-size:1.4rem;line-height:1">${fmt.emoji}</span>
<div class="flex-fill">
<div class="d-flex align-items-center gap-2 flex-wrap">
<span class="fw-semibold f-14">${fmt.label}</span>
${fmt.badge ? `<span class="badge ${fmt.badgeCls} f-11">${fmt.badge}</span>` : ''}
</div>
<div class="text-muted f-12 mt-1">${fmt.sub}</div>
</div>
${active ? `<i class="ti ti-circle-check f-20 text-primary flex-shrink-0"></i>` : `<i class="ti ti-circle f-20 text-muted opacity-25 flex-shrink-0"></i>`}
</div>
</div>`;
}).join('')}
</div>
</div>
</div>
<!-- ── Step 2: Upload ── -->
<div class="card mb-4">
<div class="card-header d-flex align-items-center gap-2 py-3">
<span class="avtar avtar-xs bg-light-primary text-primary fw-bold rounded-circle f-13">2</span>
<h5 class="mb-0">Upload File</h5>
</div>
<div class="card-body">
<input type="file" id="import-file-input" accept=".csv,.txt,.tlg"
onchange="handleImportFile(this)" class="d-none">
<div onclick="document.getElementById('import-file-input').click()"
class="import-dropzone"
style="border:2px dashed var(--bs-border-color); border-radius:14px; padding:2.5rem 1.5rem; text-align:center; cursor:pointer; transition:border-color .2s, background .2s;"
onmouseenter="this.style.borderColor='var(--bs-primary)';this.style.background='rgba(70,128,255,.03)'"
onmouseleave="this.style.borderColor='var(--bs-border-color)';this.style.background=''">
<i class="ti ti-cloud-upload f-48 text-muted opacity-40 d-block mb-3"></i>
<p class="fw-semibold mb-1 f-15" id="import-file-label">
${importState.fileName
? `<i class="ti ti-file-check text-success me-2"></i>${importState.fileName}`
: 'Click to upload or drag & drop'}
</p>
<p class="text-muted f-13 mb-0">CSV or .tlg files · Max 5MB</p>
</div>
${importState.fileName ? `
<div class="alert alert-success d-flex align-items-center gap-2 py-2 mt-3 mb-0">
<i class="ti ti-circle-check f-18"></i>
<span><strong>${importState.fileName}</strong> loaded — <strong>${importState.parsedTrades.length}</strong> trade(s) found</span>
</div>` : ''}
</div>
</div>
<!-- ── Step 3: Preview (injected by renderImportPreview) ── -->
<div id="import-preview"></div>
</div>
</div>`;
};
// ── Preview ───────────────────────────────────────────────────
window.renderImportPreview = function() {
const container = document.getElementById('import-preview');
if (!container) return;
const trades = importState.parsedTrades;
if (!trades || trades.length === 0) {
container.innerHTML = '';
return;
}
const isIBKR = importState.format === 'ibkr' || importState.format === 'ibkr-tlg';
// Summary counts
const wins = trades.filter(t => t.status === 'win').length;
const losses = trades.filter(t => t.status === 'loss').length;
const pending = trades.filter(t => !t.status || t.status === 'pending').length;
const totalPnl = trades.reduce((s, t) => {
if (t._ibkrPnl != null) return s + t._ibkrPnl;
if (t.status === 'win') return s + parseFloat(t.profitAmount || 0);
if (t.status === 'loss') return s - parseFloat(t.lossAmount || 0);
return s;
}, 0);
container.innerHTML = `
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between py-3">
<div class="d-flex align-items-center gap-2">
<span class="avtar avtar-xs bg-light-success text-success fw-bold rounded-circle f-13">3</span>
<h5 class="mb-0">Preview — ${trades.length} Trade${trades.length !== 1 ? 's' : ''}</h5>
</div>
<div class="d-flex align-items-center gap-3 f-13">
${wins > 0 ? `<span class="badge bg-light-success text-success px-2 py-1">${wins} Win</span>` : ''}
${losses > 0 ? `<span class="badge bg-light-danger text-danger px-2 py-1">${losses} Loss</span>` : ''}
${pending > 0 ? `<span class="badge bg-light-warning text-warning px-2 py-1">${pending} Open</span>` : ''}
${isIBKR && totalPnl !== 0 ? `
<span class="fw-bold ${totalPnl >= 0 ? 'text-success' : 'text-danger'}">
${totalPnl >= 0 ? '+' : ''}$${Math.abs(totalPnl).toFixed(2)} P&L
</span>` : ''}
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 f-13">
<thead class="table-light">
<tr>
<th>Symbol</th>
${isIBKR ? '<th>Date</th>' : ''}
<th class="text-end">Position</th>
<th class="text-end">Entry</th>
${isIBKR ? `
<th class="text-end">Exit</th>
<th class="text-end">Qty</th>
<th class="text-end">Fees</th>
<th class="text-end">Net P&L</th>
` : `
<th class="text-end">SL%</th>
<th class="text-end">TP%</th>
`}
<th>Status</th>
</tr>
</thead>
<tbody>
${trades.slice(0, 30).map(t => {
const pnl = t._ibkrPnl;
const statusBadge = {
win: '<span class="badge badge-win">WIN</span>',
loss: '<span class="badge badge-loss">LOSS</span>',
pending: '<span class="badge badge-pending">OPEN</span>',
}[t.status] || '<span class="badge badge-pending">OPEN</span>';
return `
<tr>
<td class="fw-semibold">${t.coin || '—'}</td>
${isIBKR ? `<td class="text-muted text-nowrap">${t.tradeDate || '—'}</td>` : ''}
<td class="text-end">${t.positionSize > 0 ? '$' + parseFloat(t.positionSize).toFixed(2) : '—'}</td>
<td class="text-end">${t.entryPrice > 0 ? '$' + parseFloat(t.entryPrice).toFixed(4) : '—'}</td>
${isIBKR ? `
<td class="text-end">${(t.actualExitPrice || 0) > 0 ? '$' + parseFloat(t.actualExitPrice).toFixed(4) : '—'}</td>
<td class="text-end text-muted">${t.quantity > 0 ? parseFloat(t.quantity).toFixed(2) : '—'}</td>
<td class="text-end text-muted">${t.fees > 0 ? '$' + parseFloat(t.fees).toFixed(2) : '—'}</td>
<td class="text-end fw-semibold ${pnl != null ? (pnl >= 0 ? 'text-success' : 'text-danger') : ''}">
${pnl != null ? (pnl >= 0 ? '+' : '') + '$' + parseFloat(pnl).toFixed(2) : '—'}
</td>
` : `
<td class="text-end text-danger">${t.stopLossPercent > 0 ? t.stopLossPercent + '%' : '—'}</td>
<td class="text-end text-success">${t.takeProfitPercent > 0 ? t.takeProfitPercent + '%' : '—'}</td>
`}
<td>${statusBadge}</td>
</tr>`;
}).join('')}
</tbody>
</table>
</div>
${trades.length > 30 ? `
<div class="text-center text-muted f-13 py-3 border-top">
<i class="ti ti-dots me-1"></i> Showing 30 of ${trades.length} trades
</div>` : ''}
</div>
<div class="card-footer">
${importState.format !== 'app' ? `
<div class="alert alert-warning d-flex align-items-start gap-2 py-2 mb-3">
<i class="ti ti-info-circle f-16 flex-shrink-0 mt-1"></i>
<span class="f-13">Exchange imports create <strong>pending</strong> trades. SL% and TP% will be 0 — you can update each trade after import.</span>
</div>` : ''}
<div class="d-flex align-items-center justify-content-between gap-3">
<div class="text-muted f-13">
<i class="ti ti-shield-check text-success me-1"></i>
No duplicates check is performed — make sure not to import the same file twice.
</div>
<button class="btn btn-primary px-4 fw-semibold" onclick="confirmImport()">
<i class="ti ti-file-import me-2"></i>Import ${trades.length} Trade${trades.length !== 1 ? 's' : ''}
</button>
</div>
</div>
</div>`;
};