From 3014962563e8683bb85efddd0c37246f3dabdc83 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 30 May 2026 22:28:37 +0200 Subject: [PATCH] refactor(v2)(ui): denomination editable per slot, position read-only (#29 v1.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI flips to position-keyed per the v1.1 redesign: - Column order: Bay | Denomination | Count | ATM-reported | Updated (position first, since it's the row identity) - Position becomes read-only: rendered as "Bay N" label - Denomination becomes an editable q-input (with the fiat code as a suffix on the input) - Count remains editable - ATM-reported column now shows " · ×" combining state_denomination + state_count for at-a-glance reconciliation (still v1: only the bootstrap snapshot; v2 reverse-channel makes this live) - Confirm-modal preview list: header is "Bay N", side shows the denomination + count being sent JS: - cassettesTable.columns reordered to put position first - markCassetteDirty pivots on position (the immutable identity) and compares denomination + count against pristine - submitCassettePublish builds {positions: {: {denomination, count}}} payload instead of {denominations: ...} No "lock icon" on denomination — the previous instinct to add one was based on the m007 misinterpretation. v1.1 design correctly makes denomination operator-editable. Tests still red until commit f. Co-Authored-By: Claude Opus 4.7 (1M context) --- static/js/index.js | 29 ++++++++++---------- templates/satmachineadmin/index.html | 41 +++++++++++++++------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index ff7cf5b..96b98ef 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -205,10 +205,10 @@ window.app = Vue.createApp({ }, cassettesTable: { columns: [ + {name: 'position', label: 'Bay', field: 'position', align: 'right'}, {name: 'denomination', label: 'Denomination', field: 'denomination', align: 'right'}, {name: 'count', label: 'Count', field: 'count', align: 'right'}, - {name: 'position', label: 'Position', field: 'position', align: 'right'}, - {name: 'state_count', label: 'ATM-reported', field: 'state_count', align: 'right'}, + {name: 'state', label: 'ATM-reported', field: 'state_denomination', align: 'right'}, {name: 'updated_at', label: 'Updated', field: 'updated_at', align: 'left'} ], pagination: {rowsPerPage: 0} // hide pagination — cassette count is small @@ -816,15 +816,16 @@ window.app = Vue.createApp({ }, markCassetteDirty(row) { - // Find pristine match by denomination and compare; flip _dirty + - // overall dirty flag accordingly. + // Find pristine match by position (the row identity) and compare; + // flip _dirty + overall dirty flag accordingly. Editable fields + // are denomination + count; position is the immutable row key. const pristine = this.machineDetail.cassettesPristine.find( - p => p.denomination === row.denomination + p => p.position === row.position ) row._dirty = !pristine || - Number(row.count) !== Number(pristine.count) || - Number(row.position) !== Number(pristine.position) + Number(row.denomination) !== Number(pristine.denomination) || + Number(row.count) !== Number(pristine.count) this.machineDetail.cassettesDirty = this.machineDetail.cassetteEdits.some(r => r._dirty) }, @@ -844,18 +845,18 @@ window.app = Vue.createApp({ }, async submitCassettePublish() { - // Build the PublishCassettesPayload shape: - // { denominations: { "": { position, count }, ... } } - // The API enforces the denomination set matches what's stored — + // Build the PublishCassettesPayload shape (v1.1, position-keyed): + // { positions: { "": { denomination, count }, ... } } + // The API enforces the position set matches what's stored — // since we only edit existing rows, this should always pass. - const denominations = {} + const positions = {} for (const row of this.machineDetail.cassetteEdits) { - denominations[String(row.denomination)] = { - position: Number(row.position), + positions[String(row.position)] = { + denomination: Number(row.denomination), count: Number(row.count) } } - const payload = {denominations} + const payload = {positions} this.machineDetail.cassettesPublishing = true try { const {data} = await LNbits.api.request( diff --git a/templates/satmachineadmin/index.html b/templates/satmachineadmin/index.html index f0ebc48..8b3ddf3 100644 --- a/templates/satmachineadmin/index.html +++ b/templates/satmachineadmin/index.html @@ -1023,7 +1023,7 @@ @@ -1032,10 +1032,15 @@ :style="props.row._dirty ? {boxShadow: 'inset 4px 0 0 0 #fdd835'} : {}"> + + + - - + - - - - - + + + + + · + + @@ -1094,17 +1098,16 @@

Sending to ATM:

+ :key="row.position"> - + - position - + · count