From c608a8aa63d8a0a8e9c2a05eebe96d4f0199079b Mon Sep 17 00:00:00 2001 From: Tero Huttunen Date: Thu, 27 Aug 2026 01:00:19 +0300 Subject: [PATCH] Add examples to inventory --- __pycache__/app.cpython-314.pyc | Bin 30773 -> 30233 bytes app.py | 58 ++++++++++++-------------------- data/inventory.db | Bin 40960 -> 49152 bytes static/app.js | 13 ++++--- static/style.css | 1 - templates/index.html | 9 ++--- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/__pycache__/app.cpython-314.pyc b/__pycache__/app.cpython-314.pyc index c8bce08310946887284f7833c435d7fd3b26f6c8..9ab368db17ff63f9e13a49c0d24f663bd0117072 100644 GIT binary patch delta 5420 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 delta 5739 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 b9xa^HRn61t52T7E0sEq;F81") def update_item(item_id): - try: - data = item_payload(request_data()) - except ValueError as error: - return jsonify({"error": str(error)}), 400 + data = item_payload(request_data()) if not data["name"] or not data["item_type"]: return jsonify({"error": "Name and type are required."}), 400 data["updated_at"] = datetime.now(timezone.utc).isoformat() @@ -346,7 +332,7 @@ def update_item(item_id): return jsonify({"error": "Each inventory item can have up to 5 pictures."}), 400 saved_images = save_images(new_images) result = connection.execute( - """UPDATE inventory_items SET name=?, item_type=?, brand=?, model=?, serial_number=?, asset_tag=?, status=?, location=?, destination=?, price=?, purchase_date=?, notes=?, cpu=?, ram=?, gpu=?, storage=?, media_format=?, updated_at=? WHERE id=?""", + """UPDATE inventory_items SET name=?, item_type=?, brand=?, model=?, serial_number=?, asset_tag=?, status=?, location=?, destination=?, checkout_date=?, purchase_date=?, notes=?, cpu=?, ram=?, gpu=?, storage=?, media_format=?, updated_at=? WHERE id=?""", (*data.values(), item_id), ) for filename, original_name in saved_images: diff --git a/data/inventory.db b/data/inventory.db index 22306ee93439d5ef78b68ef7fdd5359058f4cafa..6333c75196653522d724f76b49a711d32958de5b 100644 GIT binary patch literal 49152 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 diff --git a/static/app.js b/static/app.js index 375612e..c030fa2 100644 --- a/static/app.js +++ b/static/app.js @@ -10,10 +10,13 @@ async function request(url, options = {}) { } async function load() { - const query = new URLSearchParams({ q: $('#searchInput').value, type: $('#typeFilter').value }); + const query = new URLSearchParams({ q: $('#searchInput').value, type: $('#typeFilter').value, status: $('#statusFilter').value }); const [items, summary] = await Promise.all([request(`/api/items?${query}`), request('/api/summary')]); state.items = items; $('#totalCount').textContent = summary.total; + $('#availableCount').textContent = summary.available; + $('#checkedOutCount').textContent = summary.checked_out; + $('#maintenanceCount').textContent = summary.maintenance; $('#resultCount').textContent = `${items.length} ${items.length === 1 ? 'item' : 'items'}`; renderItems(items); } @@ -21,11 +24,11 @@ async function load() { function renderItems(items) { const body = $('#inventoryBody'); $('#emptyState').style.display = items.length ? 'none' : 'block'; - body.innerHTML = items.map((item) => `
${item.images?.length ? `${item.images.length > 1 ? `+${item.images.length - 1}` : ''}` : '
◌
'}
${escapeHtml(item.name)}${escapeHtml([item.brand, item.model].filter(Boolean).join(' · ') || 'No manufacturer details')}
${escapeHtml(item.item_type)}${escapeHtml(item.serial_number || 'No serial')}${escapeHtml(item.asset_tag || 'No asset tag')}${escapeHtml(item.location || 'Unassigned')}
`).join(''); + body.innerHTML = items.map((item) => `
${item.images?.length ? `${item.images.length > 1 ? `+${item.images.length - 1}` : ''}` : '
◌
'}
${escapeHtml(item.name)}${escapeHtml([item.brand, item.model].filter(Boolean).join(' · ') || 'No manufacturer details')}
${escapeHtml(item.item_type)}${escapeHtml(item.serial_number || 'No serial')}${escapeHtml(item.asset_tag || 'No asset tag')}${escapeHtml(item.location || 'Unassigned')}${escapeHtml(item.status)}
`).join(''); } function escapeHtml(value) { return String(value).replace(/[&<>'"]/g, (char) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[char])); } -function ensureImageControls() { if ($('#imageInput')) return; const section = document.querySelector('.form-section'); section.insertAdjacentHTML('beforebegin', ''); } +function ensureImageControls() { if (!$('#destination')) $('[name="location"]').insertAdjacentHTML('afterend', ''); if (!$('#checkoutDate')) $('#destination').parentElement.insertAdjacentHTML('afterend', ''); if ($('#imageInput')) return; const section = document.querySelector('.form-section'); section.insertAdjacentHTML('beforebegin', ''); } function renderImageGallery(images = []) { $('#imageGallery').innerHTML = images.map((image) => ``).join(''); } function openModal(item = null) { ensureImageControls(); state.editingId = item?.id || null; $('#modalTitle').textContent = item ? 'Edit item' : 'Add item'; $('#itemForm').reset(); $('#imageName').textContent = item?.images?.length ? `${item.images.length} saved picture${item.images.length === 1 ? '' : 's'}. Add more below.` : 'Up to 5 pictures'; renderImageGallery(item?.images || []); $('#modal').classList.add('open'); $('[name="name"]').focus(); if (item) Object.entries(item).forEach(([key, value]) => { const field = $(`[name="${key}"]`); if (field && key !== 'images') field.value = value || ''; }); } function closeModal() { $('#modal').classList.remove('open'); } @@ -40,11 +43,11 @@ $('#emptyAddButton').addEventListener('click', () => openModal()); $('#closeModal').addEventListener('click', closeModal); $('#cancelButton').addEventListener('click', closeModal); $('#modal').addEventListener('click', (event) => { if (event.target === $('#modal')) closeModal(); }); -['searchInput', 'typeFilter'].forEach((id) => $(`#${id}`).addEventListener(id === 'searchInput' ? 'input' : 'change', load)); +['searchInput', 'typeFilter', 'statusFilter'].forEach((id) => $(`#${id}`).addEventListener(id === 'searchInput' ? 'input' : 'change', load)); ensureImageControls(); $('#imageInput').addEventListener('change', () => { const files = [...$('#imageInput').files]; $('#imageName').textContent = files.length ? `${files.length} new picture${files.length === 1 ? '' : 's'} selected` : 'Up to 5 pictures'; }); $('#itemForm').addEventListener('submit', async (event) => { event.preventDefault(); const formData = new FormData(event.target); if (!$('#imageInput').files.length) formData.delete('images'); try { await request(state.editingId ? `/api/items/${state.editingId}` : '/api/items', { method: state.editingId ? 'PUT' : 'POST', body: formData }); closeModal(); toast(state.editingId ? 'Item updated.' : 'Item added to inventory.'); await load(); } catch (error) { toast(error.message); } }); -$('#exportButton').addEventListener('click', () => { if (!state.items.length) return toast('There are no visible items to export.'); const columns = ['name','item_type','brand','model','serial_number','asset_tag','status','location','destination','price','purchase_date','cpu','ram','gpu','storage','media_format','notes']; const csv = [columns.join(','), ...state.items.map((item) => columns.map((column) => `"${String(item[column] || '').replaceAll('"', '""')}"`).join(','))].join('\n'); const link = document.createElement('a'); link.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); link.download = 'stockroom-inventory.csv'; link.click(); URL.revokeObjectURL(link.href); }); +$('#exportButton').addEventListener('click', () => { if (!state.items.length) return toast('There are no visible items to export.'); const columns = ['name','item_type','brand','model','serial_number','asset_tag','status','location','destination','checkout_date','purchase_date','cpu','ram','gpu','storage','media_format','notes']; const csv = [columns.join(','), ...state.items.map((item) => columns.map((column) => `"${String(item[column] || '').replaceAll('"', '""')}"`).join(','))].join('\n'); const link = document.createElement('a'); link.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); link.download = 'stockroom-inventory.csv'; link.click(); URL.revokeObjectURL(link.href); }); $('#backupButton').addEventListener('click', async () => { try { const response = await fetch('/api/backup'); if (!response.ok) throw new Error('Backup could not be created.'); const link = document.createElement('a'); link.href = URL.createObjectURL(await response.blob()); link.download = 'stockroom-backup.zip'; link.click(); URL.revokeObjectURL(link.href); toast('Backup downloaded.'); } catch (error) { toast(error.message); } }); $('#restoreButton').addEventListener('click', () => $('#restoreInput').click()); $('#restoreInput').addEventListener('change', async () => { const file = $('#restoreInput').files[0]; if (!file || !confirm('Restore this backup? Current inventory and pictures will be replaced.')) return; const formData = new FormData(); formData.append('backup', file); try { await request('/api/restore', { method: 'POST', body: formData }); toast('Backup restored. Reloading...'); setTimeout(() => window.location.reload(), 700); } catch (error) { toast(error.message); } $('#restoreInput').value = ''; }); diff --git a/static/style.css b/static/style.css index 64260e2..35d5e82 100644 --- a/static/style.css +++ b/static/style.css @@ -1,4 +1,3 @@ .item-cell{display:flex;align-items:center;gap:11px}.item-thumb{width:38px;height:38px;object-fit:cover;border-radius:6px;background:#e8efeb;flex:none}.item-thumb.placeholder{display:grid;place-items:center;color:#8aa39b;font-size:20px}.image-count{background:#e7f3f0;color:var(--teal);font-size:10px;font-weight:700;padding:4px 5px;border-radius:4px;margin-left:-7px}.image-field input{padding:7px 0!important;border:0!important}.image-field span{display:block;color:#8c9994;font-size:10px;font-weight:400;margin-top:4px}.image-gallery{display:flex;flex-wrap:wrap;gap:9px;margin-top:11px}.gallery-item{position:relative;width:82px;height:64px}.gallery-item img{width:100%;height:100%;object-fit:cover;border-radius:6px;border:1px solid var(--line)}.gallery-delete{position:absolute;top:-7px;right:-7px;width:20px;height:20px;padding:0;border:0;border-radius:50%;background:#193f3b;color:#fff;cursor:pointer;line-height:18px}.hidden{display:none!important} .row-actions{opacity:1!important} -.location em,.price{display:block;color:var(--muted);font-size:10px;font-style:normal;margin-top:3px}.price{color:var(--teal);font-weight:700} *{box-sizing:border-box} :root{--ink:#17211f;--muted:#76817d;--line:#e5e9e5;--paper:#f6f8f5;--white:#fff;--teal:#0e7770;--teal-dark:#075c57;--orange:#e9823d;--shadow:0 14px 40px rgba(31,52,45,.07)} body{margin:0;background:var(--paper);color:var(--ink);font-family:'DM Sans',sans-serif}button,input,select,textarea{font:inherit}.app-shell{display:flex;min-height:100vh}.sidebar{width:242px;background:#193f3b;color:#d9e8e3;padding:26px 18px;display:flex;flex-direction:column}.brand{display:flex;align-items:center;gap:11px;margin:0 10px 58px}.brand-mark{width:35px;height:35px;border-radius:10px;background:#f0a15d;color:#193f3b;display:grid;place-items:center;font-family:'Space Grotesk';font-weight:700;font-size:20px}.brand strong{display:block;font-family:'Space Grotesk';font-size:17px;color:#fff}.brand span{display:block;font-size:10px;color:#92b1aa;margin-top:2px}.nav-item{width:100%;padding:12px 13px;border:0;border-radius:8px;background:transparent;color:#9dbab4;text-align:left;cursor:pointer;font-weight:600;display:flex;gap:12px;align-items:center;margin-bottom:5px}.nav-item.active,.nav-item:hover{background:#275852;color:#fff}.nav-icon{font-size:20px;width:18px;text-align:center}.sidebar-footer{margin-top:auto;border-top:1px solid #2a5b55;padding:18px 10px 2px;color:#90afa9;font-size:11px}.sidebar-footer small{display:block;color:#6f958d;margin-top:8px}.status-dot{display:inline-block;width:7px;height:7px;background:#75d19a;border-radius:50%;margin-right:7px}.main-content{flex:1;padding:42px 48px;max-width:1500px}.topbar{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:32px}.eyebrow{color:var(--teal);text-transform:uppercase;letter-spacing:.13em;font-size:10px;font-weight:700;margin:0 0 8px}.topbar h1,.panel-heading h2,.modal h2{font-family:'Space Grotesk';margin:0;letter-spacing:-.03em}.topbar h1{font-size:34px}.primary-button,.secondary-button{border:0;border-radius:7px;padding:11px 16px;font-weight:700;cursor:pointer}.primary-button{background:var(--orange);color:#fff;box-shadow:0 5px 12px #e9823d35}.primary-button:hover{background:#d96d2c}.primary-button span{font-size:18px;margin-right:5px}.secondary-button{background:#eef2ee;color:var(--teal-dark)}.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:13px;margin-bottom:30px}.stat-card{background:#fff;border:1px solid var(--line);border-radius:10px;padding:19px 20px;box-shadow:var(--shadow)}.stat-card.accent{border-top:3px solid var(--orange);padding-top:17px}.stat-label{font-size:12px;color:var(--muted);font-weight:600}.stat-symbol{float:right;color:#aeb9b5}.stat-symbol.green{color:#5bbd84}.stat-symbol.amber{color:#e6a14d}.stat-symbol.red{color:#d86c62}.stat-card strong{font-family:'Space Grotesk';display:block;font-size:31px;margin:11px 0 3px}.stat-card small{font-size:11px;color:#a2aaa7}.inventory-panel{background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);overflow:hidden}.panel-heading{display:flex;align-items:center;justify-content:space-between;padding:24px 25px 19px}.panel-heading h2{font-size:21px}.panel-heading p{margin:5px 0 0;color:var(--muted);font-size:12px}.item-count{font-size:12px;color:var(--muted);background:#f1f4f1;border-radius:20px;padding:6px 11px}.toolbar{display:flex;gap:10px;padding:0 25px 19px}.search-box{border:1px solid var(--line);border-radius:6px;display:flex;align-items:center;flex:1;max-width:390px;padding:0 11px;color:#a1aca8}.search-box span{font-size:22px;margin-right:7px}.search-box input{border:0;outline:0;width:100%;padding:10px 0;font-size:12px}.toolbar select{border:1px solid var(--line);border-radius:6px;color:#697571;background:#fff;padding:0 10px;font-size:12px;min-width:125px;outline:none}.table-wrap{border-top:1px solid var(--line);overflow-x:auto}table{border-collapse:collapse;width:100%;min-width:750px}th{text-align:left;color:#96a19d;font-size:10px;text-transform:uppercase;letter-spacing:.09em;font-weight:700;padding:13px 25px}td{padding:15px 25px;border-top:1px solid #eef1ee;font-size:12px;vertical-align:middle}.item-title{font-weight:700;display:block;color:#23322e}.item-sub{display:block;color:#9aa49f;font-size:11px;margin-top:4px}.type-pill{display:inline-block;color:var(--teal);background:#e7f3f0;border-radius:4px;padding:5px 8px;font-size:10px;font-weight:700}.identifier{color:#5d6c67;font-family:monospace;font-size:11px}.identifier em{color:#a4ada9;font-style:normal;display:block;margin-top:4px}.location{color:#586762}.status{font-size:11px;font-weight:700;white-space:nowrap}.status:before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:#65bd84;margin:0 7px 1px 0}.status.checked-out:before{background:#e4a04d}.status.maintenance:before{background:#da756b}.status.retired:before{background:#9ca7a3}.row-actions{opacity:0;display:flex;justify-content:flex-end;gap:4px}.inventory-row:hover .row-actions{opacity:1}.small-action{border:0;background:#f1f4f1;color:#52716b;border-radius:5px;padding:6px 8px;font-size:11px;cursor:pointer}.small-action.delete{color:#b65c55}.empty-state{text-align:center;padding:60px 20px;color:var(--muted)}.empty-icon{font-size:38px;color:#bdd0c9}.empty-state h3{font-family:'Space Grotesk';color:var(--ink);margin:7px 0}.empty-state p{font-size:12px;margin:0 0 17px}.modal-backdrop{display:none;position:fixed;inset:0;background:#183d3b66;z-index:5;align-items:center;justify-content:center;padding:20px}.modal-backdrop.open{display:flex}.modal{background:#fff;width:min(680px,100%);max-height:92vh;overflow-y:auto;border-radius:11px;box-shadow:0 25px 70px #133f3b35}.modal-header{display:flex;justify-content:space-between;align-items:flex-start;padding:24px 28px 18px;border-bottom:1px solid var(--line)}.modal h2{font-size:24px}.icon-button{border:0;background:transparent;font-size:26px;color:#8c9994;cursor:pointer}.modal form{padding:23px 28px}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px}.form-grid label{font-size:11px;font-weight:700;color:#5d6d67}.form-grid input,.form-grid select,.form-grid textarea{display:block;width:100%;border:1px solid #dfe6e1;border-radius:6px;padding:9px 10px;margin-top:6px;outline:none;font-size:12px;background:#fff;color:var(--ink)}.form-grid input:focus,.form-grid select:focus,.form-grid textarea:focus{border-color:var(--teal)}.form-grid textarea{resize:vertical}.full{grid-column:1/-1}.form-section{border-top:1px solid var(--line);padding-top:17px;margin-top:3px;text-transform:uppercase;letter-spacing:.1em;color:var(--teal)!important}.form-section small{color:#9ba7a2;text-transform:none;letter-spacing:0;margin-left:5px;font-weight:400}.form-actions{display:flex;justify-content:flex-end;gap:9px;border-top:1px solid var(--line);margin-top:22px;padding-top:18px}.toast{position:fixed;right:25px;bottom:25px;background:#193f3b;color:white;padding:13px 17px;border-radius:7px;font-size:12px;box-shadow:var(--shadow);transform:translateY(20px);opacity:0;transition:.2s;z-index:10}.toast.show{transform:translateY(0);opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:800px){.sidebar{width:68px;padding:20px 10px}.brand{margin:0 7px 48px}.brand>div:last-child,.nav-item:not(.active)::after,.nav-item{font-size:0}.nav-item.active{font-size:0}.nav-icon{font-size:20px}.sidebar-footer{font-size:0;padding-left:4px}.sidebar-footer small{display:none}.main-content{padding:28px 18px}.stats-grid{grid-template-columns:1fr 1fr}.topbar h1{font-size:28px}.toolbar{flex-wrap:wrap}.search-box{max-width:none;flex-basis:100%}.toolbar select{flex:1}.panel-heading{padding-left:18px;padding-right:18px}.toolbar{padding-left:18px;padding-right:18px}th,td{padding-left:18px;padding-right:18px}.modal form,.modal-header{padding-left:20px;padding-right:20px}}@media(max-width:480px){.form-grid{grid-template-columns:1fr}.full{grid-column:auto}.stats-grid{gap:8px}.stat-card{padding:13px}.stat-card strong{font-size:24px}.stat-card small{font-size:9px}.topbar{align-items:flex-start;gap:10px;flex-direction:column}} diff --git a/templates/index.html b/templates/index.html index db1bfab..61d96b6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,14 +2,12 @@ - Stockroom | Inventory -