The POS (Point of Sale) Interface is the core transaction processing module, allowing you to quickly add products to a cart, apply discounts, and complete sales.
Overview
The POS interface is split into two main sections:
Product Grid : Browse and select products to add to cart
Shopping Cart : Review items, adjust quantities, apply discounts, and checkout
Fast Product Selection Click any product to add it to the cart instantly
Flexible Discounts Apply percentage or fixed amount discounts
Thermal Receipts Generate print-ready receipts for customers
Cart Management Adjust quantities or remove items on the fly
Product Selection
Products are displayed in a responsive grid, with each product showing its name and price:
import { getProducts , addSale } from './db.js' ;
import { money , showToast } from './shared.js' ;
let cart = [];
async function renderProducts () {
const products = await getProducts ();
if ( products . length === 0 ) {
productGrid . innerHTML = '<div class="col-span-full text-center text-gray-500 py-8">No products available</div>' ;
return ;
}
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 hover:bg-indigo-50 transition text-left">
<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 ( '' );
document . querySelectorAll ( '.add-product' ). forEach ( btn => {
btn . addEventListener ( 'click' , () => {
const prod = {
id: Number ( btn . dataset . id ),
name: btn . dataset . name ,
price: Number ( btn . dataset . price )
};
addToCart ( prod );
});
});
}
Products highlight on hover with an indigo border, providing clear visual feedback that they’re clickable.
Cart Management
The cart automatically handles duplicate products by incrementing quantity:
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' );
}
Cart Display
Each cart item shows quantity controls and a remove button:
function renderCart () {
if ( cart . length === 0 ) {
cartItems . innerHTML = '<div class="text-center text-gray-500 py-4">Cart is empty</div>' ;
itemCountEl . textContent = '0' ;
subtotalEl . textContent = '₹0.00' ;
discountAmountEl . textContent = '-₹0.00' ;
grandTotalEl . textContent = '₹0.00' ;
return ;
}
let itemCount = 0 ;
let subtotal = 0 ;
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 w-8 h-8 rounded border border-gray-300 hover:bg-gray-100">−</button>
<button data-idx=" ${ idx } " class="inc-btn w-8 h-8 rounded border border-gray-300 hover:bg-gray-100">+</button>
<button data-idx=" ${ idx } " class="rem-btn w-8 h-8 rounded border border-red-300 text-red-600 hover:bg-red-50">×</button>
</div>
</div>
` ;
}). join ( '' );
const discount = calculateDiscount ( subtotal );
const total = Math . max ( 0 , subtotal - discount );
itemCountEl . textContent = itemCount ;
subtotalEl . textContent = `₹ ${ money ( subtotal ) } ` ;
discountAmountEl . textContent = `-₹ ${ money ( discount ) } ` ;
grandTotalEl . textContent = `₹ ${ money ( total ) } ` ;
}
Quantities can be increased or decreased, but the minimum quantity is always 1. Use the remove button (×) to delete items entirely.
Discount System
The POS supports two discount types that update in real-time:
function calculateDiscount ( subtotal ) {
const value = parseFloat ( discountValueEl . value ) || 0 ;
const type = discountTypeEl . value ;
if ( type === 'percent' ) {
return ( subtotal * ( value / 100 ));
}
return value ;
}
// Add discount input listeners
[ discountValueEl , discountTypeEl ]. forEach ( el => {
el . addEventListener ( 'input' , renderCart );
});
Discount Types
Enter a percentage value (e.g., “10” for 10% off). The discount amount is calculated based on the subtotal. Example: ₹1000 subtotal with 10% = ₹100 discount
Enter a fixed rupee amount (e.g., “50” for ₹50 off). The exact amount is deducted from the subtotal. Example: ₹1000 subtotal with ₹50 discount = ₹950 total
Checkout Process
Two checkout options are available:
async function processSale ( withPrint = false ) {
if ( cart . length === 0 ) {
showToast ( 'Cart is empty' , 'error' );
return ;
}
const subtotal = Number ( subtotalEl . textContent . slice ( 1 ));
const discount = Number ( discountAmountEl . textContent . slice ( 2 ));
const total = Number ( grandTotalEl . textContent . slice ( 1 ));
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' );
}
// Quick checkout without printing
checkoutBtn . addEventListener ( 'click' , () => processSale ( false ));
// Checkout with bill printing
document . getElementById ( 'printBillBtn' ). addEventListener ( 'click' , () => processSale ( true ));
Use the quick checkout for digital-only transactions, or print bill for customers who need physical receipts.
Thermal Receipt Generation
The system generates formatted thermal printer receipts:
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 ));
// Receipt is 32 characters wide for thermal printer
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 ;
}
Receipts are formatted to 32 characters width, optimized for standard 80mm thermal printers.
Cart Controls
Clear Cart
clearCartBtn . addEventListener ( 'click' , () => {
if ( cart . length > 0 && confirm ( 'Clear cart?' )) {
cart = [];
renderCart ();
showToast ( 'Cart cleared' );
}
});
Quantity Adjustments
// Decrease quantity (minimum 1)
document . querySelectorAll ( '.dec-btn' ). forEach ( btn => {
btn . addEventListener ( 'click' , () => {
const idx = Number ( btn . dataset . idx );
cart [ idx ]. qty = Math . max ( 1 , cart [ idx ]. qty - 1 );
renderCart ();
});
});
// Increase quantity
document . querySelectorAll ( '.inc-btn' ). forEach ( btn => {
btn . addEventListener ( 'click' , () => {
const idx = Number ( btn . dataset . idx );
cart [ idx ]. qty += 1 ;
renderCart ();
});
});
// Remove item
document . querySelectorAll ( '.rem-btn' ). forEach ( btn => {
btn . addEventListener ( 'click' , () => {
const idx = Number ( btn . dataset . idx );
cart . splice ( idx , 1 );
renderCart ();
});
});
User Workflow
Products load from database on page load
Click product cards to add them to cart
Cart updates automatically showing quantity and price
Adjust quantities using +/- buttons as needed
Optionally apply a discount (percentage or fixed)
Review the grand total
Choose checkout option:
Quick Checkout: Saves sale without printing
Print Bill: Saves sale and opens print dialog
Cart clears automatically after checkout
Success message confirms completion
Best Practices
Review Before Checkout : Always verify cart contents and totals
Discount Clarity : Communicate discount type clearly to customers
Receipt Printing : Test thermal printer settings for optimal output
Cart Management : Use clear cart sparingly to avoid accidental data loss
Quick Sales : For regular customers, quick checkout saves time
Related Pages