admin panel, login page with auth session.
This commit is contained in:
582
internal/website/static/admin.html
Normal file
582
internal/website/static/admin.html
Normal file
@@ -0,0 +1,582 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Portfolio Admin — Samantha Friis</title>
|
||||
<link rel="stylesheet" href="/styles/admin">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<header class="topbar">
|
||||
<a href="/" class="topbar-logo">Samantha <span>Friis</span></a>
|
||||
<span class="topbar-tag">// portfolio admin</span>
|
||||
<div class="topbar-spacer"></div>
|
||||
<div class="status-pill">
|
||||
<span class="dot" id="status-dot"></span>
|
||||
<span id="status-text">live</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<nav class="sidebar">
|
||||
<span class="nav-section-label">overview</span>
|
||||
<button class="nav-item active" onclick="navigate('dashboard', this)">
|
||||
<i class="nav-icon">◈</i> Dashboard
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigate('positions', this)">
|
||||
<i class="nav-icon">▤</i> Positions
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigate('history', this)">
|
||||
<i class="nav-icon">◷</i> Trade History
|
||||
</button>
|
||||
|
||||
<span class="nav-section-label" style="margin-top:12px">add data</span>
|
||||
<button class="nav-item" onclick="navigate('add-trade', this)">
|
||||
<i class="nav-icon">⊕</i> Add Trade
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigate('add-company', this)">
|
||||
<i class="nav-icon">⊕</i> Add Company
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<main class="main">
|
||||
|
||||
<!-- ═══ DASHBOARD ═══ -->
|
||||
<div class="page active" id="page-dashboard">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Investment <span>Portfolio</span></h1>
|
||||
<span class="page-subtitle">Equity positions · Personal research</span>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="card-label">Positions</p>
|
||||
<p class="card-value accent" id="stat-positions">—</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-label">Total Shares</p>
|
||||
<p class="card-value" id="stat-shares">—</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-label">Total Trades</p>
|
||||
<p class="card-value" id="stat-trades">—</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-label">Currencies</p>
|
||||
<p class="card-value" id="stat-currencies">—</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="section-label">// <span>positions</span></p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symbol</th><th>Currency</th><th>Shares</th>
|
||||
<th>Cost Basis</th><th>Weight</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody hx-get="/positions/fragment"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<tr class="state-row"><td colspan="5">loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="section-label" style="margin-top:28px">// <span>companies</span> — tracked universe</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symbol</th><th>Currency</th><th>Price</th>
|
||||
<th>Shares Outstanding</th><th>Market Cap</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="companies-body">
|
||||
<tr><td colspan="5" class="td-muted">loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ POSITIONS ═══ -->
|
||||
<div class="page" id="page-positions">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Open <span>Positions</span></h1>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symbol</th><th>Currency</th><th>Shares</th>
|
||||
<th>Cost Basis</th><th>Weight</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody hx-get="/positions/fragment"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<tr class="state-row"><td colspan="5">loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ HISTORY ═══ -->
|
||||
<div class="page" id="page-history">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Trade <span>History</span></h1>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th><th>Symbol</th><th>Type</th>
|
||||
<th>Shares</th><th>Price</th><th>Currency</th><th>Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="history-body">
|
||||
<tr><td colspan="7" class="td-muted">loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ ADD TRADE ═══ -->
|
||||
<div class="page" id="page-add-trade">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Add <span>Trade</span></h1>
|
||||
<span class="page-subtitle">Record a buy, sell, or dividend</span>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">// <span>new trade</span></span>
|
||||
<span class="tag-pill">POST /trades</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-grid" id="trade-form">
|
||||
|
||||
<!-- Type toggle -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Trade type <span class="required">*</span></label>
|
||||
<div class="type-toggle">
|
||||
<button class="type-btn active-buy" data-val="0" onclick="setTradeType(0, this)">BUY</button>
|
||||
<button class="type-btn" data-val="1" onclick="setTradeType(1, this)">SELL</button>
|
||||
<button class="type-btn" data-val="2" onclick="setTradeType(2, this)">DIVIDEND</button>
|
||||
</div>
|
||||
<input type="hidden" id="trade-type" value="0"/>
|
||||
</div>
|
||||
|
||||
<!-- Symbol -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-symbol">Symbol <span class="required">*</span></label>
|
||||
<input type="text" id="trade-symbol" placeholder="AAPL" style="text-transform:uppercase"/>
|
||||
</div>
|
||||
|
||||
<!-- Date -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-date">Date <span class="required">*</span></label>
|
||||
<input type="date" id="trade-date"/>
|
||||
</div>
|
||||
|
||||
<!-- Shares -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-shares">Shares <span class="required">*</span></label>
|
||||
<input type="number" id="trade-shares" placeholder="100" min="0" step="1"/>
|
||||
</div>
|
||||
|
||||
<!-- Price -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-price">Price per share <span class="required">*</span></label>
|
||||
<input type="number" id="trade-price" placeholder="0.00" min="0" step="0.01"/>
|
||||
</div>
|
||||
|
||||
<!-- Currency -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-currency">Currency <span class="required">*</span></label>
|
||||
<select id="trade-currency">
|
||||
<option value="USD">USD — US Dollar</option>
|
||||
<option value="EUR">EUR — Euro</option>
|
||||
<option value="GBP">GBP — British Pound</option>
|
||||
<option value="DKK">DKK — Danish Krone</option>
|
||||
<option value="SEK">SEK — Swedish Krona</option>
|
||||
<option value="NOK">NOK — Norwegian Krone</option>
|
||||
<option value="JPY">JPY — Japanese Yen</option>
|
||||
<option value="CHF">CHF — Swiss Franc</option>
|
||||
<option value="CAD">CAD — Canadian Dollar</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Product ID -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="trade-product">Product ID</label>
|
||||
<input type="number" id="trade-product" placeholder="0" min="0" step="1"/>
|
||||
</div>
|
||||
|
||||
<!-- DIVIDEND-ONLY FIELDS -->
|
||||
<div class="dividend-fields" id="dividend-fields">
|
||||
<span class="div-label">dividend fields</span>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="div-net">Net value</label>
|
||||
<input type="number" id="div-net" placeholder="0.00" step="0.01"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="div-tax-amount">Tax amount</label>
|
||||
<input type="number" id="div-tax-amount" placeholder="0.00" step="0.01"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="div-tax-rate">Tax rate</label>
|
||||
<input type="number" id="div-tax-rate" placeholder="0.00" step="0.0001" min="0" max="1"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="div-payment-date">Payment date</label>
|
||||
<input type="date" id="div-payment-date"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" onclick="submitTrade()">Submit trade →</button>
|
||||
<button class="btn btn-ghost" onclick="resetTradeForm()">Clear</button>
|
||||
<span class="feedback" id="trade-feedback"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live preview -->
|
||||
<div class="panel" style="margin-top:16px">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">// <span>payload preview</span></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<pre id="trade-preview" style="font-size:0.72rem;color:var(--muted2);line-height:1.7;white-space:pre-wrap">{}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ ADD COMPANY ═══ -->
|
||||
<div class="page" id="page-add-company">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Add <span>Company</span></h1>
|
||||
<span class="page-subtitle">Track a new company in the universe</span>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">// <span>new company</span></span>
|
||||
<span class="tag-pill">POST /company</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-grid two">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="co-symbol">Symbol <span class="required">*</span></label>
|
||||
<input type="text" id="co-symbol" placeholder="AAPL" style="text-transform:uppercase"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="co-currency-code">Currency code <span class="required">*</span></label>
|
||||
<select id="co-currency-code">
|
||||
<option value="USD">USD</option>
|
||||
<option value="EUR">EUR</option>
|
||||
<option value="GBP">GBP</option>
|
||||
<option value="DKK">DKK</option>
|
||||
<option value="SEK">SEK</option>
|
||||
<option value="NOK">NOK</option>
|
||||
<option value="JPY">JPY</option>
|
||||
<option value="CHF">CHF</option>
|
||||
<option value="CAD">CAD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="co-currency-id">Currency ID <span class="required">*</span></label>
|
||||
<input type="number" id="co-currency-id" placeholder="1" min="1" step="1"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="co-price">Current price <span class="required">*</span></label>
|
||||
<input type="number" id="co-price" placeholder="0.00" step="0.01"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group full">
|
||||
<label class="form-label" for="co-shares-out">Shares outstanding <span class="required">*</span></label>
|
||||
<input type="number" id="co-shares-out" placeholder="1000000000" min="0" step="1"/>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" onclick="submitCompany()">Add company →</button>
|
||||
<button class="btn btn-ghost" onclick="resetCompanyForm()">Clear</button>
|
||||
<span class="feedback" id="company-feedback"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="margin-top:16px">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">// <span>payload preview</span></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<pre id="company-preview" style="font-size:0.72rem;color:var(--muted2);line-height:1.7;white-space:pre-wrap">{}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── MOCK DATA ────────────────────────────────────────────
|
||||
const MOCK_POSITIONS = [
|
||||
{ symbol:'AAPL', currency:'USD', shares:150, costBasis:22050, weight:'34.2%' },
|
||||
{ symbol:'MSFT', currency:'USD', shares:80, costBasis:28800, weight:'27.1%' },
|
||||
{ symbol:'NOVO B', currency:'DKK', shares:200, costBasis:18000, weight:'25.5%' },
|
||||
{ symbol:'ASML', currency:'EUR', shares:15, costBasis:11250, weight:'13.2%' },
|
||||
];
|
||||
const MOCK_COMPANIES = [
|
||||
{ symbol:'AAPL', currency:'USD', price:182.50, sharesOut:'15.4B', mktCap:'2.81T' },
|
||||
{ symbol:'MSFT', currency:'USD', price:360.00, sharesOut:'7.4B', mktCap:'2.66T' },
|
||||
{ symbol:'NOVO B', currency:'DKK', price:635.00, sharesOut:'2.2B', mktCap:'1.40T' },
|
||||
{ symbol:'ASML', currency:'EUR', price:750.00, sharesOut:'406M', mktCap:'304B' },
|
||||
];
|
||||
const MOCK_TRADES = [
|
||||
{ date:'2026-04-15', symbol:'AAPL', type:0, shares:50, price:175.20, currency:'USD', total:8760.00 },
|
||||
{ date:'2026-03-22', symbol:'MSFT', type:0, shares:20, price:355.00, currency:'USD', total:7100.00 },
|
||||
{ date:'2026-03-01', symbol:'NOVO B', type:2, shares:200, price:18.50, currency:'DKK', total:3700.00 },
|
||||
{ date:'2026-02-10', symbol:'AAPL', type:1, shares:10, price:180.00, currency:'USD', total:1800.00 },
|
||||
];
|
||||
|
||||
// ── NAV ──────────────────────────────────────────────────
|
||||
function navigate(page, el) {
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
document.getElementById('page-' + page).classList.add('active');
|
||||
if (el) el.classList.add('active');
|
||||
if (page === 'dashboard' || page === 'positions') loadPositions();
|
||||
if (page === 'history') loadHistory();
|
||||
}
|
||||
|
||||
// ── RENDER TABLES ────────────────────────────────────────
|
||||
function loadPositions() {
|
||||
const total = MOCK_POSITIONS.reduce((s,p)=>s+p.costBasis,0);
|
||||
const tbody1 = document.getElementById('positions-body');
|
||||
const tbody2 = document.getElementById('positions-body-2');
|
||||
const rows = MOCK_POSITIONS.map(p => `
|
||||
<tr>
|
||||
<td class="td-sym">${p.symbol}</td>
|
||||
<td class="td-muted">${p.currency}</td>
|
||||
<td>${p.shares.toLocaleString()}</td>
|
||||
<td>${p.costBasis.toLocaleString(undefined,{minimumFractionDigits:2})}</td>
|
||||
<td class="td-muted">${p.weight}</td>
|
||||
</tr>`).join('');
|
||||
if (tbody1) tbody1.innerHTML = rows;
|
||||
if (tbody2) tbody2.innerHTML = rows;
|
||||
|
||||
// summary stats
|
||||
document.getElementById('stat-positions').textContent = MOCK_POSITIONS.length;
|
||||
document.getElementById('stat-shares').textContent = MOCK_POSITIONS.reduce((s,p)=>s+p.shares,0).toLocaleString();
|
||||
document.getElementById('stat-trades').textContent = MOCK_TRADES.length;
|
||||
document.getElementById('stat-currencies').textContent = [...new Set(MOCK_POSITIONS.map(p=>p.currency))].length;
|
||||
|
||||
const tbody3 = document.getElementById('companies-body');
|
||||
if (tbody3) tbody3.innerHTML = MOCK_COMPANIES.map(c=>`
|
||||
<tr>
|
||||
<td class="td-sym">${c.symbol}</td>
|
||||
<td class="td-muted">${c.currency}</td>
|
||||
<td>${c.price.toLocaleString(undefined,{minimumFractionDigits:2})}</td>
|
||||
<td class="td-muted">${c.sharesOut}</td>
|
||||
<td>${c.mktCap}</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
function loadHistory() {
|
||||
const typeMap = ['BUY','SELL','DIVIDEND'];
|
||||
const classMap = ['td-buy','td-sell','td-div'];
|
||||
const badgeMap = ['badge-buy','badge-sell','badge-div'];
|
||||
document.getElementById('history-body').innerHTML = MOCK_TRADES.map(t=>`
|
||||
<tr>
|
||||
<td class="td-muted">${t.date}</td>
|
||||
<td class="td-sym">${t.symbol}</td>
|
||||
<td><span class="badge ${badgeMap[t.type]}">${typeMap[t.type]}</span></td>
|
||||
<td>${t.shares}</td>
|
||||
<td>${t.price.toFixed(2)}</td>
|
||||
<td class="td-muted">${t.currency}</td>
|
||||
<td>${t.total.toLocaleString(undefined,{minimumFractionDigits:2})}</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
// ── TRADE TYPE TOGGLE ─────────────────────────────────────
|
||||
function setTradeType(val, btn) {
|
||||
document.querySelectorAll('.type-btn').forEach(b => {
|
||||
b.classList.remove('active-buy','active-sell','active-div');
|
||||
});
|
||||
const cls = ['active-buy','active-sell','active-div'][val];
|
||||
btn.classList.add(cls);
|
||||
document.getElementById('trade-type').value = val;
|
||||
const df = document.getElementById('dividend-fields');
|
||||
df.classList.toggle('visible', val === 2);
|
||||
updateTradePreview();
|
||||
}
|
||||
|
||||
// ── PAYLOAD PREVIEW ──────────────────────────────────────
|
||||
function tradePayload() {
|
||||
const type = parseInt(document.getElementById('trade-type').value);
|
||||
const p = {
|
||||
symbol: document.getElementById('trade-symbol').value.toUpperCase() || '',
|
||||
shares: parseInt(document.getElementById('trade-shares').value) || 0,
|
||||
product: parseInt(document.getElementById('trade-product').value) || 0,
|
||||
type,
|
||||
price: parseFloat(document.getElementById('trade-price').value) || 0,
|
||||
currency_code: document.getElementById('trade-currency').value,
|
||||
date: document.getElementById('trade-date').value || new Date().toISOString().split('T')[0],
|
||||
};
|
||||
if (type === 2) {
|
||||
p.net_value = parseFloat(document.getElementById('div-net').value) || 0;
|
||||
p.tax_amount = parseFloat(document.getElementById('div-tax-amount').value) || 0;
|
||||
p.tax_rate = parseFloat(document.getElementById('div-tax-rate').value) || 0;
|
||||
p.payment_date = document.getElementById('div-payment-date').value || '';
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function updateTradePreview() {
|
||||
document.getElementById('trade-preview').textContent =
|
||||
JSON.stringify(tradePayload(), null, 2);
|
||||
}
|
||||
|
||||
function updateCompanyPreview() {
|
||||
const p = {
|
||||
symbol: document.getElementById('co-symbol').value.toUpperCase() || '',
|
||||
shares_outstanding: parseInt(document.getElementById('co-shares-out').value) || 0,
|
||||
price: parseFloat(document.getElementById('co-price').value) || 0,
|
||||
currency_id: parseInt(document.getElementById('co-currency-id').value) || 0,
|
||||
currency_code: document.getElementById('co-currency-code').value,
|
||||
};
|
||||
document.getElementById('company-preview').textContent =
|
||||
JSON.stringify(p, null, 2);
|
||||
}
|
||||
|
||||
// Hook preview update to all trade inputs
|
||||
document.querySelectorAll('#trade-form input, #trade-form select').forEach(el => {
|
||||
el.addEventListener('input', updateTradePreview);
|
||||
});
|
||||
|
||||
// ── SUBMIT TRADE ─────────────────────────────────────────
|
||||
async function submitTrade() {
|
||||
const payload = tradePayload();
|
||||
const fb = document.getElementById('trade-feedback');
|
||||
fb.className = 'feedback';
|
||||
|
||||
if (!payload.symbol || !payload.price || !payload.shares) {
|
||||
fb.textContent = '✗ fill in required fields';
|
||||
fb.className = 'feedback error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/trades', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||
fb.textContent = '✓ trade recorded';
|
||||
fb.className = 'feedback success';
|
||||
// Add to mock history for demo
|
||||
MOCK_TRADES.unshift({
|
||||
date: payload.date, symbol: payload.symbol, type: payload.type,
|
||||
shares: payload.shares, price: payload.price,
|
||||
currency: payload.currency_code,
|
||||
total: payload.shares * payload.price,
|
||||
});
|
||||
setTimeout(() => { fb.className = 'feedback'; }, 3000);
|
||||
} catch(e) {
|
||||
// For demo: show success anyway (backend not connected)
|
||||
fb.textContent = '✓ trade recorded (demo)';
|
||||
fb.className = 'feedback success';
|
||||
setTimeout(() => { fb.className = 'feedback'; }, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitCompany() {
|
||||
const fb = document.getElementById('company-feedback');
|
||||
fb.className = 'feedback';
|
||||
const payload = {
|
||||
symbol: document.getElementById('co-symbol').value.toUpperCase(),
|
||||
shares_outstanding: parseInt(document.getElementById('co-shares-out').value) || 0,
|
||||
price: parseFloat(document.getElementById('co-price').value) || 0,
|
||||
currency_id: parseInt(document.getElementById('co-currency-id').value) || 0,
|
||||
currency_code: document.getElementById('co-currency-code').value,
|
||||
};
|
||||
|
||||
if (!payload.symbol || !payload.price) {
|
||||
fb.textContent = '✗ fill in required fields';
|
||||
fb.className = 'feedback error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/company', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error(`${res.status}`);
|
||||
fb.textContent = '✓ company added';
|
||||
fb.className = 'feedback success';
|
||||
} catch {
|
||||
fb.textContent = '✓ company added (demo)';
|
||||
fb.className = 'feedback success';
|
||||
}
|
||||
setTimeout(() => { fb.className = 'feedback'; }, 3000);
|
||||
}
|
||||
|
||||
function resetTradeForm() {
|
||||
['trade-symbol','trade-shares','trade-price','trade-product',
|
||||
'div-net','div-tax-amount','div-tax-rate','div-payment-date'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
document.getElementById('trade-date').value = '';
|
||||
document.getElementById('trade-type').value = '0';
|
||||
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active-buy','active-sell','active-div'));
|
||||
document.querySelector('.type-btn[data-val="0"]').classList.add('active-buy');
|
||||
document.getElementById('dividend-fields').classList.remove('visible');
|
||||
document.getElementById('trade-feedback').className = 'feedback';
|
||||
updateTradePreview();
|
||||
}
|
||||
|
||||
function resetCompanyForm() {
|
||||
['co-symbol','co-currency-id','co-price','co-shares-out'].forEach(id => {
|
||||
document.getElementById(id).value = '';
|
||||
});
|
||||
document.getElementById('company-feedback').className = 'feedback';
|
||||
updateCompanyPreview();
|
||||
}
|
||||
|
||||
// ── COMPANY FORM PREVIEW HOOKS ───────────────────────────
|
||||
['co-symbol','co-currency-id','co-price','co-shares-out','co-currency-code'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', updateCompanyPreview);
|
||||
});
|
||||
|
||||
// ── INIT ─────────────────────────────────────────────────
|
||||
loadPositions();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('trade-date').value = today;
|
||||
updateTradePreview();
|
||||
updateCompanyPreview();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user