Commit 0e3f8b6a authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: add occasion field to outfit_recommendations JSON and UI filter tabs

parent ed41a5ea
......@@ -420,6 +420,50 @@
padding: 4px 0;
font-style: italic;
}
/* Occasion filter tabs */
.occ-tabs {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-bottom: 8px;
padding: 4px 0;
}
.occ-tab {
font-size: 10px;
font-weight: 600;
padding: 4px 10px;
border-radius: 14px;
border: 1px solid var(--b);
background: var(--bg);
color: var(--m);
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.occ-tab:hover {
border-color: var(--primary);
color: var(--primary);
}
.occ-tab.active {
background: var(--primary);
color: #fff;
border-color: var(--primary);
}
.occ-badge {
font-size: 9px;
padding: 1px 6px;
border-radius: 8px;
font-weight: 600;
letter-spacing: 0.02em;
}
.occ-badge-hang_ngay { background: #eef1f8; color: #3b5998; }
.occ-badge-di_lam { background: #ecfdf5; color: #16a34a; }
.occ-badge-di_choi { background: #fef3c7; color: #d97706; }
.occ-badge-the_thao { background: #fce7f3; color: #db2777; }
.occ-badge-mac_nha { background: #f3e8ff; color: #7c3aed; }
.occ-badge-du_lich { background: #e0f2fe; color: #0284c7; }
.occ-badge-di_tiec { background: #fef2f2; color: #dc2626; }
</style>
</head>
<body>
......@@ -1262,6 +1306,8 @@
<span class="badge" id="outCount">0</span>
</h4>
<div id="outfitOccTabs" class="occ-tabs"></div>
<div class="ai-box">
<div class="ai-box-header">
<div class="ai-box-title"><i data-lucide="sparkles" style="width:12px;height:12px"></i> AI gợi ý set phối đồ</div>
......@@ -1444,9 +1490,51 @@
}
// ═══ OUTFIT RECOMMENDATIONS OPERATIONS ═══
const OCC_LABELS = {
'hang_ngay': 'Hàng ngày',
'di_lam': 'Đi làm',
'di_choi': 'Đi chơi',
'di_tiec': 'Đi tiệc',
'the_thao': 'Thể thao',
'mac_nha': 'Ở nhà',
'du_lich': 'Du lịch'
};
let activeOccFilter = null; // null = show all
function renderOutfitOccTabs() {
const tabsEl = document.getElementById('outfitOccTabs');
if (!tabsEl) return;
// Collect unique occasions from current outfits
const occs = [...new Set(currentOutfits.map(o => o.occasion).filter(Boolean))];
if (occs.length <= 1) {
tabsEl.innerHTML = '';
activeOccFilter = null;
return;
}
let html = `<span class="occ-tab ${!activeOccFilter ? 'active' : ''}" onclick="filterOutfitsByOcc(null)">Tất cả (${currentOutfits.length})</span>`;
occs.forEach(occ => {
const count = currentOutfits.filter(o => o.occasion === occ).length;
const label = OCC_LABELS[occ] || occ;
html += `<span class="occ-tab ${activeOccFilter === occ ? 'active' : ''}" onclick="filterOutfitsByOcc('${occ}')">${label} (${count})</span>`;
});
tabsEl.innerHTML = html;
}
function filterOutfitsByOcc(occ) {
activeOccFilter = occ;
renderOutfitOccTabs();
renderOutfitsList();
}
function renderOutfitsList() {
const container = document.getElementById('outfitListContainer');
document.getElementById('outCount').textContent = currentOutfits.length;
renderOutfitOccTabs();
// Filter by active occasion tab
const filtered = activeOccFilter
? currentOutfits.filter(o => o.occasion === activeOccFilter)
: currentOutfits;
if (!currentOutfits.length) {
container.innerHTML = `
......@@ -1460,8 +1548,19 @@
return;
}
if (!filtered.length) {
container.innerHTML = `
<div style="color:var(--m);font-size:11px;text-align:center;padding:20px">
Không có sản phẩm phối cho dịp "${OCC_LABELS[activeOccFilter] || activeOccFilter}".
</div>
`;
return;
}
container.innerHTML = '';
currentOutfits.forEach((item, idx) => {
filtered.forEach((item) => {
// Find real index in the full array (for delete/edit operations)
const idx = currentOutfits.indexOf(item);
const div = document.createElement('div');
div.className = 'outfit-item-card';
......@@ -1470,12 +1569,16 @@
const outerSel = item.role === 'outerwear' ? 'selected' : '';
const accSel = item.role === 'accessory' ? 'selected' : '';
const occKey = item.occasion || 'hang_ngay';
const occLabel = OCC_LABELS[occKey] || occKey;
const occBadge = `<span class="occ-badge occ-badge-${occKey}">${occLabel}</span>`;
div.innerHTML = `
<div class="outfit-item-top">
<img src="${formatImageUrl(item.product_image_url_thumbnail)}" onerror="this.onerror=null; this.src=NO_IMAGE_SVG;">
<div class="outfit-item-meta">
<div class="outfit-item-name" title="${item.match_product_name || ''}">${item.match_product_name || 'Tên phối đồ'}</div>
<div class="outfit-item-sku">${item.sku || '—'} ${item.color_name ? `| ${item.color_name}` : ''}</div>
<div class="outfit-item-sku">${item.sku || '—'} ${item.color_name ? `| ${item.color_name}` : ''} ${occBadge}</div>
</div>
<button class="btn-icon" onclick="removeOutfit(${idx})" title="Xóa phối đồ">
<i data-lucide="trash-2" style="width:14px;height:14px"></i>
......
......@@ -896,6 +896,8 @@ class StylistEngine:
role_items = {}
for occ, occ_data in matches.items():
for role, items in occ_data.items():
for item in items:
item["_occasion"] = occ # preserve occasion through grouping
role_items.setdefault(role, [])
role_items[role].extend(items)
......@@ -919,6 +921,7 @@ class StylistEngine:
"match_product_name": item["name"],
"role": role,
"reason": item["reason"],
"occasion": item.get("_occasion", "hang_ngay"),
"product_image_url_thumbnail": item.get("image", "")
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment