186 lines
10 KiB
HTML
186 lines
10 KiB
HTML
{% extends 'base/layout.html' %}
|
|
{% load static %}
|
|
|
|
{% block content %}
|
|
<div class="browse-container" style="display: grid; grid-template-columns: 250px 1fr; gap: 2rem;">
|
|
<!-- Sidebar Filters -->
|
|
<aside class="browse-sidebar"
|
|
style="background: var(--card-bg); padding: 1.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); height: fit-content;">
|
|
<h3 style="margin-top: 0;">Filters</h3>
|
|
<form method="get">
|
|
<div style="margin-bottom: 1rem;">
|
|
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Game</label>
|
|
<select name="game"
|
|
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);"
|
|
onchange="this.form.submit()">
|
|
<option value="">All Games</option>
|
|
{% for game in games %}
|
|
<option value="{{ game.slug }}" {% if current_game|slugify == game.slug %}selected{% endif %}>
|
|
{{game.name}}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
{% if current_game %}
|
|
<div style="margin-bottom: 1rem;">
|
|
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Set</label>
|
|
<select name="set"
|
|
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
|
|
<option value="">All Sets</option>
|
|
{% for set in sets %}
|
|
<option value="{{ set.id }}" {% if request.GET.set|add:"0" == set.id %}selected{% endif %}>{{ set.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div style="margin-bottom: 1rem; display: flex; align-items: center;">
|
|
<input type="checkbox" id="hide_out_of_stock" name="hide_out_of_stock" {% if hide_oos == 'on' %}checked{% endif %} onchange="this.form.submit()" style="margin-right: 0.5rem;">
|
|
<label for="hide_out_of_stock" style="font-size: 0.875rem; cursor: pointer;">Hide Out of Stock</label>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 1rem; position: relative;">
|
|
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Search</label>
|
|
<input type="text" name="q" id="search-input" value="{{ search_query|default:'' }}" placeholder="Card name..." autocomplete="off"
|
|
style="width: 90%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
|
|
<ul id="suggestions-list" style="display: none; position: absolute; top: 100%; left: 0; width: 90%; background: var(--bg-color); border: 1px solid var(--border-color); border-radius: 0.25rem; z-index: 1000; list-style: none; padding: 0; margin: 0; max-height: 200px; overflow-y: auto;">
|
|
</ul>
|
|
</div>
|
|
|
|
<script>
|
|
const searchInput = document.getElementById('search-input');
|
|
const suggestionsList = document.getElementById('suggestions-list');
|
|
let debounceTimer;
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
const query = this.value;
|
|
clearTimeout(debounceTimer);
|
|
if (query.length < 2) {
|
|
suggestionsList.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
debounceTimer = setTimeout(() => {
|
|
fetch(`/api/card-autocomplete/?q=${encodeURIComponent(query)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
suggestionsList.innerHTML = '';
|
|
if (data.results.length > 0) {
|
|
data.results.forEach(name => {
|
|
const li = document.createElement('li');
|
|
li.textContent = name;
|
|
li.style.padding = '0.5rem';
|
|
li.style.cursor = 'pointer';
|
|
li.style.borderBottom = '1px solid var(--border-color)';
|
|
|
|
li.addEventListener('mouseenter', () => {
|
|
li.style.background = 'var(--card-bg)';
|
|
});
|
|
li.addEventListener('mouseleave', () => {
|
|
li.style.background = 'transparent';
|
|
});
|
|
|
|
li.addEventListener('click', () => {
|
|
searchInput.value = name;
|
|
suggestionsList.style.display = 'none';
|
|
searchInput.form.submit();
|
|
});
|
|
suggestionsList.appendChild(li);
|
|
});
|
|
suggestionsList.style.display = 'block';
|
|
} else {
|
|
suggestionsList.style.display = 'none';
|
|
}
|
|
});
|
|
}, 300);
|
|
});
|
|
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target !== searchInput && e.target !== suggestionsList) {
|
|
suggestionsList.style.display = 'none';
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<button type="submit" class="btn" style="width: 100%;">Apply Filters</button>
|
|
<a href="{% url 'store:card_list' %}"
|
|
style="display: block; text-align: center; margin-top: 1rem; color: #94a3b8; font-size: 0.875rem; text-decoration: none;">Clear
|
|
Filters</a>
|
|
</form>
|
|
</aside>
|
|
|
|
<!-- Card Grid -->
|
|
<div>
|
|
<h2 style="margin-top: 0;">Browse Cards</h2>
|
|
<div class="card-grid">
|
|
{% for card in page_obj %}
|
|
<div class="tcg-card">
|
|
<a href="{% url 'store:card_detail' card.uuid %}" style="text-decoration: none; color: inherit;">
|
|
<div style="aspect-ratio: 2.5/3.5; background: #000; position: relative;">
|
|
<!-- Placeholder or Real Image -->
|
|
{% if card.image_url %}
|
|
<img src="{{ card.image_url }}" alt="{{ card.name }}"
|
|
style="width: 100%; height: 100%; object-fit: cover;">
|
|
{% else %}
|
|
<div
|
|
style="display: flex; align-items: center; justify-content: center; height: 100%; color: #64748b; background: #334155;">
|
|
No Image</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="tcg-card-body">
|
|
<h4
|
|
style="margin: 0 0 0.5rem; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
{{ card.name }}</h4>
|
|
<div
|
|
style="display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem; color: #94a3b8;">
|
|
<span>{{ card.set.code|default:card.set.game.name }}</span>
|
|
<span>{{ card.rarity }}</span>
|
|
</div>
|
|
<div
|
|
style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center;">
|
|
{% with card.listings.first as cheapest %}
|
|
{% if cheapest %}
|
|
<span style="font-weight: 700; color: var(--text-color);">From ${{ cheapest.price }}</span>
|
|
{% else %}
|
|
<span style="color: #64748b;">Out of Stock</span>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% if card.total_quantity > 0 %}
|
|
<span style="font-size: 0.75rem; color: #10b981; margin-left: auto;">{{ card.total_quantity }} in stock</span>
|
|
{% else %}
|
|
<span style="font-size: 0.75rem; color: #ef4444; margin-left: auto;">Out of Stock</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{% empty %}
|
|
<p>No cards found matching your criteria.</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<div style="margin-top: 2rem; display: flex; justify-content: center; gap: 0.5rem;">
|
|
{% if page_obj.has_previous %}
|
|
<a href="?page={{ page_obj.previous_page_number }}&game={{ current_game }}&q={{ search_query }}&hide_out_of_stock={{ hide_oos }}" class="btn"
|
|
style="padding: 0.25rem 0.5rem; font-size: 0.875rem;">Prev</a>
|
|
{% endif %}
|
|
|
|
<span
|
|
style="padding: 0.25rem 0.75rem; background: var(--card-bg); border-radius: 0.25rem; border: 1px solid var(--border-color);">
|
|
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
|
</span>
|
|
|
|
{% if page_obj.has_next %}
|
|
<a href="?page={{ page_obj.next_page_number }}&game={{ current_game }}&q={{ search_query }}&hide_out_of_stock={{ hide_oos }}" class="btn"
|
|
style="padding: 0.25rem 0.5rem; font-size: 0.875rem;">Next</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% endblock %} |