portfolio: render hidden × per row; empty state shows add form

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-27 14:55:29 +02:00
parent 84934827b8
commit 9a46a0daec
2 changed files with 32 additions and 5 deletions

View file

@ -180,17 +180,22 @@
function renderEmpty(mount) { function renderEmpty(mount) {
const expired = consumeBackupExpiredNotice(); const expired = consumeBackupExpiredNotice();
const notice = expired const notice = expired
? '<div class="settings-row__hint" style="margin-bottom:8px;">' + ? '<div class="empty-notice">Your encrypted cloud backup expired. ' +
'Your previous cloud backup couldn&rsquo;t be restored on this server. ' +
'Please re-upload your portfolio to refresh it.' + 'Please re-upload your portfolio to refresh it.' +
'</div>' '</div>'
: ''; : '';
var panel = document.getElementById('portfolio-panel');
if (panel) panel.classList.add('pf-empty');
mount.innerHTML = mount.innerHTML =
'<div class="empty" style="padding:16px;">' + '<div class="empty" style="padding:16px;">' +
notice + notice +
'No portfolio loaded in this browser. ' + 'Welcome — start by adding a position above, or ' +
'<a href="/settings#import">Import a portfolio CSV →</a>' + '<a href="/settings#import">import a CSV from your broker →</a>' +
'</div>'; '</div>';
// When empty, the add form should be visible by default — the
// edit module toggles it via the pf-empty class.
var form = document.getElementById('pf-add-form');
if (form) form.hidden = false;
} }
// Silently remove an unrecoverable cloud blob and re-render. The user // Silently remove an unrecoverable cloud blob and re-render. The user
@ -265,6 +270,9 @@
} }
function renderPanel(mount, pie, enriched, agg) { function renderPanel(mount, pie, enriched, agg) {
var panel = document.getElementById('portfolio-panel');
if (panel) panel.classList.remove('pf-empty');
const ccyPills = Object.keys(agg.by_currency) const ccyPills = Object.keys(agg.by_currency)
.sort((a, b) => agg.by_currency[b] - agg.by_currency[a]) .sort((a, b) => agg.by_currency[b] - agg.by_currency[a])
.map(c => { .map(c => {
@ -293,6 +301,10 @@
'<td class="num">' + lastDisplay + fxBadge + '</td>' + '<td class="num">' + lastDisplay + fxBadge + '</td>' +
'<td class="num ' + cls(p._ppl) + '">' + signed(p._ppl) + '</td>' + '<td class="num ' + cls(p._ppl) + '">' + signed(p._ppl) + '</td>' +
'<td class="num ' + cls(p._ppl_pct) + '">' + pct(p._ppl_pct) + '</td>' + '<td class="num ' + cls(p._ppl_pct) + '">' + pct(p._ppl_pct) + '</td>' +
'<td class="pf-row-del-cell">' +
'<button type="button" class="pf-row-del" data-idx="' + p._orig_idx + '" ' +
'title="Remove this position" aria-label="Remove">\xd7</button>' +
'</td>' +
'</tr>'; '</tr>';
}).join(''); }).join('');
@ -349,6 +361,7 @@
'<th class="num">Qty</th><th class="num">Avg</th>' + '<th class="num">Qty</th><th class="num">Avg</th>' +
'<th class="num">Last</th><th class="num">P/L</th>' + '<th class="num">Last</th><th class="num">P/L</th>' +
'<th class="num">%</th>' + '<th class="num">%</th>' +
'<th></th>' +
'</tr></thead>' + '</tr></thead>' +
'<tbody>' + rows + '</tbody>' + '<tbody>' + rows + '</tbody>' +
'</table>' + '</table>' +
@ -483,7 +496,7 @@
} }
const base = pie.base_currency || 'GBP'; const base = pie.base_currency || 'GBP';
const fx = (universeCache && universeCache.fx) || null; const fx = (universeCache && universeCache.fx) || null;
const enriched = pie.positions.map(p => enrichPosition(p, base, fx)) const enriched = pie.positions.map((p, i) => Object.assign(enrichPosition(p, base, fx), { _orig_idx: i }))
.sort((a, b) => (b._value || 0) - (a._value || 0)); .sort((a, b) => (b._value || 0) - (a._value || 0));
const agg = aggregate(enriched); const agg = aggregate(enriched);
renderPanel(mount, pie, enriched, agg); renderPanel(mount, pie, enriched, agg);

View file

@ -248,4 +248,18 @@
} }
dateInput.addEventListener('blur', fetchHistorical); dateInput.addEventListener('blur', fetchHistorical);
// ---- Per-row delete (event delegation) -----------------------------
panel.addEventListener('click', function (e) {
const btn = e.target.closest('.pf-row-del');
if (!btn) return;
const idx = parseInt(btn.dataset.idx, 10);
if (!Number.isInteger(idx)) return;
const pie = window.CassandraPortfolio.loadPie();
if (!pie || !pie.positions || idx < 0 || idx >= pie.positions.length) return;
pie.positions.splice(idx, 1);
window.CassandraPortfolio.savePie(pie);
window.CassandraPortfolio.mountAndRender();
});
})(); })();