[feat] minor tweaks to the inventory modal

This commit is contained in:
Thad Miller
2026-07-16 15:41:44 -04:00
parent f0ee662450
commit a265aea424
2 changed files with 24 additions and 98 deletions

View File

@@ -252,9 +252,9 @@ const catalogs = catalogRows
purchaseDate: canvas.dataset.purchaseDate || null, purchaseDate: canvas.dataset.purchaseDate || null,
condition: canvas.dataset.condition || 'Near Mint', condition: canvas.dataset.condition || 'Near Mint',
}; };
range = 'all'; range = '6m';
root.querySelectorAll('.inv-price-range-btn').forEach(b => root.querySelectorAll('.inv-price-range-btn').forEach(b =>
b.classList.toggle('active', b.dataset.range === 'all')); b.classList.toggle('active', b.dataset.range === '6m'));
const emptyEl = root.querySelector('#inventoryPriceEmpty'); const emptyEl = root.querySelector('#inventoryPriceEmpty');
const wrap = canvas.closest('.chart-canvas-wrap'); const wrap = canvas.closest('.chart-canvas-wrap');
@@ -390,7 +390,7 @@ const catalogs = catalogRows
const delBtn = e.target.closest('[data-inventory-delete]'); const delBtn = e.target.closest('[data-inventory-delete]');
if (!delBtn) return; if (!delBtn) return;
const form = delBtn.closest('[data-inventory-edit-form]'); const form = modal.querySelector('[data-inventory-edit-form]');
const inventoryId = form?.querySelector('[name=inventoryId]')?.value; const inventoryId = form?.querySelector('[name=inventoryId]')?.value;
const cardId = form?.querySelector('[name=cardId]')?.value; const cardId = form?.querySelector('[name=cardId]')?.value;
if (!inventoryId) return; if (!inventoryId) return;
@@ -426,17 +426,16 @@ const catalogs = catalogRows
if (!form) return; if (!form) return;
e.preventDefault(); e.preventDefault();
const btn = form.querySelector('button[type="submit"]'); const btn = modal.querySelector('button[type="submit"][form="inventoryEditForm"]');
const original = btn ? btn.textContent : ''; const original = btn ? btn.textContent : '';
if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; } if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
try { try {
const res = await fetch('/api/inventory', { method: 'POST', body: new FormData(form) }); const res = await fetch('/api/inventory', { method: 'POST', body: new FormData(form) });
if (res.ok) { if (res.ok) {
if (btn) { btn.textContent = 'Saved ✓'; btn.classList.remove('btn-success'); btn.classList.add('btn-outline-success'); } const instance = window.bootstrap?.Modal.getInstance(modal);
setTimeout(() => { if (instance) instance.hide();
if (btn) { btn.textContent = original; btn.disabled = false; btn.classList.add('btn-success'); btn.classList.remove('btn-outline-success'); } else modal.querySelector('[data-bs-dismiss=modal]')?.click();
}, 1500);
} else if (btn) { } else if (btn) {
btn.textContent = original; btn.disabled = false; btn.textContent = original; btn.disabled = false;
} }

View File

@@ -111,15 +111,8 @@ const altSearchUrl = (card: any) => {
<!-- Card image column --> <!-- Card image column -->
<div class="col-sm-12 col-md-3"> <div class="col-sm-12 col-md-3">
<div class="position-relative mt-1"> <div class="position-relative mt-1">
<div <div class="card-image-wrap rounded-4" data-energy={card?.energyType} data-rarity={card?.rarityName} data-variant={card?.variant} data-name={card?.productName}>
class="card-image-wrap rounded-4" <img src={`/static/cards/${card?.productId}.jpg`}
data-energy={card?.energyType}
data-rarity={card?.rarityName}
data-variant={card?.variant}
data-name={card?.productName}
>
<img
src={`/static/cards/${card?.productId}.jpg`}
class="card-image w-100 img-fluid rounded-4" class="card-image w-100 img-fluid rounded-4"
alt={card?.productName} alt={card?.productName}
crossorigin="anonymous" crossorigin="anonymous"
@@ -148,59 +141,19 @@ const altSearchUrl = (card: any) => {
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<label class="form-label">Condition</label> <label class="form-label">Condition</label>
<div class="btn-group condition-input w-100 col-12" role="group" aria-label="Condition"> <div class="btn-group condition-input w-100 col-12" role="group" aria-label="Condition">
<input <input type="radio" class="btn-check" name="condition" id="cond-nm" value="Near Mint" autocomplete="off" checked />
type="radio"
class="btn-check"
name="condition"
id="cond-nm"
value="Near Mint"
autocomplete="off"
checked
/>
<label class="btn btn-cond-nm" for="cond-nm">NM</label> <label class="btn btn-cond-nm" for="cond-nm">NM</label>
<input <input type="radio" class="btn-check" name="condition" id="cond-lp" value="Lightly Played" autocomplete="off" disabled />
type="radio"
class="btn-check"
name="condition"
id="cond-lp"
value="Lightly Played"
autocomplete="off"
disabled
/>
<label class="btn btn-cond-lp" for="cond-lp">LP</label> <label class="btn btn-cond-lp" for="cond-lp">LP</label>
<input <input type="radio" class="btn-check" name="condition" id="cond-mp" value="Moderately Played" autocomplete="off" disabled />
type="radio"
class="btn-check"
name="condition"
id="cond-mp"
value="Moderately Played"
autocomplete="off"
disabled
/>
<label class="btn btn-cond-mp" for="cond-mp">MP</label> <label class="btn btn-cond-mp" for="cond-mp">MP</label>
<input <input type="radio" class="btn-check" name="condition" id="cond-hp" value="Heavily Played" autocomplete="off" disabled />
type="radio"
class="btn-check"
name="condition"
id="cond-hp"
value="Heavily Played"
autocomplete="off"
disabled
/>
<label class="btn btn-cond-hp" for="cond-hp">HP</label> <label class="btn btn-cond-hp" for="cond-hp">HP</label>
<input <input type="radio" class="btn-check" name="condition" id="cond-dmg" value="Damaged" autocomplete="off" disabled />
type="radio"
class="btn-check"
name="condition"
id="cond-dmg"
value="Damaged"
autocomplete="off"
disabled
/>
<label class="btn btn-cond-dmg" for="cond-dmg">DMG</label> <label class="btn btn-cond-dmg" for="cond-dmg">DMG</label>
</div> </div>
<p class="mb-1 my-2">Purchased: <span class="text-secondary">{inv.createdAt ? new Date(inv.createdAt).toLocaleDateString() : '—'}</span></p> <p class="mb-1 my-2">Purchased: <span class="text-secondary">{inv.createdAt ? new Date(inv.createdAt).toLocaleDateString() : '—'}</span></p>
@@ -222,11 +175,7 @@ const altSearchUrl = (card: any) => {
<div class={`alert rounded p-2 flex-fill ${gainPercent == null ? 'alert-dark' : gainPercent >= 0 ? 'alert-success' : 'alert-danger'}`}> <div class={`alert rounded p-2 flex-fill ${gainPercent == null ? 'alert-dark' : gainPercent >= 0 ? 'alert-success' : 'alert-danger'}`}>
<p class="mb-auto">Gain / Loss (%)</p> <p class="mb-auto">Gain / Loss (%)</p>
<h6 class="mb-0 mt-1"> <h6 class="mb-0 mt-1">
{ { gainPercent == null ? '—' : `${gainPercent >= 0 ? '+' : ''}${Math.abs(gainPercent).toFixed(2)}%` }
gainPercent == null
? '—'
: `${gainPercent >= 0 ? '+' : ''}${Math.abs(gainPercent).toFixed(2)}%`
}
</h6> </h6>
</div> </div>
</div> </div>
@@ -234,7 +183,7 @@ const altSearchUrl = (card: any) => {
<!-- Right column: editable Note + Catalog --> <!-- Right column: editable Note + Catalog -->
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<form data-inventory-edit-form novalidate> <form id="inventoryEditForm" data-inventory-edit-form novalidate>
<input type="hidden" name="action" value="update" /> <input type="hidden" name="action" value="update" />
<input type="hidden" name="inventoryId" value={inv?.inventoryId} /> <input type="hidden" name="inventoryId" value={inv?.inventoryId} />
<input type="hidden" name="cardId" value={card?.cardId} /> <input type="hidden" name="cardId" value={card?.cardId} />
@@ -242,30 +191,15 @@ const altSearchUrl = (card: any) => {
<input type="hidden" name="purchasePrice" value={purchasePrice ?? 0} /> <input type="hidden" name="purchasePrice" value={purchasePrice ?? 0} />
<label for="catalogName" class="form-label">Catalog</label> <label for="catalogName" class="form-label">Catalog</label>
<input <input type="text" class="form-control" id="catalogName" name="catalogName" list="catalogSuggestions" placeholder="Default" maxlength="100" value={inv?.catalogName ?? ''} />
type="text"
class="form-control"
id="catalogName"
name="catalogName"
list="catalogSuggestions"
placeholder="Default"
autocomplete="off"
maxlength="100"
value={inv?.catalogName ?? ''}
/>
<datalist id="catalogSuggestions"> <datalist id="catalogSuggestions">
{catalogs.map(name => <option value={name}></option>)} {catalogs.map(name => <option value={name}></option>)}
</datalist> </datalist>
<label for="note" class="form-label mt-2">Note</label> <label for="note" class="form-label mt-2">Note</label>
<textarea <textarea class="form-control" id="note" name="note" rows="5" maxlength="255" placeholder="e.g. bought at local shop, gift, graded copy…">
class="form-control" {inv?.note ?? ''}
id="note" </textarea>
name="note"
rows="5"
maxlength="255"
placeholder="e.g. bought at local shop, gift, graded copy…"
>{inv?.note ?? ''}</textarea>
</form> </form>
</div> </div>
@@ -281,21 +215,14 @@ const altSearchUrl = (card: any) => {
No price history for this condition No price history for this condition
</div> </div>
<div class="position-relative chart-canvas-wrap" style="height: 220px;"> <div class="position-relative chart-canvas-wrap" style="height: 220px;">
<canvas <canvas id="inventoryPriceChart" class="price-history-chart" data-history={JSON.stringify(priceHistoryForChart)} data-purchase-price={purchasePrice ?? ''} data-purchase-date={purchaseDate ?? ''} data-condition={condition}></canvas>
id="inventoryPriceChart"
class="price-history-chart"
data-history={JSON.stringify(priceHistoryForChart)}
data-purchase-price={purchasePrice ?? ''}
data-purchase-date={purchaseDate ?? ''}
data-condition={condition}>
</canvas>
</div> </div>
<div class="btn-group btn-group-sm d-flex flex-row gap-1 justify-content-end mt-2" role="group" aria-label="Time range"> <div class="btn-group btn-group-sm d-flex flex-row gap-1 justify-content-end mt-2" role="group" aria-label="Time range">
<button type="button" class="btn btn-dark inv-price-range-btn" data-range="1m">1M</button> <button type="button" class="btn btn-dark inv-price-range-btn" data-range="1m">1M</button>
<button type="button" class="btn btn-dark inv-price-range-btn" data-range="3m">3M</button> <button type="button" class="btn btn-dark inv-price-range-btn" data-range="3m">3M</button>
<button type="button" class="btn btn-dark inv-price-range-btn" data-range="6m">6M</button> <button type="button" class="btn btn-dark inv-price-range-btn active" data-range="6m">6M</button>
<button type="button" class="btn btn-dark inv-price-range-btn" data-range="1y">1Y</button> <button type="button" class="btn btn-dark inv-price-range-btn" data-range="1y">1Y</button>
<button type="button" class="btn btn-dark inv-price-range-btn active" data-range="all">All</button> <button type="button" class="btn btn-dark inv-price-range-btn" data-range="all">All</button>
</div> </div>
</div> </div>
</div> </div>
@@ -310,7 +237,7 @@ const altSearchUrl = (card: any) => {
<a class="btn btn-dark mb-2 w-100 p-2" href={`${altSearchUrl(card)}`} target="_blank" onclick="dataLayer.push({'event': 'altClick', 'altUrl': this.getAttribute('href')});"><svg width="48" height="20.16" viewBox="0 0 48 20" fill="none"><path d="M14.2761 19.9996H18.5308L11.6934 0.0712891H7.76953L14.2761 19.9996Z" fill="#ffffff"></path><path d="M6.17778 19.9986H6.14536L3.19643 11.2305L0 19.9988L6.17768 19.9989L6.17778 19.9986Z" fill="#ffffff"></path><path d="M24.7842 0H20.6759V19.9661H34.3427V16.5426H24.7842V0Z" fill="#ffffff"></path><path d="M41.6644 3.42355H47.4981V0H31.5033V3.42355H37.5561V19.9661H41.6644V3.42355Z" fill="#ffffff"></path></svg></a> <a class="btn btn-dark mb-2 w-100 p-2" href={`${altSearchUrl(card)}`} target="_blank" onclick="dataLayer.push({'event': 'altClick', 'altUrl': this.getAttribute('href')});"><svg width="48" height="20.16" viewBox="0 0 48 20" fill="none"><path d="M14.2761 19.9996H18.5308L11.6934 0.0712891H7.76953L14.2761 19.9996Z" fill="#ffffff"></path><path d="M6.17778 19.9986H6.14536L3.19643 11.2305L0 19.9988L6.17768 19.9989L6.17778 19.9986Z" fill="#ffffff"></path><path d="M24.7842 0H20.6759V19.9661H34.3427V16.5426H24.7842V0Z" fill="#ffffff"></path><path d="M41.6644 3.42355H47.4981V0H31.5033V3.42355H37.5561V19.9661H41.6644V3.42355Z" fill="#ffffff"></path></svg></a>
<div class="d-flex gap-2 mt-auto align-items-end justify-content-evenly"> <div class="d-flex gap-2 mt-auto align-items-end justify-content-evenly">
<button type="button" class="btn btn-outline-danger flex-fill" data-inventory-delete>Delete</button> <button type="button" class="btn btn-outline-danger flex-fill" data-inventory-delete>Delete</button>
<button type="submit" class="btn btn-success flex-fill flex-grow-1">Save</button> <button type="submit" form="inventoryEditForm" class="btn btn-success flex-fill flex-grow-1">Save</button>
</div> </div>
</div> </div>