style tweaks to both form and existing inventory, added createdAt and modified purchasePrice (for % of market)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user