diff --git a/.gitignore b/.gitignore index 4e4931d..338fc95 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ db/pgsql/* db/shopping.db *.swp version.txt -deploy/varnish/default.vcl \ No newline at end of file +deploy/varnish/default.vcl +*.zip \ No newline at end of file diff --git a/make_zip.py b/make_zip.py new file mode 100644 index 0000000..d4d275f --- /dev/null +++ b/make_zip.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import os +import sys +import zipfile +import subprocess +from pathlib import Path + + +def run_git_command(args, repo_path: Path) -> bytes: + result = subprocess.run( + ["git", *args], + cwd=repo_path, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + return result.stdout + + +def get_files_to_archive(repo_path: Path) -> list[str]: + output = run_git_command( + ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], + repo_path, + ) + files = output.decode("utf-8", errors="surrogateescape").split("\0") + return [f for f in files if f] + + +def make_zip(repo_path: Path, output_zip: Path) -> None: + files = get_files_to_archive(repo_path) + + output_zip = output_zip.resolve() + if output_zip.exists(): + output_zip.unlink() + + with zipfile.ZipFile(output_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf: + for rel_path in files: + abs_path = repo_path / rel_path + + if not abs_path.exists(): + continue + + if abs_path.resolve() == output_zip: + continue + + zf.write(abs_path, arcname=rel_path) + + print(f"Utworzono archiwum: {output_zip}") + print(f"Added files: {len(files)}") + + +def main(): + repo_path = Path.cwd() + + if len(sys.argv) > 1: + output_zip = Path(sys.argv[1]) + else: + output_zip = repo_path / f"{repo_path.name}.zip" + + try: + run_git_command(["rev-parse", "--show-toplevel"], repo_path) + except subprocess.CalledProcessError: + print("Error: this directory is not a Git repository.", file=sys.stderr) + sys.exit(1) + + make_zip(repo_path, output_zip) + + +if __name__ == "__main__": + main() diff --git a/shopping_app/static/js/live.js b/shopping_app/static/js/live.js index 22a11c7..513fbd3 100644 --- a/shopping_app/static/js/live.js +++ b/shopping_app/static/js/live.js @@ -139,6 +139,13 @@ function setupList(listId, username) { note: '' }; + // Note: store newly added items locally so later edits do not depend on a full page refresh. + if (Array.isArray(window.currentItems)) { + window.currentItems.push(item); + } else { + window.currentItems = [item]; + } + const isOwnFreshShareItem = Boolean( window.IS_SHARE && data.added_by && @@ -216,22 +223,40 @@ function setupList(listId, username) { }); socket.on('item_edited', data => { - const idx = window.currentItems.findIndex(i => i.id === data.item_id); - if (idx !== -1) { - window.currentItems[idx].name = data.new_name; - window.currentItems[idx].quantity = data.new_quantity; + const itemId = Number(data.item_id); + const oldItem = document.getElementById(`item-${itemId}`); + const currentItems = Array.isArray(window.currentItems) ? window.currentItems : []; + const idx = currentItems.findIndex(item => Number(item.id) === itemId); + const cachedItem = idx !== -1 ? currentItems[idx] : {}; - const newItem = renderItem(window.currentItems[idx], window.IS_SHARE); - const oldItem = document.getElementById(`item-${data.item_id}`); - if (oldItem && newItem) { - oldItem.replaceWith(newItem); - } + // Note: keep the edited item visible immediately, even when the local list cache is stale. + const updatedItem = { + ...cachedItem, + id: itemId, + name: data.new_name, + quantity: data.new_quantity, + purchased: oldItem ? oldItem.classList.contains('bg-success') : !!cachedItem.purchased, + not_purchased: oldItem ? oldItem.classList.contains('bg-warning') : !!cachedItem.not_purchased, + not_purchased_reason: cachedItem.not_purchased_reason || '', + note: cachedItem.note || '' + }; + + if (idx !== -1) { + currentItems[idx] = updatedItem; + window.currentItems = currentItems; + } + + if (oldItem) { + oldItem.replaceWith(renderItem(updatedItem, window.IS_SHARE)); + } else if (window.LIST_ID) { + socket.emit('request_full_list', { list_id: window.LIST_ID }); } showToast(`Zaktualizowano produkt: ${data.new_name} (x${data.new_quantity})`, 'success'); updateProgressBar(); toggleEmptyPlaceholder(); + applyHidePurchased(); }); // --- WAŻNE: zapisz dane do reconnect --- diff --git a/shopping_app/static/js/select.js b/shopping_app/static/js/select.js index e23cff4..9e9d585 100644 --- a/shopping_app/static/js/select.js +++ b/shopping_app/static/js/select.js @@ -1,9 +1,16 @@ document.addEventListener("DOMContentLoaded", function () { - new TomSelect("#categories", { + const categoriesSelect = document.querySelector("#categories"); + + if (!categoriesSelect || typeof TomSelect === 'undefined') { + return; + } + + new TomSelect(categoriesSelect, { plugins: ['remove_button'], maxItems: 1, - placeholder: 'Wybierz jedną kategorie...', + placeholder: 'Wybierz jedną kategorię...', create: false, + dropdownParent: 'body', sortField: { field: "text", direction: "asc"