style tweaks to both form and existing inventory, added createdAt and modified purchasePrice (for % of market)

This commit is contained in:
Zach Harding
2026-04-05 16:09:52 -04:00
parent 87235ab37a
commit 404355304c
8 changed files with 1914 additions and 95 deletions

View File

@@ -47,6 +47,82 @@ import BackToTop from "./BackToTop.astro"
<script is:inline>
(function () {
// ── Price mode helpers ────────────────────────────────────────────────────
// marketPriceByCondition is injected into the modal HTML via a data attribute
// on #inventoryEntryList: data-market-prices='{"Near Mint":6.00,...}'
// See card-modal.astro for where this is set.
function getMarketPrices(form) {
const listEl = form.closest('.tab-pane')?.querySelector('#inventoryEntryList')
?? document.getElementById('inventoryEntryList');
try {
return JSON.parse(listEl?.dataset.marketPrices || '{}');
} catch {
return {};
}
}
function applyPriceModeUI(form, mode) {
const priceInput = form.querySelector('#purchasePrice');
const pricePrefix = form.querySelector('#pricePrefix');
const priceSuffix = form.querySelector('#priceSuffix');
const priceHint = form.querySelector('#priceHint');
if (!priceInput) return;
const isPct = mode === 'percent';
pricePrefix?.classList.toggle('d-none', isPct);
priceSuffix?.classList.toggle('d-none', !isPct);
priceInput.step = isPct ? '1' : '0.01';
priceInput.max = isPct ? '100' : '';
priceInput.placeholder = isPct ? '0' : '0.00';
priceInput.classList.toggle('rounded-end', !isPct);
priceInput.classList.toggle('rounded-start', isPct);
if (priceHint && !isPct) priceHint.textContent = 'Enter the purchase price.';
}
function updatePriceHint(form) {
const priceInput = form.querySelector('#purchasePrice');
const priceHint = form.querySelector('#priceHint');
if (!priceInput || !priceHint) return;
const mode = form.querySelector('input[name="priceMode"]:checked')?.value ?? 'dollar';
if (mode !== 'percent') { priceHint.textContent = 'Enter the purchase price.'; return; }
const condition = form.querySelector('input[name="condition"]:checked')?.value ?? 'Near Mint';
const prices = getMarketPrices(form);
const marketPrice = prices[condition] ?? 0;
const pct = parseFloat(priceInput.value) || 0;
const resolved = ((pct / 100) * marketPrice).toFixed(2);
priceHint.textContent = marketPrice
? `= $${resolved} (${pct}% of $${marketPrice.toFixed(2)} market)`
: 'No market price available for this condition.';
}
function resolveFormPrice(form) {
// Returns a FormData ready to POST; % is converted to $ in-place.
const data = new FormData(form);
const mode = data.get('priceMode');
if (mode === 'percent') {
const condition = data.get('condition');
const prices = getMarketPrices(form);
const marketPrice = prices[condition] ?? 0;
const pct = parseFloat(data.get('purchasePrice')) || 0;
data.set('purchasePrice', ((pct / 100) * marketPrice).toFixed(2));
}
data.delete('priceMode'); // UI-only field
return data;
}
// ── Empty state helper ────────────────────────────────────────────────────
function syncEmptyState(invList) {
const emptyState = document.getElementById('inventoryEmptyState');
if (!emptyState) return;
const hasEntries = invList.querySelector('[data-inventory-id]') !== null;
emptyState.classList.toggle('d-none', hasEntries);
}
// ── Inventory form init (binding price-mode UI events) ───────────────────
function initInventoryForms(root = document) {
// Fetch inventory entries for this card
const invList = root.querySelector('#inventoryEntryList') || document.getElementById('inventoryEntryList');
@@ -58,7 +134,10 @@ import BackToTop from "./BackToTop.astro"
body.append('cardId', cardId);
fetch('/api/inventory', { method: 'POST', body })
.then(r => r.text())
.then(html => { invList.innerHTML = html || ''; })
.then(html => {
invList.innerHTML = html || '';
syncEmptyState(invList);
})
.catch(() => { invList.innerHTML = '<span class="text-danger">Failed to load inventory</span>'; });
}
}
@@ -69,39 +148,38 @@ import BackToTop from "./BackToTop.astro"
if (form.dataset.inventoryBound === 'true') return;
form.dataset.inventoryBound = 'true';
const priceInput = form.querySelector('#purchasePrice');
const pricePrefix = form.querySelector('#pricePrefix');
const priceSuffix = form.querySelector('#priceSuffix');
const priceHint = form.querySelector('#priceHint');
const modeInputs = form.querySelectorAll('input[name="priceMode"]');
const priceInput = form.querySelector('#purchasePrice');
const modeInputs = form.querySelectorAll('input[name="priceMode"]');
const condInputs = form.querySelectorAll('input[name="condition"]');
if (!priceInput || !pricePrefix || !priceSuffix || !priceHint || !modeInputs.length) return;
function updatePriceMode(mode) {
const isPct = mode === 'percent';
pricePrefix.classList.toggle('d-none', isPct);
priceSuffix.classList.toggle('d-none', !isPct);
priceInput.step = isPct ? '1' : '0.01';
priceInput.max = isPct ? '100' : '';
priceInput.placeholder = isPct ? '100' : '0.00';
priceInput.value = '';
priceHint.textContent = isPct
? 'Enter the percentage of market price you paid.'
: 'Enter the purchase price.';
// swap rounded edge classes based on visible prepend/append
priceInput.classList.toggle('rounded-end', !isPct);
priceInput.classList.toggle('rounded-start', isPct);
}
// Set initial UI state
const checkedMode = form.querySelector('input[name="priceMode"]:checked')?.value ?? 'dollar';
applyPriceModeUI(form, checkedMode);
// Mode toggle
modeInputs.forEach((input) => {
input.addEventListener('change', () => updatePriceMode(input.value));
input.addEventListener('change', () => {
if (priceInput) priceInput.value = ''; // clear stale value on mode switch
applyPriceModeUI(form, input.value);
updatePriceHint(form);
});
});
const checked = form.querySelector('input[name="priceMode"]:checked');
updatePriceMode(checked ? checked.value : 'dollar');
// Condition change updates the hint when in % mode
condInputs.forEach((input) => {
input.addEventListener('change', () => updatePriceHint(form));
});
// Live hint as user types
priceInput?.addEventListener('input', () => updatePriceHint(form));
// Reset — restore to $ mode
form.addEventListener('reset', () => {
setTimeout(() => {
applyPriceModeUI(form, 'dollar');
updatePriceHint(form);
}, 0);
});
});
}
@@ -476,7 +554,8 @@ import BackToTop from "./BackToTop.astro"
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Saving…'; }
const body = new FormData(form);
// resolveFormPrice converts % → $ and strips priceMode before POSTing
const body = resolveFormPrice(form);
body.append('action', 'add');
body.append('cardId', cardId);
@@ -484,9 +563,13 @@ import BackToTop from "./BackToTop.astro"
const res = await fetch('/api/inventory', { method: 'POST', body });
const html = await res.text();
const invList = document.getElementById('inventoryEntryList');
if (invList) invList.innerHTML = html || '';
if (invList) {
invList.innerHTML = html || '';
syncEmptyState(invList);
}
form.reset();
form.classList.remove('was-validated');
// reset fires our listener which restores $ mode UI
} catch {
// keep current inventory list state
} finally {
@@ -538,7 +621,10 @@ import BackToTop from "./BackToTop.astro"
const res = await fetch('/api/inventory', { method: 'POST', body });
const html = await res.text();
const invList = document.getElementById('inventoryEntryList');
if (invList) invList.innerHTML = html || '';
if (invList) {
invList.innerHTML = html || '';
syncEmptyState(invList);
}
} catch {
// keep current state
} finally {