341 lines
15 KiB
HTML
341 lines
15 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h4 class="mb-0 fw-bold">{{ title }}</h4>
|
|
<a href="{% if date_str %}{% url 'home' %}{% else %}{% url 'outfit_list' %}{% endif %}" class="btn-close btn-close-white" aria-label="Close"></a>
|
|
</div>
|
|
|
|
<form method="post" id="outfit-form" enctype="multipart/form-data">
|
|
{% csrf_token %}
|
|
|
|
{% if form.errors %}
|
|
<div class="alert alert-danger small py-2">
|
|
<ul class="mb-0">
|
|
{% for field, errors in form.errors.items %}
|
|
{% for error in errors %}
|
|
<li>{{ field }}: {{ error }}</li>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-6">
|
|
<label class="form-label text-dim small text-uppercase fw-bold">Outfit Name</label>
|
|
{{ form.name }}
|
|
</div>
|
|
<div class="col-6">
|
|
<label class="form-label text-dim small text-uppercase fw-bold">Season</label>
|
|
{{ form.season }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- View Toggle & Category Header -->
|
|
<div class="d-flex justify-content-between align-items-end mb-2">
|
|
<div>
|
|
<label class="form-label text-dim small text-uppercase fw-bold mb-0">Select Items</label>
|
|
<div class="small text-accent" id="item-count">0 selected</div>
|
|
</div>
|
|
<div class="btn-group btn-group-sm bg-secondary rounded-pill p-1 shadow-sm">
|
|
<button type="button" class="btn btn-sm rounded-pill px-3 active" id="view-grid-img" title="Pictures">
|
|
<i class="fas fa-th-large"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm rounded-pill px-3" id="view-grid-text" title="Names Only">
|
|
<i class="fas fa-list"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Navigation -->
|
|
<div class="category-nav mb-3">
|
|
<div class="d-flex gap-2 overflow-x-auto no-scrollbar pb-2" id="main-cat-list">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill px-3 active-cat" data-cat-id="all">All</button>
|
|
{% for cat in main_categories %}
|
|
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill px-3 main-cat-btn" data-cat-id="{{ cat.id }}">
|
|
{{ cat.name }}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="d-flex gap-2 overflow-x-auto no-scrollbar pb-2 mt-1 d-none" id="sub-cat-list">
|
|
<!-- Subcategories will be injected here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Wardrobe Item Grid -->
|
|
<div class="item-grid-container glass-card p-3 mb-4" style="height: 350px; overflow-y: auto;">
|
|
<div class="row g-3" id="wardrobe-items-grid">
|
|
{% for item in wardrobe_items %}
|
|
<div class="col-4 col-sm-3 col-md-2 item-wrapper"
|
|
data-cat-id="{% if item.category %}{{ item.category.id }}{% else %}none{% endif %}"
|
|
data-parent-cat-id="{% if item.category %}{% if item.category.parent %}{{ item.category.parent.id }}{% else %}{{ item.category.id }}{% endif %}{% else %}none{% endif %}">
|
|
<div class="item-card position-relative rounded-2 d-flex flex-column align-items-center p-2"
|
|
data-id="{{ item.id }}" data-real-id="{{ item.id }}" data-type="item" style="cursor: pointer; transition: background 0.2s;">
|
|
<div class="item-img-container position-relative rounded-2 overflow-hidden bg-secondary w-100 shadow-sm" style="aspect-ratio: 1/1;">
|
|
<img src="{{ item.image.url }}" class="w-100 h-100 object-fit-contain item-img">
|
|
<div class="check-overlay position-absolute top-0 end-0 p-1 d-none" style="z-index: 5;">
|
|
<i class="fas fa-check-circle text-primary-accent shadow-sm" style="background: white; border-radius: 50%;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="item-name-label mt-1 text-center small text-truncate w-100 px-1" style="font-size: 0.65rem; color: var(--text-main);">
|
|
{{ item.name|default:"Item" }}
|
|
</div>
|
|
<div class="item-name-only d-none w-100 rounded-2 bg-secondary align-items-center justify-content-center p-2 text-center small fw-bold shadow-sm" style="aspect-ratio: 1/1; font-size: 0.7rem;">
|
|
{{ item.name|default:"Item" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="form-label text-dim small text-uppercase fw-bold mb-2">Select Accessories</label>
|
|
<div class="item-grid-container glass-card p-3" style="max-height: 250px; overflow-y: auto;">
|
|
<div class="row g-3" id="accessories-grid">
|
|
{% for acc in accessories %}
|
|
<div class="col-4 col-sm-3 col-md-2 accessory-wrapper">
|
|
<div class="item-card position-relative rounded-2 d-flex flex-column align-items-center p-2"
|
|
data-id="{{ acc.id }}" data-real-id="{{ acc.id }}" data-type="accessory" style="cursor: pointer; transition: background 0.2s;">
|
|
<div class="item-img-container position-relative rounded-2 overflow-hidden bg-secondary w-100 shadow-sm" style="aspect-ratio: 1/1;">
|
|
<img src="{{ acc.image.url }}" class="w-100 h-100 object-fit-contain item-img">
|
|
<div class="check-overlay position-absolute top-0 end-0 p-1 d-none" style="z-index: 5;">
|
|
<i class="fas fa-check-circle text-primary-accent shadow-sm" style="background: white; border-radius: 50%;"></i>
|
|
</div>
|
|
</div>
|
|
<div class="item-name-label mt-1 text-center small text-truncate w-100 px-1" style="font-size: 0.65rem; color: var(--text-main);">
|
|
{{ acc.name|default:"Acc" }}
|
|
</div>
|
|
<div class="item-name-only d-none w-100 rounded-2 bg-secondary align-items-center justify-content-center p-2 text-center small fw-bold shadow-sm" style="aspect-ratio: 1/1; font-size: 0.7rem;">
|
|
{{ acc.name|default:"Acc" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="form-label text-dim small text-uppercase fw-bold">Folder / Group (Optional)</label>
|
|
{{ form.folder }}
|
|
<div class="mt-2">
|
|
<a class="text-accent small text-decoration-none" data-bs-toggle="collapse" href="#newFolderCollapse">
|
|
<i class="fas fa-plus-circle me-1"></i> Add New Group
|
|
</a>
|
|
<div class="collapse mt-2" id="newFolderCollapse">
|
|
{{ form.new_folder }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden Selects -->
|
|
<div class="d-none">
|
|
{{ form.items }}
|
|
{{ form.accessories }}
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-accent w-100 py-3 fw-bold rounded-3 shadow">SAVE OUTFIT</button>
|
|
</form>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* Windows Folder Grid Inspired Styling */
|
|
.item-card:hover {
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
.item-card.selected {
|
|
background-color: rgba(var(--primary-accent-rgb, 187, 134, 252), 0.15) !important;
|
|
}
|
|
.item-card.selected .item-img-container,
|
|
.item-card.selected .item-name-only {
|
|
outline: 2px solid var(--primary-accent) !important;
|
|
outline-offset: -2px;
|
|
}
|
|
.item-card.selected .check-overlay {
|
|
display: block !important;
|
|
}
|
|
|
|
.border-transparent { border-color: transparent; }
|
|
.text-primary-accent { color: var(--primary-accent); }
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
|
|
.active-cat {
|
|
background-color: var(--primary-accent) !important;
|
|
color: #000 !important;
|
|
border-color: var(--primary-accent) !important;
|
|
}
|
|
|
|
.item-card.text-only .item-img-container,
|
|
.item-card.text-only .item-name-label {
|
|
display: none !important;
|
|
}
|
|
.item-card.text-only .item-name-only {
|
|
display: flex !important;
|
|
}
|
|
|
|
.object-fit-contain { object-fit: contain; }
|
|
|
|
.item-grid-container::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
.item-grid-container::-webkit-scrollbar-track {
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 10px;
|
|
}
|
|
.item-grid-container::-webkit-scrollbar-thumb {
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 10px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const itemCards = document.querySelectorAll('.item-card');
|
|
const itemsSelect = document.querySelector('select[name="items"]');
|
|
const accsSelect = document.querySelector('select[name="accessories"]');
|
|
const itemCountBadge = document.getElementById('item-count');
|
|
|
|
const mainCatBtns = document.querySelectorAll('.main-cat-btn');
|
|
const allCatBtn = document.querySelector('[data-cat-id="all"]');
|
|
const subCatList = document.getElementById('sub-cat-list');
|
|
const itemWrappers = document.querySelectorAll('.item-wrapper');
|
|
|
|
const viewGridImg = document.getElementById('view-grid-img');
|
|
const viewGridText = document.getElementById('view-grid-text');
|
|
|
|
function updateBadge() {
|
|
const selectedCount = document.querySelectorAll('.item-card.selected').length;
|
|
itemCountBadge.textContent = `${selectedCount} items selected`;
|
|
}
|
|
|
|
// Item Selection Logic
|
|
itemCards.forEach(card => {
|
|
card.addEventListener('click', function() {
|
|
const id = this.dataset.realId || this.dataset.id;
|
|
const type = this.dataset.type;
|
|
const isSelected = this.classList.toggle('selected');
|
|
|
|
const select = (type === 'item') ? itemsSelect : accsSelect;
|
|
|
|
if (select) {
|
|
for (let option of select.options) {
|
|
if (option.value === id) {
|
|
option.selected = isSelected;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
updateBadge();
|
|
});
|
|
});
|
|
|
|
// View Toggle Logic
|
|
if (viewGridImg && viewGridText) {
|
|
viewGridImg.addEventListener('click', () => {
|
|
viewGridImg.classList.add('active');
|
|
viewGridText.classList.remove('active');
|
|
document.querySelectorAll('.item-card').forEach(card => {
|
|
card.classList.remove('text-only');
|
|
});
|
|
});
|
|
|
|
viewGridText.addEventListener('click', () => {
|
|
viewGridText.classList.add('active');
|
|
viewGridImg.classList.remove('active');
|
|
document.querySelectorAll('.item-card').forEach(card => {
|
|
card.classList.add('text-only');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Category Filtering Logic
|
|
function filterItems(mainCatId, subCatId = null) {
|
|
itemWrappers.forEach(wrapper => {
|
|
const itemMainCatId = wrapper.dataset.parentCatId;
|
|
const itemSubCatId = wrapper.dataset.catId;
|
|
|
|
if (mainCatId === 'all') {
|
|
wrapper.classList.remove('d-none');
|
|
} else {
|
|
if (subCatId) {
|
|
wrapper.classList.toggle('d-none', itemSubCatId !== subCatId);
|
|
} else {
|
|
wrapper.classList.toggle('d-none', itemMainCatId !== mainCatId);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (allCatBtn) {
|
|
allCatBtn.addEventListener('click', function() {
|
|
document.querySelectorAll('.category-nav button').forEach(b => b.classList.remove('active-cat'));
|
|
this.classList.add('active-cat');
|
|
if (subCatList) subCatList.classList.add('d-none');
|
|
filterItems('all');
|
|
});
|
|
}
|
|
|
|
mainCatBtns.forEach(btn => {
|
|
btn.addEventListener('click', async function() {
|
|
document.querySelectorAll('.category-nav button').forEach(b => b.classList.remove('active-cat'));
|
|
this.classList.add('active-cat');
|
|
|
|
const catId = this.dataset.catId;
|
|
|
|
// Fetch subcategories
|
|
try {
|
|
const response = await fetch(`/ajax/get-subcategories/?parent_id=${catId}`);
|
|
const subcats = await response.json();
|
|
|
|
if (subCatList) {
|
|
if (subcats.length > 0) {
|
|
subCatList.innerHTML = '';
|
|
// Add "All [Parent]" option
|
|
const allSubBtn = document.createElement('button');
|
|
allSubBtn.type = 'button';
|
|
allSubBtn.className = 'btn btn-xs btn-outline-accent rounded-pill px-3 active-cat';
|
|
allSubBtn.textContent = 'All ' + this.textContent.trim();
|
|
allSubBtn.addEventListener('click', function() {
|
|
subCatList.querySelectorAll('button').forEach(b => b.classList.remove('active-cat'));
|
|
this.classList.add('active-cat');
|
|
filterItems(catId);
|
|
});
|
|
subCatList.appendChild(allSubBtn);
|
|
|
|
subcats.forEach(sc => {
|
|
const scBtn = document.createElement('button');
|
|
scBtn.type = 'button';
|
|
scBtn.className = 'btn btn-xs btn-outline-accent rounded-pill px-3';
|
|
scBtn.textContent = sc.name;
|
|
scBtn.addEventListener('click', function() {
|
|
subCatList.querySelectorAll('button').forEach(b => b.classList.remove('active-cat'));
|
|
this.classList.add('active-cat');
|
|
filterItems(catId, sc.id.toString());
|
|
});
|
|
subCatList.appendChild(scBtn);
|
|
});
|
|
subCatList.classList.remove('d-none');
|
|
} else {
|
|
subCatList.classList.add('d-none');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching subcategories:', error);
|
|
}
|
|
|
|
filterItems(catId);
|
|
});
|
|
});
|
|
|
|
updateBadge();
|
|
});
|
|
</script>
|
|
{% endblock %} |