portfolio-edit: rebuild form as compact inline strip
Replace the multi-row wizard-style form (Ticker / Qty on row 1, mode radios on row 2, Date+Cost on row 3) with a single horizontal strip that sits unobtrusively above the portfolio table. The radio toggle is gone; a small calendar icon next to the Cost input pops out a date picker that auto-fills cost on selection and then hides itself. Same input IDs, so the existing validate/Add/× handlers work unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
70da4cdf84
commit
a9b7d4d8bb
3 changed files with 95 additions and 98 deletions
|
|
@ -2336,79 +2336,93 @@ a.btn-secondary:hover { color: var(--accent); border-color: var(--accent); }
|
||||||
#portfolio-panel.pf-editing .pf-row-del { display: inline; }
|
#portfolio-panel.pf-editing .pf-row-del { display: inline; }
|
||||||
#portfolio-panel.pf-editing .pf-row-del:hover { color: var(--err, #f55); }
|
#portfolio-panel.pf-editing .pf-row-del:hover { color: var(--err, #f55); }
|
||||||
|
|
||||||
/* Add-position form. */
|
/* Add-position form — compact inline strip that visually sits above the
|
||||||
|
portfolio table rather than as a separate boxed form. */
|
||||||
.pf-add-form {
|
.pf-add-form {
|
||||||
border: 1px solid var(--neu-dim, #333);
|
margin-bottom: 6px;
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
background: var(--surface-2, #1a1a1a);
|
|
||||||
}
|
}
|
||||||
.pf-add-row {
|
.pf-add-strip {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
margin-bottom: 8px;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.pf-add-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1 1 140px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.pf-add-label {
|
.pf-add-input {
|
||||||
color: var(--neu-dim, #888);
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
.pf-add-field input[type="text"],
|
|
||||||
.pf-add-field input[type="number"],
|
|
||||||
.pf-add-field input[type="date"] {
|
|
||||||
background: var(--surface, #111);
|
background: var(--surface, #111);
|
||||||
border: 1px solid var(--neu-dim, #444);
|
border: 1px solid var(--neu-dim, #444);
|
||||||
color: var(--text, #ccc);
|
color: var(--text, #ccc);
|
||||||
padding: 4px 6px;
|
padding: 3px 6px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.pf-add-cost-mode { gap: 16px; }
|
|
||||||
.pf-add-radio {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.pf-add-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent, #5af);
|
||||||
|
}
|
||||||
|
.pf-add-input--ticker { width: 90px; text-transform: uppercase; }
|
||||||
|
.pf-add-input--qty { width: 70px; }
|
||||||
|
.pf-add-input--cost { width: 100px; }
|
||||||
|
.pf-add-input--date { width: 130px; }
|
||||||
|
|
||||||
|
.pf-add-cost-wrap {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
.pf-add-currency {
|
.pf-add-currency {
|
||||||
color: var(--neu-dim, #888);
|
color: var(--neu-dim, #888);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-top: 2px;
|
min-width: 28px;
|
||||||
|
}
|
||||||
|
.pf-add-icon-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--neu-dim, #444);
|
||||||
|
color: var(--neu-dim, #888);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.pf-add-icon-btn:hover {
|
||||||
|
color: var(--text, #ccc);
|
||||||
|
border-color: var(--accent, #5af);
|
||||||
}
|
}
|
||||||
.pf-add-submit {
|
.pf-add-submit {
|
||||||
background: var(--accent, #5af);
|
background: var(--accent, #5af);
|
||||||
color: var(--bg, #000);
|
color: var(--bg, #000);
|
||||||
border: none;
|
border: none;
|
||||||
padding: 6px 14px;
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
align-self: flex-end;
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.pf-add-submit:disabled {
|
.pf-add-submit:disabled {
|
||||||
background: var(--neu-dim, #444);
|
background: var(--neu-dim, #444);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
.pf-add-status {
|
.pf-add-meta {
|
||||||
font-size: 11px;
|
display: flex;
|
||||||
margin-top: 2px;
|
gap: 12px;
|
||||||
|
margin-top: 3px;
|
||||||
min-height: 14px;
|
min-height: 14px;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
.pf-add-status:empty { display: none; }
|
||||||
.pf-add-status--pending { color: var(--neu-dim, #888); }
|
.pf-add-status--pending { color: var(--neu-dim, #888); }
|
||||||
.pf-add-status--ok { color: var(--ok, #6c6); }
|
.pf-add-status--ok { color: var(--ok, #6c6); }
|
||||||
.pf-add-status--err { color: var(--err, #f55); }
|
.pf-add-status--err { color: var(--err, #f55); }
|
||||||
.pf-add-warning {
|
.pf-add-warning {
|
||||||
color: var(--warn, #fb3);
|
color: var(--warn, #fb3);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-top: 8px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,32 +177,26 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- Cost mode toggle + historical lookup --------------------------
|
// ---- Calendar-icon → historical lookup -----------------------------
|
||||||
|
|
||||||
const dateField = document.getElementById('pf-add-date-field');
|
const dateBtn = document.getElementById('pf-add-date-btn');
|
||||||
const dateInput = document.getElementById('pf-add-date');
|
const dateInput = document.getElementById('pf-add-date');
|
||||||
const dateStatus = document.getElementById('pf-add-date-status');
|
const dateStatus = document.getElementById('pf-add-date-status');
|
||||||
const costInput = document.getElementById('pf-add-cost');
|
const costInput = document.getElementById('pf-add-cost');
|
||||||
|
|
||||||
function onModeChange() {
|
dateBtn.addEventListener('click', function () {
|
||||||
const mode = document.querySelector(
|
if (!validated) {
|
||||||
'input[name="pf-cost-mode"]:checked'
|
setStatus(dateStatus, 'enter a valid ticker first', 'err');
|
||||||
).value;
|
return;
|
||||||
if (mode === 'date') {
|
}
|
||||||
dateField.hidden = false;
|
dateInput.hidden = !dateInput.hidden;
|
||||||
costInput.readOnly = true;
|
if (!dateInput.hidden) {
|
||||||
costInput.placeholder = 'auto-filled from date';
|
dateInput.focus();
|
||||||
|
if (typeof dateInput.showPicker === 'function') dateInput.showPicker();
|
||||||
} else {
|
} else {
|
||||||
dateField.hidden = true;
|
|
||||||
costInput.readOnly = false;
|
|
||||||
costInput.placeholder = '150.25';
|
|
||||||
setStatus(dateStatus, '', '');
|
setStatus(dateStatus, '', '');
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
document.querySelectorAll('input[name="pf-cost-mode"]').forEach(r =>
|
|
||||||
r.addEventListener('change', onModeChange)
|
|
||||||
);
|
|
||||||
|
|
||||||
async function fetchHistorical() {
|
async function fetchHistorical() {
|
||||||
if (!validated) {
|
if (!validated) {
|
||||||
|
|
@ -212,8 +206,6 @@
|
||||||
const d = dateInput.value;
|
const d = dateInput.value;
|
||||||
if (!d) {
|
if (!d) {
|
||||||
setStatus(dateStatus, '', '');
|
setStatus(dateStatus, '', '');
|
||||||
costInput.value = '';
|
|
||||||
updateSubmitState();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setStatus(dateStatus, 'looking up…', 'pending');
|
setStatus(dateStatus, 'looking up…', 'pending');
|
||||||
|
|
@ -225,7 +217,6 @@
|
||||||
if (r.status === 400) {
|
if (r.status === 400) {
|
||||||
const j = await r.json().catch(() => ({detail: 'invalid date'}));
|
const j = await r.json().catch(() => ({detail: 'invalid date'}));
|
||||||
setStatus(dateStatus, '✗ ' + (j.detail || 'invalid date'), 'err');
|
setStatus(dateStatus, '✗ ' + (j.detail || 'invalid date'), 'err');
|
||||||
costInput.value = '';
|
|
||||||
updateSubmitState();
|
updateSubmitState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -236,18 +227,18 @@
|
||||||
? '✓ from ' + j.actual_date
|
? '✓ from ' + j.actual_date
|
||||||
: '✓';
|
: '✓';
|
||||||
setStatus(dateStatus, tag, 'ok');
|
setStatus(dateStatus, tag, 'ok');
|
||||||
|
// Hide the date picker after a successful fill — keeps the row clean.
|
||||||
|
dateInput.hidden = true;
|
||||||
} else {
|
} else {
|
||||||
setStatus(dateStatus, '✗ ' + (j.error || 'no data'), 'err');
|
setStatus(dateStatus, '✗ ' + (j.error || 'no data'), 'err');
|
||||||
costInput.value = '';
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus(dateStatus, '✗ couldn\'t fetch — try again', 'err');
|
setStatus(dateStatus, '✗ couldn\'t fetch — try again', 'err');
|
||||||
costInput.value = '';
|
|
||||||
}
|
}
|
||||||
updateSubmitState();
|
updateSubmitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
dateInput.addEventListener('blur', fetchHistorical);
|
dateInput.addEventListener('change', fetchHistorical);
|
||||||
|
|
||||||
// ---- Per-row delete (event delegation) -----------------------------
|
// ---- Per-row delete (event delegation) -----------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,42 +61,34 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div id="pf-add-form" class="pf-add-form" hidden>
|
<div id="pf-add-form" class="pf-add-form" hidden>
|
||||||
<div class="pf-add-row">
|
<div class="pf-add-strip">
|
||||||
<label class="pf-add-field">
|
<input type="text" id="pf-add-ticker" class="pf-add-input pf-add-input--ticker"
|
||||||
<span class="pf-add-label">Ticker</span>
|
autocomplete="off" spellcheck="false" maxlength="32"
|
||||||
<input type="text" id="pf-add-ticker" autocomplete="off"
|
placeholder="Ticker">
|
||||||
spellcheck="false" maxlength="32" placeholder="AAPL">
|
<input type="number" id="pf-add-qty" class="pf-add-input pf-add-input--qty"
|
||||||
<span id="pf-add-ticker-status" class="pf-add-status"></span>
|
min="0" step="any" placeholder="Qty">
|
||||||
</label>
|
<span class="pf-add-cost-wrap">
|
||||||
<label class="pf-add-field">
|
<input type="number" id="pf-add-cost" class="pf-add-input pf-add-input--cost"
|
||||||
<span class="pf-add-label">Quantity</span>
|
min="0" step="any" placeholder="Cost / share">
|
||||||
<input type="number" id="pf-add-qty" min="0" step="any" placeholder="100">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="pf-add-row pf-add-cost-mode">
|
|
||||||
<label class="pf-add-radio">
|
|
||||||
<input type="radio" name="pf-cost-mode" value="avg" checked>
|
|
||||||
Avg cost per share
|
|
||||||
</label>
|
|
||||||
<label class="pf-add-radio">
|
|
||||||
<input type="radio" name="pf-cost-mode" value="date">
|
|
||||||
Bought on date
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="pf-add-row">
|
|
||||||
<label class="pf-add-field" id="pf-add-date-field" hidden>
|
|
||||||
<span class="pf-add-label">Acquisition date</span>
|
|
||||||
<input type="date" id="pf-add-date">
|
|
||||||
<span id="pf-add-date-status" class="pf-add-status"></span>
|
|
||||||
</label>
|
|
||||||
<label class="pf-add-field">
|
|
||||||
<span class="pf-add-label">Cost per share</span>
|
|
||||||
<input type="number" id="pf-add-cost" min="0" step="any" placeholder="150.25">
|
|
||||||
<span id="pf-add-cost-currency" class="pf-add-currency"></span>
|
<span id="pf-add-cost-currency" class="pf-add-currency"></span>
|
||||||
</label>
|
<button type="button" id="pf-add-date-btn" class="pf-add-icon-btn"
|
||||||
<button type="button" id="pf-add-submit" class="pf-add-submit" disabled>
|
title="Auto-fill from historical date" aria-label="Pick date">
|
||||||
+ Add
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
||||||
</button>
|
stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||||
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<line x1="16" y1="2" x2="16" y2="6"/>
|
||||||
|
<line x1="8" y1="2" x2="8" y2="6"/>
|
||||||
|
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input type="date" id="pf-add-date" class="pf-add-input pf-add-input--date" hidden>
|
||||||
|
</span>
|
||||||
|
<button type="button" id="pf-add-submit" class="pf-add-submit"
|
||||||
|
disabled title="Add position">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="pf-add-meta">
|
||||||
|
<span id="pf-add-ticker-status" class="pf-add-status"></span>
|
||||||
|
<span id="pf-add-date-status" class="pf-add-status"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="pf-add-warning" class="pf-add-warning" hidden></div>
|
<div id="pf-add-warning" class="pf-add-warning" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue