/* =========================================================================
* catalog.js — каталог: филтри, сортиране, грид (клиентски рендер, фаза 1)
* Поведението повтаря оригиналния макет: URL параметри tab, category, q,
* type, brand, culture, sub, pkg, avail, wh, sort; страниране по 12.
* Фаза 2: същата логика става server-side в Spring MVC контролер
* (правило от salex: sort/filter параметрите идват от URL).
* ========================================================================= */
(function () {
'use strict';
var esc = window.ACM.esc, fmt = window.ACM.fmtPrice, DEMO = window.ACM.demo;
var PRODUCTS = window.ACM_PRODUCTS || [];
var PAGE_SIZE = 12;
/* ---------------------- Икони (Lucide, inline) ------------------------- */
function icon(paths, size, sw) {
return '';
}
var IC = {
shield: '',
sprout: '',
droplets: '',
grid: '',
wheat: '',
sun: '',
flower: '',
grape: '',
carrot: '',
clover: '',
minus: '',
plus: '',
x: '',
chevron: '',
package: ''
};
var CULTURE_ICONS = {
'Пшеница': IC.wheat, 'Царевица': IC.sprout, 'Слънчоглед': IC.sun, 'Рапица': IC.flower,
'Ечемик': IC.wheat, 'Лозя': IC.grape, 'Зеленчуци': IC.carrot, 'Люцерна': IC.clover
};
var QUICKNAV = [
{ category: 'Растителна защита', label: 'Растителна защита', ic: IC.shield,
activeCls: 'border-green-deep bg-green-deep text-white', idleChip: 'bg-green-tint text-green-deep', idleHover: 'hover:border-green-deep' },
{ category: 'Семена', label: 'Семена', ic: IC.sprout,
activeCls: 'border-orange-deep bg-orange-deep text-white', idleChip: 'bg-[#fdf1e8] text-orange-deep', idleHover: 'hover:border-orange-deep' },
{ category: 'Торове', label: 'Торове', ic: IC.droplets,
activeCls: 'border-info bg-info text-white', idleChip: 'bg-[#eef5ff] text-info', idleHover: 'hover:border-info' },
{ category: '', label: 'Всички продукти', ic: IC.grid,
activeCls: 'border-ink bg-ink text-white', idleChip: 'bg-ink/[0.055] text-ink', idleHover: 'hover:border-ink' }
];
/* ---------------------- Състояние от URL ------------------------------- */
function listParam(v) { return v ? v.split(',').filter(Boolean) : []; }
function readState() {
var p = new URLSearchParams(window.location.search);
var sort = p.get('sort');
return {
tab: p.get('tab') === 'culture' ? 'culture' : 'category',
category: p.get('category') || '',
q: p.get('q') || '',
types: listParam(p.get('type')),
brands: listParam(p.get('brand')),
cultures: listParam(p.get('culture')),
substances: listParam(p.get('sub')),
packages: listParam(p.get('pkg')),
onlyAvailable: p.get('avail') === '1',
warehouse: p.get('wh') || '',
sort: (sort === 'price-asc' || sort === 'price-desc' || sort === 'stock') ? sort : 'name'
};
}
function writeState(s) {
var p = new URLSearchParams();
if (s.tab === 'culture') p.set('tab', 'culture');
if (s.category) p.set('category', s.category);
if (s.q) p.set('q', s.q);
if (s.types.length) p.set('type', s.types.join(','));
if (s.brands.length) p.set('brand', s.brands.join(','));
if (s.cultures.length) p.set('culture', s.cultures.join(','));
if (s.substances.length) p.set('sub', s.substances.join(','));
if (s.packages.length) p.set('pkg', s.packages.join(','));
if (s.onlyAvailable) p.set('avail', '1');
if (s.warehouse) p.set('wh', s.warehouse);
if (s.sort !== 'name') p.set('sort', s.sort);
var qs = p.toString();
history.replaceState(null, '', window.location.pathname + (qs ? '?' + qs : ''));
}
var state = readState();
var shown = PAGE_SIZE;
/* ---------------------- Филтрираща логика (като оригинала) -------------- */
var WH_BY_NAME = {};
DEMO.warehouses.forEach(function (w) { WH_BY_NAME[w.name] = w.key; });
function packageBucket(p) {
var u = p.package.unitLabel, bc = p.package.boxContent;
if (u === 'кг') return 'кг';
if (u === 'пакет' || u === 'бр.') return 'брой';
var n = parseFloat(String(bc).replace(/[^0-9.,]/g, '').replace(',', '.'));
if (!isFinite(n)) return '';
return n <= 1 ? 'до 1 л' : n <= 5 ? '1–5 л' : n <= 20 ? '5–20 л' : '20+ л';
}
function applyFilters(list, s) {
var wh = s.warehouse ? WH_BY_NAME[s.warehouse] : null;
var q = s.q.trim().toLowerCase();
return list.filter(function (e) {
if (s.category && e.category !== s.category) return false;
if (q) {
var inName = e.name.toLowerCase().indexOf(q) !== -1;
var inSub = e.activeSubstances.some(function (a) { return a.name.toLowerCase().indexOf(q) !== -1; });
if (!inName && !inSub) return false;
}
if (s.types.length && s.types.indexOf(e.productType) === -1) return false;
if (s.brands.length && s.brands.indexOf(e.brand) === -1) return false;
if (s.cultures.length && !s.cultures.some(function (c) { return e.cultureTags.indexOf(c) !== -1; })) return false;
if (s.substances.length && !e.activeSubstances.some(function (a) { return s.substances.indexOf(a.name) !== -1; })) return false;
if (s.packages.length && s.packages.indexOf(packageBucket(e)) === -1) return false;
if (s.onlyAvailable) {
if (wh) {
var st = e.stock[wh];
if (st !== 'green' && st !== 'orange') return false;
} else if (!Object.keys(e.stock).some(function (k) { return e.stock[k] === 'green' || e.stock[k] === 'orange'; })) {
return false;
}
}
return true;
});
}
var STOCK_RANK = { green: 0, orange: 1, grey: 2, red: 3 };
function stockRank(p) {
return Math.min.apply(null, Object.keys(p.stock).map(function (k) { return STOCK_RANK[p.stock[k]]; }));
}
function sortProducts(list, sort) {
var a = list.slice();
if (sort === 'price-asc') a.sort(function (x, y) { return x.price.perBox - y.price.perBox; });
else if (sort === 'price-desc') a.sort(function (x, y) { return y.price.perBox - x.price.perBox; });
else if (sort === 'stock') a.sort(function (x, y) { return stockRank(x) - stockRank(y) || x.name.localeCompare(y.name, 'bg'); });
else a.sort(function (x, y) { return x.name.localeCompare(y.name, 'bg'); });
return a;
}
/* Броячи по измерение (без собственото, като оригинала) */
function computeCounts(s) {
var types = {}, brands = {}, cultures = {};
applyFilters(PRODUCTS, mergeState(s, { types: [] })).forEach(function (p) {
types[p.productType] = (types[p.productType] || 0) + 1;
});
applyFilters(PRODUCTS, mergeState(s, { brands: [] })).forEach(function (p) {
if (p.brand) brands[p.brand] = (brands[p.brand] || 0) + 1;
});
applyFilters(PRODUCTS, mergeState(s, { cultures: [] })).forEach(function (p) {
p.cultureTags.forEach(function (c) { cultures[c] = (cultures[c] || 0) + 1; });
});
return { types: types, brands: brands, cultures: cultures };
}
function mergeState(s, patch) {
var out = {};
Object.keys(s).forEach(function (k) { out[k] = s[k]; });
Object.keys(patch).forEach(function (k) { out[k] = patch[k]; });
return out;
}
/* ---------------------- Продуктова карта (както в оригинала) ------------ */
function stockButtons(p) {
return DEMO.warehouses.map(function (w) {
var st = DEMO.stockStatus[p.stock[w.key]];
return '' +
'';
}).join('');
}
function productCard(p) {
var brandBadge = p.brand
? '' +
'' + esc(p.brand) + ''
: '';
var img = p.image
? '
'
: '' + icon(IC.package, 48, 1.25) + '';
var chips = 'кашон ' + esc(p.package.boxContent) + '';
if (p.package.palletSize) {
chips += 'пале ' + esc(p.package.palletSize) + '';
}
var isPack = p.package.unitLabel === 'пакет';
var qtyUnit = isPack ? 'бр.' : 'каш.';
return '
';
}
/* ---------------------- Рендер: бърза навигация ------------------------- */
function renderQuickNav() {
var counts = {};
PRODUCTS.forEach(function (p) { counts[p.category] = (counts[p.category] || 0) + 1; });
var el = document.getElementById('catalog-quicknav');
el.innerHTML = QUICKNAV.map(function (c) {
var active = state.category === c.category && state.tab === 'category';
var n = c.category === '' ? PRODUCTS.length : (counts[c.category] || 0);
return '';
}).join('');
Array.prototype.forEach.call(el.querySelectorAll('button[data-cat]'), function (b) {
b.addEventListener('click', function () {
var c = b.getAttribute('data-cat');
update({ tab: 'category', category: state.category === c ? '' : c });
});
});
}
/* ---------------------- Рендер: табове и култури ------------------------ */
function renderTabs() {
var tabCat = document.getElementById('catalog-tab-category');
var tabCul = document.getElementById('catalog-tab-culture');
var underline = '';
[[tabCat, 'category', 'По категория'], [tabCul, 'culture', 'По култура']].forEach(function (t) {
var el = t[0], key = t[1], label = t[2], sel = state.tab === key;
el.setAttribute('aria-selected', String(sel));
el.className = 'relative min-h-[44px] px-4 text-[15px] font-medium transition-colors ' + (sel ? 'text-ink' : 'text-muted hover:text-ink');
el.innerHTML = esc(label) + (sel ? underline : '');
});
document.getElementById('catalog-culture-picker').hidden = state.tab !== 'culture';
document.getElementById('catalog-layout').className = state.tab === 'category' ? 'lg:grid lg:grid-cols-[280px_1fr] lg:gap-8' : '';
document.getElementById('catalog-sidebar').style.display = state.tab === 'category' ? '' : 'none';
document.getElementById('catalog-filters-open').style.display = state.tab === 'category' ? '' : 'none';
if (state.tab === 'culture') renderCultureChips();
}
function renderCultureChips() {
var el = document.getElementById('catalog-culture-chips');
el.innerHTML = DEMO.cultures.map(function (c) {
var sel = state.cultures.indexOf(c) !== -1;
return '';
}).join('');
Array.prototype.forEach.call(el.querySelectorAll('button[data-culture]'), function (b) {
b.addEventListener('click', function () {
var c = b.getAttribute('data-culture');
var list = state.cultures.slice();
var i = list.indexOf(c);
if (i === -1) list.push(c); else list.splice(i, 1);
update({ cultures: list });
});
});
}
/* ---------------------- Рендер: панел с филтри -------------------------- */
function checkRow(group, value, label, count, checked) {
return '';
}
function filterSection(title, inner, open) {
return '' +
'' +
esc(title) + '' + icon(IC.chevron, 16) + '
' +
'' + inner + '
';
}
function uniqueSubstances() {
var set = {};
PRODUCTS.forEach(function (p) {
p.activeSubstances.forEach(function (s) { set[s.name] = true; });
});
return Object.keys(set).sort(function (a, b) { return a.localeCompare(b, 'bg'); });
}
function renderFilters() {
var counts = computeCounts(state);
var types = {};
PRODUCTS.forEach(function (p) { types[p.productType] = true; });
var html = '';
html += filterSection('Тип продукт', Object.keys(types).sort(function (a, b) { return a.localeCompare(b, 'bg'); }).map(function (t) {
return checkRow('types', t, t, counts.types[t] || 0, state.types.indexOf(t) !== -1);
}).join(''), true);
var brands = {};
PRODUCTS.forEach(function (p) { if (p.brand) brands[p.brand] = true; });
html += filterSection('Марка', Object.keys(brands).sort().map(function (b) {
return checkRow('brands', b, b, counts.brands[b] || 0, state.brands.indexOf(b) !== -1);
}).join(''), state.brands.length > 0);
html += filterSection('Култура', DEMO.cultures.map(function (c) {
return checkRow('cultures', c, c, counts.cultures[c] || 0, state.cultures.indexOf(c) !== -1);
}).join(''), state.cultures.length > 0);
html += filterSection('Активно вещество', uniqueSubstances().map(function (s) {
return checkRow('substances', s, s, null, state.substances.indexOf(s) !== -1);
}).join(''), state.substances.length > 0);
html += filterSection('Опаковка', DEMO.packagingBuckets.map(function (b) {
return checkRow('packages', b, b, null, state.packages.indexOf(b) !== -1);
}).join(''), state.packages.length > 0);
var whOptions = '' + DEMO.warehouses.map(function (w) {
return '';
}).join('');
html += filterSection('Наличност',
checkRow('avail', '1', 'Само налични', null, state.onlyAvailable) +
'', state.onlyAvailable);
['catalog-filters', 'catalog-filters-mobile'].forEach(function (id) {
var el = document.getElementById(id);
el.innerHTML = html;
Array.prototype.forEach.call(el.querySelectorAll('input[data-fgroup]'), function (inp) {
inp.addEventListener('change', function () {
var g = inp.getAttribute('data-fgroup');
if (g === 'avail') { update({ onlyAvailable: inp.checked }); return; }
var list = state[g].slice();
var i = list.indexOf(inp.value);
if (inp.checked && i === -1) list.push(inp.value);
if (!inp.checked && i !== -1) list.splice(i, 1);
var patch = {}; patch[g] = list;
update(patch);
});
});
var sel = el.querySelector('select[data-fgroup="warehouse"]');
if (sel) sel.addEventListener('change', function () { update({ warehouse: sel.value }); });
});
}
/* ---------------------- Рендер: активни филтри -------------------------- */
function activeFilterChips(s) {
var chips = [];
s.types.forEach(function (v) { chips.push({ key: 'type:' + v, label: v }); });
s.brands.forEach(function (v) { chips.push({ key: 'brand:' + v, label: v }); });
s.cultures.forEach(function (v) { chips.push({ key: 'culture:' + v, label: v }); });
s.substances.forEach(function (v) { chips.push({ key: 'sub:' + v, label: v }); });
s.packages.forEach(function (v) { chips.push({ key: 'pkg:' + v, label: v }); });
if (s.onlyAvailable) chips.push({ key: 'avail', label: s.warehouse ? 'Налични: ' + s.warehouse : 'Само налични' });
if (s.q) chips.push({ key: 'q', label: 'Търсене: „' + s.q + '"' });
return chips;
}
function removeChip(key) {
if (key === 'avail') { update({ onlyAvailable: false }); return; }
if (key === 'q') { update({ q: '' }); return; }
var i = key.indexOf(':');
var g = { type: 'types', brand: 'brands', culture: 'cultures', sub: 'substances', pkg: 'packages' }[key.slice(0, i)];
var v = key.slice(i + 1);
if (!g) return;
var patch = {}; patch[g] = state[g].filter(function (x) { return x !== v; });
update(patch);
}
function renderChipsRow(resultCount) {
var chips = activeFilterChips(state);
var el = document.getElementById('catalog-chips');
var countTxt = '' + resultCount + ' ' + (resultCount === 1 ? 'продукт' : 'продукта') + '';
if (!chips.length) { el.innerHTML = countTxt; return; }
el.innerHTML = '' + countTxt +
chips.map(function (c) {
return '';
}).join('') +
'
';
Array.prototype.forEach.call(el.querySelectorAll('button[data-chip]'), function (b) {
b.addEventListener('click', function () { removeChip(b.getAttribute('data-chip')); });
});
var clr = el.querySelector('button[data-clear]');
if (clr) clr.addEventListener('click', clearFilters);
}
function clearFilters() {
update({ types: [], brands: [], cultures: [], substances: [], packages: [], onlyAvailable: false, warehouse: '', q: '' });
}
/* ---------------------- Рендер: грид ------------------------------------ */
function renderGrid() {
var results = sortProducts(applyFilters(PRODUCTS, state), state.sort);
var visible = results.slice(0, shown);
var grid = document.getElementById('catalog-grid');
grid.innerHTML = visible.map(productCard).join('');
document.getElementById('catalog-empty').hidden = results.length > 0;
document.getElementById('catalog-more-wrap').hidden = results.length <= shown;
renderChipsRow(results.length);
document.getElementById('catalog-filters-open-label').textContent = 'Филтри (' + activeFilterChips(state).length + ')';
document.getElementById('catalog-sheet-apply').textContent = 'Покажи ' + results.length + ' ' + (results.length === 1 ? 'продукт' : 'продукта');
document.getElementById('catalog-breadcrumb').textContent = state.category || 'Каталог';
/* Свързване на купуващите контроли (стъпер + Добавяне) */
Array.prototype.forEach.call(grid.querySelectorAll('div.group'), function (card) {
var a = card.querySelector('a[href^="/product/"]');
if (!a) return;
var id = (a.getAttribute('href').match(/^\/product\/([^\/]+)\//) || [])[1];
var input = card.querySelector('input[aria-label="Количество"]');
var btns = card.querySelectorAll('.inline-flex.items-stretch button');
var addBtn = card.querySelector('button[data-add]');
function val() { var v = parseInt(input.value, 10); return isNaN(v) ? 1 : Math.min(9999, Math.max(1, v)); }
function sync() { input.value = String(val()); btns[0].disabled = val() <= 1; btns[1].disabled = val() >= 9999; }
btns[0].addEventListener('click', function () { input.value = String(val() - 1); sync(); });
btns[1].addEventListener('click', function () { input.value = String(val() + 1); sync(); });
input.addEventListener('change', sync);
addBtn.addEventListener('click', function () {
var p = window.ACM.findProduct(id);
window.ACM.cart.add(id, val());
window.ACM.showToast(p.name + ' е добавен в количката', {
actionLabel: 'Виж количката',
onAction: function () { window.location.href = '/cart/'; }
});
});
});
}
/* ---------------------- Обновяване -------------------------------------- */
function update(patch) {
state = mergeState(state, patch);
shown = PAGE_SIZE;
writeState(state);
renderAll();
}
function renderAll() {
renderQuickNav();
renderTabs();
renderFilters();
renderGrid();
}
/* ---------------------- Инициализация ----------------------------------- */
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('catalog-tab-category').addEventListener('click', function () { update({ tab: 'category' }); });
document.getElementById('catalog-tab-culture').addEventListener('click', function () { update({ tab: 'culture' }); });
document.getElementById('catalog-sort').value = state.sort;
document.getElementById('catalog-sort').addEventListener('change', function (e) { update({ sort: e.target.value }); });
document.getElementById('catalog-more').addEventListener('click', function () { shown += PAGE_SIZE; renderGrid(); });
document.getElementById('catalog-empty-clear').addEventListener('click', clearFilters);
var sheet = document.getElementById('catalog-sheet');
function setSheet(open) {
sheet.hidden = !open;
document.body.style.overflow = open ? 'hidden' : '';
}
document.getElementById('catalog-filters-open').addEventListener('click', function () { setSheet(true); });
document.getElementById('catalog-sheet-close').addEventListener('click', function () { setSheet(false); });
document.getElementById('catalog-sheet-scrim').addEventListener('click', function () { setSheet(false); });
document.getElementById('catalog-sheet-apply').addEventListener('click', function () { setSheet(false); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !sheet.hidden) setSheet(false); });
renderAll();
});
})();