From ec69b8b3c2d20ac579ffe45c545739fb87b9900f Mon Sep 17 00:00:00 2001 From: Tero Date: Sat, 29 Aug 2026 10:17:28 +0300 Subject: [PATCH] Add inventory type and edit popup --- data/inventory.db | Bin 884736 -> 884736 bytes static/app.js | 13 +++++++++---- static/style.css | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/inventory.db b/data/inventory.db index 664a2e5b805cf3f8bfe19155bfdc08250719ab73..f75f19890bc94ce2a757c51039bfb73c6932bb25 100644 GIT binary patch delta 1444 zcmaKs?Q0WB9LHxeyV-V|ZB0w9CQ7r#Mr%ym=gBT8+9$P+B6af}qt}i*KsdGYTSz$K8S80~~VPAK(j75D{OBv!;|aO}p^@z4*b*XTIN= z{f&)jV`JL57dA|+iNJM@9GxVjhN0qx=hb#Q$FLI)r zUz_zp4`>an=IcSdmv8mf@=aVc8YN2s$C+=kj(TTeB$FQT<<`w!%e|j0b4j`(2qCO! zx)RR)KkLU?f2Rk?f!Zc7sBh#qH$`Mjwy@}c3+_k`5Bv5UN%jvM9Z08qSXG6#`3+y* ze3tt#Nq0$MLlr`bsAE+Q2m;x?Ig!aEkNoP>)!0uyS!m2MF%J+61ULV~`qjL5fw1Kp zp)HXa$c!FH`mll*0C2i4_htIL1&I@gpoVZ*R}|rUNNcKpn5;bD>qrfy(!Zp#be9bl zT^1(UVb=>FY{+^@S7Z%`mj|6X)@=j*=(W4zi(Q57uFUas4mPLXIBgO4O4ty>Az9KT zRV@(P)4ur#v#wqn!!n~UBv-hbrIIr@ohOWj>Dt`E>%Uu^Zb>r)JtQe|Si=R*qUhM3 zg_q#wlix*GMB7<>QCp@i7AUHrilMNCHLNT_p*n1o;Ck8SsVKKal+QEYD0?N~IEE1POl)7-f)FUD5P+Rl ziCLw~D&1D;wMvgwI;_%HheSuTIVkk22NSZa2PI7iw(X%lRs9Q3kOLiqX8Z|a@=4W^ zW+^g#Hjx3d;r=$q-yhvEr=J;Qrs&u7TW2->#cAaKlh8W{#q0pp=Ao02Pj=Oy0Gyu; zk@U|aqhtVdm+1AD==GH7b(HA!ne7_-yp=ftYRK{IUo{Gp7r{%c@2r{ueqyS@96bqZ gVC>8TjEE>An#f7S5S0;?6R|`UM6Ub;%(b+91>abYZ~y=R delta 1050 zcmaJINAN2}*NCEW z7pw@})PFG@0(I3?>R_(4hdLAn9iqBa)TK+8C?k;|f)4#hQRa{x-WwjnJ(ypph80RS+7UP7-SSrxSc zi^9Iw7iL2DpXe;fTx>t6wij{5pxDM$+w!?mR4xpo(+A<`bQ=0@TH)~-$I3FcY^7fV ziV^@NFjVfXT-tn??xFSOInyO7!?6XFB6MTpWJ02JWyy&0svBD{_sJbyv9`|s!M<2L zd$9)_d!EPqVSX{2%y;Gs^O^a?d}Q8ctvudCV5d;Q6H4?MLFV)A-p-iYEje92-dR(t z`w0McIU}$au&w`D*^Bp6'); + document.body.insertAdjacentHTML('beforeend', ''); $('#closeTypeManager').addEventListener('click', () => $('#typeManagerModal').classList.remove('open')); - $('#resetTypeForm').addEventListener('click', resetTypeForm); $('#typeManagerModal').addEventListener('click', (event) => { if (event.target === $('#typeManagerModal')) $('#typeManagerModal').classList.remove('open'); }); + $('#newTypeButton').addEventListener('click', () => openTypeEditor()); + $('#closeTypeEditor').addEventListener('click', closeTypeEditor); + $('#cancelTypeEditor').addEventListener('click', closeTypeEditor); + $('#typeEditorModal').addEventListener('click', (event) => { if (event.target === $('#typeEditorModal')) closeTypeEditor(); }); $('#typeForm').addEventListener('submit', saveType); $('#typeList').addEventListener('click', (event) => { const button = event.target.closest('[data-type-action]'); if (!button) return; const name = button.dataset.typeName; if (button.dataset.typeAction === 'edit') editType(name); if (button.dataset.typeAction === 'delete') removeType(name); }); } @@ -85,9 +88,11 @@ function renderTypeManager() { $('#typeList').innerHTML = state.types.map((type) => `
${escapeHtml(type.name)}${type.fields.map((field) => escapeHtml(fieldLabels[field])).join(', ') || 'Name, type, and status only'}
`).join(''); } async function openTypeManager() { ensureTypeManager(); await loadTypes(); resetTypeForm(); renderTypeManager(); $('#typeManagerModal').classList.add('open'); } -function editType(name) { const type = state.types.find((entry) => entry.name === name); if (!type) return; $('#editingTypeName').value = type.name; $('#typeName').value = type.name; document.querySelectorAll('[name="type_field"]').forEach((field) => { field.checked = type.fields.includes(field.value); }); } +function closeTypeEditor() { $('#typeEditorModal').classList.remove('open'); } +function openTypeEditor(type = null) { resetTypeForm(); $('#typeEditorTitle').textContent = type ? 'Edit type' : 'New type'; $('#typeFields').innerHTML = `Fields shown for this type
${Object.entries(fieldLabels).map(([key, label]) => ``).join('')}
`; if (type) { $('#editingTypeName').value = type.name; $('#typeName').value = type.name; document.querySelectorAll('[name="type_field"]').forEach((field) => { field.checked = type.fields.includes(field.value); }); } $('#typeEditorModal').classList.add('open'); $('#typeName').focus(); } +function editType(name) { const type = state.types.find((entry) => entry.name === name); if (type) openTypeEditor(type); } async function removeType(name) { if (!confirm(`Delete the "${name}" inventory type?`)) return; try { await request(`/api/types/${encodeURIComponent(name)}`, { method: 'DELETE' }); await loadTypes(); renderTypeManager(); toast('Inventory type deleted.'); } catch (error) { toast(error.message); } } -async function saveType(event) { event.preventDefault(); const previousName = $('#editingTypeName').value; const fields = [...document.querySelectorAll('[name="type_field"]:checked')].map((field) => field.value); try { await request(previousName ? `/api/types/${encodeURIComponent(previousName)}` : '/api/types', { method: previousName ? 'PUT' : 'POST', body: JSON.stringify({ name: $('#typeName').value, fields }) }); await loadTypes(); renderTypeManager(); resetTypeForm(); toast('Inventory type saved.'); } catch (error) { toast(error.message); } } +async function saveType(event) { event.preventDefault(); const previousName = $('#editingTypeName').value; const fields = [...document.querySelectorAll('[name="type_field"]:checked')].map((field) => field.value); try { await request(previousName ? `/api/types/${encodeURIComponent(previousName)}` : '/api/types', { method: previousName ? 'PUT' : 'POST', body: JSON.stringify({ name: $('#typeName').value, fields }) }); await loadTypes(); renderTypeManager(); closeTypeEditor(); toast('Inventory type saved.'); } catch (error) { toast(error.message); } } window.editItem = (id) => openModal(state.items.find((item) => item.id === id)); window.deleteImage = async (imageId) => { if (!state.editingId || !confirm('Delete this picture?')) return; try { const item = await request(`/api/items/${state.editingId}/images/${imageId}`, { method: 'DELETE' }); renderImageGallery(item.images); $('#imageName').textContent = `${item.images.length} saved picture${item.images.length === 1 ? '' : 's'}. Add more below.`; state.items = state.items.map((entry) => entry.id === item.id ? item : entry); renderItems(state.items); toast('Picture deleted.'); } catch (error) { toast(error.message); } }; diff --git a/static/style.css b/static/style.css index 18eed0c..2a40be5 100644 --- a/static/style.css +++ b/static/style.css @@ -1,5 +1,5 @@ .bulk-actions{display:flex;align-items:center;gap:9px;margin-left:auto;color:var(--muted);font-size:12px;font-weight:700}.selection-column{width:34px;text-align:center}.selection-column input{accent-color:var(--teal)} -.type-manager{max-width:720px}.type-fields>span{display:block;color:var(--muted);font-size:12px;font-weight:700;margin-bottom:9px}.field-checklist{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px 14px}.field-checklist label{display:grid;grid-template-columns:16px minmax(0,1fr);align-items:center;gap:7px;min-height:28px;font-size:12px;color:var(--ink);white-space:normal}.field-checklist input{width:16px;height:16px;margin:0}.type-list{border-top:1px solid var(--line);margin-top:22px;padding-top:4px}.type-row{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--line);padding:13px 0}.type-row strong,.type-row span{display:block}.type-row span{color:var(--muted);font-size:11px;margin-top:4px;max-width:460px}.type-row .row-actions{display:flex;gap:7px;flex:none}@media(max-width:640px){.field-checklist{grid-template-columns:repeat(2,minmax(0,1fr))}.type-row{align-items:flex-start}.type-row .row-actions{flex-direction:column}.type-row span{max-width:230px}.bulk-actions{width:100%;margin-left:0;justify-content:space-between}} +.type-manager{max-width:720px}.type-manager-actions{padding:20px 28px 0}.type-editor{max-width:640px}.type-editor-backdrop{z-index:6}.type-fields>span{display:block;color:var(--muted);font-size:12px;font-weight:700;margin-bottom:9px}.field-checklist{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px 14px}.field-checklist label{display:grid;grid-template-columns:16px minmax(0,1fr);align-items:center;gap:7px;min-height:28px;font-size:12px;color:var(--ink);white-space:normal}.field-checklist input{width:16px;height:16px;margin:0}.type-list{border-top:1px solid var(--line);margin-top:22px;padding:4px 28px 0}.type-row{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--line);padding:13px 0}.type-row strong,.type-row span{display:block}.type-row span{color:var(--muted);font-size:11px;margin-top:4px;max-width:460px}.type-row .row-actions{display:flex;gap:7px;flex:none}@media(max-width:640px){.field-checklist{grid-template-columns:repeat(2,minmax(0,1fr))}.type-row{align-items:flex-start}.type-row .row-actions{flex-direction:column}.type-row span{max-width:230px}.bulk-actions{width:100%;margin-left:0;justify-content:space-between}} .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} .login-page{min-height:100vh;display:grid;place-items:center;background:linear-gradient(135deg,#f6f8f5,#dcece5)}.login-card{width:min(390px,calc(100% - 32px));padding:34px;background:#fff;border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow)}.login-card .brand-mark{margin-bottom:22px}.login-card h1{font-family:'Space Grotesk';margin:0 0 24px}.login-card form{display:grid;gap:16px}.login-card label,.user-form label{display:grid;gap:6px;color:var(--muted);font-size:12px;font-weight:700}.login-card input,.user-form input,.user-form select{width:100%;padding:11px;border:1px solid var(--line);border-radius:6px;background:#fff}.login-card .primary-button{margin-top:8px}.login-error{padding:10px;background:#fff0ed;color:#a33e2b;border-radius:6px;font-size:12px}.logout-button{margin-top:12px;border:0;background:transparent;color:#90afa9;padding:0;cursor:pointer;font-size:11px}.users-panel{margin-top:24px;padding:24px;background:#fff;border:1px solid var(--line);border-radius:8px}.user-form{display:flex;align-items:end;gap:12px;flex-wrap:wrap}.user-form label{min-width:150px}.user-form .checkbox-label{display:flex;align-items:center;gap:7px;min-width:auto}.user-form .checkbox-label input{width:auto}.user-row{display:flex;justify-content:space-between;border-top:1px solid var(--line);padding:10px 0;margin:18px 0 0;font-size:12px}.user-row span{color:var(--muted)}