From 59403b203b4b49353c1743d5f9e8a32406f53ec6 Mon Sep 17 00:00:00 2001 From: Gi7mo! Date: Mon, 24 Feb 2025 20:15:21 +0100 Subject: [PATCH] feat(article-number): implement article numbers and qr codes --- app.py | 35 ++++++++++++++++++++++++++++++++++- db.py | 27 +++++++++++++++++++-------- requirements.txt | Bin 422 -> 480 bytes static/script.js | 40 +++++++++++++++++++++++++++++++++++++--- static/settings.js | 16 +++++++++++++++- templates/index.html | 43 ++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 147 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index f353456..980e5dd 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,9 @@ import requests import time import os +import qrcode +from PIL import Image +import io from werkzeug.utils import secure_filename # Creating a Flask application instance @@ -32,7 +35,6 @@ def proxy_image(): response = requests.get(image_url, stream=True) return Response(response.content, content_type=response.headers['Content-Type']) - # Route to Favicon @app.route('/favicon.ico') def favicon(): @@ -228,6 +230,37 @@ def item(id): else: return jsonify({'error': 'Invalid action'}), 400 +@app.route('/api/items//qr-code', methods=['GET']) +def qr_code(id): + item = db.get_item(id) + value = item['name'] + if item['article_number']: + value = item['article_number'] + + qr = qrcode.QRCode( + version=5, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=10, + border=4 + ) + qr.add_data(value) + qr.make(fit=True) + qr_img = qr.make_image(fill='black', back_color='white').convert('RGBA') + + logo = Image.open('./favicon.png') + + qr_width, qr_height = qr_img.size + logo_size = qr_width // 4 + logo = logo.resize((logo_size, logo_size), Image.LANCZOS) + + pos = ((qr_width - logo_size) // 2, (qr_height - logo_size) // 2) + qr_img.paste(logo, pos, mask=logo) + + img_io = io.BytesIO() + qr_img.save(img_io, format='PNG') + img_io.seek(0) + + return Response(img_io, mimetype='image/png') def send_request(target_ip, data, timeout=0.2): url = f"http://{target_ip}/json/state" diff --git a/db.py b/db.py index cc8c8ab..4636d79 100644 --- a/db.py +++ b/db.py @@ -21,6 +21,7 @@ def create_combined_db(): CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, + article_number TEXT, link TEXT, image TEXT, position TEXT, @@ -52,7 +53,8 @@ def create_combined_db(): timeout INTEGER DEFAULT 5, lightMode TEXT DEFAULT 'light', colors TEXT DEFAULT '[#00ff00, #00ff00]', - language TEXT DEFAULT 'en' + language TEXT DEFAULT 'en', + search_locates BOOLEAN NOT NULL CHECK (search_locates IN (0, 1)) DEFAULT 0 ) ''') @@ -71,6 +73,15 @@ def create_combined_db(): if 'language' not in columns: cursor.execute("ALTER TABLE settings ADD COLUMN language TEXT DEFAULT 'en'") conn_combined.commit() + if 'search_locates' not in columns: + cursor.execute("ALTER TABLE settings ADD COLUMN search_locates BOOLEAN NOT NULL CHECK (search_locates IN (0, 1)) DEFAULT 0") + conn_combined.commit() + + cursor.execute("PRAGMA table_info(items)") + columns = [column[1] for column in cursor.fetchall()] + if 'article_number' not in columns: + cursor.execute("ALTER TABLE items ADD COLUMN article_number TEXT NULL") + conn_combined.commit() return conn_combined @@ -86,8 +97,8 @@ def read_items(): def write_item(item): conn = create_combined_db() cursor = conn.cursor() - cursor.execute('INSERT INTO items (name, link, image, position, quantity, ip, tags) VALUES (?, ?, ?, ?, ?, ?, ?)', - [item['name'], item['link'], item['image'], item['position'], item['quantity'], item['ip'], + cursor.execute('INSERT INTO items (name, article_number, link, image, position, quantity, ip, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [item['name'], item['article_number'], item['link'], item['image'], item['position'], item['quantity'], item['ip'], item['tags']]) lastId = cursor.lastrowid conn.commit() @@ -116,8 +127,8 @@ def update_item(id, data): try: conn.execute( - 'UPDATE items SET name = ?, link = ?, image = ?, position = ?, quantity = ?, ip = ?, tags = ? WHERE id = ?', - [data['name'], data['link'], data['image'], data['position'], data['quantity'], data['ip'], data['tags'], + 'UPDATE items SET name = ?, article_number = ?, link = ?, image = ?, position = ?, quantity = ?, ip = ?, tags = ? WHERE id = ?', + [data['name'], data['article_number'], data['link'], data['image'], data['position'], data['quantity'], data['ip'], data['tags'], id]) conn.commit() except sqlite3.Error as e: @@ -336,9 +347,9 @@ def update_settings(settings): cursor = conn.cursor() cursor.execute('DELETE FROM settings') # Clear existing settings cursor.execute(''' - INSERT INTO settings (brightness, timeout, lightMode, colors, language) - VALUES (?, ?, ?, ?, ?) - ''', [settings['brightness'], settings['timeout'], settings['lightMode'], settings['colors'], settings['language']]) + INSERT INTO settings (brightness, timeout, lightMode, colors, language, search_locates) + VALUES (?, ?, ?, ?, ?, ?) + ''', [settings['brightness'], settings['timeout'], settings['lightMode'], settings['colors'], settings['language'], settings['search_locates']]) conn.commit() except sqlite3.Error as e: print(f"SQLite error while updating settings: {e}") diff --git a/requirements.txt b/requirements.txt index b948f81eed86c18478cd6fd64bb6b433153260f5..ca49287dfb2773b9f17a67b213622d847a98a8e1 100644 GIT binary patch delta 65 zcmZ3+{D66b1EXmHLncEG5au(KGuQ&5ArR_;u>k`w0~bRfLlHwVP&S1j6)bN7lr@-a H&6o`Ut{4l# delta 11 ScmaFByo`B+1LNc>#xwvLE(7ua diff --git a/static/script.js b/static/script.js index 330fe14..e233bac 100644 --- a/static/script.js +++ b/static/script.js @@ -36,6 +36,7 @@ async function addItem(event) { // Gather item information from the form const name = document.getElementById("item_name").value; const link = document.getElementById("item_url").value || ""; + const article_number = document.getElementById("item_article_number").value || ""; const image = document.getElementById("item_image").value.replace(window.location.href, ""); let position = localStorage.getItem('led_positions'); let quantity = document.getElementById("item_quantity").value; @@ -53,6 +54,7 @@ async function addItem(event) { const item = { name, link, + article_number, image, position, quantity, @@ -196,6 +198,7 @@ function createItem(item) { // Set dataset attributes to store item information col.dataset.id = item.id; col.dataset.name = item.name; + col.dataset.item_article_number = item.article_number; col.dataset.quantity = parseInt(item.quantity, 10); // Store as numbers col.dataset.ip = item.ip; if (!Array.isArray(item.position)) { @@ -238,6 +241,7 @@ function createItem(item) {
  • Copy Item
  • Delete
  • Crop Image
  • +
  • View QR-Code
  • @@ -381,8 +385,17 @@ function createItem(item) { } }); + col.querySelector('.qr-code-btn').addEventListener('click', () => { + const qrCodeModal = document.getElementById('qrCodeModal'); + const qrCodeImage = document.getElementById('qr-code-image'); + const qrCodeUrl = `/api/items/${item.id}/qr-code`; + const downloadQrCodeButton = document.getElementById('download-qr-code-btn'); + qrCodeImage.src = qrCodeUrl; + downloadQrCodeButton.setAttribute('data-download-url', qrCodeUrl); + downloadQrCodeButton.setAttribute('data-item-id', item.id); - + $(qrCodeModal).modal("show"); + }); col.querySelector('.edit-btn').addEventListener('click', () => { // Set flag for editing, remove local storage, and show the item modal @@ -390,6 +403,7 @@ function createItem(item) { removeLocalStorage(); $("#item-modal").modal("show"); document.getElementById("item_name").value = item.name; + document.getElementById("item_article_number").value = item.article_number; document.getElementById("item_url").value = item.link; document.getElementById("item_image").value = item.image; document.getElementById("item_quantity").value = item.quantity; @@ -492,6 +506,7 @@ function initialiseTooltips() { function resetModal() { document.getElementById("item_name").value = ""; document.getElementById("item_url").value = ""; + document.getElementById("item_article_number").value = ""; document.getElementById("item_image").value = ""; document.getElementById("item_quantity").value = ""; document.getElementById("item_image_upload").value = ""; @@ -541,14 +556,26 @@ document.getElementById("search").addEventListener("input", function (e){ const searchText = e.target.value.toLowerCase(); const items = Array.from(itemsContainer.children); + let foundItems = []; + Array.from(items).forEach((item) => { const itemName = item.dataset["name"]; - if (itemName.toLowerCase().indexOf(searchText) !== -1) { + const itemArticleNumber = item.dataset["item_article_number"]; + if (itemName.toLowerCase().indexOf(searchText) !== -1 || (itemArticleNumber && itemArticleNumber.toLowerCase().indexOf(searchText) !== -1)) { item.style.display = "flex"; + foundItems.push(item.dataset); } else { item.style.display = "none"; } }); + + if (foundItems.length == 1 && e.target.getAttribute('data-search-locates') == 1) { + fetch(`/api/items/${foundItems[0].id}`, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ action: "locate" }), + }).catch((error) => console.error(error)); + } }); @@ -612,7 +639,14 @@ const handleEmptyFields = (formType) => { return false; }; - +document.getElementById('download-qr-code-btn').addEventListener('click', function (e) { + const link = document.createElement('a'); + link.href = e.target.getAttribute('data-download-url'); + link.download = 'qrcode' + e.target.getAttribute('data-item-id') + '.png'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}); loadItems(); window.addEventListener('resize', function() { diff --git a/static/settings.js b/static/settings.js index 805c1d2..e94c989 100644 --- a/static/settings.js +++ b/static/settings.js @@ -40,7 +40,8 @@ function addSettings(event) { if (language === undefined){ language = "en" } - const settings = {brightness, timeout, lightMode, colors, language}; + const search_locates = document.getElementById('search_locates').checked ? 1 : 0; + const settings = { brightness, timeout, lightMode, colors, language, search_locates }; // Save the settings in the database using fetch fetch("/api/settings", { method: "POST", @@ -48,6 +49,9 @@ function addSettings(event) { body: JSON.stringify(settings), }) .then((response) => response.json()) + .then(() => { + updateSearchInput(settings.search_locates); + }) .catch((error) => console.error(error)); } @@ -72,10 +76,20 @@ function loadSettings() { lightMode = settings.lightMode; language = settings.language; loadAvailableLanguages(); + + document.getElementById('search_locates').checked = settings.search_locates; + updateSearchInput(settings.search_locates); }) .catch((error) => console.error(error)); } +document.getElementById('search_locates').addEventListener('change', (e) => { + addSettings(e); +}); +function updateSearchInput(value) { + document.getElementById('search').setAttribute('data-search-locates', value); +} + window.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('[data-bs-theme-value]') .forEach(toggle => { diff --git a/templates/index.html b/templates/index.html index a3c8dfa..dd91cce 100644 --- a/templates/index.html +++ b/templates/index.html @@ -199,6 +199,20 @@
    Inventur

    +
    + +
    +
    + + +
    + +
    +
    +
    +
    + + +
    @@ -440,7 +459,29 @@
    - + +