fixy w js
This commit is contained in:
5
app.py
5
app.py
@@ -44,7 +44,8 @@ PROTECTED_JS_FILES = {
|
||||
"expenses.js",
|
||||
"toggle_button.js",
|
||||
"user_management.js",
|
||||
"mass_add.js"
|
||||
"mass_add.js",
|
||||
"functions.js"
|
||||
}
|
||||
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
@@ -1141,7 +1142,7 @@ def handle_join(data):
|
||||
emit('user_joined', {'username': username}, to=room)
|
||||
emit('user_list', {'users': list(active_users[room])}, to=room)
|
||||
emit('joined_confirmation', {'room': room, 'list_title': list_title})
|
||||
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect(sid):
|
||||
global active_users
|
||||
|
240
static/js/functions.js
Normal file
240
static/js/functions.js
Normal file
@@ -0,0 +1,240 @@
|
||||
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.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(link).then(() => {
|
||||
showToast('Link skopiowany do schowka!');
|
||||
}).catch((err) => {
|
||||
console.error('Błąd clipboard API:', err);
|
||||
fallbackCopyText(link);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyText(link);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopyText(text) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.top = 0;
|
||||
textarea.style.left = 0;
|
||||
textarea.style.opacity = 0;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
showToast('Link skopiowany do schowka!');
|
||||
} else {
|
||||
showToast('Nie udało się skopiować linku', 'warning');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fallback błąd kopiowania:', err);
|
||||
showToast('Nie udało się skopiować linku', 'warning');
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function isListDifferent(oldItems, newItems) {
|
||||
if (oldItems.length !== newItems.length) return true;
|
||||
|
||||
const oldIds = Array.from(oldItems).map(li => parseInt(li.id.replace('item-', ''), 10)).sort();
|
||||
const newIds = newItems.map(i => i.id).sort();
|
||||
|
||||
for (let i = 0; i < newIds.length; i++) {
|
||||
if (oldIds[i] !== newIds[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateListSmoothly(newItems) {
|
||||
const itemsContainer = document.getElementById('items');
|
||||
const existingItemsMap = new Map();
|
||||
|
||||
Array.from(itemsContainer.querySelectorAll('li')).forEach(li => {
|
||||
const id = parseInt(li.id.replace('item-', ''), 10);
|
||||
existingItemsMap.set(id, li);
|
||||
});
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
newItems.forEach(item => {
|
||||
let li = existingItemsMap.get(item.id);
|
||||
let quantityBadge = '';
|
||||
if (item.quantity && item.quantity > 1) {
|
||||
quantityBadge = `<span class="badge bg-secondary">x${item.quantity}</span>`;
|
||||
}
|
||||
|
||||
if (li) {
|
||||
// Checkbox
|
||||
const checkbox = li.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) {
|
||||
checkbox.checked = item.purchased;
|
||||
checkbox.disabled = false; // Zdejmij disabled jeśli było
|
||||
}
|
||||
|
||||
// Klasy
|
||||
li.classList.remove('bg-success', 'text-white', 'item-not-checked', 'opacity-50');
|
||||
if (item.purchased) {
|
||||
li.classList.add('bg-success', 'text-white');
|
||||
} else {
|
||||
li.classList.add('item-not-checked');
|
||||
}
|
||||
|
||||
// Nazwa
|
||||
const nameSpan = li.querySelector(`#name-${item.id}`);
|
||||
const expectedName = `${item.name} ${quantityBadge}`.trim();
|
||||
if (nameSpan && nameSpan.innerHTML.trim() !== expectedName) {
|
||||
nameSpan.innerHTML = expectedName;
|
||||
}
|
||||
|
||||
// Notatka
|
||||
let noteEl = li.querySelector('small');
|
||||
if (item.note) {
|
||||
if (!noteEl) {
|
||||
const newNote = document.createElement('small');
|
||||
newNote.className = 'text-danger ms-4';
|
||||
newNote.innerHTML = `[ <b>${item.note}</b> ]`;
|
||||
nameSpan.insertAdjacentElement('afterend', newNote);
|
||||
} else {
|
||||
noteEl.innerHTML = `[ <b>${item.note}</b> ]`;
|
||||
}
|
||||
} else if (noteEl) {
|
||||
noteEl.remove();
|
||||
}
|
||||
|
||||
// Usuń spinner jeśli był
|
||||
const sp = li.querySelector('.spinner-border');
|
||||
if (sp) sp.remove();
|
||||
|
||||
} else {
|
||||
// Twórz nowy element
|
||||
li = document.createElement('li');
|
||||
li.className = `list-group-item d-flex justify-content-between align-items-center flex-wrap ${item.purchased ? 'bg-success text-white' : 'item-not-checked'}`;
|
||||
li.id = `item-${item.id}`;
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||
<input class="large-checkbox" type="checkbox" ${item.purchased ? 'checked' : ''}>
|
||||
<span id="name-${item.id}" class="text-white">${item.name} ${quantityBadge}</span>
|
||||
${item.note ? `<small class="text-danger ms-4">[ <b>${item.note}</b> ]</small>` : ''}
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-info" onclick="openNoteModal(event, ${item.id})">📝</button>
|
||||
`;
|
||||
}
|
||||
|
||||
fragment.appendChild(li);
|
||||
});
|
||||
|
||||
// Wyczyść i wstaw nowy porządek
|
||||
itemsContainer.innerHTML = '';
|
||||
itemsContainer.appendChild(fragment);
|
||||
|
||||
updateProgressBar();
|
||||
}
|
@@ -204,11 +204,11 @@ function setupList(listId, username) {
|
||||
|
||||
|
||||
socket.on('user_joined', function(data) {
|
||||
showToast(`${data.username} dołączył do listy 👥`, 'info');
|
||||
showToast(`${data.username} dołączył do listy`, 'info');
|
||||
});
|
||||
|
||||
socket.on('user_left', function(data) {
|
||||
showToast(`${data.username} opuścił listę ❌`, 'warning');
|
||||
showToast(`${data.username} opuścił listę`, 'warning');
|
||||
});
|
||||
|
||||
socket.on('user_list', function(data) {
|
||||
@@ -217,210 +217,16 @@ socket.on('user_list', function(data) {
|
||||
});
|
||||
|
||||
socket.on('full_list', function(data) {
|
||||
const itemsContainer = document.getElementById('items');
|
||||
const oldItems = Array.from(itemsContainer.querySelectorAll('li'));
|
||||
|
||||
if (isListDifferent(oldItems, data.items)) {
|
||||
updateListSmoothly(data.items);
|
||||
showToast('🔄 Lista została zaktualizowana', 'info');
|
||||
showToast('Lista została zaktualizowana', 'info');
|
||||
} else {
|
||||
updateListSmoothly(data.items);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function updateListSmoothly(newItems) {
|
||||
const itemsContainer = document.getElementById('items');
|
||||
const existingItemsMap = new Map();
|
||||
|
||||
Array.from(itemsContainer.querySelectorAll('li')).forEach(li => {
|
||||
const id = parseInt(li.id.replace('item-', ''), 10);
|
||||
existingItemsMap.set(id, li);
|
||||
});
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
newItems.forEach(item => {
|
||||
let li = existingItemsMap.get(item.id);
|
||||
let quantityBadge = '';
|
||||
if (item.quantity && item.quantity > 1) {
|
||||
quantityBadge = `<span class="badge bg-secondary">x${item.quantity}</span>`;
|
||||
}
|
||||
|
||||
if (li) {
|
||||
// Checkbox
|
||||
const checkbox = li.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) {
|
||||
checkbox.checked = item.purchased;
|
||||
checkbox.disabled = false; // Zdejmij disabled jeśli było
|
||||
}
|
||||
|
||||
// Klasy
|
||||
li.classList.remove('bg-success', 'text-white', 'item-not-checked', 'opacity-50');
|
||||
if (item.purchased) {
|
||||
li.classList.add('bg-success', 'text-white');
|
||||
} else {
|
||||
li.classList.add('item-not-checked');
|
||||
}
|
||||
|
||||
// Nazwa
|
||||
const nameSpan = li.querySelector(`#name-${item.id}`);
|
||||
const expectedName = `${item.name} ${quantityBadge}`.trim();
|
||||
if (nameSpan && nameSpan.innerHTML.trim() !== expectedName) {
|
||||
nameSpan.innerHTML = expectedName;
|
||||
}
|
||||
|
||||
// Notatka
|
||||
let noteEl = li.querySelector('small');
|
||||
if (item.note) {
|
||||
if (!noteEl) {
|
||||
const newNote = document.createElement('small');
|
||||
newNote.className = 'text-danger ms-4';
|
||||
newNote.innerHTML = `[ <b>${item.note}</b> ]`;
|
||||
nameSpan.insertAdjacentElement('afterend', newNote);
|
||||
} else {
|
||||
noteEl.innerHTML = `[ <b>${item.note}</b> ]`;
|
||||
}
|
||||
} else if (noteEl) {
|
||||
noteEl.remove();
|
||||
}
|
||||
|
||||
// Usuń spinner jeśli był
|
||||
const sp = li.querySelector('.spinner-border');
|
||||
if (sp) sp.remove();
|
||||
|
||||
} else {
|
||||
// Twórz nowy element
|
||||
li = document.createElement('li');
|
||||
li.className = `list-group-item d-flex justify-content-between align-items-center flex-wrap ${item.purchased ? 'bg-success text-white' : 'item-not-checked'}`;
|
||||
li.id = `item-${item.id}`;
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||
<input class="large-checkbox" type="checkbox" ${item.purchased ? 'checked' : ''}>
|
||||
<span id="name-${item.id}" class="text-white">${item.name} ${quantityBadge}</span>
|
||||
${item.note ? `<small class="text-danger ms-4">[ <b>${item.note}</b> ]</small>` : ''}
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-info" onclick="openNoteModal(event, ${item.id})">📝</button>
|
||||
`;
|
||||
}
|
||||
|
||||
fragment.appendChild(li);
|
||||
});
|
||||
|
||||
// Wyczyść i wstaw nowy porządek
|
||||
itemsContainer.innerHTML = '';
|
||||
itemsContainer.appendChild(fragment);
|
||||
|
||||
updateProgressBar();
|
||||
}
|
||||
|
@@ -72,6 +72,7 @@
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='bootstrap.bundle.min.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='socket.io.min.js') }}"></script>
|
||||
{% if request.endpoint != 'system_auth' %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='functions.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='live.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='hide_list.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='socket_reconnect.js') }}"></script>
|
||||
|
@@ -137,7 +137,6 @@
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='toggle_button.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
Reference in New Issue
Block a user