/* Dashboard-native portfolio editing. * * Owns: the EDIT button toggle, the add-position form behaviour * (ticker validation on blur, qty/cost inputs, date-mode historical * lookup, Add click), and per-row delete via event delegation. * * Reads/writes the portfolio via window.CassandraPortfolio.loadPie / * savePie / mountAndRender — the same surface portfolio.js exposes * for the CSV-import preview. */ (function () { 'use strict'; const panel = document.getElementById('portfolio-panel'); const editBtn = document.getElementById('pf-edit-btn'); const doneBtn = document.getElementById('pf-done-btn'); const form = document.getElementById('pf-add-form'); if (!panel || !editBtn || !doneBtn || !form) return; function enterEditMode() { panel.classList.add('pf-editing'); form.hidden = false; editBtn.hidden = true; doneBtn.hidden = false; editBtn.setAttribute('aria-pressed', 'true'); document.getElementById('pf-add-ticker').focus(); } function exitEditMode() { panel.classList.remove('pf-editing'); // Form stays visible when the pie is empty (empty-state UX handled // by portfolio.js setting the pf-empty class on the panel). if (!panel.classList.contains('pf-empty')) { form.hidden = true; } editBtn.hidden = false; doneBtn.hidden = true; editBtn.setAttribute('aria-pressed', 'false'); } editBtn.addEventListener('click', enterEditMode); doneBtn.addEventListener('click', exitEditMode); // ---- Ticker validation on blur ------------------------------------- const tickerInput = document.getElementById('pf-add-ticker'); const tickerStatus = document.getElementById('pf-add-ticker-status'); const costCurrencyEl = document.getElementById('pf-add-cost-currency'); const submitBtn = document.getElementById('pf-add-submit'); const warningEl = document.getElementById('pf-add-warning'); let validated = null; // {symbol, price, currency, as_of} or null function setStatus(el, text, kind) { el.textContent = text; el.className = 'pf-add-status' + (kind ? ' pf-add-status--' + kind : ''); } function updateSubmitState() { const qty = parseFloat(document.getElementById('pf-add-qty').value); const cost = parseFloat(document.getElementById('pf-add-cost').value); submitBtn.disabled = !( validated && qty > 0 && cost > 0 && isFinite(qty) && isFinite(cost) ); } function clearDuplicateWarning() { warningEl.hidden = true; warningEl.textContent = ''; } function showDuplicateWarning(existing) { warningEl.hidden = false; warningEl.textContent = `Already in your portfolio (${existing.qty} shares @ ` + `${existing.avg_cost.toFixed(2)}). Adding will create a duplicate row.`; } async function validateTicker() { const raw = tickerInput.value.trim().toUpperCase(); if (!raw) { validated = null; setStatus(tickerStatus, '', ''); costCurrencyEl.textContent = ''; clearDuplicateWarning(); updateSubmitState(); return; } setStatus(tickerStatus, 'checking…', 'pending'); try { const r = await fetch('/api/ticker/validate?symbol=' + encodeURIComponent(raw)); if (!r.ok) throw new Error('HTTP ' + r.status); const j = await r.json(); if (j.ok) { validated = j; setStatus( tickerStatus, '✓ ' + j.price.toFixed(2) + ' ' + (j.currency || ''), 'ok', ); costCurrencyEl.textContent = j.currency || ''; // Duplicate detection. const pie = window.CassandraPortfolio.loadPie(); const existing = pie && (pie.positions || []).find( p => (p.yahoo_ticker || '').toUpperCase() === j.symbol ); if (existing) showDuplicateWarning(existing); else clearDuplicateWarning(); } else { validated = null; setStatus(tickerStatus, '✗ ' + (j.error || 'not recognised'), 'err'); costCurrencyEl.textContent = ''; clearDuplicateWarning(); } } catch (e) { validated = null; setStatus(tickerStatus, '✗ couldn't validate — try again', 'err'); costCurrencyEl.textContent = ''; clearDuplicateWarning(); } updateSubmitState(); } tickerInput.addEventListener('blur', validateTicker); document.getElementById('pf-add-qty').addEventListener('input', updateSubmitState); document.getElementById('pf-add-cost').addEventListener('input', updateSubmitState); })();