read.markets/app/static/js/portfolio_edit.js
Giorgio Gilestro ee6966399c portfolio-edit: ticker validate on blur + duplicate warning
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:50:48 +02:00

127 lines
4.4 KiB
JavaScript

/* 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);
})();