From 696a414e953b2d694a46b1e0414fbcce345ab287 Mon Sep 17 00:00:00 2001 From: d11n Date: Thu, 2 Nov 2023 20:03:34 +0100 Subject: [PATCH] POS Keypad: Add plus and change clear functionality (#5396) Closes #5299. --- BTCPayServer.Tests/SeleniumTests.cs | 14 ++- .../Shared/PointOfSale/Public/VueLight.cshtml | 29 +++--- BTCPayServer/wwwroot/pos/common.js | 8 -- BTCPayServer/wwwroot/pos/keypad.css | 14 +-- BTCPayServer/wwwroot/pos/keypad.js | 98 ++++++++++++------- 5 files changed, 86 insertions(+), 77 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 0400aab31..c28b1841d 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -2217,7 +2217,7 @@ namespace BTCPayServer.Tests Assert.Contains("EUR", s.Driver.FindElement(By.Id("Currency")).Text); Assert.Contains("0,00", s.Driver.FindElement(By.Id("Amount")).Text); Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text); - Assert.True(s.Driver.FindElement(By.Id("ModeTablist-amount")).Selected); + Assert.True(s.Driver.FindElement(By.Id("ModeTablist-amounts")).Selected); Assert.False(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled); Assert.False(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled); @@ -2226,13 +2226,17 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.CssSelector(".keypad [data-key='2']")).Click(); s.Driver.FindElement(By.CssSelector(".keypad [data-key='3']")).Click(); s.Driver.FindElement(By.CssSelector(".keypad [data-key='4']")).Click(); - s.Driver.FindElement(By.CssSelector(".keypad [data-key='.']")).Click(); + s.Driver.FindElement(By.CssSelector(".keypad [data-key='0']")).Click(); + s.Driver.FindElement(By.CssSelector(".keypad [data-key='0']")).Click(); + Assert.Equal("1.234,00", s.Driver.FindElement(By.Id("Amount")).Text); + Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text); + s.Driver.FindElement(By.CssSelector(".keypad [data-key='+']")).Click(); s.Driver.FindElement(By.CssSelector(".keypad [data-key='5']")).Click(); s.Driver.FindElement(By.CssSelector(".keypad [data-key='6']")).Click(); Assert.Equal("1.234,56", s.Driver.FindElement(By.Id("Amount")).Text); Assert.True(s.Driver.FindElement(By.Id("ModeTablist-discount")).Enabled); Assert.True(s.Driver.FindElement(By.Id("ModeTablist-tip")).Enabled); - Assert.Equal("", s.Driver.FindElement(By.Id("Calculation")).Text); + Assert.Equal("1.234,00 € + 0,56 €", s.Driver.FindElement(By.Id("Calculation")).Text); // Discount: 10% s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-discount']")).Click(); @@ -2240,14 +2244,14 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.CssSelector(".keypad [data-key='0']")).Click(); Assert.Contains("1.111,10", s.Driver.FindElement(By.Id("Amount")).Text); Assert.Contains("10% discount", s.Driver.FindElement(By.Id("Discount")).Text); - Assert.Contains("1.234,56 € - 123,46 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text); + Assert.Contains("1.234,00 € + 0,56 € - 123,46 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text); // Tip: 10% s.Driver.FindElement(By.CssSelector("label[for='ModeTablist-tip']")).Click(); s.Driver.WaitForElement(By.Id("Tip-Custom")); s.Driver.FindElement(By.Id("Tip-10")).Click(); Assert.Contains("1.222,21", s.Driver.FindElement(By.Id("Amount")).Text); - Assert.Contains("1.234,56 € - 123,46 € (10%) + 111,11 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text); + Assert.Contains("1.234,00 € + 0,56 € - 123,46 € (10%) + 111,11 € (10%)", s.Driver.FindElement(By.Id("Calculation")).Text); // Pay s.Driver.FindElement(By.Id("pay-button")).Click(); diff --git a/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml index 605cc2795..652748724 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml @@ -1,12 +1,12 @@ -@using Microsoft.AspNetCore.Mvc.TagHelpers @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
- + +
{{currencyCode}}
{{ formatCurrency(total, false) }}
-
{{ calculation }}
+
{{ calculation }}
@@ -44,24 +44,17 @@
- +
-
- - -
- + diff --git a/BTCPayServer/wwwroot/pos/common.js b/BTCPayServer/wwwroot/pos/common.js index 7b859ee57..92669fcb4 100644 --- a/BTCPayServer/wwwroot/pos/common.js +++ b/BTCPayServer/wwwroot/pos/common.js @@ -44,14 +44,6 @@ const posCommon = { totalNumeric () { return parseFloat(parseFloat(this.total).toFixed(this.currencyInfo.divisibility)) }, - calculation () { - if (!this.tipNumeric && !this.discountNumeric) return null - let calc = this.formatCurrency(this.amountNumeric, true) - if (this.discountNumeric > 0) calc += ` - ${this.formatCurrency(this.discountNumeric, true)} (${this.discountPercent}%)` - if (this.tipNumeric > 0) calc += ` + ${this.formatCurrency(this.tipNumeric, true)}` - if (this.tipPercent) calc += ` (${this.tipPercent}%)` - return calc - }, posdata () { const data = { subTotal: this.amountNumeric, diff --git a/BTCPayServer/wwwroot/pos/keypad.css b/BTCPayServer/wwwroot/pos/keypad.css index d8e314db8..bc262fdff 100644 --- a/BTCPayServer/wwwroot/pos/keypad.css +++ b/BTCPayServer/wwwroot/pos/keypad.css @@ -27,9 +27,8 @@ max-height: 6rem; color: var(--btcpay-body-text); } -.keypad .btn[data-key="del"] svg { - --btn-icon-size: 2.25rem; - transform: rotate(180deg); +.keypad .btn[data-key="+"] { + font-size: 2.25em; } .btcpay-pills label, .btn-secondary.rounded-pill { @@ -57,15 +56,6 @@ z-index: 1; } -.actions { - display: flex; - align-items: center; - justify-content: center; -} -.actions .btn { - flex: 1 1 50%; -} - #Calculation { min-height: 1.5rem; } diff --git a/BTCPayServer/wwwroot/pos/keypad.js b/BTCPayServer/wwwroot/pos/keypad.js index 175af7a6a..5fa45f52a 100644 --- a/BTCPayServer/wwwroot/pos/keypad.js +++ b/BTCPayServer/wwwroot/pos/keypad.js @@ -5,28 +5,37 @@ document.addEventListener("DOMContentLoaded",function () { mixins: [posCommon], data () { return { - mode: 'amount', + mode: 'amounts', fontSize: displayFontSize, defaultFontSize: displayFontSize, - keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', 'del'] + keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '+'], + amounts: [null] } }, computed: { modes () { - const modes = [{ title: 'Amount', type: 'amount' }] + const modes = [{ title: 'Amount', type: 'amounts' }] if (this.showDiscount) modes.push({ title: 'Discount', type: 'discount' }) if (this.enableTips) modes.push({ title: 'Tip', type: 'tip'}) return modes }, keypadTarget () { switch (this.mode) { - case 'amount': - return 'amount'; + case 'amounts': + return 'amounts'; case 'discount': return 'discountPercent'; case 'tip': return 'tip'; } + }, + calculation () { + if (!this.tipNumeric && !(this.discountNumeric > 0 || this.discountPercentNumeric > 0) && this.amounts.length < 2) return null + let calc = this.amounts.map(amt => this.formatCurrency(amt, true)).join(' + ') + if (this.discountNumeric > 0 || this.discountPercentNumeric > 0) calc += ` - ${this.formatCurrency(this.discountNumeric, true)} (${this.discountPercent}%)` + if (this.tipNumeric > 0) calc += ` + ${this.formatCurrency(this.tipNumeric, true)}` + if (this.tipPercent) calc += ` (${this.tipPercent}%)` + return calc } }, watch: { @@ -46,47 +55,68 @@ document.addEventListener("DOMContentLoaded",function () { this.fontSize = Math.min(this.fontSize * gamma, this.defaultFontSize); } }); + }, + amounts (values) { + this.amount = values.reduce((total, current) => total + parseFloat(current || '0'), 0); } }, methods: { - getWidth (el) { + getWidth(el) { const styles = window.getComputedStyle(el), width = parseFloat(el.clientWidth), padL = parseFloat(styles.paddingLeft), padR = parseFloat(styles.paddingRight); return width - padL - padR; }, - clear () { - this.amount = this.tip = this.discount = this.tipPercent = this.discountPercent = null; - this.mode = 'amount'; + clear() { + this.amounts = [null]; + this.tip = this.discount = this.tipPercent = this.discountPercent = null; + this.mode = 'amounts'; }, - applyKeyToValue (key, value) { - if (!value) value = ''; - if (key === 'del') { - value = value.substring(0, value.length - 1); - value = value === '' ? '0' : value; - } else if (key === '.') { - // Only add decimal point if it doesn't exist yet - if (value.indexOf('.') === -1) { - value += key; - } - } else { // Is a digit - if (!value || value === '0') { - value = ''; - } - value += key; - const { divisibility } = this.currencyInfo; - const decimalIndex = value.indexOf('.') - if (decimalIndex !== -1 && (value.length - decimalIndex - 1 > divisibility)) { - value = value.replace('.', ''); - value = value.substr(0, value.length - divisibility) + '.' + - value.substr(value.length - divisibility); - } - } - return value; + applyKeyToValue(key, value, divisibility) { + if (!value || value === '0') value = ''; + value = (value + key) + .replace('.', '') + .padStart(divisibility, '0') + .replace(new RegExp(`(\\d*)(\\d{${divisibility}})`), '$1.$2'); + return parseFloat(value).toFixed(divisibility); }, keyPressed (key) { - this[this.keypadTarget] = this.applyKeyToValue(key, this[this.keypadTarget]); + if (this.keypadTarget === 'amounts') { + const lastIndex = this.amounts.length - 1; + const lastAmount = this.amounts[lastIndex]; + if (key === 'C') { + if (!lastAmount && lastIndex === 0) { + // clear completely + this.clear(); + } else if (!lastAmount) { + // remove latest value + this.amounts.pop(); + } else { + // clear latest value + Vue.set(this.amounts, lastIndex, null); + } + } else if (key === '+' && parseFloat(lastAmount || '0')) { + this.amounts.push(null); + } else { // Is a digit + const { divisibility } = this.currencyInfo; + const value = this.applyKeyToValue(key, lastAmount, divisibility); + Vue.set(this.amounts, lastIndex, value); + } + } else { + if (key === 'C') { + this[this.keypadTarget] = null; + } else { + const divisibility = this.keypadTarget === 'tip' ? this.currencyInfo.divisibility : 0; + this[this.keypadTarget] = this.applyKeyToValue(key, this[this.keypadTarget], divisibility); + } + } + }, + doubleClick (key) { + if (key === 'C') { + // clear completely + this.clear(); + } } }, created() {