Skip to main content
The Sales Reports module provides comprehensive transaction analysis with date filtering and export options for accounting and business intelligence.

Overview

Sales reports allow you to:
  • Filter transactions by date range
  • View detailed transaction history
  • Calculate totals and transaction counts
  • Export data to CSV for spreadsheet analysis
  • Print formatted reports as PDF

Date Range Filtering

View sales for any date range with instant updates

CSV Export

Download transaction data for Excel or Google Sheets

PDF Printing

Generate printable reports for physical records

Real-time Totals

Automatic calculation of totals and item counts

Date Range Selection

Reports default to today’s date but can be adjusted to any range:
js/reports.js
import { getSalesInRange } from './db.js';
import { money } from './shared.js';

const fromDate = document.getElementById('fromDate');
const toDate = document.getElementById('toDate');

function setDefaultDates() {
  const today = new Date().toISOString().slice(0, 10);
  fromDate.value = today;
  toDate.value = today;
}

// Auto-load on date change
fromDate.addEventListener('change', loadReport);
toDate.addEventListener('change', loadReport);
Reports update automatically when you change either date field, providing instant feedback without clicking a submit button.

Loading Report Data

The report loader queries IndexedDB with the selected date range:
js/reports.js
async function loadReport() {
  const from = new Date(fromDate.value || '1970-01-01');
  const to = new Date(toDate.value || '2999-12-31');
  to.setHours(23, 59, 59, 999);

  const rows = await getSalesInRange(from.toISOString(), to.toISOString());
  const total = rows.reduce((a, r) => a + r.total, 0);

  reportSummary.textContent = `${rows.length} transactions, total ₹${money(total)}`;
  reportTable.innerHTML = tableHTML(rows);

  return rows;
}
The end date is automatically adjusted to include all transactions up to 11:59:59 PM on the selected day.

Report Table

Transactions are displayed in a structured table with totals:
js/reports.js
function tableHTML(rows) {
  if (rows.length === 0) {
    return '<div class="text-center text-gray-500 py-8">No sales in selected range</div>';
  }

  return `
    <table class="min-w-full border-collapse border border-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th class="border border-gray-200 px-4 py-2 text-left font-semibold">ID</th>
          <th class="border border-gray-200 px-4 py-2 text-left font-semibold">Date</th>
          <th class="border border-gray-200 px-4 py-2 text-right font-semibold">Items</th>
          <th class="border border-gray-200 px-4 py-2 text-right font-semibold">Total</th>
        </tr>
      </thead>
      <tbody>
        ${rows.map(r => `
          <tr class="hover:bg-gray-50">
            <td class="border border-gray-200 px-4 py-2">${r.id}</td>
            <td class="border border-gray-200 px-4 py-2">${new Date(r.date).toLocaleString()}</td>
            <td class="border border-gray-200 px-4 py-2 text-right">${r.items.reduce((a, b) => a + b.qty, 0)}</td>
            <td class="border border-gray-200 px-4 py-2 text-right font-semibold">₹${money(r.total)}</td>
          </tr>
        `).join('')}
        <tr class="bg-gray-50 font-bold">
          <td colspan="3" class="border border-gray-200 px-4 py-2 text-right">Grand Total:</td>
          <td class="border border-gray-200 px-4 py-2 text-right">₹${money(rows.reduce((a, r) => a + r.total, 0))}</td>
        </tr>
      </tbody>
    </table>
  `;
}

Table Columns

  • ID: Unique transaction identifier
  • Date: Transaction timestamp in local format
  • Items: Total quantity of items sold in the transaction
  • Total: Final transaction amount after discounts

CSV Export

Generate downloadable CSV files for external analysis:
js/reports.js
exportCsvBtn.addEventListener('click', async () => {
  const rows = await loadReport();
  if (rows.length === 0) return;

  const header = ['ID', 'Date', 'Items', 'Total'];
  const lines = [header.join(',')];

  rows.forEach(r => {
    const items = r.items.reduce((a, b) => a + b.qty, 0);
    lines.push([r.id, r.date, items, money(r.total)].join(','));
  });

  const csv = lines.join('\n');
  const blob = new Blob([csv], { type: 'text/csv' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `sales-report-${fromDate.value}-to-${toDate.value}.csv`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
});
CSV files are automatically named with the date range, making it easy to organize exported reports.

CSV Format

Exported CSV files contain:
  • Header row with column names
  • One row per transaction
  • Comma-separated values
  • Compatible with Excel, Google Sheets, and accounting software

PDF Printing

Generate printable reports for physical archiving:
js/reports.js
printPdfBtn.addEventListener('click', async () => {
  const rows = await loadReport();
  if (rows.length === 0) return;

  printArea.style.display = 'block';
  printArea.innerHTML = `
    <div>
      <h1 class="text-2xl font-bold mb-2">Sales Report</h1>
      <p class="text-sm mb-4">Period: ${fromDate.value} to ${toDate.value}</p>
      ${tableHTML(rows)}
    </div>
  `;
  window.print();
  setTimeout(() => { printArea.style.display = 'none'; }, 100);
});
The print dialog uses a special hidden area that becomes visible only during printing, keeping the interface clean.

Database Query

Reports use an indexed query for fast date range filtering:
js/db.js
export async function getSalesInRange(fromISO, toISO) {
  return tx('sales', 'readonly', (s) => {
    const idx = s.index('date');
    const range = IDBKeyRange.bound(fromISO, toISO);
    return new Promise((resolve) => {
      const rows = [];
      idx.openCursor(range).onsuccess = (e) => {
        const cur = e.target.result;
        if (cur) { rows.push(cur.value); cur.continue(); }
        else resolve(rows);
      };
    });
  });
}
The date index on the sales store makes range queries extremely fast, even with thousands of transactions.

User Workflow

  1. Navigate to Reports page
  2. Reports default to today’s date
  3. Adjust “From Date” if needed
  4. Adjust “To Date” if needed
  5. Report updates automatically
  6. Review summary (transaction count, total)
  7. Review detailed table
  8. Export options:
    • Click “Export CSV” for spreadsheet analysis
    • Click “Print PDF” for physical copy
  9. Files are named with the date range

Report Features

Real-time Calculation

  • Transaction Count: Automatically counted from filtered results
  • Grand Total: Sum of all transaction totals in the range
  • Item Totals: Calculated per transaction by summing item quantities

Empty State

When no transactions exist in the selected range:
if (rows.length === 0) {
  return '<div class="text-center text-gray-500 py-8">No sales in selected range</div>';
}

Best Practices

  1. Regular Exports: Export reports weekly or monthly for backup
  2. Date Accuracy: Double-check date ranges before exporting
  3. File Organization: Use the auto-generated filenames to organize exports
  4. Trend Analysis: Compare different date ranges to identify patterns
  5. Accounting Integration: Import CSV files into accounting software
  6. Print Backups: Keep physical copies of monthly reports for compliance

Performance

  • Indexed Queries: Date-based filtering uses IndexedDB indexes for speed
  • Client-side Processing: All calculations happen in the browser
  • No Server Required: Reports work completely offline
  • Instant Updates: Date changes trigger immediate report refresh

Export Formats

Use Case: Data analysis, accounting software, spreadsheetsFormat: Comma-separated valuesFile Extension: .csvOpens With: Excel, Google Sheets, LibreOffice CalcBenefits: Editable, can create charts, calculate custom metrics

Build docs developers (and LLMs) love