● —
?
Total Loaned
ZMW 0
0 borrowers
Collected
ZMW 0
0%
Outstanding
ZMW 0
0 active
Cash In Hand
ZMW 0
Repayments received
Recent Repayments
FileDateNameDuePaidBalanceStatusBy
📋
No data yet.
Record a Repayment
Register New Loan
FileDateNameDuePaidBalanceStatusByDel
🔎
Connect your Sheet and refresh.
👤
Load records first.

Cash Balance Sheet

Team Members
Add Team Member

⚠️ Security note: Credentials are stored in localStorage and your Google Sheet. Suitable for a small trusted team. For sensitive data, consider a proper backend auth system.

📄 Google Sheets Setup Guide

Step 1 — Create your Google Sheet

Go to sheets.google.com, create a spreadsheet named LoanTrack. Rename Sheet1 to Repayments, add a second sheet named Users.


Repayments headers (Row 1):

File Number | Date | Name | Amount Due | Amount Paid | Balance | Entered By | Timestamp

Users headers (Row 1):

username | password | role

Add your team in rows below. Roles: admin or staff.


Step 2 — Apps Script

Go to Extensions → Apps Script, delete existing code, paste:

const SS = SpreadsheetApp.getActiveSpreadsheet();

function doGet(e) {
  const action = e.parameter.action;

  if (action === 'login') {
    const rows = SS.getSheetByName('Users').getDataRange().getValues().slice(1);
    const user = rows.find(r =>
      String(r[0]).toLowerCase() === String(e.parameter.username).toLowerCase() &&
      String(r[1]) === String(e.parameter.password)
    );
    return user
      ? json({ status:'ok', role:user[2], username:user[0] })
      : json({ status:'error', message:'Invalid credentials' });
  }

  if (action === 'getRecords') {
    const sh = SS.getSheetByName('Repayments');
    const data = sh.getDataRange().getValues();
    const headers = data[0];
    const rows = data.slice(1).filter(r => r[0] !== '').map(r => {
      const obj = {};
      headers.forEach((h,i) => obj[h] = r[i]);
      return obj;
    });
    return json({ status:'ok', data:rows });
  }

  if (action === 'getUsers') {
    const rows = SS.getSheetByName('Users').getDataRange().getValues().slice(1)
      .filter(r => r[0]).map(r => ({ username:r[0], role:r[2] }));
    return json({ status:'ok', users:rows });
  }
  return json({ status:'error', message:'Unknown action' });
}

function doPost(e) {
  const p = JSON.parse(e.postData.contents);

  if (p.action === 'append') {
    SS.getSheetByName('Repayments').appendRow([
      p.fileNo, p.date, p.name,
      p.amountDue, p.amountPaid, p.balance,
      p.enteredBy, new Date().toISOString()
    ]);
    return json({ status:'ok' });
  }

  if (p.action === 'deleteRow') {
    const sh = SS.getSheetByName('Repayments');
    const data = sh.getDataRange().getValues();
    for (let i = data.length - 1; i >= 1; i--) {
      if (String(data[i][0])===String(p.fileNo) &&
          String(data[i][1])===String(p.date) &&
          String(data[i][2])===String(p.name)) {
        sh.deleteRow(i + 1);
        return json({ status:'ok' });
      }
    }
    return json({ status:'error', message:'Row not found' });
  }

  if (p.action === 'addUser') {
    SS.getSheetByName('Users').appendRow([p.username, p.password, p.role]);
    return json({ status:'ok' });
  }
  return json({ status:'error', message:'Unknown action' });
}

function json(obj) {
  return ContentService.createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}

Step 3 — Deploy

  1. Click Deploy → New deployment
  2. Type: Web app
  3. Execute as: Me
  4. Who has access: Anyone
  5. Click Deploy → copy the URL

Step 4 — Connect & Deploy to Azure

On the login screen click "Configure Google Sheet connection" and paste the URL. Then drop loan-app.html into your Azure Static Web App. Done.


⚠️ After any code change in Apps Script, always create a new deployment — editing an existing one won't update the live URL.