Add bottle size, bourbon list modal, and stat improvements
- Add bottle_size field to User model and UserResponse/UserUpdate schemas - Settings modal includes bottle size input (shots capacity) - Community bottles and My Bottle page show fill bar based on bottle size - Community bottle cards are clickable — opens searchable bourbon list modal - Add total_shots_added stat to replace duplicate net volume on dashboard - Reorder dashboard stats: Bourbons Added, Total Poured In, Shots Remaining, Est. Proof - Theme-matched custom scrollbar (amber on dark) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,9 +33,23 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bourbon list modal -->
|
||||
<div id="bourbon-modal" class="modal-overlay" style="display:none">
|
||||
<div class="modal-box" style="max-width:480px">
|
||||
<h2 id="bourbon-modal-title"></h2>
|
||||
<input type="text" id="bourbon-search" placeholder="Search bourbons…" style="margin-bottom:1rem" oninput="filterBourbons()" />
|
||||
<div id="bourbon-list" style="max-height:380px;overflow-y:auto"></div>
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:1rem">
|
||||
<button class="btn btn-ghost" onclick="closeBourbonModal()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script>
|
||||
let allBourbons = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await Auth.renderNav('home');
|
||||
|
||||
@@ -54,12 +68,16 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = stats.map(u => {
|
||||
const maxShots = 25;
|
||||
const pct = Math.min(100, (u.current_total_shots / maxShots) * 100);
|
||||
container.innerHTML = stats.map((u, i) => {
|
||||
const proof = u.estimated_proof != null ? `${u.estimated_proof}` : '—';
|
||||
const hasSize = u.bottle_size != null && u.bottle_size > 0;
|
||||
const pct = hasSize ? Math.min(100, (u.current_total_shots / u.bottle_size) * 100) : 0;
|
||||
const barHtml = hasSize ? `
|
||||
<div class="bottle-bar-wrap">
|
||||
<div class="bottle-bar" style="width:${pct}%"></div>
|
||||
</div>` : '';
|
||||
return `
|
||||
<div class="user-card">
|
||||
<div class="user-card" style="cursor:pointer" onclick="openBourbonModal(${i})">
|
||||
<div class="user-card-name">${escHtml(u.display_name)}</div>
|
||||
<div class="stats-grid" style="margin-bottom:.75rem">
|
||||
<div class="stat-box">
|
||||
@@ -75,20 +93,51 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
<span class="stat-label">Shots Left</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottle-bar-wrap">
|
||||
<div class="bottle-bar" style="width:${pct}%"></div>
|
||||
</div>
|
||||
<div class="bottle-label">${u.current_total_shots} shots remaining</div>
|
||||
${barHtml}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Store stats for modal use
|
||||
window._communityStats = stats;
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-error">Could not load stats: ${err.message}</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
function openBourbonModal(index) {
|
||||
const u = window._communityStats[index];
|
||||
allBourbons = u.bourbons || [];
|
||||
|
||||
document.getElementById('bourbon-modal-title').textContent = `${u.display_name}'s Bottle`;
|
||||
document.getElementById('bourbon-search').value = '';
|
||||
renderBourbonList(allBourbons);
|
||||
document.getElementById('bourbon-modal').style.display = 'flex';
|
||||
document.getElementById('bourbon-search').focus();
|
||||
}
|
||||
|
||||
function closeBourbonModal() {
|
||||
document.getElementById('bourbon-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
function filterBourbons() {
|
||||
const q = document.getElementById('bourbon-search').value.toLowerCase();
|
||||
renderBourbonList(allBourbons.filter(b => b.toLowerCase().includes(q)));
|
||||
}
|
||||
|
||||
function renderBourbonList(list) {
|
||||
const el = document.getElementById('bourbon-list');
|
||||
if (list.length === 0) {
|
||||
el.innerHTML = `<div style="color:var(--cream-dim);font-size:.9rem;padding:.5rem 0">No bourbons found.</div>`;
|
||||
return;
|
||||
}
|
||||
el.innerHTML = list.map(b => `
|
||||
<div style="padding:.5rem 0;border-bottom:1px solid var(--border);color:var(--cream)">${escHtml(b)}</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'bourbon-modal') closeBourbonModal();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user