sortowanie_w_mass_add

This commit is contained in:
Mateusz Gruszczyński
2025-08-16 12:22:22 +02:00
parent 68f235d605
commit 529130a622
3 changed files with 188 additions and 122 deletions

View File

@@ -1,116 +1,184 @@
document.addEventListener('DOMContentLoaded', function () {
const modal = document.getElementById('massAddModal');
const productList = document.getElementById('mass-add-list');
const sortBar = document.getElementById('sort-bar');
const productCountDisplay = document.getElementById('product-count');
// Funkcja normalizacji (usuwa diakrytyki i zamienia na lowercase)
// Normalizacja nazw
function normalize(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
return str ? str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase() : '';
}
modal.addEventListener('show.bs.modal', async function () {
let addedProducts = new Set();
document.querySelectorAll('#items li').forEach(li => {
if (li.dataset.name) {
addedProducts.add(normalize(li.dataset.name));
let sortMode = 'popularity'; // 'popularity' lub 'alphabetical'
let limit = 50;
let offset = 0;
let loading = false;
let reachedEnd = false;
let allProducts = [];
let addedProducts = new Set();
function renderSortBar() {
if (!sortBar) return;
sortBar.innerHTML = `
<a href="#" id="sort-popularity" ${sortMode === "popularity" ? 'style="font-weight:bold"' : ''}>Sortuj: Popularność</a> |
<a href="#" id="sort-alphabetical" ${sortMode === "alphabetical" ? 'style="font-weight:bold"' : ''}>Alfabetycznie</a>
`;
document.getElementById('sort-popularity').onclick = (e) => {
e.preventDefault();
if (sortMode !== 'popularity') {
sortMode = 'popularity';
resetAndFetchProducts();
}
});
};
document.getElementById('sort-alphabetical').onclick = (e) => {
e.preventDefault();
if (sortMode !== 'alphabetical') {
sortMode = 'alphabetical';
resetAndFetchProducts();
}
};
}
productList.innerHTML = '<li class="list-group-item bg-dark text-light">Ładowanie...</li>';
function resetAndFetchProducts() {
offset = 0;
reachedEnd = false;
allProducts = [];
productList.innerHTML = '';
fetchProducts(true);
renderSortBar();
if (productCountDisplay) productCountDisplay.textContent = '';
}
async function fetchProducts(reset = false) {
if (loading || reachedEnd) return;
loading = true;
try {
const res = await fetch('/all_products');
productList.innerHTML = '<li class="list-group-item bg-dark text-light">Ładowanie...</li>';
const res = await fetch(`/all_products?sort=${sortMode}&limit=${limit}&offset=${offset}`);
const data = await res.json();
const allproducts = data.allproducts;
productList.innerHTML = '';
allproducts.forEach(name => {
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light';
if (addedProducts.has(normalize(name))) {
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
li.appendChild(nameSpan);
li.classList.add('opacity-50');
const badge = document.createElement('span');
badge.className = 'badge bg-success ms-auto';
badge.textContent = 'Dodano';
li.appendChild(badge);
} else {
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
nameSpan.style.flex = '1 1 auto';
li.appendChild(nameSpan);
const qtyWrapper = document.createElement('div');
qtyWrapper.className = 'd-flex align-items-center ms-2 quantity-controls';
const minusBtn = document.createElement('button');
minusBtn.type = 'button';
minusBtn.className = 'btn btn-outline-light btn-sm px-2';
minusBtn.textContent = '';
minusBtn.onclick = () => {
qty.value = Math.max(1, parseInt(qty.value) - 1);
};
const qty = document.createElement('input');
qty.type = 'number';
qty.min = 1;
qty.value = 1;
qty.className = 'form-control text-center p-1 rounded';
qty.style.width = '50px';
qty.style.margin = '0 2px';
qty.title = 'Ilość';
const plusBtn = document.createElement('button');
plusBtn.type = 'button';
plusBtn.className = 'btn btn-outline-light btn-sm px-2';
plusBtn.textContent = '+';
plusBtn.onclick = () => {
qty.value = parseInt(qty.value) + 1;
};
qtyWrapper.appendChild(minusBtn);
qtyWrapper.appendChild(qty);
qtyWrapper.appendChild(plusBtn);
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-primary ms-4';
btn.textContent = '+';
btn.onclick = () => {
const quantity = parseInt(qty.value) || 1;
socket.emit('add_item', { list_id: LIST_ID, name: name, quantity: quantity });
};
li.appendChild(qtyWrapper);
li.appendChild(btn);
}
productList.appendChild(li);
});
const products = data.products || data.allproducts || [];
if (products.length < limit) reachedEnd = true;
allProducts = reset ? products : allProducts.concat(products);
renderProducts(products, reset);
offset += limit;
if (productCountDisplay) {
productCountDisplay.textContent = `Wyświetlono ${allProducts.length} produktów.`;
}
} catch (err) {
productList.innerHTML = '<li class="list-group-item text-danger bg-dark">Błąd ładowania danych</li>';
}
loading = false;
}
function getAlreadyAddedProducts() {
const set = new Set();
document.querySelectorAll('#items li').forEach(li => {
if (li.dataset.name) {
set.add(normalize(li.dataset.name));
}
});
return set;
}
function renderProducts(products, reset) {
if (reset) productList.innerHTML = '';
addedProducts = getAlreadyAddedProducts();
products.forEach(name => {
if (typeof name === "object" && name.name) name = name.name;
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light';
if (addedProducts.has(normalize(name))) {
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
li.appendChild(nameSpan);
li.classList.add('opacity-50');
const badge = document.createElement('span');
badge.className = 'badge bg-success ms-auto';
badge.textContent = 'Dodano';
li.appendChild(badge);
} else {
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
nameSpan.style.flex = '1 1 auto';
li.appendChild(nameSpan);
const qtyWrapper = document.createElement('div');
qtyWrapper.className = 'd-flex align-items-center ms-2 quantity-controls';
const minusBtn = document.createElement('button');
minusBtn.type = 'button';
minusBtn.className = 'btn btn-outline-light btn-sm px-2';
minusBtn.textContent = '';
const qty = document.createElement('input');
qty.type = 'number';
qty.min = 1;
qty.value = 1;
qty.className = 'form-control text-center p-1 rounded';
qty.style.width = '50px';
qty.style.margin = '0 2px';
qty.title = 'Ilość';
const plusBtn = document.createElement('button');
plusBtn.type = 'button';
plusBtn.className = 'btn btn-outline-light btn-sm px-2';
plusBtn.textContent = '+';
minusBtn.onclick = () => {
qty.value = Math.max(1, parseInt(qty.value) - 1);
};
plusBtn.onclick = () => {
qty.value = parseInt(qty.value) + 1;
};
qtyWrapper.appendChild(minusBtn);
qtyWrapper.appendChild(qty);
qtyWrapper.appendChild(plusBtn);
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-primary ms-4';
btn.textContent = '+';
btn.onclick = () => {
const quantity = parseInt(qty.value) || 1;
socket.emit('add_item', { list_id: LIST_ID, name: name, quantity: quantity });
};
li.appendChild(qtyWrapper);
li.appendChild(btn);
}
productList.appendChild(li);
});
}
// Infinite scroll
modal.addEventListener('scroll', function () {
if (!loading && !reachedEnd && (modal.scrollTop + modal.offsetHeight > modal.scrollHeight - 80)) {
fetchProducts(false);
}
});
// Modal otwarty
modal.addEventListener('show.bs.modal', function () {
resetAndFetchProducts();
});
renderSortBar();
// Obsługa dodania przez socket
socket.on('item_added', data => {
document.querySelectorAll('#mass-add-list li').forEach(li => {
const itemName = li.firstChild?.textContent.trim();
if (normalize(itemName) === normalize(data.name) && !li.classList.contains('opacity-50')) {
li.classList.add('opacity-50');
// Usuń poprzednie przyciski
li.querySelectorAll('button').forEach(btn => btn.remove());
const quantityControls = li.querySelector('.quantity-controls');
if (quantityControls) quantityControls.remove();
// Badge "Dodano"
const badge = document.createElement('span');
badge.className = 'badge bg-success';
badge.textContent = 'Dodano';
// Grupowanie przycisku + licznika
const btnGroup = document.createElement('div');
btnGroup.className = 'btn-group btn-group-sm me-2';
btnGroup.role = 'group';
@@ -127,14 +195,12 @@ document.addEventListener('DOMContentLoaded', function () {
btnGroup.appendChild(undoBtn);
btnGroup.appendChild(timerBtn);
// Kontener na prawą stronę
const rightWrapper = document.createElement('div');
rightWrapper.className = 'd-flex align-items-center gap-2 ms-auto';
rightWrapper.appendChild(btnGroup);
rightWrapper.appendChild(badge);
li.appendChild(rightWrapper);
// Odliczanie
const intervalId = setInterval(() => {
secondsLeft--;
if (secondsLeft > 0) {
@@ -145,14 +211,11 @@ document.addEventListener('DOMContentLoaded', function () {
}
}, 1000);
// Obsługa cofnięcia
undoBtn.onclick = () => {
clearInterval(intervalId);
btnGroup.remove();
badge.remove();
li.classList.remove('opacity-50');
// Przywróć kontrolki ilości
const qtyWrapper = document.createElement('div');
qtyWrapper.className = 'd-flex align-items-center ms-2 quantity-controls';
@@ -185,7 +248,6 @@ document.addEventListener('DOMContentLoaded', function () {
qtyWrapper.append(minusBtn, qty, plusBtn);
li.appendChild(qtyWrapper);
// Dodaj przycisk dodawania
const addBtn = document.createElement('button');
addBtn.className = 'btn btn-sm btn-primary ms-4';
addBtn.textContent = '+';
@@ -199,13 +261,10 @@ document.addEventListener('DOMContentLoaded', function () {
};
li.appendChild(addBtn);
// Usuń z listy
socket.emit('delete_item', { item_id: data.id });
};
}
});
});
});