This commit is contained in:
Mateusz Gruszczyński
2025-07-06 10:49:45 +02:00
parent 5ea203cbe1
commit 7b0975158e
4 changed files with 322 additions and 0 deletions

View File

@ -327,3 +327,4 @@ function showToast(message, type = 'primary') {
toastContainer.appendChild(toast);
setTimeout(() => { toast.remove(); }, 1750);
}

319
static/js/live_backup.js Normal file
View File

@ -0,0 +1,319 @@
const socket = io();
function setupList(listId, username) {
socket.emit('join_list', { room: listId, username: username });
const newItemInput = document.getElementById('newItem');
const parentDiv = newItemInput.closest('.input-group');
if (parentDiv) {
parentDiv.classList.add('position-relative');
}
const suggestionsBox = document.createElement('div');
suggestionsBox.className = 'list-group position-absolute w-100';
suggestionsBox.style.top = '100%';
suggestionsBox.style.left = '0';
suggestionsBox.style.zIndex = '9999';
newItemInput.parentNode.appendChild(suggestionsBox);
newItemInput.addEventListener('input', async function () {
const query = this.value;
if (query.length < 2) {
suggestionsBox.innerHTML = '';
return;
}
const res = await fetch(`/suggest_products?q=${encodeURIComponent(query)}`);
const data = await res.json();
suggestionsBox.innerHTML = '';
data.suggestions.forEach(s => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'list-group-item list-group-item-action';
item.textContent = s;
item.onclick = () => {
newItemInput.value = s;
suggestionsBox.innerHTML = '';
};
suggestionsBox.appendChild(item);
});
});
newItemInput.addEventListener('blur', () => {
setTimeout(() => { suggestionsBox.innerHTML = ''; }, 200);
});
newItemInput.focus();
newItemInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
addItem(listId);
}
});
const itemsContainer = document.getElementById('items');
itemsContainer.addEventListener('change', function (e) {
if (e.target && e.target.type === 'checkbox') {
const li = e.target.closest('li');
if (li) {
const id = parseInt(li.id.replace('item-', ''), 10);
if (e.target.checked) {
socket.emit('check_item', { item_id: id });
} else {
socket.emit('uncheck_item', { item_id: id });
}
e.target.disabled = true;
li.classList.add('opacity-50');
let existingSpinner = li.querySelector('.spinner-border');
if (!existingSpinner) {
const spinner = document.createElement('span');
spinner.className = 'spinner-border spinner-border-sm ms-2';
spinner.setAttribute('role', 'status');
spinner.setAttribute('aria-hidden', 'true');
e.target.parentElement.appendChild(spinner);
}
}
}
});
socket.on('item_checked', data => {
updateItemState(data.item_id, true);
});
socket.on('item_unchecked', data => {
updateItemState(data.item_id, false);
});
socket.on('expense_added', data => {
const badgeEl = document.getElementById('total-expense1');
if (badgeEl) {
badgeEl.innerHTML = `💸 ${data.total.toFixed(2)} PLN`;
badgeEl.classList.remove('bg-secondary');
badgeEl.classList.add('bg-success');
badgeEl.style.display = '';
}
const summaryEl = document.getElementById('total-expense2');
if (summaryEl) {
summaryEl.innerHTML = `<b>💸 Łącznie wydano:</b> ${data.total.toFixed(2)} PLN`;
}
showToast(`Dodano wydatek: ${data.amount.toFixed(2)} PLN`);
});
socket.on('item_added', data => {
showToast(`${data.added_by} dodał: ${data.name}`);
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center flex-wrap item-not-checked';
li.id = `item-${data.id}`;
let quantityBadge = '';
if (data.quantity && data.quantity > 1) {
quantityBadge = `<span class="badge bg-secondary">x${data.quantity}</span>`;
}
li.innerHTML = `
<div class="d-flex align-items-center flex-wrap gap-2">
<input type="checkbox">
<span id="name-${data.id}" class="text-white">${data.name} ${quantityBadge}</span>
</div>
<div class="mt-2 mt-md-0">
<button class="btn btn-sm btn-outline-warning me-1" onclick="editItem(${data.id}, '${data.name}', ${data.quantity || 1})">✏️ Edytuj</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteItem(${data.id})">🗑️ Usuń</button>
</div>
`;
document.getElementById('items').appendChild(li);
updateProgressBar();
});
socket.on('item_deleted', data => {
const li = document.getElementById(`item-${data.item_id}`);
if (li) {
li.remove();
}
showToast('Usunięto produkt');
updateProgressBar();
});
socket.on('progress_updated', function(data) {
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = data.percent + '%';
progressBar.setAttribute('aria-valuenow', data.percent);
progressBar.textContent = `${Math.round(data.percent)}%`;
}
const progressTitle = document.getElementById('progress-title');
if (progressTitle) {
progressTitle.textContent = `📊 Postęp listy — ${data.purchased_count}/${data.total_count} kupionych (${Math.round(data.percent)}%)`;
}
});
socket.on('note_updated', data => {
const itemEl = document.getElementById(`item-${data.item_id}`);
if (itemEl) {
let noteEl = itemEl.querySelector('small');
if (noteEl) {
noteEl.innerHTML = `[ Notatka: <b>${data.note}</b> ]`;
} else {
const newNote = document.createElement('small');
newNote.className = 'text-danger ms-4';
newNote.innerHTML = `[ Notatka: <b>${data.note}</b> ]`;
const flexColumn = itemEl.querySelector('.d-flex.flex-column');
if (flexColumn) {
flexColumn.appendChild(newNote);
} else {
itemEl.appendChild(newNote);
}
}
}
showToast('Notatka zaktualizowana!');
});
socket.on('item_edited', data => {
const nameSpan = document.getElementById(`name-${data.item_id}`);
if (nameSpan) {
let quantityBadge = '';
if (data.new_quantity && data.new_quantity > 1) {
quantityBadge = ` <span class="badge bg-secondary">x${data.new_quantity}</span>`;
}
nameSpan.innerHTML = `${data.new_name}${quantityBadge}`;
}
showToast(`Zaktualizowano produkt: ${data.new_name} (x${data.new_quantity})`);
});
updateProgressBar();
// --- NOWE: zapisz dane do reconnect ---
window.LIST_ID = listId;
window.usernameForReconnect = username;
socket.on('joined_confirmation', function(data) {
console.log('Dołączono do pokoju:', data.room);
showToast('Pokój ponownie dołączony ✅', 'info');
});
}
function updateItemState(itemId, isChecked) {
const checkbox = document.querySelector(`#item-${itemId} input[type='checkbox']`);
if (checkbox) {
checkbox.checked = isChecked;
checkbox.disabled = false;
const li = checkbox.closest('li');
li.classList.remove('opacity-50', 'bg-light', 'text-dark', 'bg-success', 'text-white');
if (isChecked) {
li.classList.add('bg-success', 'text-white');
} else {
li.classList.add('item-not-checked');
}
const sp = li.querySelector('.spinner-border');
if (sp) sp.remove();
}
updateProgressBar();
}
function updateProgressBar() {
const items = document.querySelectorAll('#items li');
const total = items.length;
const purchased = Array.from(items).filter(li => li.classList.contains('bg-success')).length;
const percent = total > 0 ? Math.round((purchased / total) * 100) : 0;
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = `${percent}%`;
progressBar.setAttribute('aria-valuenow', percent);
progressBar.textContent = `${percent}%`;
}
}
function addItem(listId) {
const name = document.getElementById('newItem').value;
const quantityInput = document.getElementById('newQuantity');
let quantity = 1;
if (quantityInput) {
quantity = parseInt(quantityInput.value);
if (isNaN(quantity) || quantity < 1) {
quantity = 1;
}
}
if (name.trim() === '') return;
socket.emit('add_item', { list_id: listId, name: name, quantity: quantity });
document.getElementById('newItem').value = '';
if (quantityInput) quantityInput.value = 1;
document.getElementById('newItem').focus();
}
function deleteItem(id) {
if (confirm('Na pewno usunąć produkt?')) {
socket.emit('delete_item', { item_id: id });
}
}
function editItem(id, oldName, oldQuantity) {
const newName = prompt('Podaj nową nazwę (lub zostaw starą):', oldName);
if (newName === null) return;
const newQuantityStr = prompt('Podaj nową ilość:', oldQuantity);
if (newQuantityStr === null) return;
const finalName = newName.trim() !== '' ? newName.trim() : oldName;
let newQuantity = parseInt(newQuantityStr);
if (isNaN(newQuantity) || newQuantity < 1) {
newQuantity = oldQuantity;
}
socket.emit('edit_item', { item_id: id, new_name: finalName, new_quantity: newQuantity });
}
function submitExpense(listId) {
const amountInput = document.getElementById('expenseAmount');
const amount = parseFloat(amountInput.value);
if (isNaN(amount) || amount <= 0) {
showToast('Podaj poprawną kwotę!');
return;
}
socket.emit('add_expense', {
list_id: listId,
amount: amount
});
amountInput.value = '';
}
function copyLink(link) {
if (navigator.share) {
navigator.share({
title: 'Lista zakupów',
text: 'Udostępniam Ci moją listę zakupów:',
url: link
}).catch((err) => console.log('Udostępnianie anulowane', err));
} else {
navigator.clipboard.writeText(link).then(() => {
showToast('Link skopiowany do schowka!');
});
}
}
function showToast(message, type = 'primary') {
const toastContainer = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast align-items-center text-bg-${type} border-0 show`;
toast.setAttribute('role', 'alert');
toast.innerHTML = `<div class="d-flex"><div class="toast-body">${message}</div></div>`;
toastContainer.appendChild(toast);
setTimeout(() => { toast.remove(); }, 1750);
}

View File

@ -27,6 +27,7 @@ socket.on('connect', function() {
showToast('Połączono z serwerem! 🔄', 'info');
// Automatyczne ponowne dołączenie do pokoju
if (window.LIST_ID && window.usernameForReconnect) {
console.log('Ponownie wysyłam join_list:', window.LIST_ID, window.usernameForReconnect);
socket.emit('join_list', { room: window.LIST_ID, username: window.usernameForReconnect });
}
}