Overview
The POS component (pos.js) is the core transaction processing interface. It handles product selection, cart management, discount calculations, checkout operations, and thermal receipt generation.
Responsibilities
- Display available products for sale
- Manage shopping cart (add, update, remove items)
- Calculate subtotals, discounts, and grand totals
- Process sales and save to database
- Generate and print thermal receipts
- Handle both quick checkout and print-with-checkout flows
State Management
Cart State
The cart is an array of cart items with structure:
{
id: number,
name: string,
price: number,
qty: number
}
DOM Elements
#productGrid - Container for product buttons
#cartItems - Container for cart item list
#itemCount - Display total item quantity
#subtotal - Display subtotal amount
#discountAmount - Display calculated discount
#grandTotal - Display final total
#discountValue - Input for discount value
#discountType - Select for discount type (percent/fixed)
#clearCart - Button to clear entire cart
#checkoutBtn - Quick checkout button
#printBillBtn - Checkout with print button
Functions
renderProducts()
Fetches and displays all available products as clickable buttons.
async function renderProducts()
Behavior:
- Fetches products from database
- Shows empty state if no products
- Renders product grid with name and price
- Attaches click handlers to add products to cart
Example Output:
productGrid.innerHTML = products.map(p => `
<button data-id="${p.id}" data-name="${p.name}" data-price="${p.price}"
class="add-product border-2 border-gray-200 rounded-lg p-4 hover:border-indigo-500">
<div class="font-semibold text-gray-900 mb-1">${p.name}</div>
<div class="text-lg font-bold text-indigo-600">₹${money(p.price)}</div>
</button>
`).join('');
addToCart(prod)
Adds a product to the cart or increments quantity if already present.
Product object with id, name, and price
function addToCart(prod) {
const found = cart.find(c => c.id === prod.id);
if (found) found.qty += 1;
else cart.push({ ...prod, qty: 1 });
renderCart();
showToast(`${prod.name} added to cart`, 'success');
}
calculateDiscount(subtotal)
Calculates discount amount based on type and value.
The cart subtotal before discount
Returns: number - Discount amount
function calculateDiscount(subtotal) {
const value = parseFloat(discountValueEl.value) || 0;
const type = discountTypeEl.value;
if (type === 'percent') {
return (subtotal * (value / 100));
}
return value;
}
Discount Types:
percent - Percentage-based discount (e.g., 10% off)
fixed - Fixed amount discount (e.g., ₹50 off)
renderCart()
Renders the shopping cart UI and updates all totals.
Behavior:
- Shows empty state if cart has no items
- Renders each cart item with quantity controls
- Calculates subtotal, discount, and grand total
- Attaches event handlers for increment, decrement, and remove buttons
Cart Item Controls:
- Decrement (−): Reduces quantity (minimum 1)
- Increment (+): Increases quantity
- Remove (×): Removes item from cart
Example Cart Item Rendering:
cartItems.innerHTML = cart.map((c, idx) => {
itemCount += c.qty;
subtotal += c.qty * c.price;
return `
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex-1">
<div class="font-medium text-gray-900">${c.name}</div>
<div class="text-sm text-gray-600">₹${money(c.price)} × ${c.qty}</div>
</div>
<div class="flex items-center gap-2">
<button data-idx="${idx}" class="dec-btn">−</button>
<button data-idx="${idx}" class="inc-btn">+</button>
<button data-idx="${idx}" class="rem-btn">×</button>
</div>
</div>
`;
}).join('');
generateThermalReceipt()
Generates a formatted thermal receipt string (32 characters wide).
Returns: string - Formatted receipt text
function generateThermalReceipt() {
const date = new Date().toLocaleString();
const subtotal = Number(subtotalEl.textContent.slice(1));
const discount = Number(discountAmountEl.textContent.slice(2));
const total = Number(grandTotalEl.textContent.slice(1));
let receipt = [
'Mini POS - Codecraft by Syed',
'--------------------------------',
`Date: ${date}`,
'--------------------------------',
'Items:',
...cart.map(item => [
item.name,
`${item.qty} x ₹${money(item.price)} ₹${money(item.qty * item.price)}`
]).flat(),
'--------------------------------',
`Subtotal: ₹${money(subtotal)}`,
`Discount: -₹${money(discount)}`,
'--------------------------------',
`Total: ₹${money(total)}`,
'--------------------------------',
'',
' Thank You!',
' Please Visit Again',
''
].join('\\n');
return receipt;
}
printReceipt(receipt)
Opens a print dialog with formatted thermal receipt.
The formatted receipt text from generateThermalReceipt()
Behavior:
- Opens a 300x600px popup window
- Injects receipt HTML with thermal printer styling (80mm width)
- Automatically triggers print dialog
- Closes window after printing
function printReceipt(receipt) {
const printWindow = window.open('', '', 'width=300,height=600');
printWindow.document.write(`
<html>
<head>
<style>
body {
font-family: 'Courier New', monospace;
font-size: 12px;
width: 80mm;
margin: 0;
padding: 10px;
}
</style>
</head>
<body>
<pre>${receipt}</pre>
<script>
window.onload = function() {
window.print();
window.onafterprint = function() {
window.close();
};
};
</script>
</body>
</html>
`);
}
processSale(withPrint)
Processes the current cart as a sale and optionally prints receipt.
Whether to print a thermal receipt after checkout
Returns: Promise<void>
Behavior:
- Validates cart is not empty
- Collects subtotal, discount, and total from DOM
- Creates sale object with timestamp and items
- Saves sale to database via
addSale()
- Optionally generates and prints receipt
- Clears cart and shows success toast
async function processSale(withPrint = false) {
if (cart.length === 0) {
showToast('Cart is empty', 'error');
return;
}
const sale = {
date: new Date().toISOString(),
items: cart.map(({ id, name, price, qty }) => ({ id, name, price, qty })),
subtotal,
discount,
total
};
await addSale(sale);
if (withPrint) {
const receipt = generateThermalReceipt();
printReceipt(receipt);
}
cart = [];
renderCart();
showToast('Sale completed successfully!', 'success');
}
Event Handlers
Clear Cart
clearCartBtn.addEventListener('click', () => {
if (cart.length > 0 && confirm('Clear cart?')) {
cart = [];
renderCart();
showToast('Cart cleared');
}
});
Auto-recalculates totals when discount value or type changes:
[discountValueEl, discountTypeEl].forEach(el => {
el.addEventListener('input', renderCart);
});
// Quick checkout without printing
checkoutBtn.addEventListener('click', () => processSale(false));
// Checkout with bill printing
document.getElementById('printBillBtn').addEventListener('click', () => processSale(true));
Dependencies
Provides getProducts() and addSale() functions
Provides money() formatting and showToast() notification functions
Auto-Execution
On module load:
renderProducts();
renderCart();