btcpayserver/BTCPayServer/wwwroot/pos/cart.js

222 lines
9.4 KiB
JavaScript

document.addEventListener("DOMContentLoaded",function () {
function storageKey(name) {
return `${srvModel.appId}-${srvModel.currencyCode}-${name}`;
}
function saveState(name, data) {
localStorage.setItem(storageKey(name), JSON.stringify(data));
}
function loadState(name) {
const data = localStorage.getItem(storageKey(name))
if (!data) return []
const cart = JSON.parse(data);
for (let i = cart.length-1; i >= 0; i--) {
if (!cart[i]) {
cart.splice(i, 1);
continue;
}
//check if the pos items still has the cached cart items
const matchedItem = srvModel.items.find(item => item.id === cart[i].id);
if (!matchedItem){
cart.splice(i, 1);
} else {
if (matchedItem.inventory != null && matchedItem.inventory <= 0){
//item is out of stock
cart.splice(i, 1);
} else if (matchedItem.inventory != null && matchedItem.inventory < cart[i].count){
//not enough stock for original cart amount, reduce to available stock
cart[i].count = matchedItem.inventory;
//update its stock
cart[i].inventory = matchedItem.inventory;
}
}
}
return cart;
}
const POS_ITEM_ADDED_CLASS = 'posItem--added';
new Vue({
el: '#PosCart',
mixins: [posCommon],
data () {
return {
displayCategory: '*',
searchTerm: null,
cart: loadState('cart'),
categoriesScrollable: false,
$cart: null
}
},
computed: {
cartCount() {
return this.cart.reduce((res, item) => res + (parseInt(item.count) || 0), 0)
},
amountNumeric () {
return parseFloat(this.cart.reduce((res, item) => res + (item.price||0) * item.count, 0).toFixed(this.currencyInfo.divisibility))
},
posdata () {
const data = { cart: this.cart, subTotal: this.amountNumeric }
if (this.discountNumeric > 0) data.discountAmount = this.discountNumeric
if (this.discountPercentNumeric > 0) data.discountPercentage = this.discountPercentNumeric
if (this.tipNumeric > 0) data.tip = this.tipNumeric
data.total = this.totalNumeric
return JSON.stringify(data)
}
},
watch: {
searchTerm(term) {
this.updateDisplay()
},
displayCategory(category) {
this.updateDisplay()
},
cart: {
handler(newCart) {
newCart.forEach(item => {
if (!item.count) item.count = 1
if (item.inventory && item.inventory < item.count) item.count = item.inventory
})
saveState('cart', newCart)
if (!newCart || newCart.length === 0) {
this.$cart.hide()
}
},
deep: true
}
},
methods: {
toggleCart() {
this.$cart.toggle()
},
forEachItem(callback) {
this.$refs.posItems.querySelectorAll('.posItem').forEach(callback)
},
inStock(index) {
const item = this.items[index]
const itemInCart = this.cart.find(lineItem => lineItem.id === item.id)
return item.inventory == null || item.inventory > (itemInCart ? itemInCart.count : 0)
},
inventoryText(index) {
const item = this.items[index]
if (item.inventory == null) return null
const itemInCart = this.cart.find(lineItem => lineItem.id === item.id)
const left = item.inventory - (itemInCart ? itemInCart.count : 0)
return left > 0 ? `${item.inventory} left` : 'Sold out'
},
addToCart(index) {
if (!this.inStock(index)) return false;
const item = this.items[index];
const $posItem = this.$refs.posItems.querySelectorAll('.posItem')[index];
// Check if price is needed
const isFixedPrice = item.priceType.toLowerCase() === 'fixed';
if (!isFixedPrice) {
const $amount = $posItem.querySelector('input[name="amount"]');
if (!$amount.reportValidity()) return false;
item.price = parseFloat($amount.value);
}
let itemInCart = this.cart.find(lineItem => lineItem.id === item.id && lineItem.price === item.price);
// Add new item because it doesn't exist yet
if (!itemInCart) {
itemInCart = {
id: item.id,
title: item.title,
price: item.price,
inventory: item.inventory,
count: 0
}
this.cart.push(itemInCart);
}
itemInCart.count += 1;
// Animate
if(!$posItem.classList.contains(POS_ITEM_ADDED_CLASS)) $posItem.classList.add(POS_ITEM_ADDED_CLASS);
return true;
},
removeFromCart(id) {
const index = this.cart.findIndex(lineItem => lineItem.id === id);
this.cart.splice(index, 1);
},
updateQuantity(id, count) {
const itemInCart = this.cart.find(lineItem => lineItem.id === id);
const applyable = (count < 0 && itemInCart.count + count > 0) ||
(count > 0 && (itemInCart.inventory == null || itemInCart.count + count <= itemInCart.inventory));
if (applyable) {
itemInCart.count += count;
}
},
clearCart() {
this.cart = [];
},
displayItem(item) {
const inSearch = !this.searchTerm ||
decodeURIComponent(item.dataset.search ? item.dataset.search.toLowerCase() : '')
.indexOf(this.searchTerm.toLowerCase()) !== -1
const inCategories = this.displayCategory === "*" ||
(item.dataset.categories ? JSON.parse(item.dataset.categories) : [])
.includes(this.displayCategory)
return inSearch && inCategories
},
updateDisplay() {
this.forEachItem(item => {
item.classList[this.displayItem(item) ? 'add' : 'remove']('posItem--displayed')
item.classList.remove('posItem--first')
item.classList.remove('posItem--last')
})
const $displayed = this.$refs.posItems.querySelectorAll('.posItem.posItem--displayed')
if ($displayed.length > 0) {
$displayed[0].classList.add('posItem--first')
$displayed[$displayed.length - 1].classList.add('posItem--last')
}
}
},
mounted() {
this.$cart = new bootstrap.Offcanvas(this.$refs.cart, { backdrop: false })
if (this.$refs.categories) {
const getInnerNavWidth = () => {
// set to inline display, get width to get the real inner width, then set back to flex
this.$refs.categoriesNav.classList.remove('d-flex');
this.$refs.categoriesNav.classList.add('d-inline-flex');
const navWidth = this.$refs.categoriesNav.clientWidth - 32; // 32 is the margin
this.$refs.categoriesNav.classList.remove('d-inline-flex');
this.$refs.categoriesNav.classList.add('d-flex');
return navWidth;
}
const adjustCategories = () => {
const navWidth = getInnerNavWidth();
Vue.set(this, 'categoriesScrollable', this.$refs.categories.clientWidth < navWidth);
const activeEl = document.querySelector('#Categories .btcpay-pills input:checked + label')
if (activeEl) activeEl.scrollIntoView({ block: 'end', inline: 'center' })
}
window.addEventListener('resize', e => {
debounce('resize', adjustCategories, 50)
});
adjustCategories();
}
window.addEventListener('pagehide', () => {
if (this.payButtonLoading) {
this.unsetPayButtonLoading();
localStorage.removeItem(storageKey('cart'));
}
})
this.forEachItem(item => {
item.addEventListener('transitionend', () => {
if (item.classList.contains(POS_ITEM_ADDED_CLASS)) {
item.classList.remove(POS_ITEM_ADDED_CLASS);
}
});
})
this.updateDisplay()
},
});
});