phase-2: extract shared utils

This commit is contained in:
Kazuma
2026-06-04 11:06:30 -04:00
parent b75e8bda72
commit 0a0a368b87
49 changed files with 299 additions and 120 deletions
+11 -34
View File
@@ -3,6 +3,7 @@
import MarketContext from '$lib/MarketContext.svelte';
import Spinner from '$lib/Spinner.svelte';
import { addHolding, removeHolding } from '$lib/api.js';
import { sigOrd, fmt, fmtShort, glClass, advClass } from '$lib/utils.js';
let { data: _data } = $props(); // unused — we load client-side
@@ -142,8 +143,6 @@
let sortCol = $state('ticker');
let sortDir = $state(1); // 1 = asc, -1 = desc
const sigOrd = s => ({'✅ Strong Buy':0,'⚡ Momentum':1,'🔄 Neutral':2,'⚠️ Speculation':3,'❌ Avoid':4})[s] ?? 5;
function toggleSort(col) {
if (sortCol === col) sortDir = sortDir === 1 ? -1 : 1;
else { sortCol = col; sortDir = 1; }
@@ -172,23 +171,6 @@
const sortIcon = (col) => sortCol !== col ? '⇅' : sortDir === 1 ? '↑' : '↓';
const fmt = (n) => n != null
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n)
: '—';
const fmtShort = (n) => n != null
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n)
: '—';
const glClass = (pct) => parseFloat(pct) >= 0 ? 'green' : 'red';
const advClass = (a) => {
if (a?.includes('🟢')) return 'green';
if (a?.includes('🟡')) return 'yellow';
if (a?.includes('🟠')) return 'orange';
if (a?.includes('🔴')) return 'red';
return 'gray';
};
const totalValue = $derived(data?.advice?.reduce((s, a) => s + (parseFloat(a.marketValue) || 0), 0) ?? 0);
const totalCost = $derived(data?.advice?.reduce((s, a) => s + (a.costBasis ?? 0) * a.shares, 0) ?? 0);
@@ -221,20 +203,20 @@
<div class="form-title">Add Holding</div>
<div class="form-row">
<div class="field">
<label>Ticker</label>
<input bind:value={form.ticker} placeholder="AAPL" maxlength="10" style="text-transform:uppercase" />
<label for="form-ticker">Ticker</label>
<input id="form-ticker" bind:value={form.ticker} placeholder="AAPL" maxlength="10" style="text-transform:uppercase" />
</div>
<div class="field">
<label>Shares</label>
<input bind:value={form.shares} placeholder="10" type="number" min="0" step="any" />
<label for="form-shares">Shares</label>
<input id="form-shares" bind:value={form.shares} placeholder="10" type="number" min="0" step="any" />
</div>
<div class="field">
<label>Cost Basis / share</label>
<input bind:value={form.costBasis} placeholder="150.00" type="number" min="0" step="any" />
<label for="form-cost">Cost Basis / share</label>
<input id="form-cost" bind:value={form.costBasis} placeholder="150.00" type="number" min="0" step="any" />
</div>
<div class="field">
<label>Type</label>
<select bind:value={form.type}>
<label for="form-type">Type</label>
<select id="form-type" bind:value={form.type}>
<option value="stock">Stock</option>
<option value="etf">ETF</option>
<option value="bond">Bond</option>
@@ -242,8 +224,8 @@
</select>
</div>
<div class="field">
<label>Source</label>
<input bind:value={form.source} placeholder="Robinhood" />
<label for="form-source">Source</label>
<input id="form-source" bind:value={form.source} placeholder="Robinhood" />
</div>
<button class="btn-save" onclick={submitHolding} disabled={saving}>
{saving ? 'Saving…' : 'Save'}
@@ -569,11 +551,6 @@
margin-bottom: 14px;
}
.field input.readonly {
opacity: 0.5;
cursor: not-allowed;
}
.btn-cancel-edit {
background: transparent;
border: 1px solid #2d3f55;