offerpk3 commited on
Commit
0c5073c
·
verified ·
1 Parent(s): d3671b2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +404 -576
index.html CHANGED
@@ -3,326 +3,92 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Device & Loan Management System</title>
7
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
-
12
  :root {
13
- --primary: #4f46e5;
14
- --secondary: #db2777;
15
- --success: #10b981;
16
- --warning: #f59e0b;
17
- --danger: #ef4444;
18
- --info: #3b82f6;
19
- }
20
-
21
- body {
22
- font-family: 'Inter', sans-serif;
23
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
24
- }
25
-
26
- .card {
27
- background: white;
28
- border-radius: 16px;
29
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
30
- transition: all 0.3s ease;
31
- }
32
-
33
- .status-badge {
34
- padding: 4px 12px;
35
- border-radius: 20px;
36
- font-size: 11px;
37
- font-weight: 600;
38
- display: inline-block;
39
- text-transform: uppercase;
40
- letter-spacing: 0.5px;
41
  }
42
-
 
 
43
  .active-badge { background-color: #ecfdf5; color: #065f46; }
44
- .blocked-badge, .default-badge { background-color: #fef2f2; color: #991b1b; }
45
- .paid-badge { background-color: #ecfdf5; color: #065f46; }
46
- .partial-badge { background-color: #fffbeb; color: #92400e; }
47
- .unpaid-badge { background-color: #fef2f2; color: #991b1b; }
48
-
49
- .form-input {
50
- border: 1px solid #e2e8f0;
51
- border-radius: 10px;
52
- padding: 12px 16px;
53
- transition: all 0.3s;
54
- width: 100%;
55
- background-color: #f8fafc;
56
- }
57
-
58
- .form-input:focus {
59
- border-color: var(--primary);
60
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
61
- outline: none;
62
- background-color: white;
63
- }
64
-
65
- .toast {
66
- position: fixed;
67
- bottom: 30px;
68
- right: 30px;
69
- padding: 16px 24px;
70
- border-radius: 12px;
71
- color: white;
72
- font-weight: 500;
73
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
74
- z-index: 1050;
75
- transform: translateY(100px);
76
- opacity: 0;
77
- transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
78
- }
79
-
80
  .toast.show { transform: translateY(0); opacity: 1; }
81
- .toast.success { background: var(--success); }
82
- .toast.error { background: var(--danger); }
83
- .toast.info { background: var(--info); }
84
- .toast.warning { background: var(--warning); }
85
-
86
- .btn {
87
- border-radius: 10px;
88
- padding: 12px 24px;
89
- font-weight: 600;
90
- transition: all 0.3s;
91
- border: none;
92
- cursor: pointer;
93
- display: inline-flex;
94
- align-items: center;
95
- justify-content: center;
96
- }
97
-
98
  .btn-primary { background: var(--primary); color: white; }
99
- .btn-primary:hover { background: #4338ca; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(79, 70, 229, 0.3); }
100
- .btn-secondary { background: var(--secondary); color: white; }
101
- .btn-secondary:hover { background: #be185d; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(219, 39, 119, 0.3); }
102
-
103
- .action-btn {
104
- width: 36px; height: 36px; border-radius: 10px; display: inline-flex;
105
- align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s;
106
- color: #64748b; background: #f1f5f9;
107
- }
108
- .action-btn:hover { transform: scale(1.1); }
109
-
110
- .mac-address {
111
- font-family: 'Courier New', monospace; background: #f1f5f9; padding: 4px 8px;
112
- border-radius: 6px; font-weight: 600; color: #475569;
113
- }
114
-
115
- .modal-overlay {
116
- position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 23, 42, 0.6);
117
- backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center;
118
- z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s;
119
- }
120
-
121
  .modal-overlay.active { opacity: 1; pointer-events: all; }
122
-
123
- .modal-content {
124
- background: white; border-radius: 16px; width: 90%; max-width: 500px; max-height: 90vh;
125
- overflow-y: auto; transform: translateY(20px) scale(0.95); opacity: 0;
126
- transition: all 0.3s ease-out;
127
- }
128
-
129
- .modal-overlay.active .modal-content { transform: translateY(0) scale(1); opacity: 1; }
130
-
131
- .tab-button {
132
- padding: 10px 20px; border-radius: 8px; cursor: pointer; transition: all 0.3s;
133
- font-weight: 600; border: 2px solid transparent;
134
- }
135
-
136
- .tab-button.active {
137
- color: var(--primary); background-color: #eef2ff; border-color: var(--primary);
138
- }
139
  </style>
140
  </head>
141
- <body class="min-h-screen py-8 px-4">
142
- <!-- Toast Container -->
143
  <div id="toast-container" class="fixed bottom-0 right-0 p-8 space-y-3 z-50"></div>
144
-
145
- <!-- Payment Modal -->
146
- <div class="modal-overlay" id="payment-modal">
147
- <div class="modal-content">
148
- <div class="p-6">
149
- <div class="flex justify-between items-center mb-4">
150
- <h3 class="text-xl font-bold text-gray-800">Payment Management</h3>
151
- <button class="action-btn close-payment-modal" title="Close"><i class="fas fa-times"></i></button>
152
- </div>
153
-
154
- <div class="bg-gray-50 rounded-lg p-4 mb-6">
155
- <h4 class="font-medium text-gray-700 mb-2">Entry Details</h4>
156
- <div id="modal-entry-details" class="grid grid-cols-2 gap-x-4 gap-y-2">
157
- <!-- Dynamic content here -->
158
- </div>
159
- </div>
160
-
161
- <div class="grid grid-cols-3 gap-4 mb-6 text-center">
162
- <div class="bg-indigo-50 p-4 rounded-lg">
163
- <p class="text-indigo-500 text-sm">Total Loan</p>
164
- <p id="payment-total" class="text-xl font-bold text-indigo-700">Rs.0</p>
165
- </div>
166
- <div class="bg-green-50 p-4 rounded-lg">
167
- <p class="text-green-500 text-sm">Amount Paid</p>
168
- <p id="payment-paid" class="text-xl font-bold text-green-700">Rs.0</p>
169
- </div>
170
- <div class="bg-red-50 p-4 rounded-lg">
171
- <p class="text-red-500 text-sm">Remaining</p>
172
- <p id="payment-remaining" class="text-xl font-bold text-red-700">Rs.0</p>
173
- </div>
174
- </div>
175
-
176
- <div class="w-full bg-gray-200 rounded-full h-2.5 mb-6">
177
- <div id="payment-progress" class="bg-green-500 h-2.5 rounded-full" style="width: 0%"></div>
178
- </div>
179
-
180
- <div class="mb-6">
181
- <h4 class="font-medium text-gray-700 mb-2">Payment History</h4>
182
- <div class="max-h-48 overflow-y-auto pr-2 bg-gray-50 rounded-lg p-3">
183
- <div id="payment-history" class="space-y-2"></div>
184
- <div id="no-payments" class="py-4 text-center text-gray-500 hidden">
185
- <i class="fas fa-receipt text-3xl mb-2 text-gray-400"></i>
186
- <p>No payment history found</p>
187
- </div>
188
- </div>
189
- </div>
190
-
191
- <form id="add-payment-form" class="flex space-x-3">
192
- <input type="number" id="new-payment-amount" class="form-input flex-1" placeholder="Amount (Rs.)" min="1" required>
193
- <button type="submit" class="btn bg-green-600 hover:bg-green-700 text-white">
194
- <i class="fas fa-plus-circle mr-2"></i> Add
195
- </button>
196
- </form>
197
- </div>
198
- </div>
199
- </div>
200
-
201
  <div class="max-w-7xl mx-auto">
202
- <div class="flex flex-col md:flex-row justify-between items-center mb-8">
203
  <div>
204
- <h1 class="text-3xl md:text-4xl font-bold text-gray-800">Unified Management System</h1>
205
- <p class="text-gray-600 mt-2">Track network device payments and standalone loans.</p>
206
  </div>
 
 
 
207
  </div>
208
-
209
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
210
- <!-- Left Column - Form and Stats -->
211
- <div class="lg:col-span-1 space-y-8">
212
- <div class="card p-6">
213
- <h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center"><i class="fas fa-plus-circle mr-3 text-indigo-500"></i>Add New Entry</h2>
214
- <form id="entry-form" class="space-y-4">
215
- <div class="space-y-2">
216
- <label class="block text-gray-700 mb-1 font-medium text-sm">Entry Type</label>
217
- <div class="flex space-x-4">
218
- <label class="flex items-center space-x-2 cursor-pointer">
219
- <input type="radio" name="entryType" value="device" class="form-radio text-primary focus:ring-primary" checked>
220
- <span>Network Device</span>
221
- </label>
222
- <label class="flex items-center space-x-2 cursor-pointer">
223
- <input type="radio" name="entryType" value="loan" class="form-radio text-secondary focus:ring-secondary">
224
- <span>Standalone Loan</span>
225
- </label>
226
- </div>
227
- </div>
228
-
229
- <div id="device-fields">
230
- <div>
231
- <label for="mac-address" class="block text-gray-700 mb-1 font-medium text-sm">MAC Address</label>
232
- <input type="text" id="mac-address" class="form-input" placeholder="e.g. 00:1A:2B:3C:4D:5E" required>
233
- </div>
234
- <div>
235
- <label for="device-name" class="block text-gray-700 mt-2 mb-1 font-medium text-sm">Device Name</label>
236
- <input type="text" id="device-name" class="form-input" placeholder="e.g. John's iPhone" required>
237
- </div>
238
- </div>
239
-
240
- <div id="loan-fields" class="hidden">
241
- <div>
242
- <label for="loan-purpose" class="block text-gray-700 mb-1 font-medium text-sm">Loan Purpose</label>
243
- <input type="text" id="loan-purpose" class="form-input" placeholder="e.g. Personal Expense, Business Investment">
244
- </div>
245
- </div>
246
 
247
- <div>
248
- <label for="user-name" class="block text-gray-700 mt-2 mb-1 font-medium text-sm">User Name</label>
249
- <input type="text" id="user-name" class="form-input" placeholder="e.g. John Doe" required>
250
- </div>
251
-
252
- <div class="grid grid-cols-2 gap-4">
253
- <div>
254
- <label for="total-amount" class="block text-gray-700 mb-1 font-medium text-sm">Total Loan (Rs.)</label>
255
- <input type="number" id="total-amount" class="form-input" placeholder="e.g. 50000" min="1" required>
256
- </div>
257
- <div>
258
- <label for="initial-payment" class="block text-gray-700 mb-1 font-medium text-sm">Down Payment (Rs.)</label>
259
- <input type="number" id="initial-payment" class="form-input" placeholder="e.g. 10000" min="0">
260
- </div>
261
  </div>
262
-
263
- <button type="submit" id="add-entry-btn" class="btn btn-primary w-full !mt-6">
264
- <i class="fas fa-save mr-2"></i> Add Device
265
  </button>
266
- </form>
267
- </div>
268
-
269
- <div class="card p-6">
270
- <h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center"><i class="fas fa-chart-pie mr-3 text-indigo-500"></i>Overall Financials</h2>
271
- <div class="space-y-3">
272
- <div class="flex justify-between items-center bg-green-50 p-3 rounded-lg">
273
- <span class="font-medium text-green-800">Total Collected</span>
274
- <span class="font-bold text-green-600 text-lg" id="total-revenue">Rs.0</span>
275
- </div>
276
- <div class="flex justify-between items-center bg-red-50 p-3 rounded-lg">
277
- <span class="font-medium text-red-800">Total Outstanding</span>
278
- <span class="font-bold text-red-600 text-lg" id="outstanding-balance">Rs.0</span>
279
- </div>
280
- <div class="flex justify-between items-center bg-gray-100 p-3 rounded-lg">
281
- <span class="font-medium text-gray-800">Total Loan Value</span>
282
- <span class="font-bold text-gray-600 text-lg" id="total-loan-value">Rs.0</span>
283
- </div>
284
  </div>
285
  </div>
286
- </div>
287
-
288
- <!-- Right Column - Table -->
289
- <div class="lg:col-span-2">
290
- <div class="card p-6">
291
- <div class="flex justify-between items-center mb-4 border-b pb-4">
292
- <div class="flex space-x-2">
293
- <button class="tab-button active" data-tab="all">All (<span id="count-all">0</span>)</button>
294
- <button class="tab-button" data-tab="device">Devices (<span id="count-device">0</span>)</button>
295
- <button class="tab-button" data-tab="loan">Loans (<span id="count-loan">0</span>)</button>
296
- </div>
297
- <div class="flex space-x-3 w-full md:w-auto">
298
- <div class="relative w-full md:w-64">
299
- <input type="text" id="search-input" class="form-input pl-10" placeholder="Search...">
300
- <i class="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
301
- </div>
302
- <button id="export-btn" class="btn bg-gray-700 hover:bg-gray-900 text-white" title="Export as CSV">
303
- <i class="fas fa-file-csv mr-2"></i> Export
304
- </button>
305
- </div>
306
- </div>
307
-
308
- <div class="overflow-x-auto">
309
- <table class="w-full text-sm">
310
- <thead>
311
- <tr class="text-left text-gray-500 font-medium border-b-2 border-gray-200">
312
- <th class="p-3">Type</th>
313
- <th class="p-3">User / Details</th>
314
- <th class="p-3">Loan Progress</th>
315
- <th class="p-3">Status</th>
316
- <th class="p-3 text-right">Actions</th>
317
- </tr>
318
- </thead>
319
- <tbody id="entries-table" class="divide-y divide-gray-100"></tbody>
320
- </table>
321
- <div id="empty-state" class="py-16 text-center hidden">
322
- <i class="fas fa-inbox text-5xl text-gray-300 mb-4"></i>
323
- <h3 class="text-lg font-medium text-gray-700">No Entries Found</h3>
324
- <p class="text-gray-500 mt-1">Use the form to add a new device or loan.</p>
325
- </div>
326
  </div>
327
  </div>
328
  </div>
@@ -332,98 +98,106 @@
332
  <script>
333
  document.addEventListener('DOMContentLoaded', () => {
334
  // --- STATE MANAGEMENT ---
335
- let entries = JSON.parse(localStorage.getItem('loanAndDeviceApp_v1')) || [];
336
- let currentPaymentEntry = null;
337
  let activeTab = 'all';
338
 
339
  // --- DOM ELEMENTS ---
340
- const entryForm = document.getElementById('entry-form');
341
  const entriesTable = document.getElementById('entries-table');
342
  const emptyState = document.getElementById('empty-state');
343
- const paymentModal = document.getElementById('payment-modal');
344
  const searchInput = document.getElementById('search-input');
345
  const exportBtn = document.getElementById('export-btn');
346
- const addPaymentForm = document.getElementById('add-payment-form');
347
- const addEntryBtn = document.getElementById('add-entry-btn');
348
-
349
- const deviceFields = document.getElementById('device-fields');
350
- const loanFields = document.getElementById('loan-fields');
351
- const loanPurposeInput = document.getElementById('loan-purpose');
352
- const macAddressInput = document.getElementById('mac-address');
353
- const deviceNameInput = document.getElementById('device-name');
354
-
355
- // --- HELPER FUNCTIONS ---
356
- const saveData = () => localStorage.setItem('loanAndDeviceApp_v1', JSON.stringify(entries));
357
- const formatCurrency = (amount) => `Rs.${amount.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
358
- const formatMAC = (mac) => mac.replace(/[^0-9A-Fa-f]/g, '').toUpperCase().match(/.{1,2}/g)?.join(':') || mac.toUpperCase();
359
-
360
- const showToast = (message, type = 'info') => {
361
- const container = document.getElementById('toast-container');
362
- const toast = document.createElement('div');
363
- const icons = { success: 'fa-check-circle', error: 'fa-times-circle', info: 'fa-info-circle', warning: 'fa-exclamation-triangle' };
364
- toast.className = `toast ${type} flex items-center space-x-3`;
365
- toast.innerHTML = `<i class="fas ${icons[type]}"></i><span>${message}</span>`;
366
- container.prepend(toast);
367
- setTimeout(() => toast.classList.add('show'), 10);
368
- setTimeout(() => {
369
- toast.classList.remove('show');
370
- toast.addEventListener('transitionend', () => toast.remove());
371
- }, 3000);
372
- };
373
-
374
- // --- UI TOGGLE ---
375
- const toggleEntryTypeFields = () => {
376
- const selectedType = document.querySelector('input[name="entryType"]:checked').value;
377
- if (selectedType === 'device') {
378
- deviceFields.classList.remove('hidden');
379
- loanFields.classList.add('hidden');
380
- addEntryBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Add Device';
381
- addEntryBtn.className = 'btn btn-primary w-full !mt-6';
382
- macAddressInput.required = true;
383
- deviceNameInput.required = true;
384
- loanPurposeInput.required = false;
385
- } else {
386
- deviceFields.classList.add('hidden');
387
- loanFields.classList.remove('hidden');
388
- addEntryBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Add Loan';
389
- addEntryBtn.className = 'btn btn-secondary w-full !mt-6';
390
- macAddressInput.required = false;
391
- deviceNameInput.required = false;
392
- loanPurposeInput.required = true;
393
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  };
 
395
 
396
- // --- CORE LOGIC ---
397
- const updateSummariesAndCounts = () => {
398
- let totalRevenue = 0, totalLoanValue = 0;
399
- let countAll = entries.length;
400
- let countDevice = 0, countLoan = 0;
401
-
402
- entries.forEach(e => {
403
- totalRevenue += e.payments.reduce((sum, p) => sum + p.amount, 0);
404
- totalLoanValue += e.totalAmount;
405
- if(e.type === 'device') countDevice++;
406
- else countLoan++;
407
- });
408
-
409
- document.getElementById('total-revenue').textContent = formatCurrency(totalRevenue);
410
- document.getElementById('outstanding-balance').textContent = formatCurrency(Math.max(0, totalLoanValue - totalRevenue));
411
- document.getElementById('total-loan-value').textContent = formatCurrency(totalLoanValue);
412
- document.getElementById('count-all').textContent = countAll;
413
- document.getElementById('count-device').textContent = countDevice;
414
- document.getElementById('count-loan').textContent = countLoan;
415
- };
416
 
 
417
  const renderTable = () => {
418
  const searchTerm = searchInput.value.toLowerCase();
419
  const filteredEntries = entries.filter(e => {
420
  const matchesTab = activeTab === 'all' || e.type === activeTab;
421
- const matchesSearch = !searchTerm || (
422
- (e.userName?.toLowerCase().includes(searchTerm)) ||
423
- (e.type === 'device' && (e.mac.toLowerCase().includes(searchTerm) || e.deviceName.toLowerCase().includes(searchTerm))) ||
424
- (e.type === 'loan' && e.loanPurpose.toLowerCase().includes(searchTerm))
425
- );
426
- return matchesTab && matchesSearch;
427
  });
428
 
429
  entriesTable.innerHTML = '';
@@ -431,265 +205,319 @@
431
 
432
  filteredEntries.forEach(entry => {
433
  const paidAmount = entry.payments.reduce((sum, p) => sum + p.amount, 0);
434
- const progress = Math.min(100, (paidAmount / entry.totalAmount) * 100);
 
435
 
436
  let paymentStatus, paymentStatusClass;
437
  if (paidAmount >= entry.totalAmount) { paymentStatus = 'Paid Off'; paymentStatusClass = 'paid-badge'; }
438
  else if (paidAmount > 0) { paymentStatus = 'Partial'; paymentStatusClass = 'partial-badge'; }
439
  else { paymentStatus = 'Unpaid'; paymentStatusClass = 'unpaid-badge'; }
440
-
441
  const isDevice = entry.type === 'device';
442
- const typeIcon = isDevice ? 'fa-wifi text-primary' : 'fa-hand-holding-usd text-secondary';
443
- const typeText = isDevice ? 'Device' : 'Loan';
444
- const statusClass = entry.status === 'Active' ? 'active-badge' : (isDevice ? 'blocked-badge' : 'default-badge');
445
 
446
  const row = document.createElement('tr');
447
  row.className = 'hover:bg-gray-50';
448
  row.innerHTML = `
449
- <td class="p-3"><div class="flex items-center gap-2 font-semibold"><i class="fas ${typeIcon} fa-lg"></i> ${typeText}</div></td>
450
- <td class="p-3">
451
- <div class="font-bold text-gray-800">${entry.userName}</div>
452
- <div class="text-gray-500">${isDevice ? `<span class="mac-address">${entry.mac}</span> | ${entry.deviceName}` : entry.loanPurpose}</div>
453
- </td>
454
  <td class="p-3">
455
- <div class="w-full bg-gray-200 rounded-full h-2">
456
- <div class="bg-green-500 h-2 rounded-full" style="width: ${progress}%"></div>
 
 
 
 
457
  </div>
458
- <div class="text-xs text-gray-500 mt-1">${formatCurrency(paidAmount)} / ${formatCurrency(entry.totalAmount)}</div>
459
  </td>
460
  <td class="p-3">
461
- <div class="flex flex-col space-y-1">
462
- <span class="status-badge ${statusClass}">${entry.status}</span>
463
- <span class="status-badge ${paymentStatusClass}">${paymentStatus}</span>
 
 
 
464
  </div>
465
  </td>
 
466
  <td class="p-3 text-right">
467
  <div class="inline-flex space-x-2">
468
  <button class="action-btn" data-action="payment" data-id="${entry.id}" title="Manage Payments"><i class="fas fa-money-check-alt"></i></button>
469
- <button class="action-btn" data-action="status" data-id="${entry.id}" title="Toggle Status"><i class="fas fa-power-off"></i></button>
470
  <button class="action-btn" data-action="delete" data-id="${entry.id}" title="Delete Entry"><i class="fas fa-trash"></i></button>
471
  </div>
472
  </td>
473
  `;
474
  entriesTable.appendChild(row);
475
  });
476
- updateSummariesAndCounts();
477
  };
478
-
479
- const addNewEntry = (e) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  e.preventDefault();
481
- const type = document.querySelector('input[name="entryType"]:checked').value;
482
- const totalAmount = parseFloat(document.getElementById('total-amount').value);
483
- const initialPayment = parseFloat(document.getElementById('initial-payment').value) || 0;
484
 
485
- if (initialPayment > totalAmount) {
486
- showToast('Initial payment cannot exceed total loan.', 'error');
487
- return;
488
- }
489
 
490
- const newEntry = {
491
- id: Date.now(),
492
- type: type,
493
- userName: document.getElementById('user-name').value.trim(),
494
- totalAmount: totalAmount,
495
- payments: [],
496
- addedTime: new Date().toISOString(),
497
- status: 'Active'
498
- };
 
 
 
 
 
 
499
 
500
- if (type === 'device') {
501
- newEntry.mac = formatMAC(macAddressInput.value);
502
- newEntry.deviceName = deviceNameInput.value.trim();
503
- if (entries.some(en => en.type === 'device' && en.mac === newEntry.mac)) {
504
- showToast('A device with this MAC address already exists.', 'error');
505
- return;
 
506
  }
507
- } else {
508
- newEntry.loanPurpose = loanPurposeInput.value.trim();
509
  }
510
 
511
- if (initialPayment > 0) {
512
- newEntry.payments.push({ id: Date.now(), date: new Date().toISOString(), amount: initialPayment });
513
- }
514
-
515
- entries.unshift(newEntry);
516
  saveData();
517
  renderTable();
518
- entryForm.reset();
519
- toggleEntryTypeFields();
520
- showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} added successfully!`, 'success');
521
  };
522
 
523
- const openPaymentModal = (id) => {
524
- currentPaymentEntry = entries.find(e => e.id == id);
525
- if (!currentPaymentEntry) return;
 
 
 
 
526
 
527
- const paidAmount = currentPaymentEntry.payments.reduce((sum, p) => sum + p.amount, 0);
528
- const remaining = currentPaymentEntry.totalAmount - paidAmount;
529
- const progress = (paidAmount / currentPaymentEntry.totalAmount) * 100;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
 
531
- const detailsContainer = document.getElementById('modal-entry-details');
532
- const isDevice = currentPaymentEntry.type === 'device';
533
- detailsContainer.innerHTML = `
534
- <div><p class="text-gray-500 text-sm">User Name</p><p class="font-medium text-gray-800">${currentPaymentEntry.userName}</p></div>
535
- <div><p class="text-gray-500 text-sm">Status</p><div class="status-badge ${currentPaymentEntry.status === 'Active' ? 'active-badge' : 'blocked-badge'}">${currentPaymentEntry.status}</div></div>
536
- <div><p class="text-gray-500 text-sm">${isDevice ? 'Device Name' : 'Loan Purpose'}</p><p class="font-medium text-gray-800">${isDevice ? currentPaymentEntry.deviceName : currentPaymentEntry.loanPurpose}</p></div>
537
- ${isDevice ? `<div><p class="text-gray-500 text-sm">MAC Address</p><p class="font-medium text-gray-800 mac-address">${currentPaymentEntry.mac}</p></div>` : ''}
538
- `;
539
-
540
- document.getElementById('payment-total').textContent = formatCurrency(currentPaymentEntry.totalAmount);
541
- document.getElementById('payment-paid').textContent = formatCurrency(paidAmount);
542
- document.getElementById('payment-remaining').textContent = formatCurrency(Math.max(0, remaining));
543
- document.getElementById('payment-progress').style.width = `${progress}%`;
544
 
545
- const historyContainer = document.getElementById('payment-history');
546
- const noPaymentsEl = document.getElementById('no-payments');
547
- historyContainer.innerHTML = '';
548
- noPaymentsEl.classList.toggle('hidden', currentPaymentEntry.payments.length > 0);
 
 
549
 
550
- [...currentPaymentEntry.payments].reverse().forEach(p => {
551
- const item = document.createElement('div');
552
- item.className = 'payment-item flex justify-between items-center';
553
- item.innerHTML = `
554
- <div>
555
- <div class="font-bold text-green-700">${formatCurrency(p.amount)}</div>
556
- <div class="text-gray-500 text-xs">${new Date(p.date).toLocaleString()}</div>
557
- </div>
558
- <button class="text-red-500 hover:text-red-700 text-xs" data-action="delete-payment" data-id="${p.id}" title="Delete Payment"><i class="fas fa-trash-alt"></i></button>
559
- `;
560
- historyContainer.appendChild(item);
561
- });
562
- paymentModal.classList.add('active');
563
- };
 
 
564
 
565
- const closePaymentModal = () => {
566
- paymentModal.classList.remove('active');
567
- currentPaymentEntry = null;
568
- document.getElementById('new-payment-amount').value = '';
569
- };
 
 
 
 
570
 
571
- const handleTableClick = (e) => {
572
- const button = e.target.closest('button[data-action]');
573
- if (!button) return;
574
- const { action, id } = button.dataset;
575
- if (action === 'payment') openPaymentModal(id);
576
- if (action === 'status') toggleStatus(id);
577
- if (action === 'delete') deleteEntry(id);
578
- };
579
-
580
- const handleModalEvents = (e) => {
581
- if (e.target.classList.contains('modal-overlay') || e.target.closest('.close-payment-modal')) {
582
- closePaymentModal();
583
- }
584
- const deleteBtn = e.target.closest('button[data-action="delete-payment"]');
585
- if(deleteBtn) {
586
- if(confirm('Delete this payment record? This cannot be undone.')) {
587
- deletePayment(deleteBtn.dataset.id);
588
- }
589
- }
590
- };
591
-
592
- const deletePayment = (paymentId) => {
593
- if (!currentPaymentEntry) return;
594
- currentPaymentEntry.payments = currentPaymentEntry.payments.filter(p => p.id != paymentId);
595
- saveData();
596
- openPaymentModal(currentPaymentEntry.id); // Refresh modal
597
- renderTable();
598
- showToast('Payment deleted.', 'info');
599
- }
600
 
601
- const addNewPayment = (e) => {
602
- e.preventDefault();
603
- const amountInput = document.getElementById('new-payment-amount');
604
- const amount = parseFloat(amountInput.value);
605
- const paidAmount = currentPaymentEntry.payments.reduce((sum, p) => sum + p.amount, 0);
606
- const remaining = currentPaymentEntry.totalAmount - paidAmount;
607
 
608
- if (isNaN(amount) || amount <= 0) return showToast('Enter a valid positive amount.', 'error');
609
- if (amount > remaining) return showToast(`Payment exceeds remaining amount of ${formatCurrency(remaining)}.`, 'warning');
610
-
611
- currentPaymentEntry.payments.push({ id: Date.now(), date: new Date().toISOString(), amount });
612
- saveData();
613
- openPaymentModal(currentPaymentEntry.id);
614
- renderTable();
615
- amountInput.value = '';
616
- showToast('Payment added successfully!', 'success');
617
- };
618
 
619
- const toggleStatus = (id) => {
620
- const entry = entries.find(e => e.id == id);
621
- if (!entry) return;
622
- const oldStatus = entry.status;
623
- entry.status = entry.status === 'Active' ? (entry.type === 'device' ? 'Blocked' : 'On-Hold') : 'Active';
624
- saveData();
625
- renderTable();
626
- showToast(`${entry.type} status changed from ${oldStatus} to ${entry.status}.`, 'info');
627
- };
 
 
 
 
 
 
 
 
 
628
 
629
- const deleteEntry = (id) => {
630
- if (confirm('Delete this entry and all its payment history? This is irreversible.')) {
631
- entries = entries.filter(e => e.id != id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  saveData();
 
633
  renderTable();
634
- showToast('Entry deleted successfully.', 'success');
635
  }
636
  };
637
-
638
- const exportToCSV = () => {
639
- if (entries.length === 0) return showToast('No entries to export.', 'warning');
640
-
641
- let csvContent = "data:text/csv;charset=utf-8,Type,User Name,MAC Address,Device Name,Loan Purpose,Total Amount (Rs),Paid Amount (Rs),Remaining Amount (Rs),Status,Date Added\n";
642
-
643
- entries.forEach(e => {
644
- const paidAmount = e.payments.reduce((sum, p) => sum + p.amount, 0);
645
- const remaining = e.totalAmount - paidAmount;
646
- const row = [
647
- e.type, `"${e.userName}"`, e.mac || '', `"${e.deviceName || ''}"`, `"${e.loanPurpose || ''}"`,
648
- e.totalAmount, paidAmount, remaining, e.status, new Date(e.addedTime).toLocaleDateString()
649
- ].join(',');
650
- csvContent += row + "\n";
651
- });
652
-
653
- const link = document.createElement("a");
654
- link.setAttribute("href", encodeURI(csvContent));
655
- link.setAttribute("download", "system_export.csv");
656
- document.body.appendChild(link);
657
- link.click();
658
- document.body.removeChild(link);
659
  };
660
-
661
- // --- EVENT LISTENERS ---
662
- document.querySelectorAll('input[name="entryType"]').forEach(radio => radio.addEventListener('change', toggleEntryTypeFields));
663
- entryForm.addEventListener('submit', addNewEntry);
664
- addPaymentForm.addEventListener('submit', addNewPayment);
665
- entriesTable.addEventListener('click', handleTableClick);
666
- paymentModal.addEventListener('click', handleModalEvents);
667
- searchInput.addEventListener('input', renderTable);
668
- exportBtn.addEventListener('click', exportToCSV);
669
- document.querySelector('.card .flex.space-x-2').addEventListener('click', (e) => {
670
- const button = e.target.closest('.tab-button');
671
- if (!button) return;
672
- document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
673
- button.classList.add('active');
674
- activeTab = button.dataset.tab;
675
- renderTable();
676
- });
677
-
678
- // --- INITIALIZATION ---
679
- const initializeSampleData = () => {
680
- if (entries.length > 0) return;
681
  entries = [
682
- { id: 1, type: 'device', mac: 'AA:BB:CC:11:22:33', deviceName: 'Office Router', userName: 'Ali Khan', totalAmount: 15000, payments: [ { id: 101, date: new Date().toISOString(), amount: 5000 } ], addedTime: new Date().toISOString(), status: 'Active' },
683
- { id: 2, type: 'loan', loanPurpose: 'Emergency Medical', userName: 'Fatima Jilani', totalAmount: 550000, payments: [ { id: 201, date: new Date().toISOString(), amount: 100000 } ], addedTime: new Date().toISOString(), status: 'Active' },
684
- { id: 3, type: 'device', mac: '11:22:33:AA:BB:CC', deviceName: 'Guest Wifi AP', userName: 'Guest Access', totalAmount: 8000, payments: [], addedTime: new Date().toISOString(), status: 'Blocked' }
685
  ];
686
  saveData();
687
- showToast('Welcome! Sample data has been loaded.', 'info');
688
  };
689
 
690
  initializeSampleData();
691
  renderTable();
692
- toggleEntryTypeFields();
693
  });
694
  </script>
695
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Loan & Device Management System</title>
7
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
 
11
  :root {
12
+ --primary: #4f46e5; --secondary: #db2777; --success: #10b981;
13
+ --warning: #f59e0b; --danger: #ef4444; --info: #3b82f6;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
+ body { font-family: 'Inter', sans-serif; background: #f1f5f9; }
16
+ .card { background: white; border-radius: 16px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); }
17
+ .status-badge { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
18
  .active-badge { background-color: #ecfdf5; color: #065f46; }
19
+ .blocked-badge, .default-badge, .on-hold-badge { background-color: #fef2f2; color: #991b1b; }
20
+ .paid-badge { background-color: #dcfce7; color: #166534; }
21
+ .partial-badge { background-color: #fefce8; color: #854d0e; }
22
+ .unpaid-badge { background-color: #fee2e2; color: #991b1b; }
23
+ .form-input { border: 1px solid #e2e8f0; border-radius: 10px; padding: 10px 14px; width: 100%; background-color: #f8fafc; }
24
+ .form-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15); outline: none; }
25
+ .toast { position: fixed; bottom: 30px; right: 30px; padding: 16px 24px; border-radius: 12px; color: white; font-weight: 500; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); z-index: 1050; transform: translateY(120px); opacity: 0; transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  .toast.show { transform: translateY(0); opacity: 1; }
27
+ .toast.success { background: var(--success); } .toast.error { background: var(--danger); }
28
+ .toast.info { background: var(--info); } .toast.warning { background: var(--warning); }
29
+ .btn { border-radius: 10px; padding: 12px 24px; font-weight: 600; transition: all 0.3s; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  .btn-primary { background: var(--primary); color: white; }
31
+ .btn-primary:hover { background: #4338ca; }
32
+ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 23, 42, 0.6); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  .modal-overlay.active { opacity: 1; pointer-events: all; }
34
+ .modal-content { background: white; border-radius: 16px; width: 95%; max-width: 600px; max-height: 90vh; overflow-y: auto; transform: scale(0.95); opacity: 0; transition: all 0.3s ease-out; }
35
+ .modal-overlay.active .modal-content { transform: scale(1); opacity: 1; }
36
+ .tab-button { padding: 10px 20px; border-radius: 8px; font-weight: 600; border: 2px solid transparent; }
37
+ .tab-button.active { color: var(--primary); background-color: #eef2ff; }
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  </style>
39
  </head>
40
+ <body class="min-h-screen p-4 md:p-8">
 
41
  <div id="toast-container" class="fixed bottom-0 right-0 p-8 space-y-3 z-50"></div>
42
+
43
+ <!-- Modals Container -->
44
+ <div id="modal-container"></div>
45
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  <div class="max-w-7xl mx-auto">
47
+ <div class="flex flex-col md:flex-row justify-between items-center mb-6">
48
  <div>
49
+ <h1 class="text-3xl font-bold text-gray-800">Advanced Management System</h1>
50
+ <p class="text-gray-600 mt-1">Manage Loans, Devices, Payments, and Documents.</p>
51
  </div>
52
+ <button id="open-add-modal-btn" class="btn btn-primary mt-4 md:mt-0">
53
+ <i class="fas fa-plus-circle mr-2"></i> Add New Entry
54
+ </button>
55
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ <div class="grid grid-cols-1">
58
+ <div class="card p-4 sm:p-6">
59
+ <div class="flex flex-col md:flex-row justify-between items-center mb-4 border-b pb-4 gap-4">
60
+ <div class="flex-wrap flex space-x-2">
61
+ <button class="tab-button active" data-tab="all">All (<span id="count-all">0</span>)</button>
62
+ <button class="tab-button" data-tab="device">Devices (<span id="count-device">0</span>)</button>
63
+ <button class="tab-button" data-tab="loan">Loans (<span id="count-loan">0</span>)</button>
64
+ </div>
65
+ <div class="flex space-x-3 w-full md:w-auto">
66
+ <div class="relative flex-grow">
67
+ <input type="text" id="search-input" class="form-input pl-10 w-full" placeholder="Search...">
68
+ <i class="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
 
 
69
  </div>
70
+ <button id="export-btn" class="btn bg-gray-700 hover:bg-gray-800 text-white" title="Export as CSV">
71
+ <i class="fas fa-file-csv"></i>
 
72
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  </div>
74
  </div>
75
+
76
+ <div class="overflow-x-auto">
77
+ <table class="w-full text-sm">
78
+ <thead>
79
+ <tr class="text-left text-gray-500 font-medium border-b-2 border-gray-200">
80
+ <th class="p-3">User / Details</th>
81
+ <th class="p-3">Loan Progress</th>
82
+ <th class="p-3">Status</th>
83
+ <th class="p-3 text-right">Actions</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody id="entries-table" class="divide-y divide-gray-100"></tbody>
87
+ </table>
88
+ <div id="empty-state" class="py-16 text-center hidden">
89
+ <i class="fas fa-inbox text-5xl text-gray-300 mb-4"></i>
90
+ <h3 class="text-lg font-medium text-gray-700">No Entries Found</h3>
91
+ <p class="text-gray-500 mt-1">Click "Add New Entry" to get started.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  </div>
93
  </div>
94
  </div>
 
98
  <script>
99
  document.addEventListener('DOMContentLoaded', () => {
100
  // --- STATE MANAGEMENT ---
101
+ let entries = JSON.parse(localStorage.getItem('loanAndDeviceApp_v2')) || [];
 
102
  let activeTab = 'all';
103
 
104
  // --- DOM ELEMENTS ---
 
105
  const entriesTable = document.getElementById('entries-table');
106
  const emptyState = document.getElementById('empty-state');
 
107
  const searchInput = document.getElementById('search-input');
108
  const exportBtn = document.getElementById('export-btn');
109
+ const openAddModalBtn = document.getElementById('open-add-modal-btn');
110
+ const modalContainer = document.getElementById('modal-container');
111
+
112
+ // --- TEMPLATES ---
113
+ const modalTemplate = (type, entry = {}) => {
114
+ const isEdit = type === 'edit';
115
+ const title = isEdit ? 'Edit Entry' : 'Add New Entry';
116
+ const btnText = isEdit ? 'Save Changes' : 'Add Entry';
117
+ const formId = isEdit ? 'edit-form' : 'add-form';
118
+ const entryType = entry.type || 'device';
119
+
120
+ return `
121
+ <div class="modal-overlay active" id="${formId}-modal-overlay">
122
+ <div class="modal-content">
123
+ <form id="${formId}" data-id="${entry.id || ''}">
124
+ <div class="p-6">
125
+ <div class="flex justify-between items-center mb-6">
126
+ <h3 class="text-xl font-bold text-gray-800">${title}</h3>
127
+ <button type="button" class="action-btn-close"><i class="fas fa-times"></i></button>
128
+ </div>
129
+ <div class="space-y-4">
130
+ <!-- Common Fields -->
131
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
132
+ <div>
133
+ <label class="font-medium text-sm">Entry Type</label>
134
+ <select name="type" class="form-input mt-1" ${isEdit ? 'disabled' : ''}>
135
+ <option value="device" ${entryType === 'device' ? 'selected' : ''}>Network Device</option>
136
+ <option value="loan" ${entryType === 'loan' ? 'selected' : ''}>Standalone Loan</option>
137
+ </select>
138
+ </div>
139
+ <div>
140
+ <label for="userName" class="font-medium text-sm">User Name</label>
141
+ <input type="text" name="userName" class="form-input mt-1" value="${entry.userName || ''}" required>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Device Specific -->
146
+ <div class="form-section device-fields grid grid-cols-1 md:grid-cols-2 gap-4">
147
+ <div><label class="font-medium text-sm">MAC Address</label><input type="text" name="mac" class="form-input mt-1" value="${entry.mac || ''}"></div>
148
+ <div><label class="font-medium text-sm">Device Name</label><input type="text" name="deviceName" class="form-input mt-1" value="${entry.deviceName || ''}"></div>
149
+ </div>
150
+
151
+ <!-- Loan Specific -->
152
+ <div class="form-section loan-fields space-y-4">
153
+ <div><label class="font-medium text-sm">Loan Purpose</label><input type="text" name="loanPurpose" class="form-input mt-1" value="${entry.loanPurpose || ''}"></div>
154
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
155
+ <div><label class="font-medium text-sm">Phone (Optional)</label><input type="tel" name="phone" class="form-input mt-1" value="${entry.phone || ''}"></div>
156
+ <div><label class="font-medium text-sm">Address (Optional)</label><input type="text" name="address" class="form-input mt-1" value="${entry.address || ''}"></div>
157
+ </div>
158
+ <div><label class="font-medium text-sm">Document (Optional)</label><input type="file" name="document" class="form-input mt-1"></div>
159
+ </div>
160
+
161
+ <!-- Financials -->
162
+ <div class="border-t pt-4 space-y-4">
163
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
164
+ <div><label class="font-medium text-sm">Total Loan (Rs.)</label><input type="number" name="totalAmount" class="form-input mt-1" value="${entry.totalAmount || ''}" required min="1"></div>
165
+ <div><label class="font-medium text-sm">${isEdit ? 'Current Down Payment' : 'Down Payment'} (Rs.)</label><input type="number" name="initialPayment" class="form-input mt-1" value="${isEdit ? (entry.payments.find(p => p.isInitial)?.amount || 0) : ''}" min="0" ${isEdit ? 'disabled' : ''}></div>
166
+ </div>
167
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
168
+ <div><label class="font-medium text-sm">Installment Day (1-31)</label><input type="number" name="installmentDay" class="form-input mt-1" min="1" max="31" value="${entry.installmentDay || ''}"></div>
169
+ <div><label class="font-medium text-sm">Installment Amount (Rs.)</label><input type="number" name="installmentAmount" class="form-input mt-1" min="1" value="${entry.installmentAmount || ''}"></div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ <div class="p-4 bg-gray-50 text-right"><button type="submit" class="btn btn-primary">${btnText}</button></div>
175
+ </form>
176
+ </div>
177
+ </div>`;
178
  };
179
+ const paymentModalTemplate = (entry) => { /* ... see implementation below ... */ };
180
 
181
+ // --- HELPER FUNCTIONS ---
182
+ const saveData = () => localStorage.setItem('loanAndDeviceApp_v2', JSON.stringify(entries));
183
+ const formatCurrency = (amount) => `Rs.${(amount || 0).toLocaleString('en-IN')}`;
184
+ const showToast = (message, type = 'info') => { /* implementation unchanged */ };
185
+
186
+ const getFileAsBase64 = (file) => new Promise((resolve) => {
187
+ if (!file) { resolve(null); return; }
188
+ const reader = new FileReader();
189
+ reader.readAsDataURL(file);
190
+ reader.onload = () => resolve({ name: file.name, data: reader.result });
191
+ });
 
 
 
 
 
 
 
 
 
192
 
193
+ // --- RENDER & UI LOGIC ---
194
  const renderTable = () => {
195
  const searchTerm = searchInput.value.toLowerCase();
196
  const filteredEntries = entries.filter(e => {
197
  const matchesTab = activeTab === 'all' || e.type === activeTab;
198
+ if (!matchesTab) return false;
199
+ const searchCorpus = [e.userName, e.mac, e.deviceName, e.loanPurpose, e.phone, e.address].join(' ').toLowerCase();
200
+ return !searchTerm || searchCorpus.includes(searchTerm);
 
 
 
201
  });
202
 
203
  entriesTable.innerHTML = '';
 
205
 
206
  filteredEntries.forEach(entry => {
207
  const paidAmount = entry.payments.reduce((sum, p) => sum + p.amount, 0);
208
+ const remainingAmount = entry.totalAmount - paidAmount;
209
+ const paidPercent = (paidAmount / entry.totalAmount) * 100;
210
 
211
  let paymentStatus, paymentStatusClass;
212
  if (paidAmount >= entry.totalAmount) { paymentStatus = 'Paid Off'; paymentStatusClass = 'paid-badge'; }
213
  else if (paidAmount > 0) { paymentStatus = 'Partial'; paymentStatusClass = 'partial-badge'; }
214
  else { paymentStatus = 'Unpaid'; paymentStatusClass = 'unpaid-badge'; }
215
+
216
  const isDevice = entry.type === 'device';
217
+ const statusClass = `status-badge ${entry.status.toLowerCase().replace(' ','-')}-badge`;
 
 
218
 
219
  const row = document.createElement('tr');
220
  row.className = 'hover:bg-gray-50';
221
  row.innerHTML = `
 
 
 
 
 
222
  <td class="p-3">
223
+ <div class="flex items-center gap-3">
224
+ <i class="fas ${isDevice ? 'fa-wifi text-primary' : 'fa-hand-holding-usd text-secondary'} fa-lg"></i>
225
+ <div>
226
+ <div class="font-bold text-gray-800">${entry.userName}</div>
227
+ <div class="text-gray-500">${isDevice ? entry.mac : entry.loanPurpose}</div>
228
+ </div>
229
  </div>
 
230
  </td>
231
  <td class="p-3">
232
+ <div class="w-full bg-gray-200 rounded-full h-2.5">
233
+ <div class="bg-green-500 h-2.5 rounded-full" style="width: ${paidPercent}%"></div>
234
+ </div>
235
+ <div class="text-xs text-gray-600 mt-1 flex justify-between">
236
+ <span class="text-green-600 font-medium">Paid: ${paidPercent.toFixed(0)}%</span>
237
+ <span class="text-red-600 font-medium">Due: ${(100 - paidPercent).toFixed(0)}%</span>
238
  </div>
239
  </td>
240
+ <td class="p-3"><span class="${statusClass}">${entry.status}</span></td>
241
  <td class="p-3 text-right">
242
  <div class="inline-flex space-x-2">
243
  <button class="action-btn" data-action="payment" data-id="${entry.id}" title="Manage Payments"><i class="fas fa-money-check-alt"></i></button>
244
+ <button class="action-btn" data-action="edit" data-id="${entry.id}" title="Edit Entry"><i class="fas fa-edit"></i></button>
245
  <button class="action-btn" data-action="delete" data-id="${entry.id}" title="Delete Entry"><i class="fas fa-trash"></i></button>
246
  </div>
247
  </td>
248
  `;
249
  entriesTable.appendChild(row);
250
  });
251
+ updateCounts();
252
  };
253
+ const updateCounts = () => { /* updates counts in tabs */ };
254
+
255
+ // --- MODAL HANDLING ---
256
+ const openModal = (html) => {
257
+ closeAllModals();
258
+ modalContainer.innerHTML = html;
259
+ // Add dynamic form logic
260
+ const form = modalContainer.querySelector('form');
261
+ if(form) {
262
+ const typeSelect = form.querySelector('select[name="type"]');
263
+ const toggle = () => {
264
+ const isDevice = typeSelect.value === 'device';
265
+ form.querySelector('.device-fields').style.display = isDevice ? 'grid' : 'none';
266
+ form.querySelector('.loan-fields').style.display = isDevice ? 'none' : 'block';
267
+ };
268
+ typeSelect.addEventListener('change', toggle);
269
+ toggle();
270
+ }
271
+ };
272
+ const closeAllModals = () => modalContainer.innerHTML = '';
273
+
274
+ const openAddModal = () => openModal(modalTemplate('add'));
275
+ const openEditModal = (id) => {
276
+ const entry = entries.find(e => e.id == id);
277
+ openModal(modalTemplate('edit', entry));
278
+ };
279
+ const openPaymentModal = (id) => { /* implementation below */ };
280
+
281
+ // --- CRUD & ACTIONS ---
282
+ const handleFormSubmit = async (e) => {
283
  e.preventDefault();
284
+ const form = e.target;
285
+ const isEdit = form.id === 'edit-form';
286
+ const id = isEdit ? form.dataset.id : Date.now();
287
 
288
+ const formData = new FormData(form);
289
+ const data = Object.fromEntries(formData.entries());
 
 
290
 
291
+ let entry = isEdit ? entries.find(e => e.id == id) : { id, payments: [], documents: [] };
292
+
293
+ Object.assign(entry, {
294
+ type: data.type,
295
+ userName: data.userName,
296
+ totalAmount: parseFloat(data.totalAmount),
297
+ mac: data.mac || null,
298
+ deviceName: data.deviceName || null,
299
+ loanPurpose: data.loanPurpose || null,
300
+ phone: data.phone || null,
301
+ address: data.address || null,
302
+ installmentDay: data.installmentDay ? parseInt(data.installmentDay) : null,
303
+ installmentAmount: data.installmentAmount ? parseFloat(data.installmentAmount) : null,
304
+ status: entry.status || 'Active'
305
+ });
306
 
307
+ const doc = await getFileAsBase64(data.document);
308
+ if (doc) entry.documents = [doc]; // Simple overwrite for now
309
+
310
+ if (!isEdit) {
311
+ const initialPayment = parseFloat(data.initialPayment) || 0;
312
+ if (initialPayment > 0) {
313
+ entry.payments.push({ id: Date.now(), date: new Date().toISOString(), amount: initialPayment, isInitial: true });
314
  }
315
+ entry.addedTime = new Date().toISOString();
316
+ entries.unshift(entry);
317
  }
318
 
 
 
 
 
 
319
  saveData();
320
  renderTable();
321
+ closeAllModals();
322
+ showToast(`Entry ${isEdit ? 'updated' : 'added'} successfully!`, 'success');
 
323
  };
324
 
325
+ const deleteEntry = (id) => {
326
+ if (confirm('Delete this entry and all its history? This is irreversible.')) {
327
+ entries = entries.filter(e => e.id != id);
328
+ saveData();
329
+ renderTable();
330
+ }
331
+ };
332
 
333
+ // --- EVENT LISTENERS ---
334
+ openAddModalBtn.addEventListener('click', openAddModal);
335
+ document.addEventListener('click', (e) => {
336
+ // Modal closing
337
+ if (e.target.matches('.modal-overlay, .action-btn-close, .action-btn-close i')) {
338
+ closeAllModals();
339
+ }
340
+ // Table actions
341
+ const actionBtn = e.target.closest('button[data-action]');
342
+ if (actionBtn && actionBtn.closest('tbody')) {
343
+ const { action, id } = actionBtn.dataset;
344
+ if (action === 'edit') openEditModal(id);
345
+ if (action === 'delete') deleteEntry(id);
346
+ if (action === 'payment') openPaymentModal(id);
347
+ }
348
+ // Tab switching
349
+ const tabBtn = e.target.closest('.tab-button');
350
+ if (tabBtn) {
351
+ document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
352
+ tabBtn.classList.add('active');
353
+ activeTab = tabBtn.dataset.tab;
354
+ renderTable();
355
+ }
356
+ });
357
+ document.addEventListener('submit', (e) => {
358
+ if (e.target.matches('#add-form, #edit-form')) {
359
+ handleFormSubmit(e);
360
+ }
361
+ // Add payment form is handled inside its modal logic
362
+ });
363
 
364
+ // --- INITIALIZATION ---
365
+ const initializeSampleData = () => { /* ... */ };
366
+ renderTable();
 
 
 
 
 
 
 
 
 
 
367
 
368
+ // --- FULL IMPLEMENTATION OF COMPLEX FUNCTIONS ---
369
+ // (to keep the above structure clean)
370
+ const paymentModalLogic = {
371
+ open: (id) => {
372
+ const entry = entries.find(e => e.id == id);
373
+ if (!entry) return;
374
 
375
+ const paidAmount = entry.payments.reduce((sum, p) => sum + p.amount, 0);
376
+ const remaining = entry.totalAmount - paidAmount;
377
+ const progress = (paidAmount / entry.totalAmount) * 100;
378
+
379
+ // Installment calculations
380
+ let nextPaymentDateStr = 'N/A';
381
+ let totalInstallments = 'N/A';
382
+ if (entry.installmentDay && entry.installmentAmount > 0) {
383
+ totalInstallments = Math.ceil(entry.totalAmount / entry.installmentAmount);
384
+ const today = new Date();
385
+ let nextDate = new Date(today.getFullYear(), today.getMonth(), entry.installmentDay);
386
+ if (today.getDate() > entry.installmentDay) {
387
+ nextDate.setMonth(nextDate.getMonth() + 1);
388
+ }
389
+ nextPaymentDateStr = nextDate.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric'});
390
+ }
391
 
392
+ // Render documents
393
+ let docsHtml = '<p class="text-sm text-gray-500">No documents attached.</p>';
394
+ if (entry.documents && entry.documents.length > 0) {
395
+ docsHtml = entry.documents.map(doc =>
396
+ `<a href="${doc.data}" download="${doc.name}" class="text-indigo-600 hover:underline flex items-center gap-2">
397
+ <i class="fas fa-file-alt"></i> ${doc.name}
398
+ </a>`
399
+ ).join('');
400
+ }
401
 
402
+ // Build modal HTML
403
+ const modalHtml = `
404
+ <div class="modal-overlay active" id="payment-modal-overlay">
405
+ <div class="modal-content">
406
+ <div class="p-6 space-y-4">
407
+ <div class="flex justify-between items-center">
408
+ <h3 class="text-xl font-bold text-gray-800">Payment Details</h3>
409
+ <button type="button" class="action-btn-close"><i class="fas fa-times"></i></button>
410
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
+ <div class="grid grid-cols-2 gap-4 text-sm p-4 bg-gray-50 rounded-lg">
413
+ <div><p class="text-gray-500">User</p><p class="font-semibold">${entry.userName}</p></div>
414
+ ${entry.phone ? `<div><p class="text-gray-500">Phone</p><p class="font-semibold">${entry.phone}</p></div>` : ''}
415
+ ${entry.address ? `<div class="col-span-2"><p class="text-gray-500">Address</p><p class="font-semibold">${entry.address}</p></div>` : ''}
416
+ </div>
 
417
 
418
+ <div class="grid grid-cols-3 gap-4 text-center">
419
+ <div class="p-2 rounded-lg bg-indigo-50"><p class="text-sm">Total Loan</p><p class="font-bold">${formatCurrency(entry.totalAmount)}</p></div>
420
+ <div class="p-2 rounded-lg bg-green-50"><p class="text-sm">Paid</p><p class="font-bold text-green-600">${formatCurrency(paidAmount)}</p></div>
421
+ <div class="p-2 rounded-lg bg-red-50"><p class="text-sm">Remaining</p><p class="font-bold text-red-600">${formatCurrency(remaining)}</p></div>
422
+ </div>
 
 
 
 
 
423
 
424
+ <div class="border-t pt-4 space-y-3">
425
+ <h4 class="font-semibold">Installment Details</h4>
426
+ <div class="grid grid-cols-3 gap-4 text-sm text-center">
427
+ <div><p class="text-gray-500">Next Payment</p><p class="font-semibold">${nextPaymentDateStr}</p></div>
428
+ <div><p class="text-gray-500">Installment</p><p class="font-semibold">${formatCurrency(entry.installmentAmount)}</p></div>
429
+ <div><p class="text-gray-500">Total Installments</p><p class="font-semibold">${totalInstallments}</p></div>
430
+ </div>
431
+ </div>
432
+
433
+ <div class="border-t pt-4 space-y-2">
434
+ <h4 class="font-semibold">Documents</h4>
435
+ ${docsHtml}
436
+ </div>
437
+
438
+ <div class="border-t pt-4">
439
+ <h4 class="font-semibold mb-2">Payment History</h4>
440
+ <div class="max-h-32 overflow-y-auto pr-2" id="payment-history-list"></div>
441
+ </div>
442
 
443
+ <form id="add-payment-form" data-id="${id}" class="flex gap-3 pt-4 border-t">
444
+ <input type="number" name="amount" class="form-input flex-grow" placeholder="Amount (Rs.)" required>
445
+ <button type="submit" class="btn bg-green-600 text-white hover:bg-green-700">Add</button>
446
+ </form>
447
+ </div>
448
+ </div>
449
+ </div>`;
450
+ openModal(modalHtml);
451
+ this.renderHistory(entry);
452
+
453
+ // Add submit listener for this specific form
454
+ document.getElementById('add-payment-form').addEventListener('submit', this.addPayment);
455
+ },
456
+ renderHistory: (entry) => {
457
+ const list = document.getElementById('payment-history-list');
458
+ list.innerHTML = '';
459
+ if (entry.payments.length === 0) {
460
+ list.innerHTML = '<p class="text-sm text-gray-500">No payments recorded.</p>';
461
+ return;
462
+ }
463
+ [...entry.payments].reverse().forEach(p => {
464
+ const item = document.createElement('div');
465
+ item.className = 'text-sm flex justify-between items-center p-2 rounded hover:bg-gray-100';
466
+ item.innerHTML = `
467
+ <div>
468
+ <p class="font-semibold">${formatCurrency(p.amount)}</p>
469
+ <p class="text-xs text-gray-500">${new Date(p.date).toLocaleString()}</p>
470
+ </div>
471
+ ${p.isInitial ? '<span class="text-xs font-bold text-blue-600">DOWN PAYMENT</span>' : ''}
472
+ `;
473
+ list.appendChild(item);
474
+ });
475
+ },
476
+ addPayment: (e) => {
477
+ e.preventDefault();
478
+ const form = e.target;
479
+ const id = form.dataset.id;
480
+ const entry = entries.find(e => e.id == id);
481
+ const amount = parseFloat(form.elements.amount.value);
482
+
483
+ if (!entry || isNaN(amount) || amount <= 0) return;
484
+
485
+ entry.payments.push({ id: Date.now(), date: new Date().toISOString(), amount });
486
  saveData();
487
+ this.open(id); // Re-open the modal to refresh it
488
  renderTable();
 
489
  }
490
  };
491
+ openPaymentModal = paymentModalLogic.open.bind(paymentModalLogic);
492
+ updateCounts = () => {
493
+ document.getElementById('count-all').textContent = entries.length;
494
+ document.getElementById('count-device').textContent = entries.filter(e => e.type === 'device').length;
495
+ document.getElementById('count-loan').textContent = entries.filter(e => e.type === 'loan').length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  };
497
+ showToast = (message, type = 'info') => {
498
+ const container = document.getElementById('toast-container');
499
+ const toast = document.createElement('div');
500
+ const icons = { success: 'fa-check-circle', error: 'fa-times-circle', info: 'fa-info-circle', warning: 'fa-exclamation-triangle' };
501
+ toast.className = `toast ${type} flex items-center space-x-3`;
502
+ toast.innerHTML = `<i class="fas ${icons[type]}"></i><span>${message}</span>`;
503
+ container.prepend(toast);
504
+ setTimeout(() => toast.classList.add('show'), 10);
505
+ setTimeout(() => {
506
+ toast.classList.remove('show');
507
+ toast.addEventListener('transitionend', () => toast.remove());
508
+ }, 3000);
509
+ };
510
+ initializeSampleData = () => {
511
+ if (localStorage.getItem('loanAndDeviceApp_v2')) return;
 
 
 
 
 
 
512
  entries = [
513
+ { id: 1, type: 'loan', userName: 'Shehroz Ali', totalAmount: 500000, installmentDay: 22, installmentAmount: 20000, phone: '0300-1234567', address: '123 Main St, Karachi', loanPurpose: 'Car Purchase', payments: [{id: 101, date: new Date().toISOString(), amount: 50000, isInitial: true}], documents: [], addedTime: new Date().toISOString(), status: 'Active' },
514
+ { id: 2, type: 'device', userName: 'Fatima Jilani', totalAmount: 15000, mac: 'AA:BB:CC:11:22:33', deviceName: 'Office Router', payments: [], addedTime: new Date().toISOString(), status: 'Active' }
 
515
  ];
516
  saveData();
 
517
  };
518
 
519
  initializeSampleData();
520
  renderTable();
 
521
  });
522
  </script>
523
  </body>