এখন পর্যন্ত আমরা প্রোডাক্ট ম্যানেজমেন্ট (যোগ, এডিট, ডিলিট) শিখেছি। আজ আমরা দোকানের সবচেয়ে গুরুত্বপূর্ণ অংশ—বিলিং কাউন্টার বানানো শুরু করবো। প্রথম ধাপে আমরা একটি সার্চ বার ও কার্ট তৈরি করবো, যাতে দোকানদার সহজেই প্রোডাক্ট খুঁজে বের করে কার্টে যোগ করতে পারে এবং মোট দাম দেখতে পারে।

বিলিং সিস্টেমের অংশ

একটি পূর্ণাঙ্গ বিলিং সিস্টেমে যে যে অংশ থাকে, আমরা ধাপে ধাপে সবগুলোই বানাবো:

অংশকী করবে?
সার্চ বারনাম লিখলেই প্রোডাক্ট খুঁজে দেখাবে
প্রোডাক্ট লিস্টসার্চ রেজাল্ট দেখাবে, ক্লিক করলেই কার্টে যোগ হবে
কার্টসিলেক্ট করা সব প্রোডাক্ট জমা রাখবে
পরিমাণ কন্ট্রোল+/− বাটন দিয়ে কতগুলো নিচ্ছে তা ঠিক করা
মোট দামকার্টের সব আইটেমের দাম × পরিমাণ-এর যোগফল দেখাবে

ধাপ ১: bill.html তৈরি করো

cd ~/shop-api/public
micro bill.html

ধাপ ২: সম্পূর্ণ bill.html কোড

নিচের সম্পূর্ণ কোডটি হুবহু কপি করে পেস্ট করো। আমরা গ্রিড লেআউট ব্যবহার করছি—বাঁ দিকে প্রোডাক্ট লিস্ট, ডান দিকে কার্ট।

<!DOCTYPE html>
<html lang="bn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>🧾 বিলিং কাউন্টার - মুদি দোকান</title>
  <style>
    :root { --bg: #f1f5f9; --surface: #ffffff; --text: #1e293b; --accent: #2563eb; }
    body.dark { --bg: #0f172a; --surface: #1e293b; --text: #e2e8f0; }
    * { margin:0; padding:0; box-sizing:border-box; }
    body { font-family:'Segoe UI', sans-serif; background:var(--bg); color:var(--text); padding:1rem; }
    .container { max-width:1000px; margin:0 auto; }
    h1 { text-align:center; margin-bottom:1rem; }
    #searchInput { width:100%; padding:0.75rem; border:2px solid var(--accent); border-radius:8px; font-size:1rem; margin-bottom:1rem; }
    .main-layout { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
    .panel { background:var(--surface); border-radius:12px; padding:1rem; box-shadow:0 2px 8px rgba(0,0,0,0.1); }
    .product-item { display:flex; justify-content:space-between; align-items:center; padding:10px; border-bottom:1px solid #e2e8f0; cursor:pointer; transition:0.2s; }
    .product-item:hover { background:#f1f5f9; }
    .cart-item { display:flex; justify-content:space-between; align-items:center; padding:8px 0; border-bottom:1px solid #e2e8f0; }
    .qty-control { display:flex; align-items:center; gap:8px; }
    .qty-btn { background:var(--accent); color:#fff; border:none; width:28px; height:28px; border-radius:6px; cursor:pointer; font-size:1rem; font-weight:bold; }
    .qty-btn:hover { opacity:0.8; }
    .total-row { display:flex; justify-content:space-between; padding:12px 0; font-size:1.2rem; font-weight:bold; margin-top:8px; border-top:2px solid var(--accent); }
  </style>
</head>
<body>
  <div class="container">
    <h1>🧾 বিলিং কাউন্টার</h1>
    <input type="text" id="searchInput" placeholder="🔍 প্রোডাক্ট খুঁজুন (নাম লিখুন)..." oninput="searchProducts()">
    <div class="main-layout">
      <div class="panel">
        <h3>📦 প্রোডাক্ট</h3>
        <div id="productResults"><p style="color:#64748b;">উপরে সার্চ করুন...</p></div>
      </div>
      <div class="panel">
        <h3>🛒 কার্ট</h3>
        <div id="cartItems"><p style="color:#64748b;">কার্ট খালি</p></div>
        <div class="total-row"><span>মোট</span><span id="cartTotal">০ ৳</span></div>
      </div>
    </div>
  </div>
  <script>
    let cart = [];
    const PRODUCTS_URL = '/products';

    async function searchProducts() {
      const q = document.getElementById('searchInput').value.trim().toLowerCase();
      const res = await fetch(PRODUCTS_URL);
      const all = await res.json();
      const filtered = q ? all.filter(p => p.name.toLowerCase().includes(q)) : [];
      const container = document.getElementById('productResults');
      if (filtered.length === 0) { container.innerHTML = '<p style="color:#64748b;">কোনো প্রোডাক্ট পাওয়া যায়নি</p>'; return; }
      container.innerHTML = filtered.map(p => `
        <div class="product-item" onclick="addToCart(${p.id})">
          <span>${p.name}</span>
          <span>৳ ${p.price}</span>
        </div>
      `).join('');
    }

    async function addToCart(id) {
      const res = await fetch(PRODUCTS_URL);
      const all = await res.json();
      const product = all.find(p => p.id === id);
      if (!product) return;
      const existing = cart.find(item => item.id === id);
      if (existing) { existing.qty += 1; }
      else { cart.push({ id:product.id, name:product.name, price:product.price, qty:1 }); }
      renderCart();
    }

    function changeQty(id, delta) {
      const item = cart.find(i => i.id === id);
      if (!item) return;
      item.qty += delta;
      if (item.qty <= 0) { cart = cart.filter(i => i.id !== id); }
      renderCart();
    }

    function renderCart() {
      const container = document.getElementById('cartItems');
      if (cart.length === 0) { container.innerHTML = '<p style="color:#64748b;">কার্ট খালি</p>'; document.getElementById('cartTotal').textContent = '০ ৳'; return; }
      container.innerHTML = cart.map(item => `
        <div class="cart-item">
          <span>${item.name}</span>
          <div class="qty-control">
            <button class="qty-btn" onclick="changeQty(${item.id}, -1)">−</button>
            <span>${item.qty}</span>
            <button class="qty-btn" onclick="changeQty(${item.id}, 1)">+</button>
          </div>
          <span>৳ ${item.price * item.qty}</span>
        </div>
      `).join('');
      const total = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
      document.getElementById('cartTotal').textContent = total + ' ৳';
    }
  </script>
</body>
</html>

লাইন-বাই-লাইন ব্যাখ্যা

ফাংশন/অংশকী করছে?সহজ ভাষায়
searchProducts()সার্চ বারের টেক্সট ধরে API থেকে সব প্রোডাক্ট এনে ফিল্টার করছে"যে নাম দিয়েছো, সেটা খুঁজো"
addToCart(id)নির্দিষ্ট id-র প্রোডাক্ট কার্টে যোগ করে; যদি আগে থেকে থাকে তবে qty ১ বাড়ায়"এই জিনিসটা কার্টে দাও"
changeQty(id, delta)পরিমাণ +১ বা −১ করে; qty শূন্য হলে আইটেম সরিয়ে দেয়"একটা বাড়াও / কমান"
renderCart()কার্টের সব আইটেম দেখায় এবং মোট দাম আপডেট করে"কার্টটা আবার দেখাও"
cart অ্যারেসব সিলেক্ট করা প্রোডাক্ট (id, name, price, qty) জমা থাকে"কী কী নিচ্ছে তার তালিকা"

ধাপ ৩: টেস্ট করো

node server.js
termux-open-url http://localhost:3000/bill.html

এখন সার্চ বারে কিছু লিখলেই প্রোডাক্টের তালিকা আসবে। প্রোডাক্টে ক্লিক করলে সেটা কার্টে যোগ হবে। +/− বাটন দিয়ে পরিমাণ ঠিক করতে পারবে, আর নিচে মোট দাম দেখাবে।

আজ তুমি কী শিখলে

  • সার্চ বারoninput ইভেন্টে রিয়েল-টাইম সার্চ
  • কার্ট অ্যারে — জাভাস্ক্রিপ্ট অ্যারেতে সাময়িক ডেটা রাখা
  • পরিমাণ কন্ট্রোল — +/− বাটন দিয়ে qty পরিবর্তন
  • মোট দাম হিসাবreduce দিয়ে সব আইটেমের price × qty যোগ করে টোটাল দেখানো
  • ডুপ্লিকেট হ্যান্ডলিং — একই প্রোডাক্ট আবার যোগ করলে qty শুধু বাড়বে, নতুন করে যোগ হবে না

পরবর্তী পর্বে আমরা সম্পূর্ণ বিক্রয় (Complete Sale) ফিচার যোগ করবো — বিল কাটলে অটো স্টক কমে যাবে এবং সেলস রেকর্ড জমা হবে।

So far we've learned product management (add, edit, delete). Today we'll start building the most important part of any shop—the Billing Counter. First, we'll create a search bar and cart where the shopkeeper can easily find products, add them to the cart, and see the total price.

Parts of the Billing System

PartFunction
Search BarReal-time product search as you type
Product ListShows filtered results, click to add to cart
CartHolds all selected products temporarily
Quantity Control+/− buttons to adjust quantity
Total PriceDisplays sum of price × quantity for all cart items

Step 1: Create bill.html

cd ~/shop-api/public
micro bill.html

Step 2: Add full code with search, cart & total logic

Copy the complete HTML code provided in the Bengali section above—it includes searchProducts(), addToCart(), changeQty(), and renderCart() functions.

Step 3: Test

node server.js
termux-open-url http://localhost:3000/bill.html

Next episode: Complete Sale — auto stock update and sales record when a bill is finalized.