<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rahman Rental Service</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root {
--primary-color: #0c4a6e;
--secondary-color: #0ea5e9;
--danger-color: #b91c1c;
--success-color: #16a34a;
--background-gradient: linear-gradient(120deg, #f0f9ff 0%, #e0f2fe 100%);
--card-bg: rgba(255, 255, 255, 0.85);
--text-color: #1e293b;
--label-color: #334155;
--border-color: rgba(203, 213, 225, 0.7);
--shadow-color: rgba(12, 74, 110, 0.15);
--info-bg: rgba(239, 246, 255, 0.9);
}
body {
font-family: 'Inter', sans-serif;
background: var(--background-gradient);
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 40px 20px;
box-sizing: border-box;
margin: 0;
}
body.modal-open { overflow: hidden; }
#app-wrapper.hidden { display: none; }
.app-container {
background: var(--card-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
padding: 40px;
border-radius: 20px;
box-shadow: 0 12px 40px var(--shadow-color);
border: 1px solid var(--border-color);
width: 100%;
max-width: 800px;
}
.header { text-align: center; margin-bottom: 30px; }
h1 { color: var(--primary-color); font-size: 28px; font-weight: 700; margin: 0; }
.address { color: var(--label-color); font-size: 15px; margin-top: 5px; }
.phone-numbers { color: var(--label-color); font-size: 14px; margin-top: 8px; font-weight: 500; }
.tab-navigation { display: flex; border-bottom: 1px solid #e2e8f0; margin-bottom: 30px; flex-wrap: wrap; }
.tab-btn { padding: 12px 15px; border: none; background: none; cursor: pointer; font-size: 15px; font-weight: 600; color: var(--label-color); position: relative; transition: color 0.3s ease; }
.tab-btn:after { content: ''; position: absolute; bottom: -1px; left: 0; width: 100%; height: 3px; background: var(--secondary-color); transform: scaleX(0); transition: transform 0.3s ease; }
.tab-btn.active { color: var(--primary-color); }
.tab-btn.active:after { transform: scaleX(1); }
.tab-content { display: none; }
.tab-content.active { display: block; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.input-group { margin-bottom: 0; }
.input-group.full-width { grid-column: 1 / -1; }
.input-group label { display: block; margin-bottom: 8px; color: var(--label-color); font-weight: 500; font-size: 14px; }
.input-group input, .input-group select { width: 100%; padding: 12px 15px; border: 1px solid #cbd5e1; background-color: #fff; border-radius: 8px; box-sizing: border-box; font-family: 'Inter', sans-serif; font-size: 15px; color: var(--text-color); transition: border-color 0.2s, box-shadow 0.2s; }
.input-group input:disabled { background-color: #f1f5f9; color: var(--label-color); cursor: not-allowed; }
.input-group input:focus, .input-group select:focus { outline: none; border-color: var(--secondary-color); box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.2); }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; width: 100%; padding: 15px; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 15px; transition: transform 0.2s, box-shadow 0.2s; background: linear-gradient(45deg, var(--secondary-color), var(--primary-color)); color: white; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(0,0,0,0.1); }
.btn-edit { background: #64748b; margin-top: 25px; }
.btn-edit:hover { background: #475569; }
.btn-vacate { background: #d97706; margin-top: 10px; }
.btn-vacate:hover { background: #b45309; }
.btn-success { background: var(--success-color); }
.btn-danger { background: var(--danger-color); margin-top: 10px; }
.btn-danger:hover { background: #991b1b; }
#generatedMessage { background-color: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; white-space: pre-wrap; font-size: 15px; line-height: 1.6; color: var(--text-color); margin-top: 15px; }
.action-buttons { display: flex; gap: 15px; margin-top: 20px; }
.action-buttons .action-btn { flex-grow: 1; text-align: center; text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 10px; padding: 12px; border-radius: 8px; font-weight: 600; font-size: 16px; transition: transform 0.2s, box-shadow 0.2s; cursor: pointer; border: none; }
.btn-whatsapp { background-color: #25d366; color: white; }
.btn-copy { background-color: #f1f5f9; color: var(--label-color); border: 1px solid #e2e8f0; }
.billing-summary { background-color: var(--info-bg); border-radius: 10px; padding: 20px; margin: 25px 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; text-align: center; }
.summary-item .label { font-size: 14px; font-weight: 500; color: var(--label-color); }
.summary-item .value { font-size: 22px; font-weight: 700; color: var(--primary-color); margin-top: 5px; }
#toast { visibility: hidden; min-width: 250px; background-color: #333; color: #fff; text-align: center; border-radius: 8px; padding: 16px; position: fixed; z-index: 1002; left: 50%; transform: translateX(-50%); bottom: 30px; transition: visibility 0s, opacity 0.5s linear; opacity: 0; }
#toast.show { visibility: visible; opacity: 1; }
.details-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 20px; background-color: var(--info-bg); padding: 20px; border-radius: 10px; }
.details-grid div { font-size: 15px; word-break: break-word; }
#paymentHistoryTable, #vacatedPaymentHistoryTable { width: 100%; margin-top: 20px; border-collapse: collapse; }
#paymentHistoryTable th, #paymentHistoryTable td, #vacatedPaymentHistoryTable th, #vacatedPaymentHistoryTable td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e2e8f0; }
.data-management-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.data-section { border-top: 1px solid #e2e8f0; padding-top: 20px; margin-top: 20px; }
.data-section h3 { margin-top:0; color: var(--primary-color); }
.data-management-text, .vacated-text { margin-bottom: 25px; line-height: 1.6; color: var(--label-color); }
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(5px); display: none; justify-content: center; align-items: center; z-index: 1001; }
.modal-content { background: white; padding: 30px 40px; border-radius: 15px; width: 90%; max-width: 700px; box-shadow: 0 5px 25px rgba(0,0,0,0.2); }
.modal-content h3 { margin-top: 0; color: var(--primary-color); }
.modal-actions { display: flex; gap: 15px; margin-top: 25px; }
.payment-action-btn { padding: 5px 10px; font-size: 13px; margin: 0 4px; border-radius: 6px; cursor: pointer; border: none; color: white; font-weight: 500; transition: background-color 0.2s; }
.payment-action-btn.edit { background-color: var(--secondary-color); }
.payment-action-btn.edit:hover { background-color: var(--primary-color); }
.payment-action-btn.delete { background-color: #ef4444; }
.payment-action-btn.delete:hover { background-color: var(--danger-color); }
/* Styles for Guided Email Modal */
#email-steps-container .step { margin-bottom: 20px; padding: 15px; background-color: var(--info-bg); border-radius: 8px; }
#email-steps-container .step-number { font-weight: 700; color: var(--primary-color); font-size: 18px; margin-right: 10px; }
#email-steps-container .step p { margin: 5px 0 10px 0; }
#email-steps-container .step button.disabled { background: #9ca3af; cursor: not-allowed; }
#email-steps-container .step button.disabled:hover { transform: none; box-shadow: none; }
</style>
</head>
<body onload="checkPassword()">
<div id="app-wrapper" class="app-container hidden">
<div class="header"><h1>Rahman Rental Service</h1><p class="address">Tezpur, Assam</p><p class="phone-numbers">Ph: 9864362448 / 9101775321</p></div>
<div class="tab-navigation">
<button class="tab-btn active" onclick="switchTab('entry', this)">Add New Tenant</button>
<button class="tab-btn" onclick="switchTab('bill', this)">Generate Bill</button>
<button class="tab-btn" onclick="switchTab('payment', this)">Record Payment</button>
<button class="tab-btn" onclick="switchTab('history', this)">View Active Tenants</button>
<button class="tab-btn" onclick="switchTab('vacated', this)">Vacated Tenants</button>
<button class="tab-btn" onclick="switchTab('data', this)">Data Management</button>
</div>
<div id="entryTab" class="tab-content active"><form id="tenantEntryForm"><div class="form-grid"><div class="input-group"><label>Name</label><input type="text" id="tenantName" required></div><div class="input-group"><label>Phone Number</label><input type="text" id="tenantPhone"></div><div class="input-group"><label>Whatsapp Number</label><input type="text" id="whatsappNumber"></div><div class="input-group"><label>AADHAR Card No.</label><input type="text" id="tenantAadhar"></div><div class="input-group"><label>Father's Name</label><input type="text" id="fatherName"></div><div class="input-group full-width"><label>Permanent Address</label><input type="text" id="permanentAddress"></div><div class="input-group"><label>Date of Possession</label><input type="date" id="possessionDate"></div><div class="input-group"><label>Room No.</label><input type="text" id="flatNo"></div><div class="input-group"><label>Fixed Monthly Rent (₹)</label><input type="number" id="monthlyRent" required></div><div class="input-group"><label>Advance Money Received (₹)</label><input type="number" id="advanceMoney"></div><div class="input-group"><label>Security Amount (₹)</label><input type="number" id="securityAmount"></div><div class="input-group"><label>Initial Meter Reading</label><input type="number" id="initialMeterReading" required></div></div><button type="submit" class="btn">Add & Save Tenant</button></form></div>
<div id="billTab" class="tab-content"><div class="form-grid"><div class="input-group full-width"><label>Select Tenant</label><select id="billTenantSelect" class="active-tenant-select" onchange="handleTenantSelectionChange()"></select></div><div class="input-group"><label>Last Recorded Reading</label><input type="number" id="lastReadingDisplay" disabled></div><div class="input-group"><label>New Meter Reading</label><input type="number" id="currentReading" oninput="calculateBilling()"></div><div class="input-group full-width"><label>Rate per Unit (₹)</label><input type="number" id="unitRate" value="10" oninput="calculateBilling()"></div></div><div class="billing-summary"><div class="summary-item"><div class="label">Units Consumed</div><div class="value" id="consumedUnitsDisplay">0</div></div><div class="summary-item"><div class="label">Electricity Bill</div><div class="value" id="electricityBillDisplay">₹0.00</div></div><div class="summary-item"><div class="label">Total Amount Due</div><div class="value" id="totalAmountDisplay">₹0.00</div></div></div><button class="btn" onclick="generateBillMessage()">Generate & Update Reading</button><div id="message-generation-section" style="display:none;"><div id="generatedMessage"></div><div class="action-buttons"><a id="whatsappBtn" class="action-btn btn-whatsapp" href="#" target="_blank">Send via WhatsApp</a><button class="action-btn btn-copy" onclick="copyMessage()">Copy Message</button></div></div></div>
<div id="paymentTab" class="tab-content"><form id="paymentForm"><div class="form-grid"><div class="input-group full-width"><label>Select Tenant</label><select id="paymentTenantSelect" class="active-tenant-select"></select></div><div class="input-group"><label>Payment For Month/Year</label><input type="month" id="paymentForMonth" required></div><div class="input-group"><label>Date Paid</label><input type="date" id="paymentDate" required></div><div class="input-group"><label>Rent Amount Paid (₹)</label><input type="number" id="rentAmountPaid" placeholder="e.g., 5000"></div><div class="input-group"><label>Electricity Amount Paid (₹)</label><input type="number" id="electricityAmountPaid" placeholder="e.g., 850"></div></div><button type="submit" class="btn">Save Payment Record</button></form></div>
<div id="historyTab" class="tab-content"><div class="input-group full-width"><label>Select Tenant</label><select id="historyTenantSelect" class="active-tenant-select" onchange="displayTenantHistory()"></select></div><div id="tenantDetailsDisplay" style="display: none;"><h3>Tenant Details</h3><div class="details-grid" id="detailsGrid"></div><h3>Payment History</h3><table id="paymentHistoryTable"><thead><tr></tr></thead><tbody id="paymentHistoryBody"></tbody></table><button class="btn btn-edit" onclick="openEditModal()">Edit Tenant Details</button><button class="btn btn-vacate" onclick="markTenantAsVacated()">Mark Tenant as Vacated</button></div></div>
<div id="vacatedTab" class="tab-content"><p class="vacated-text">Archive of tenants who have vacated. Data is read-only unless you choose to delete it permanently.</p><div class="input-group full-width"><label>Select Vacated Tenant</label><select id="vacatedTenantSelect" onchange="displayVacatedTenantHistory()"></select></div><div id="vacatedTenantDetailsDisplay" style="display: none;"><h3>Tenant Details</h3><div class="details-grid" id="vacatedDetailsGrid"></div><h3>Payment History</h3><table id="vacatedPaymentHistoryTable"><thead><tr></tr></thead><tbody id="vacatedPaymentHistoryBody"></tbody></table><button class="btn btn-danger" onclick="deleteVacatedTenant()">Delete Tenant Permanently</button></div></div>
<div id="dataTab" class="tab-content">
<div class="data-section">
<h3>File Backup & Restore</h3>
<p class="data-management-text">Download a backup to your device for safekeeping or to transfer to another device. Use the "Restore From File" button to load data from a `.json` or `.xlsx` file.</p>
<div class="data-management-grid">
<button class="btn" onclick="backupData()">Download Backup (.json)</button>
<button class="btn btn-success" onclick="backupDataExcel()">Download as Excel (.xlsx)</button>
</div>
<div class="input-group full-width" style="margin-top: 15px;">
<button class="btn" onclick="document.getElementById('restoreInput').click()">Restore From File</button>
</div>
<input type="file" id="restoreInput" style="display: none;" onchange="handleRestore(event)" accept=".json, .xlsx">
</div>
<div class="data-section">
<h3>Email Backup</h3>
<p class="data-management-text">Enter your email below. The "Quick Email" is fast but may fail for large data sets. For a guaranteed email backup, use the "Guided Email" which helps you attach the file manually.</p>
<div class="input-group full-width" style="margin-bottom:15px;">
<label>Your Backup Email Address</label>
<input type="email" id="backupEmail" onchange="saveConfig()" placeholder="Enter and save your email address">
</div>
<div class="data-management-grid">
<button class="btn btn-success" onclick="prepareEmailBackup()">Quick Email (Small Backups)</button>
<button class="btn btn-success" onclick="prepareLargeEmailBackup()">Guided Email (Large Backups)</button>
</div>
</div>
</div>
</div>
<div id="toast"></div>
<div id="edit-modal-overlay" class="modal-overlay"><div id="edit-modal-content" class="modal-content"><h3>Edit Tenant Details</h3><form id="editTenantForm"><input type="hidden" id="editTenantIndex"><div class="form-grid"><div class="input-group"><label>Name</label><input type="text" id="editTenantName" required></div><div class="input-group"><label>Phone Number</label><input type="text" id="editTenantPhone"></div><div class="input-group"><label>Whatsapp Number</label><input type="text" id="editWhatsappNumber"></div><div class="input-group"><label>AADHAR Card No.</label><input type="text" id="editTenantAadhar"></div><div class="input-group"><label>Father's Name</label><input type="text" id="editFatherName"></div><div class="input-group full-width"><label>Permanent Address</label><input type="text" id="editPermanentAddress"></div><div class="input-group"><label>Date of Possession</label><input type="date" id="editPossessionDate"></div><div class="input-group"><label>Room No.</label><input type="text" id="editFlatNo"></div><div class="input-group"><label>Fixed Monthly Rent (₹)</label><input type="number" id="editMonthlyRent" required></div><div class="input-group"><label>Advance Money Received (₹)</label><input type="number" id="editAdvanceMoney"></div><div class="input-group"><label>Security Amount (₹)</label><input type="number" id="editSecurityAmount"></div><div class="input-group"><label>Last Recorded Reading</label><input type="number" id="editLastMeterReading" required></div></div><div class="modal-actions"><button type="button" class="btn btn-vacate" onclick="closeEditModal()">Cancel</button><button type="submit" class="btn">Update Tenant</button></div></form></div></div>
<div id="edit-payment-modal-overlay" class="modal-overlay"><div id="edit-payment-modal-content" class="modal-content"><h3>Edit Payment Record</h3><form id="editPaymentForm"><input type="hidden" id="editPaymentTenantIndex"><input type="hidden" id="editPaymentRecordIndex"><div class="form-grid"><div class="input-group"><label>Payment For Month/Year</label><input type="month" id="editPaymentForMonth" required></div><div class="input-group"><label>Date Paid</label><input type="date" id="editPaymentDate" required></div><div class="input-group"><label>Rent Amount Paid (₹)</label><input type="number" id="editRentAmountPaid"></div><div class="input-group"><label>Electricity Amount Paid (₹)</label><input type="number" id="editElectricityAmountPaid"></div></div><div class="modal-actions"><button type="button" class="btn btn-vacate" onclick="closeEditPaymentModal()">Cancel</button><button type="submit" class="btn">Update Payment</button></div></form></div></div>
<!-- NEW: Guided Email Modal -->
<div id="email-large-backup-modal" class="modal-overlay">
<div id="email-large-backup-content" class="modal-content">
<h3>Guided Email Backup</h3>
<p>Follow these two simple steps to email a large backup file.</p>
<div id="email-steps-container">
<div class="step">
<span class="step-number">Step 1:</span>
<p>First, save the backup file to your device.</p>
<button id="modal-download-btn" class="btn" onclick="runGuidedDownload()">Download Backup File</button>
</div>
<div class="step">
<span class="step-number">Step 2:</span>
<p>Next, open your email client. The email will be pre-filled. **You must manually attach the file you just downloaded.**</p>
<a id="modal-email-link" href="#" target="_blank" class="btn disabled" onclick="if(this.classList.contains('disabled')) return false;">Open Email Client</a>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-vacate" onclick="closeLargeEmailModal()">Close</button>
</div>
</div>
</div>
<script>
const APP_PASSWORD = 'rent';
const formatToINR = (num) => num.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 2 });
const ACTIVE_TENANTS_KEY = 'rahman_rental_service_active_tenants';
const VACATED_TENANTS_KEY = 'rahman_rental_service_vacated_tenants';
const CONFIG_KEY = 'rahman_rental_service_config';
function checkPassword() {
const enteredPassword = prompt("Please enter the password to access the app:", "");
if (enteredPassword === APP_PASSWORD) {
document.getElementById('app-wrapper').classList.remove('hidden');
initializeApp();
} else {
alert("Incorrect password. Access denied.");
document.body.innerHTML = '<h1 style="text-align:center; color:#b91c1c; margin-top:50px;">Access Denied</h1>';
}
}
function initializeApp() {
loadConfig();
loadActiveTenantsIntoDropdowns();
loadVacatedTenantsIntoDropdown();
document.getElementById('tenantEntryForm').addEventListener('submit', saveTenant);
document.getElementById('paymentForm').addEventListener('submit', recordPayment);
document.getElementById('editTenantForm').addEventListener('submit', saveEditedTenant);
document.getElementById('editPaymentForm').addEventListener('submit', saveEditedPayment);
switchTab('entry', document.querySelector('.tab-btn'));
displayTenantHistory();
}
function switchTab(tabName, clickedButton) {
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(tabName + 'Tab').classList.add('active');
clickedButton.classList.add('active');
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.innerText = message; toast.className = "show";
setTimeout(() => { toast.className = toast.className.replace("show", ""); }, 3000);
}
function getActiveTenants() { return JSON.parse(localStorage.getItem(ACTIVE_TENANTS_KEY)) || []; }
function saveAllActiveTenants(tenants) { localStorage.setItem(ACTIVE_TENANTS_KEY, JSON.stringify(tenants)); }
function getVacatedTenants() { return JSON.parse(localStorage.getItem(VACATED_TENANTS_KEY)) || []; }
function saveAllVacatedTenants(tenants) { localStorage.setItem(VACATED_TENANTS_KEY, JSON.stringify(tenants)); }
function getConfig() { return JSON.parse(localStorage.getItem(CONFIG_KEY)) || {}; }
function saveConfig() {
const config = { backupEmail: document.getElementById('backupEmail').value };
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
showToast("Backup email saved!");
}
function loadConfig() {
const config = getConfig();
if (config.backupEmail) { document.getElementById('backupEmail').value = config.backupEmail; }
}
function saveTenant(event) {
event.preventDefault();
const newTenant = {
name: document.getElementById('tenantName').value, phone: document.getElementById('tenantPhone').value,
whatsappNumber: document.getElementById('whatsappNumber').value, aadhar: document.getElementById('tenantAadhar').value,
fatherName: document.getElementById('fatherName').value, permanentAddress: document.getElementById('permanentAddress').value,
possessionDate: document.getElementById('possessionDate').value, roomNo: document.getElementById('flatNo').value,
monthlyRent: parseFloat(document.getElementById('monthlyRent').value) || 0,
advanceMoney: parseFloat(document.getElementById('advanceMoney').value) || 0,
securityAmount: parseFloat(document.getElementById('securityAmount').value) || 0,
lastMeterReading: parseFloat(document.getElementById('initialMeterReading').value) || 0,
paymentHistory: []
};
const tenants = getActiveTenants();
tenants.push(newTenant);
saveAllActiveTenants(tenants);
document.getElementById('tenantEntryForm').reset();
showToast('Tenant Added Successfully!');
loadActiveTenantsIntoDropdowns();
}
function openEditModal() {
const tenantIndex = document.getElementById('historyTenantSelect').value;
if (tenantIndex === "") { showToast("Please select a tenant to edit."); return; }
const tenant = getActiveTenants()[tenantIndex];
document.getElementById('editTenantIndex').value = tenantIndex;
document.getElementById('editTenantName').value = tenant.name || '';
document.getElementById('editTenantPhone').value = tenant.phone || '';
document.getElementById('editWhatsappNumber').value = tenant.whatsappNumber || '';
document.getElementById('editTenantAadhar').value = tenant.aadhar || '';
document.getElementById('editFatherName').value = tenant.fatherName || '';
document.getElementById('editPermanentAddress').value = tenant.permanentAddress || '';
document.getElementById('editPossessionDate').value = tenant.possessionDate || '';
document.getElementById('editFlatNo').value = tenant.roomNo || '';
document.getElementById('editMonthlyRent').value = tenant.monthlyRent || 0;
document.getElementById('editAdvanceMoney').value = tenant.advanceMoney || 0;
document.getElementById('editSecurityAmount').value = tenant.securityAmount || 0;
document.getElementById('editLastMeterReading').value = tenant.lastMeterReading || 0;
document.getElementById('edit-modal-overlay').style.display = 'flex';
document.body.classList.add('modal-open');
}
function closeEditModal() {
document.getElementById('edit-modal-overlay').style.display = 'none';
document.body.classList.remove('modal-open');
}
function saveEditedTenant(event) {
event.preventDefault();
const tenantIndex = document.getElementById('editTenantIndex').value;
let tenants = getActiveTenants();
tenants[tenantIndex] = {
...tenants[tenantIndex],
name: document.getElementById('editTenantName').value, phone: document.getElementById('editTenantPhone').value,
whatsappNumber: document.getElementById('editWhatsappNumber').value, aadhar: document.getElementById('editTenantAadhar').value,
fatherName: document.getElementById('editFatherName').value, permanentAddress: document.getElementById('editPermanentAddress').value,
possessionDate: document.getElementById('editPossessionDate').value, roomNo: document.getElementById('editFlatNo').value,
monthlyRent: parseFloat(document.getElementById('editMonthlyRent').value) || 0,
advanceMoney: parseFloat(document.getElementById('editAdvanceMoney').value) || 0,
securityAmount: parseFloat(document.getElementById('editSecurityAmount').value) || 0,
lastMeterReading: parseFloat(document.getElementById('editLastMeterReading').value) || 0,
};
saveAllActiveTenants(tenants);
closeEditModal();
showToast("Tenant details updated successfully!");
loadActiveTenantsIntoDropdowns();
displayTenantHistory();
}
function markTenantAsVacated() {
const tenantIndex = document.getElementById('historyTenantSelect').value;
if (tenantIndex === "") { showToast("Please select a tenant."); return; }
if (confirm("Are you sure you want to move this tenant to the 'Vacated' archive?")) {
let activeTenants = getActiveTenants();
let vacatedTenants = getVacatedTenants();
const tenantToMove = activeTenants.splice(tenantIndex, 1)[0];
tenantToMove.vacatedDate = new Date().toISOString().slice(0, 10);
vacatedTenants.unshift(tenantToMove);
saveAllActiveTenants(activeTenants);
saveAllVacatedTenants(vacatedTenants);
showToast("Tenant moved to Vacated archive.");
loadActiveTenantsIntoDropdowns();
loadVacatedTenantsIntoDropdown();
displayTenantHistory();
}
}
function deleteVacatedTenant() {
const tenantIndex = document.getElementById('vacatedTenantSelect').value;
if (tenantIndex === "") { showToast("Please select a vacated tenant to delete."); return; }
const tenants = getVacatedTenants();
const tenantName = tenants[tenantIndex].name;
if (confirm(`Are you sure you want to PERMANENTLY delete all records for '${tenantName}'? This action cannot be undone.`)) {
tenants.splice(tenantIndex, 1);
saveAllVacatedTenants(tenants);
showToast("Tenant record deleted permanently.");
loadVacatedTenantsIntoDropdown();
}
}
function loadActiveTenantsIntoDropdowns() {
const tenants = getActiveTenants();
document.querySelectorAll('.active-tenant-select').forEach(select => {
const currentVal = select.value;
select.innerHTML = tenants.length === 0 ? '<option value="">No active tenants found</option>' : tenants.map((tenant, index) => `<option value="${index}">${tenant.name} (Room: ${tenant.roomNo || 'N/A'})</option>`).join('');
select.value = currentVal;
if (select.selectedIndex === -1 && tenants.length > 0) { select.selectedIndex = 0; }
});
handleTenantSelectionChange();
displayTenantHistory();
}
function loadVacatedTenantsIntoDropdown() {
const tenants = getVacatedTenants();
const select = document.getElementById('vacatedTenantSelect');
select.innerHTML = tenants.length === 0 ? '<option value="">No vacated tenants yet</option>' : tenants.map((tenant, index) => `<option value="${index}">${tenant.name} (Room: ${tenant.roomNo || 'N/A'})</option>`).join('');
displayVacatedTenantHistory();
}
function recordPayment(event) {
event.preventDefault();
const tenantIndex = document.getElementById('paymentTenantSelect').value;
if (tenantIndex === "") { showToast("Please select a tenant."); return; }
const paymentForMonth = document.getElementById('paymentForMonth').value;
const paymentDate = document.getElementById('paymentDate').value;
const rentPaid = parseFloat(document.getElementById('rentAmountPaid').value) || 0;
const electricityPaid = parseFloat(document.getElementById('electricityAmountPaid').value) || 0;
if (!paymentForMonth || !paymentDate) { showToast("Please fill all required fields (Month and Date)."); return; }
const tenants = getActiveTenants();
const paymentRecord = { monthYear: paymentForMonth, datePaid: paymentDate, rentPaid: rentPaid, electricityPaid: electricityPaid };
if (!tenants[tenantIndex].paymentHistory) { tenants[tenantIndex].paymentHistory = []; }
tenants[tenantIndex].paymentHistory.push(paymentRecord);
tenants[tenantIndex].paymentHistory.sort((a, b) => new Date(b.monthYear) - new Date(a.monthYear));
saveAllActiveTenants(tenants);
showToast("Payment recorded successfully!");
document.getElementById('paymentForm').reset();
if (document.getElementById('historyTenantSelect').value === tenantIndex) {
displayTenantHistory();
}
}
function displayTenantHistory() {
const tenantIndex = document.getElementById("historyTenantSelect").value;
const displayArea = document.getElementById("tenantDetailsDisplay");
if (tenantIndex === "" || getActiveTenants().length === 0) {
displayArea.style.display = "none"; return;
}
displayArea.style.display = "block";
const tenant = getActiveTenants()[tenantIndex];
document.getElementById("detailsGrid").innerHTML = `
<div><strong>Name:</strong> ${tenant.name || "N/A"}</div><div><strong>Room No:</strong> ${tenant.roomNo || "N/A"}</div>
<div><strong>Phone:</strong> ${tenant.phone || "N/A"}</div><div><strong>Whatsapp:</strong> ${tenant.whatsappNumber || "N/A"}</div>
<div><strong>Aadhar:</strong> ${tenant.aadhar || "N/A"}</div><div><strong>Father's Name:</strong> ${tenant.fatherName || "N/A"}</div>
<div style="grid-column: 1 / -1;"><strong>Rent:</strong> ${formatToINR(tenant.monthlyRent || 0)}</div>
<div><strong>Advance Paid:</strong> ${formatToINR(tenant.advanceMoney || 0)}</div><div><strong>Security Deposit:</strong> ${formatToINR(tenant.securityAmount || 0)}</div>
<div><strong>Last Reading:</strong> ${tenant.lastMeterReading || "N/A"}</div>`;
renderPaymentHistoryTable(tenant.paymentHistory, "paymentHistoryBody", tenantIndex);
}
function displayVacatedTenantHistory() {
const tenantIndex = document.getElementById("vacatedTenantSelect").value;
const displayArea = document.getElementById("vacatedTenantDetailsDisplay");
if (tenantIndex === "" || getVacatedTenants().length === 0) {
displayArea.style.display = "none"; return;
}
displayArea.style.display = "block";
const tenant = getVacatedTenants()[tenantIndex];
document.getElementById("vacatedDetailsGrid").innerHTML = `
<div><strong>Name:</strong> ${tenant.name || "N/A"}</div><div><strong>Room No:</strong> ${tenant.roomNo || "N/A"}</div>
<div><strong>Phone:</strong> ${tenant.phone || "N/A"}</div><div><strong>Whatsapp:</strong> ${tenant.whatsappNumber || "N/A"}</div>
<div><strong>Aadhar:</strong> ${tenant.aadhar || "N/A"}</div><div><strong>Rent:</strong> ${formatToINR(tenant.monthlyRent || 0)}</div>
<div><strong>Advance Paid:</strong> ${formatToINR(tenant.advanceMoney || 0)}</div><div><strong>Security Deposit:</strong> ${formatToINR(tenant.securityAmount || 0)}</div>
<div style="grid-column: 1 / -1;"><strong>Vacated On:</strong> ${tenant.vacatedDate ? new Date(tenant.vacatedDate + "T00:00:00").toLocaleDateString() : "N/A"}</div>`;
renderPaymentHistoryTable(tenant.paymentHistory, "vacatedPaymentHistoryBody");
}
function renderPaymentHistoryTable(history, tbodyId, tenantIndex = null) {
const tbody = document.getElementById(tbodyId);
const thead = tbody.parentElement.querySelector('thead tr');
tbody.innerHTML = "";
const hasActions = tenantIndex !== null;
thead.innerHTML = hasActions ? '<th>Paid For</th><th>Date Paid</th><th>Rent Paid</th><th>Elec. Paid</th><th>Actions</th>' : '<th>Paid For</th><th>Date Paid</th><th>Rent Paid</th><th>Elec. Paid</th>';
if (history && history.length > 0) {
history.forEach((record, recordIndex) => {
const row = tbody.insertRow();
row.innerHTML = `
<td>${new Date(record.monthYear + "-02").toLocaleString("default", { month: "long", year: "numeric" })}</td>
<td>${new Date(record.datePaid + "T00:00:00").toLocaleDateString()}</td>
<td>${formatToINR(record.rentPaid || 0)}</td>
<td>${formatToINR(record.electricityPaid || 0)}</td>
${hasActions ? `<td>
<button class="payment-action-btn edit" onclick="openEditPaymentModal(${tenantIndex}, ${recordIndex})">Edit</button>
<button class="payment-action-btn delete" onclick="deletePaymentRecord(${tenantIndex}, ${recordIndex})">Delete</button>
</td>` : ''}`;
});
} else {
tbody.innerHTML = `<tr><td colspan="${hasActions ? 5 : 4}">No payment history recorded.</td></tr>`;
}
}
function openEditPaymentModal(tenantIndex, paymentIndex) {
const tenant = getActiveTenants()[tenantIndex];
const record = tenant.paymentHistory[paymentIndex];
document.getElementById('editPaymentTenantIndex').value = tenantIndex;
document.getElementById('editPaymentRecordIndex').value = paymentIndex;
document.getElementById('editPaymentForMonth').value = record.monthYear || '';
document.getElementById('editPaymentDate').value = record.datePaid || '';
document.getElementById('editRentAmountPaid').value = record.rentPaid === undefined ? '' : record.rentPaid;
document.getElementById('editElectricityAmountPaid').value = record.electricityPaid === undefined ? '' : record.electricityPaid;
document.getElementById('edit-payment-modal-overlay').style.display = 'flex';
document.body.classList.add('modal-open');
}
function closeEditPaymentModal() {
document.getElementById('edit-payment-modal-overlay').style.display = 'none';
document.body.classList.remove('modal-open');
}
function saveEditedPayment(event) {
event.preventDefault();
const tenantIndex = document.getElementById('editPaymentTenantIndex').value;
const paymentIndex = document.getElementById('editPaymentRecordIndex').value;
let tenants = getActiveTenants();
tenants[tenantIndex].paymentHistory[paymentIndex] = {
monthYear: document.getElementById('editPaymentForMonth').value,
datePaid: document.getElementById('editPaymentDate').value,
rentPaid: parseFloat(document.getElementById('editRentAmountPaid').value) || 0,
electricityPaid: parseFloat(document.getElementById('editElectricityAmountPaid').value) || 0
};
tenants[tenantIndex].paymentHistory.sort((a, b) => new Date(b.monthYear) - new Date(a.monthYear));
saveAllActiveTenants(tenants);
closeEditPaymentModal();
showToast("Payment record updated successfully!");
displayTenantHistory();
}
function deletePaymentRecord(tenantIndex, paymentIndex) {
if (confirm("Are you sure you want to delete this payment record permanently?")) {
let tenants = getActiveTenants();
tenants[tenantIndex].paymentHistory.splice(paymentIndex, 1);
saveAllActiveTenants(tenants);
showToast("Payment record deleted.");
displayTenantHistory();
}
}
function handleTenantSelectionChange() {
const tenantIndex = document.getElementById("billTenantSelect").value;
document.getElementById("lastReadingDisplay").value = tenantIndex === "" ? "" : getActiveTenants()[tenantIndex].lastMeterReading || 0;
calculateBilling();
}
function calculateBilling() {
const tenantIndex = document.getElementById("billTenantSelect").value;
if (tenantIndex === "") {
document.getElementById("consumedUnitsDisplay").innerText = "0";
document.getElementById("electricityBillDisplay").innerText = formatToINR(0);
document.getElementById("totalAmountDisplay").innerText = formatToINR(0);
return;
}
const tenant = getActiveTenants()[tenantIndex];
const lastReading = tenant.lastMeterReading || 0;
const currentReading = parseFloat(document.getElementById("currentReading").value) || 0;
const unitsConsumed = Math.max(0, currentReading - lastReading);
const unitRate = parseFloat(document.getElementById("unitRate").value) || 10;
const electricityBill = unitsConsumed * unitRate;
const totalAmount = (tenant.monthlyRent || 0) + electricityBill;
document.getElementById("consumedUnitsDisplay").innerText = unitsConsumed;
document.getElementById("electricityBillDisplay").innerText = formatToINR(electricityBill);
document.getElementById("totalAmountDisplay").innerText = formatToINR(totalAmount);
}
function generateBillMessage() {
const tenantIndex = document.getElementById("billTenantSelect").value;
if (tenantIndex === "") { showToast("Please select a tenant first."); return; }
const tenants = getActiveTenants();
const tenant = tenants[tenantIndex];
const lastReading = tenant.lastMeterReading || 0;
const newReading = parseFloat(document.getElementById("currentReading").value) || 0;
if (newReading <= lastReading) { showToast("New reading must be greater than the last reading."); return; }
const unitsConsumed = newReading - lastReading;
const unitRate = parseFloat(document.getElementById("unitRate").value) || 10;
const electricityBill = unitsConsumed * unitRate;
const totalAmount = tenant.monthlyRent + electricityBill;
const elecDetails = `(${newReading} - ${lastReading}) = ${unitsConsumed} units @ ₹${unitRate}/unit = ${formatToINR(electricityBill)}`;
const messageBody = `Dear ${tenant.name},\n\nThis is a notification for your monthly dues. Please find the breakdown below:\n\n- Monthly Rent: ${formatToINR(tenant.monthlyRent)}\n- Electricity Bill: ${elecDetails}\n\n*Total Amount Due: ${formatToINR(totalAmount)}*\n\nKindly pay the amount at the earliest.\n\nThank you,\nRahman Rental Service\nTezpur, Assam\nPh: 9864362448 / 9101775321`;
document.getElementById("generatedMessage").innerText = messageBody;
let phoneForWhatsapp = (tenant.whatsappNumber || tenant.phone) || "";
const whatsappLink = phoneForWhatsapp ? `https://wa.me/${"91" + phoneForWhatsapp.replace(/\D/g, "").slice(-10)}?text=${encodeURIComponent(messageBody)}` : `https://wa.me/?text=${encodeURIComponent(messageBody)}`;
document.getElementById("whatsappBtn").href = whatsappLink;
document.getElementById("message-generation-section").style.display = "block";
tenants[tenantIndex].lastMeterReading = newReading;
saveAllActiveTenants(tenants);
showToast("Message generated & reading updated!");
handleTenantSelectionChange();
}
function copyMessage() { navigator.clipboard.writeText(document.getElementById("generatedMessage").innerText).then(() => showToast("Message copied!")); }
function backupData(isGuided = false) {
const backupObject = { active: getActiveTenants(), vacated: getVacatedTenants() };
if (backupObject.active.length === 0 && backupObject.vacated.length === 0) {
if(!isGuided) showToast("No data to backup.");
return false;
}
const date = new Date().toISOString().slice(0, 10);
const filename = `rahman-rental-backup-${date}.json`;
const dataStr = JSON.stringify(backupObject, null, 2);
const blob = new Blob([dataStr], {type: "application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
if(!isGuided) showToast("JSON Backup file downloaded!");
return filename; // Return filename for guided process
}
function backupDataExcel() {
if (typeof XLSX === 'undefined') { showToast("Excel library not loaded. Check internet."); return; }
const activeTenants = getActiveTenants();
const vacatedTenants = getVacatedTenants();
if (activeTenants.length === 0 && vacatedTenants.length === 0) { showToast("No data to backup."); return; }
const processTenants = (tenants) => {
let flatData = [];
tenants.forEach(t => {
const info = {'Name': t.name, 'Room No': t.roomNo, 'Phone': t.phone, 'Whatsapp': t.whatsappNumber, 'Aadhar': t.aadhar, "Father's Name": t.fatherName, 'Permanent Address': t.permanentAddress, 'Date of Possession': t.possessionDate, 'Monthly Rent (₹)': t.monthlyRent, 'Advance (₹)': t.advanceMoney, 'Security (₹)': t.securityAmount, 'Last Meter Reading': t.lastMeterReading, 'Vacated Date': t.vacatedDate || 'N/A' };
if (t.paymentHistory && t.paymentHistory.length > 0) {
t.paymentHistory.forEach(p => flatData.push({ ...info, 'Payment Month': p.monthYear, 'Payment Date': p.datePaid, 'Rent Paid (₹)': p.rentPaid, 'Electricity Paid (₹)': p.electricityPaid }));
} else {
flatData.push({ ...info, 'Payment Month': 'N/A', 'Payment Date': 'N/A', 'Rent Paid (₹)': 'N/A', 'Electricity Paid (₹)': 'N/A' });
}
});
return flatData;
};
const wb = XLSX.utils.book_new();
if (activeTenants.length > 0) XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(processTenants(activeTenants)), "Active Tenants");
if (vacatedTenants.length > 0) XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(processTenants(vacatedTenants)), "Vacated Tenants");
XLSX.writeFile(wb, `rahman-rental-excel-backup-${new Date().toISOString().slice(0, 10)}.xlsx`);
showToast("Excel backup downloaded!");
}
function prepareEmailBackup() {
const email = getConfig().backupEmail;
if (!email) { showToast("Please enter and save your backup email address first."); document.getElementById('backupEmail').focus(); return; }
const backupObject = { active: getActiveTenants(), vacated: getVacatedTenants() };
if (backupObject.active.length === 0 && backupObject.vacated.length === 0) { showToast("No data to backup."); return; }
const dataStr = JSON.stringify(backupObject);
const date = new Date().toLocaleString('en-IN', { dateStyle: 'long', timeStyle: 'short' });
const subject = `Rahman Rental Service - JSON Backup - ${date}`;
const body = `This is your automated data backup from the Rahman Rental Service app.\n\nDate: ${date}\n\nTo restore, save the text between the lines into a new file named 'backup.json', then use the 'Restore from File' button in the app.\n\n--- BEGIN BACKUP DATA ---\n\n${dataStr}\n\n--- END BACKUP DATA ---`;
const mailtoLink = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
if (mailtoLink.length > 2000) {
alert("Backup data is too large for an email link. Please use the 'Guided Email' option instead.");
return;
}
window.location.href = mailtoLink;
}
// --- Guided Email Functions ---
function prepareLargeEmailBackup() {
const email = getConfig().backupEmail;
if (!email) {
showToast("Please enter and save your backup email address first.");
document.getElementById('backupEmail').focus();
return;
}
document.getElementById('email-large-backup-modal').style.display = 'flex';
document.body.classList.add('modal-open');
}
function closeLargeEmailModal() {
const modal = document.getElementById('email-large-backup-modal');
modal.style.display = 'none';
document.body.classList.remove('modal-open');
// Reset modal state
document.getElementById('modal-email-link').classList.add('disabled');
}
function runGuidedDownload() {
const filename = backupData(true); // true indicates a guided download
if (filename) {
const emailLink = document.getElementById('modal-email-link');
const email = getConfig().backupEmail;
const date = new Date().toLocaleString('en-IN', { dateStyle: 'long' });
const subject = `Rahman Rental Service - Manual Backup - ${date}`;
const body = `Hi,\n\nPlease find attached the data backup file: ${filename}\n\nThis backup was generated from the Rahman Rental Service app. To restore, use the 'Restore From File' option in the app.\n\nThank you.`;
emailLink.href = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
emailLink.classList.remove('disabled');
showToast("File downloaded! You can now proceed to Step 2.");
} else {
showToast("No data to back up.");
}
}
function handleRestore(event) {
const file = event.target.files[0];
if (!file) return;
if (!confirm("Are you sure you want to restore from this file? This will OVERWRITE all current data.")) {
event.target.value = null; return;
}
if (file.name.endsWith('.json')) {
restoreFromJson(file);
} else if (file.name.endsWith('.xlsx')) {
restoreFromExcel(file);
} else {
showToast("Invalid file type. Please select a .json or .xlsx file.");
}
event.target.value = null;
}
function restoreFromJson(file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (data && Array.isArray(data.active) && Array.isArray(data.vacated)) {
saveAllActiveTenants(data.active);
saveAllVacatedTenants(data.vacated);
showToast("Data successfully restored from JSON!");
initializeApp();
} else { showToast("Invalid JSON backup file format."); }
} catch (err) { showToast("Error reading or parsing JSON file."); }
};
reader.readAsText(file);
}
function restoreFromExcel(file) {
if (typeof XLSX === 'undefined') { showToast("Excel library not loaded. Check internet."); return; }
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, {type: 'array', cellDates:true});
const processSheet = (sheetName) => {
const worksheet = workbook.Sheets[sheetName];
if (!worksheet) return [];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
const tenantsMap = new Map();
for (const row of jsonData) {
const key = `${row['Name']}_${row['Room No']}`;
if (!tenantsMap.has(key)) {
tenantsMap.set(key, {
name: row['Name'], roomNo: row['Room No'], phone: row['Phone'], whatsappNumber: row['Whatsapp'], aadhar: row['Aadhar'], fatherName: row["Father's Name"], permanentAddress: row['Permanent Address'],
possessionDate: row['Date of Possession'] instanceof Date ? row['Date of Possession'].toISOString().slice(0,10) : row['Date of Possession'],
monthlyRent: row['Monthly Rent (₹)'], advanceMoney: row['Advance (₹)'], securityAmount: row['Security (₹)'],
lastMeterReading: row['Last Meter Reading'],
vacatedDate: row['Vacated Date'] === 'N/A' ? undefined : (row['Vacated Date'] instanceof Date ? row['Vacated Date'].toISOString().slice(0,10) : row['Vacated Date']),
paymentHistory: []
});
}
if (row['Payment Month'] && row['Payment Month'] !== 'N/A') {
tenantsMap.get(key).paymentHistory.push({
monthYear: row['Payment Month'] instanceof Date ? row['Payment Month'].toISOString().slice(0,7) : row['Payment Month'],
datePaid: row['Payment Date'] instanceof Date ? row['Payment Date'].toISOString().slice(0,10) : row['Payment Date'],
rentPaid: row['Rent Paid (₹)'],
electricityPaid: row['Electricity Paid (₹)']
});
}
}
return Array.from(tenantsMap.values());
};
const activeTenants = processSheet('Active Tenants');
const vacatedTenants = processSheet('Vacated Tenants');
saveAllActiveTenants(activeTenants);
saveAllVacatedTenants(vacatedTenants);
showToast("Data successfully restored from Excel!");
initializeApp();
} catch (err) {
console.error("Excel restore error:", err);
showToast("Error reading or parsing Excel file.");
}
};
reader.readAsArrayBuffer(file);
}
</script>
</body>
</html>