zmiany ux i nowe funckje

This commit is contained in:
Mateusz Gruszczyński
2025-07-10 13:03:33 +02:00
parent 40fa601bbe
commit f36739aa40
11 changed files with 317 additions and 77 deletions

View File

@@ -32,6 +32,16 @@
white-space: nowrap;
}
/* rodzic już ma position-relative */
.progress-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none; /* klikalne przyciski obok paska nie ucierpią */
white-space: nowrap;
}
.progress-thin {
height: 12px;
}
@@ -250,3 +260,8 @@ input.form-control {
align-items: center;
justify-content: space-between;
}
#empty-placeholder {
font-style: italic;
pointer-events: none;
}

View File

@@ -281,6 +281,7 @@ function updateListSmoothly(newItems) {
itemsContainer.appendChild(fragment);
updateProgressBar();
toggleEmptyPlaceholder();
}
document.addEventListener("DOMContentLoaded", function() {

View File

@@ -1,5 +1,25 @@
const socket = io();
/*──────────────── placeholder pustej listy ────────────────*/
function toggleEmptyPlaceholder() {
const list = document.getElementById('items');
if (!list) return;
// prawdziwe <li> to te z dataname lub id="item…"
const hasRealItems = list.querySelector('li[data-name], li[id^="item-"]') !== null;
const placeholder = document.getElementById('empty-placeholder');
if (!hasRealItems && !placeholder) {
const li = document.createElement('li');
li.id = 'empty-placeholder';
li.className = 'list-group-item bg-dark text-secondary text-center w-100';
li.textContent = 'Brak produktów w tej liście.';
list.appendChild(li);
} else if (hasRealItems && placeholder) {
placeholder.remove();
}
}
function setupList(listId, username) {
socket.emit('join_list', { room: listId, username: username });
@@ -135,6 +155,7 @@ function setupList(listId, username) {
document.getElementById('items').appendChild(li);
updateProgressBar();
toggleEmptyPlaceholder();
});
socket.on('item_deleted', data => {
@@ -144,6 +165,7 @@ function setupList(listId, username) {
}
showToast('Usunięto produkt');
updateProgressBar();
toggleEmptyPlaceholder();
});
socket.on('progress_updated', function(data) {
@@ -197,10 +219,10 @@ function setupList(listId, username) {
});
updateProgressBar();
toggleEmptyPlaceholder();
// --- WAŻNE: zapisz dane do reconnect ---
window.LIST_ID = listId;
window.usernameForReconnect = username;
}
}

View File

@@ -3,7 +3,6 @@ document.addEventListener('DOMContentLoaded', function () {
const productList = document.getElementById('mass-add-list');
modal.addEventListener('show.bs.modal', async function () {
// 🔥 Za każdym razem od nowa budujemy zbiór produktów już na liście
let addedProducts = new Set();
document.querySelectorAll('#items li').forEach(li => {
if (li.dataset.name) {
@@ -91,16 +90,11 @@ document.addEventListener('DOMContentLoaded', function () {
productList.appendChild(li);
});
} catch (err) {
productList.innerHTML = '<li class="list-group-item text-danger bg-dark">Błąd ładowania danych</li>';
}
});
// 🔥 Aktualizacja na żywo po dodaniu
socket.on('item_added', data => {
document.querySelectorAll('#mass-add-list li').forEach(li => {
const itemName = li.firstChild.textContent.trim();

View File

@@ -0,0 +1,85 @@
let receiptToastShown = false;
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("receiptForm");
const input = document.getElementById("receiptInput");
const gallery = document.getElementById("receiptGallery");
const progressContainer = document.getElementById("progressContainer");
const progressBar = document.getElementById("progressBar");
if (!form || !input || !gallery) return;
form.addEventListener("submit", function (e) {
e.preventDefault();
const file = input.files[0];
if (!file) {
showToast("Nie wybrano pliku!", "warning");
return;
}
const formData = new FormData();
formData.append("receipt", file);
const xhr = new XMLHttpRequest();
xhr.open("POST", form.action, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percent + "%";
progressBar.textContent = percent + "%";
}
};
xhr.onloadstart = function () {
progressContainer.style.display = "block";
progressBar.style.width = "0%";
progressBar.textContent = "0%";
};
xhr.onloadend = function () {
progressContainer.style.display = "none";
progressBar.style.width = "0%";
progressBar.textContent = "";
input.value = "";
};
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
if (res.success && res.url) {
fetch(window.location.href)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const newGallery = doc.getElementById("receiptGallery");
if (newGallery) {
gallery.innerHTML = newGallery.innerHTML;
if (!receiptToastShown) {
showToast("Wgrano paragon", "success");
receiptToastShown = true;
}
socket.emit("receipt_uploaded", {
list_id: LIST_ID,
url: res.url
});
}
});
} else {
showToast(res.message || "Błąd podczas wgrywania.", "danger");
}
} else {
showToast("Błąd serwera. Spróbuj ponownie.", "danger");
}
}
};
xhr.send(formData);
});
});

View File

@@ -1,3 +1,5 @@
let didReceiveFirstFullList = false;
// --- Automatyczny reconnect po powrocie do karty/przywróceniu internetu ---
function reconnectIfNeeded() {
if (!socket.connected) {
@@ -75,14 +77,45 @@ socket.on('user_list', function(data) {
}
});
socket.on('full_list', function(data) {
const itemsContainer = document.getElementById('items');
const oldItems = Array.from(itemsContainer.querySelectorAll('li'));
socket.on('receipt_added', function (data) {
const gallery = document.getElementById("receiptGallery");
if (!gallery) return;
if (isListDifferent(oldItems, data.items)) {
updateListSmoothly(data.items);
showToast('Lista została zaktualizowana', 'info');
} else {
updateListSmoothly(data.items);
// Usuń placeholder, jeśli istnieje
const alert = gallery.querySelector(".alert");
if (alert) {
alert.remove();
}
// Sprawdź, czy już istnieje obraz z tym URL
const existing = Array.from(gallery.querySelectorAll("img")).find(img => img.src === data.url);
if (!existing) {
const col = document.createElement("div");
col.className = "col-6 col-md-4 col-lg-3 text-center";
col.innerHTML = `
<a href="${data.url}" data-lightbox="receipt" data-title="Paragon">
<img src="${data.url}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
`;
gallery.appendChild(col);
}
});
socket.on('full_list', function (data) {
const itemsContainer = document.getElementById('items');
const oldItems = Array.from(
itemsContainer.querySelectorAll('li[data-name], li[id^="item-"]')
);
const isDifferent = isListDifferent(oldItems, data.items);
updateListSmoothly(data.items);
toggleEmptyPlaceholder();
if (didReceiveFirstFullList && isDifferent) {
showToast('Lista została zaktualizowana', 'info');
}
didReceiveFirstFullList = true;
});