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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +344 -478
index.html CHANGED
@@ -3,16 +3,15 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Network Device Monitoring with Loan 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
- <!-- Chart.js is not used in this implementation to keep it simple, but the script tag is here if you wish to extend it -->
10
- <!-- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> -->
11
  <style>
12
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
13
 
14
  :root {
15
  --primary: #4f46e5;
 
16
  --success: #10b981;
17
  --warning: #f59e0b;
18
  --danger: #ef4444;
@@ -22,7 +21,6 @@
22
  body {
23
  font-family: 'Inter', sans-serif;
24
  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
25
- min-height: 100vh;
26
  }
27
 
28
  .card {
@@ -30,47 +28,23 @@
30
  border-radius: 16px;
31
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
32
  transition: all 0.3s ease;
33
- overflow: hidden;
34
- }
35
-
36
- .card:hover {
37
- box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
38
  }
39
 
40
  .status-badge {
41
  padding: 4px 12px;
42
  border-radius: 20px;
43
- font-size: 12px;
44
  font-weight: 600;
45
  display: inline-block;
46
  text-transform: uppercase;
47
  letter-spacing: 0.5px;
48
  }
49
 
50
- .active-badge {
51
- background-color: #ecfdf5;
52
- color: #065f46;
53
- }
54
-
55
- .blocked-badge {
56
- background-color: #fef2f2;
57
- color: #991b1b;
58
- }
59
-
60
- .paid-badge {
61
- background-color: #ecfdf5;
62
- color: #065f46;
63
- }
64
-
65
- .partial-badge {
66
- background-color: #fffbeb;
67
- color: #92400e;
68
- }
69
-
70
- .unpaid-badge {
71
- background-color: #fef2f2;
72
- color: #991b1b;
73
- }
74
 
75
  .form-input {
76
  border: 1px solid #e2e8f0;
@@ -97,17 +71,13 @@
97
  color: white;
98
  font-weight: 500;
99
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
100
- z-index: 1000;
101
  transform: translateY(100px);
102
  opacity: 0;
103
  transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
104
  }
105
 
106
- .toast.show {
107
- transform: translateY(0);
108
- opacity: 1;
109
- }
110
-
111
  .toast.success { background: var(--success); }
112
  .toast.error { background: var(--danger); }
113
  .toast.info { background: var(--info); }
@@ -125,114 +95,46 @@
125
  justify-content: center;
126
  }
127
 
128
- .btn-primary {
129
- background: var(--primary);
130
- color: white;
131
- }
132
-
133
- .btn-primary:hover {
134
- background: #4338ca;
135
- transform: translateY(-2px);
136
- box-shadow: 0 10px 20px rgba(79, 70, 229, 0.3);
137
- }
138
-
139
- .btn-success { background: var(--success); color: white; }
140
- .btn-warning { background: var(--warning); color: white; }
141
- .btn-danger { background: var(--danger); color: white; }
142
-
143
  .action-btn {
144
- width: 36px;
145
- height: 36px;
146
- border-radius: 10px;
147
- display: inline-flex;
148
- align-items: center;
149
- justify-content: center;
150
- cursor: pointer;
151
- transition: all 0.2s;
152
- color: #64748b;
153
- background: #f1f5f9;
154
- }
155
-
156
- .action-btn:hover {
157
- transform: scale(1.1);
158
  }
159
-
160
- .payment-btn:hover { background-color: #ecfdf5; color: var(--success); }
161
- .block-btn.active-device:hover { background-color: #fef2f2; color: var(--danger); }
162
- .block-btn.blocked-device:hover { background-color: #ecfdf5; color: var(--success); }
163
- .delete-btn:hover { background-color: #fee2e2; color: var(--danger); }
164
 
165
  .mac-address {
166
- font-family: 'Courier New', monospace;
167
- background: #f1f5f9;
168
- padding: 4px 8px;
169
- border-radius: 6px;
170
- font-weight: 600;
171
- color: #475569;
172
- }
173
-
174
- .progress-bar {
175
- height: 8px;
176
- border-radius: 4px;
177
- background: #e2e8f0;
178
- overflow: hidden;
179
- }
180
-
181
- .progress-fill {
182
- height: 100%;
183
- border-radius: 4px;
184
- transition: width 0.5s ease;
185
  }
186
 
187
  .modal-overlay {
188
- position: fixed;
189
- top: 0;
190
- left: 0;
191
- right: 0;
192
- bottom: 0;
193
- background: rgba(15, 23, 42, 0.6);
194
- backdrop-filter: blur(4px);
195
- display: flex;
196
- align-items: center;
197
- justify-content: center;
198
- z-index: 1000;
199
- opacity: 0;
200
- pointer-events: none;
201
- transition: opacity 0.3s;
202
  }
203
 
204
- .modal-overlay.active {
205
- opacity: 1;
206
- pointer-events: all;
207
- }
208
 
209
  .modal-content {
210
- background: white;
211
- border-radius: 16px;
212
- width: 90%;
213
- max-width: 500px;
214
- max-height: 90vh;
215
- overflow-y: auto;
216
- transform: translateY(20px) scale(0.95);
217
- opacity: 0;
218
  transition: all 0.3s ease-out;
219
  }
220
 
221
- .modal-overlay.active .modal-content {
222
- transform: translateY(0) scale(1);
223
- opacity: 1;
224
- }
225
 
226
- .payment-item {
227
- display: flex;
228
- justify-content: space-between;
229
- align-items: center;
230
- padding: 12px 0;
231
- border-bottom: 1px solid #e2e8f0;
232
  }
233
 
234
- .payment-item:last-child {
235
- border-bottom: none;
236
  }
237
  </style>
238
  </head>
@@ -246,191 +148,158 @@
246
  <div class="p-6">
247
  <div class="flex justify-between items-center mb-4">
248
  <h3 class="text-xl font-bold text-gray-800">Payment Management</h3>
249
- <button class="action-btn close-payment-modal" title="Close">
250
- <i class="fas fa-times"></i>
251
- </button>
252
  </div>
253
 
254
  <div class="bg-gray-50 rounded-lg p-4 mb-6">
255
- <h4 class="font-medium text-gray-700 mb-2">Device Information</h4>
256
- <div class="grid grid-cols-2 gap-x-4 gap-y-2">
257
- <div>
258
- <p class="text-gray-500 text-sm">MAC Address</p>
259
- <p id="payment-mac" class="font-medium text-gray-800">-</p>
260
- </div>
261
- <div>
262
- <p class="text-gray-500 text-sm">Device Name</p>
263
- <p id="payment-device" class="font-medium text-gray-800">-</p>
264
- </div>
265
- <div>
266
- <p class="text-gray-500 text-sm">User Name</p>
267
- <p id="payment-user" class="font-medium text-gray-800">-</p>
268
- </div>
269
- <div>
270
- <p class="text-gray-500 text-sm">Status</p>
271
- <div id="payment-status-badge"></div>
272
- </div>
273
  </div>
274
  </div>
275
 
276
  <div class="grid grid-cols-3 gap-4 mb-6 text-center">
277
  <div class="bg-indigo-50 p-4 rounded-lg">
278
  <p class="text-indigo-500 text-sm">Total Loan</p>
279
- <p id="payment-total" class="text-xl font-bold text-indigo-700">₹0</p>
280
  </div>
281
  <div class="bg-green-50 p-4 rounded-lg">
282
  <p class="text-green-500 text-sm">Amount Paid</p>
283
- <p id="payment-paid" class="text-xl font-bold text-green-700">₹0</p>
284
  </div>
285
  <div class="bg-red-50 p-4 rounded-lg">
286
  <p class="text-red-500 text-sm">Remaining</p>
287
- <p id="payment-remaining" class="text-xl font-bold text-red-700">₹0</p>
288
  </div>
289
  </div>
290
 
291
- <div class="progress-bar mb-6">
292
- <div id="payment-progress" class="progress-fill bg-green-500" style="width: 0%"></div>
293
  </div>
294
 
295
  <div class="mb-6">
296
  <h4 class="font-medium text-gray-700 mb-2">Payment History</h4>
297
- <div id="payment-history-container" class="max-h-48 overflow-y-auto pr-2 bg-gray-50 rounded-lg p-3">
298
- <div id="payment-history" class="space-y-2">
299
- <!-- Payment history will be populated here -->
300
- </div>
301
- <div id="no-payments" class="py-4 text-center text-gray-500">
302
  <i class="fas fa-receipt text-3xl mb-2 text-gray-400"></i>
303
  <p>No payment history found</p>
304
  </div>
305
  </div>
306
  </div>
307
 
308
- <div>
309
- <h4 class="font-medium text-gray-700 mb-2">Add New Payment</h4>
310
- <form id="add-payment-form" class="flex space-x-3">
311
- <input type="number" id="new-payment-amount" class="form-input flex-1" placeholder="Amount" min="1" required>
312
- <button id="add-payment-btn" type="submit" class="btn btn-success">
313
- <i class="fas fa-plus-circle mr-2"></i> Add
314
- </button>
315
- </form>
316
- </div>
317
  </div>
318
  </div>
319
  </div>
320
 
321
  <div class="max-w-7xl mx-auto">
322
- <!-- Header -->
323
- <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8">
324
  <div>
325
- <h1 class="text-3xl md:text-4xl font-bold text-gray-800">Network Device Monitor</h1>
326
- <p class="text-gray-600 mt-2">Track, manage, and monitor all network devices with loan tracking.</p>
327
- </div>
328
- <div class="flex items-center space-x-4 mt-4 md:mt-0">
329
- <div class="flex items-center bg-white rounded-xl shadow py-2 px-4">
330
- <div class="bg-indigo-100 p-2 rounded-lg mr-3">
331
- <i class="fas fa-user-shield text-indigo-600"></i>
332
- </div>
333
- <div>
334
- <p class="text-sm font-medium text-gray-800">Admin User</p>
335
- <p class="text-xs text-gray-500">Network Administrator</p>
336
- </div>
337
- </div>
338
  </div>
339
  </div>
340
 
341
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
342
  <!-- Left Column - Form and Stats -->
343
  <div class="lg:col-span-1 space-y-8">
344
- <!-- Add Device Form -->
345
  <div class="card p-6">
346
- <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>Register New Device</h2>
347
- <form id="device-form" class="space-y-4">
348
- <div>
349
- <label for="mac-address" class="block text-gray-700 mb-1 font-medium text-sm">MAC Address</label>
350
- <input type="text" id="mac-address" class="form-input" placeholder="e.g. 00:1A:2B:3C:4D:5E" required>
351
- <p class="text-gray-500 text-xs mt-1">Any format is accepted, we'll standardize it for you.</p>
 
 
 
 
 
 
 
 
352
  </div>
353
-
354
- <div>
355
- <label for="device-name" class="block text-gray-700 mb-1 font-medium text-sm">Device Name</label>
356
- <input type="text" id="device-name" class="form-input" placeholder="e.g. John's iPhone" required>
 
 
 
 
 
 
357
  </div>
358
 
 
 
 
 
 
 
 
359
  <div>
360
- <label for="user-name" class="block text-gray-700 mb-1 font-medium text-sm">User Name</label>
361
  <input type="text" id="user-name" class="form-input" placeholder="e.g. John Doe" required>
362
  </div>
363
 
364
  <div class="grid grid-cols-2 gap-4">
365
  <div>
366
- <label for="total-amount" class="block text-gray-700 mb-1 font-medium text-sm">Total Loan ()</label>
367
- <input type="number" id="total-amount" class="form-input" placeholder="e.g. 3000" min="1" required>
368
  </div>
369
  <div>
370
- <label for="initial-payment" class="block text-gray-700 mb-1 font-medium text-sm">Initial Payment ()</label>
371
- <input type="number" id="initial-payment" class="form-input" placeholder="e.g. 1000" min="0">
372
  </div>
373
  </div>
374
 
375
- <button type="submit" class="btn btn-primary w-full !mt-6">
376
- <i class="fas fa-save mr-2"></i> Add & Save Device
377
  </button>
378
  </form>
379
  </div>
380
 
381
- <!-- Financial Summary -->
382
  <div class="card p-6">
383
- <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>Financial Summary</h2>
384
- <div class="space-y-4">
385
- <div>
386
- <div class="flex justify-between mb-1">
387
- <span class="text-gray-600 font-medium">Total Collected</span>
388
- <span class="font-bold text-green-600" id="total-revenue">₹0</span>
389
- </div>
390
- </div>
391
-
392
- <div>
393
- <div class="flex justify-between mb-1">
394
- <span class="text-gray-600 font-medium">Total Outstanding</span>
395
- <span class="font-bold text-red-600" id="outstanding-balance">₹0</span>
396
- </div>
397
  </div>
398
- <div class="border-t pt-4">
399
- <div class="flex justify-between mb-1">
400
- <span class="text-gray-600 font-medium">Total Loan Value</span>
401
- <span class="font-bold text-gray-800" id="total-loan-value">₹0</span>
402
- </div>
403
  </div>
404
- </div>
405
-
406
- <div class="mt-6 grid grid-cols-3 gap-4 text-center">
407
- <div class="bg-green-50 p-3 rounded-lg">
408
- <p class="text-green-600 text-sm font-semibold">Fully Paid</p>
409
- <p class="text-2xl font-bold text-green-700" id="fully-paid-count">0</p>
410
- </div>
411
- <div class="bg-yellow-50 p-3 rounded-lg">
412
- <p class="text-yellow-600 text-sm font-semibold">Partial Paid</p>
413
- <p class="text-2xl font-bold text-yellow-700" id="partial-paid-count">0</p>
414
- </div>
415
- <div class="bg-red-50 p-3 rounded-lg">
416
- <p class="text-red-600 text-sm font-semibold">Unpaid</p>
417
- <p class="text-2xl font-bold text-red-700" id="unpaid-count">0</p>
418
  </div>
419
  </div>
420
  </div>
421
  </div>
422
 
423
- <!-- Right Column - Device Table -->
424
  <div class="lg:col-span-2">
425
  <div class="card p-6">
426
- <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
427
- <h2 class="text-xl font-bold text-gray-800 flex items-center"><i class="fas fa-list-ul mr-3 text-indigo-500"></i>Registered Devices (<span id="device-count">0</span>)</h2>
428
- <div class="flex space-x-3 mt-4 md:mt-0 w-full md:w-auto">
 
 
 
 
429
  <div class="relative w-full md:w-64">
430
  <input type="text" id="search-input" class="form-input pl-10" placeholder="Search...">
431
  <i class="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
432
  </div>
433
- <button id="export-btn" class="btn btn-primary bg-gray-700 hover:bg-gray-900" title="Export as CSV">
434
  <i class="fas fa-file-csv mr-2"></i> Export
435
  </button>
436
  </div>
@@ -440,24 +309,19 @@
440
  <table class="w-full text-sm">
441
  <thead>
442
  <tr class="text-left text-gray-500 font-medium border-b-2 border-gray-200">
443
- <th class="p-3">MAC Address</th>
444
- <th class="p-3">User / Device</th>
445
- <th class="p-3">Payment Progress</th>
446
  <th class="p-3">Status</th>
447
  <th class="p-3 text-right">Actions</th>
448
  </tr>
449
  </thead>
450
- <tbody id="device-table" class="divide-y divide-gray-100">
451
- <!-- Device rows will be populated here -->
452
- </tbody>
453
  </table>
454
-
455
  <div id="empty-state" class="py-16 text-center hidden">
456
- <div class="mb-4">
457
- <i class="fas fa-inbox text-5xl text-gray-300"></i>
458
- </div>
459
- <h3 class="text-lg font-medium text-gray-700">No Devices Registered Yet</h3>
460
- <p class="text-gray-500 mt-1">Use the form on the left to add a new device.</p>
461
  </div>
462
  </div>
463
  </div>
@@ -468,26 +332,31 @@
468
  <script>
469
  document.addEventListener('DOMContentLoaded', () => {
470
  // --- STATE MANAGEMENT ---
471
- let devices = JSON.parse(localStorage.getItem('networkDevices_v2')) || [];
472
- let currentPaymentDevice = null;
473
- const APP_VERSION = 'v2'; // To avoid conflicts with old data structures
474
 
475
  // --- DOM ELEMENTS ---
476
- const deviceForm = document.getElementById('device-form');
477
- const deviceTable = document.getElementById('device-table');
478
  const emptyState = document.getElementById('empty-state');
479
  const paymentModal = document.getElementById('payment-modal');
480
  const searchInput = document.getElementById('search-input');
481
  const exportBtn = document.getElementById('export-btn');
482
  const addPaymentForm = document.getElementById('add-payment-form');
 
 
 
 
 
 
 
483
 
484
  // --- HELPER FUNCTIONS ---
485
- const saveData = () => {
486
- localStorage.setItem(`networkDevices_${APP_VERSION}`, JSON.stringify(devices));
487
- };
488
-
489
- const formatCurrency = (amount) => `₹${amount.toFixed(2)}`;
490
-
491
  const showToast = (message, type = 'info') => {
492
  const container = document.getElementById('toast-container');
493
  const toast = document.createElement('div');
@@ -495,7 +364,6 @@
495
  toast.className = `toast ${type} flex items-center space-x-3`;
496
  toast.innerHTML = `<i class="fas ${icons[type]}"></i><span>${message}</span>`;
497
  container.prepend(toast);
498
-
499
  setTimeout(() => toast.classList.add('show'), 10);
500
  setTimeout(() => {
501
  toast.classList.remove('show');
@@ -503,327 +371,325 @@
503
  }, 3000);
504
  };
505
 
506
- const formatMAC = (mac) => {
507
- let cleanMac = mac.replace(/[^0-9A-Fa-f]/g, '').toUpperCase();
508
- if (cleanMac.length !== 12) return mac.toUpperCase(); // Return original if not 12 hex chars
509
- return cleanMac.match(/.{1,2}/g).join(':');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  };
511
 
512
  // --- CORE LOGIC ---
513
- const updateSummary = () => {
514
- let totalRevenue = 0;
515
- let totalLoanValue = 0;
516
- let fullyPaid = 0, partialPaid = 0, unpaid = 0;
517
-
518
- devices.forEach(d => {
519
- const paidAmount = d.payments.reduce((sum, p) => sum + p.amount, 0);
520
- totalRevenue += paidAmount;
521
- totalLoanValue += d.totalAmount;
522
-
523
- if (paidAmount >= d.totalAmount) fullyPaid++;
524
- else if (paidAmount > 0) partialPaid++;
525
- else unpaid++;
526
  });
527
 
528
- const totalOutstanding = totalLoanValue - totalRevenue;
529
-
530
  document.getElementById('total-revenue').textContent = formatCurrency(totalRevenue);
531
- document.getElementById('outstanding-balance').textContent = formatCurrency(Math.max(0, totalOutstanding));
532
  document.getElementById('total-loan-value').textContent = formatCurrency(totalLoanValue);
533
- document.getElementById('fully-paid-count').textContent = fullyPaid;
534
- document.getElementById('partial-paid-count').textContent = partialPaid;
535
- document.getElementById('unpaid-count').textContent = unpaid;
536
- document.getElementById('device-count').textContent = devices.length;
537
  };
538
 
539
- const renderDeviceTable = (data = devices) => {
540
- deviceTable.innerHTML = '';
541
- emptyState.classList.toggle('hidden', data.length > 0);
 
 
 
 
 
 
 
 
542
 
543
- data.forEach(device => {
544
- const paidAmount = device.payments.reduce((sum, p) => sum + p.amount, 0);
545
- const remainingAmount = device.totalAmount - paidAmount;
546
- const progress = Math.min(100, (paidAmount / device.totalAmount) * 100);
547
 
548
- let paymentStatus, paymentStatusClass;
549
- if (paidAmount >= device.totalAmount) {
550
- paymentStatus = 'Fully Paid';
551
- paymentStatusClass = 'paid-badge';
552
- } else if (paidAmount > 0) {
553
- paymentStatus = 'Partial';
554
- paymentStatusClass = 'partial-badge';
555
- } else {
556
- paymentStatus = 'Unpaid';
557
- paymentStatusClass = 'unpaid-badge';
558
- }
559
 
560
- const deviceStatusClass = device.status === 'Active' ? 'active-badge' : 'blocked-badge';
 
 
 
 
 
 
 
 
561
 
562
  const row = document.createElement('tr');
563
  row.className = 'hover:bg-gray-50';
564
  row.innerHTML = `
565
- <td class="p-3"><span class="mac-address">${device.mac}</span></td>
566
  <td class="p-3">
567
- <div class="font-bold text-gray-800">${device.userName}</div>
568
- <div class="text-gray-500">${device.deviceName}</div>
569
  </td>
570
  <td class="p-3">
571
- <div class="flex items-center">
572
- <div class="w-full bg-gray-200 rounded-full h-2 mr-2">
573
- <div class="bg-green-500 h-2 rounded-full" style="width: ${progress}%"></div>
574
- </div>
575
- <span class="font-medium text-gray-600">${progress.toFixed(0)}%</span>
576
  </div>
577
- <div class="text-xs text-gray-500 mt-1">${formatCurrency(paidAmount)} / ${formatCurrency(device.totalAmount)}</div>
578
  </td>
579
  <td class="p-3">
580
  <div class="flex flex-col space-y-1">
581
- <span class="status-badge ${deviceStatusClass}">${device.status}</span>
582
  <span class="status-badge ${paymentStatusClass}">${paymentStatus}</span>
583
  </div>
584
  </td>
585
  <td class="p-3 text-right">
586
  <div class="inline-flex space-x-2">
587
- <button class="action-btn payment-btn" data-id="${device.id}" title="Manage Payments"><i class="fas fa-money-check-alt"></i></button>
588
- <button class="action-btn block-btn ${device.status === 'Active' ? 'active-device' : 'blocked-device'}" data-id="${device.id}" title="${device.status === 'Active' ? 'Block' : 'Unblock'} Device">
589
- <i class="fas ${device.status === 'Active' ? 'fa-ban' : 'fa-check-circle'}"></i>
590
- </button>
591
- <button class="action-btn delete-btn" data-id="${device.id}" title="Delete Device"><i class="fas fa-trash"></i></button>
592
  </div>
593
  </td>
594
  `;
595
- deviceTable.appendChild(row);
596
  });
597
- updateSummary();
598
  };
599
 
600
- const addNewDevice = (e) => {
601
  e.preventDefault();
602
- const mac = document.getElementById('mac-address').value.trim();
603
- const initialPayment = parseFloat(document.getElementById('initial-payment').value) || 0;
604
  const totalAmount = parseFloat(document.getElementById('total-amount').value);
605
-
606
- if (devices.some(d => d.mac === formatMAC(mac))) {
607
- showToast('A device with this MAC address already exists.', 'error');
608
- return;
609
- }
610
-
611
  if (initialPayment > totalAmount) {
612
- showToast('Initial payment cannot be greater than the total amount.', 'error');
613
  return;
614
  }
615
 
616
- const newDevice = {
617
  id: Date.now(),
618
- mac: formatMAC(mac),
619
- deviceName: document.getElementById('device-name').value.trim(),
620
  userName: document.getElementById('user-name').value.trim(),
621
- totalAmount,
622
  payments: [],
623
  addedTime: new Date().toISOString(),
624
  status: 'Active'
625
  };
626
 
 
 
 
 
 
 
 
 
 
 
 
627
  if (initialPayment > 0) {
628
- newDevice.payments.push({ id: Date.now(), date: new Date().toISOString(), amount: initialPayment });
629
  }
630
 
631
- devices.unshift(newDevice);
632
  saveData();
633
- renderDeviceTable();
634
- deviceForm.reset();
635
- showToast('Device registered successfully!', 'success');
 
636
  };
637
 
638
- const openPaymentModal = (deviceId) => {
639
- currentPaymentDevice = devices.find(d => d.id == deviceId);
640
- if (!currentPaymentDevice) return;
641
-
642
- const paidAmount = currentPaymentDevice.payments.reduce((sum, p) => sum + p.amount, 0);
643
- const remainingAmount = currentPaymentDevice.totalAmount - paidAmount;
644
- const progress = Math.min(100, (paidAmount / currentPaymentDevice.totalAmount) * 100);
645
-
646
- document.getElementById('payment-mac').textContent = currentPaymentDevice.mac;
647
- document.getElementById('payment-device').textContent = currentPaymentDevice.deviceName;
648
- document.getElementById('payment-user').textContent = currentPaymentDevice.userName;
649
- const statusBadge = document.getElementById('payment-status-badge');
650
- statusBadge.innerHTML = `<span class="status-badge ${currentPaymentDevice.status === 'Active' ? 'active-badge' : 'blocked-badge'}">${currentPaymentDevice.status}</span>`;
651
-
652
- document.getElementById('payment-total').textContent = formatCurrency(currentPaymentDevice.totalAmount);
 
 
 
653
  document.getElementById('payment-paid').textContent = formatCurrency(paidAmount);
654
- document.getElementById('payment-remaining').textContent = formatCurrency(Math.max(0, remainingAmount));
655
  document.getElementById('payment-progress').style.width = `${progress}%`;
656
-
657
  const historyContainer = document.getElementById('payment-history');
658
  const noPaymentsEl = document.getElementById('no-payments');
659
  historyContainer.innerHTML = '';
660
- noPaymentsEl.classList.toggle('hidden', currentPaymentDevice.payments.length > 0);
661
 
662
- [...currentPaymentDevice.payments].reverse().forEach(p => {
663
  const item = document.createElement('div');
664
- item.className = 'payment-item';
665
  item.innerHTML = `
666
  <div>
667
  <div class="font-bold text-green-700">${formatCurrency(p.amount)}</div>
668
  <div class="text-gray-500 text-xs">${new Date(p.date).toLocaleString()}</div>
669
  </div>
670
- <div class="text-right">
671
- <button class="text-red-500 hover:text-red-700 text-xs delete-payment-btn" data-id="${p.id}" title="Delete Payment"><i class="fas fa-trash-alt"></i></button>
672
- </div>
673
  `;
674
  historyContainer.appendChild(item);
675
  });
676
-
677
  paymentModal.classList.add('active');
678
  };
679
 
680
  const closePaymentModal = () => {
681
  paymentModal.classList.remove('active');
682
- currentPaymentDevice = null;
683
  document.getElementById('new-payment-amount').value = '';
684
  };
685
 
686
- const addNewPayment = (e) => {
687
- e.preventDefault();
688
- if (!currentPaymentDevice) return;
689
- const amountInput = document.getElementById('new-payment-amount');
690
- const amount = parseFloat(amountInput.value);
691
-
692
- const paidAmount = currentPaymentDevice.payments.reduce((sum, p) => sum + p.amount, 0);
693
- const remaining = currentPaymentDevice.totalAmount - paidAmount;
694
-
695
- if (isNaN(amount) || amount <= 0) {
696
- showToast('Please enter a valid positive amount.', 'error');
697
- return;
698
  }
699
- if (amount > remaining) {
700
- showToast(`Payment cannot exceed remaining amount of ${formatCurrency(remaining)}.`, 'warning');
701
- return;
 
 
702
  }
703
-
704
- currentPaymentDevice.payments.push({ id: Date.now(), date: new Date().toISOString(), amount: amount });
705
- saveData();
706
- openPaymentModal(currentPaymentDevice.id); // Refresh modal
707
- renderDeviceTable(devices.filter(d => filterMatch(d, searchInput.value))); // Refresh table
708
- amountInput.value = '';
709
- showToast('Payment added successfully!', 'success');
710
  };
711
 
712
  const deletePayment = (paymentId) => {
713
- if (!currentPaymentDevice) return;
714
- currentPaymentDevice.payments = currentPaymentDevice.payments.filter(p => p.id != paymentId);
715
- saveData();
716
- openPaymentModal(currentPaymentDevice.id); // Refresh modal
717
- renderDeviceTable(devices.filter(d => filterMatch(d, searchInput.value))); // Refresh table
718
- showToast('Payment deleted.', 'info');
719
  }
720
 
721
- const handleTableClick = (e) => {
722
- const target = e.target.closest('button');
723
- if (!target) return;
724
- const id = target.dataset.id;
 
 
 
 
 
725
 
726
- if (target.classList.contains('payment-btn')) openPaymentModal(id);
727
- if (target.classList.contains('block-btn')) toggleBlockDevice(id);
728
- if (target.classList.contains('delete-btn')) deleteDevice(id);
729
- };
730
-
731
- const handleModalClick = (e) => {
732
- if (e.target.classList.contains('modal-overlay') || e.target.closest('.close-payment-modal')) {
733
- closePaymentModal();
734
- }
735
- const deleteBtn = e.target.closest('.delete-payment-btn');
736
- if (deleteBtn) {
737
- if (confirm('Are you sure you want to delete this payment record? This action cannot be undone.')) {
738
- deletePayment(deleteBtn.dataset.id);
739
- }
740
- }
741
  };
742
 
743
- const toggleBlockDevice = (id) => {
744
- const device = devices.find(d => d.id == id);
745
- if (!device) return;
746
- device.status = device.status === 'Active' ? 'Blocked' : 'Active';
 
747
  saveData();
748
- renderDeviceTable(devices.filter(d => filterMatch(d, searchInput.value)));
749
- showToast(`Device ${device.mac} has been ${device.status.toLowerCase()}.`, 'info');
750
  };
751
 
752
- const deleteDevice = (id) => {
753
- if (confirm('Are you sure you want to delete this device and all its payment history? This is irreversible.')) {
754
- devices = devices.filter(d => d.id != id);
755
  saveData();
756
- renderDeviceTable(devices.filter(d => filterMatch(d, searchInput.value)));
757
- showToast('Device deleted successfully.', 'success');
758
  }
759
  };
760
-
761
- const filterMatch = (device, term) => {
762
- const lowerTerm = term.toLowerCase();
763
- return (
764
- device.mac.toLowerCase().includes(lowerTerm) ||
765
- device.deviceName.toLowerCase().includes(lowerTerm) ||
766
- device.userName.toLowerCase().includes(lowerTerm)
767
- );
768
- };
769
-
770
- const handleSearch = () => {
771
- const searchTerm = searchInput.value;
772
- const filtered = devices.filter(d => filterMatch(d, searchTerm));
773
- renderDeviceTable(filtered);
774
- };
775
-
776
  const exportToCSV = () => {
777
- if (devices.length === 0) {
778
- showToast('No devices to export.', 'warning');
779
- return;
780
- }
781
- let csvContent = "data:text/csv;charset=utf-8,MAC Address,Device Name,User Name,Total Amount,Paid Amount,Remaining Amount,Device Status,Payment Status,Date Added\n";
782
 
783
- devices.forEach(d => {
784
- const paidAmount = d.payments.reduce((sum, p) => sum + p.amount, 0);
785
- const remaining = d.totalAmount - paidAmount;
786
- let paymentStatus;
787
- if (paidAmount >= d.totalAmount) paymentStatus = 'Fully Paid';
788
- else if (paidAmount > 0) paymentStatus = 'Partial Paid';
789
- else paymentStatus = 'Unpaid';
790
-
791
- const row = [d.mac, d.deviceName, d.userName, d.totalAmount, paidAmount, remaining, d.status, paymentStatus, new Date(d.addedTime).toLocaleDateString()].join(',');
792
  csvContent += row + "\n";
793
  });
794
 
795
- const encodedUri = encodeURI(csvContent);
796
  const link = document.createElement("a");
797
- link.setAttribute("href", encodedUri);
798
- link.setAttribute("download", "network_devices_export.csv");
799
  document.body.appendChild(link);
800
  link.click();
801
  document.body.removeChild(link);
802
- showToast('CSV export started.', 'success');
803
  };
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  const initializeSampleData = () => {
806
- if (devices.length > 0) return;
807
- devices = [
808
- { id: 1678886400000, mac: 'AA:BB:CC:11:22:33', deviceName: 'John\'s iPhone 14', userName: 'John Smith', totalAmount: 3000, payments: [ { id: 1, date: '2023-03-15T12:00:00Z', amount: 1500 } ], addedTime: '2023-03-15T12:00:00Z', status: 'Active' },
809
- { id: 1678972800000, mac: 'DD:EE:FF:44:55:66', deviceName: 'Office Printer', userName: 'Admin', totalAmount: 5000, payments: [ { id: 2, date: '2023-03-16T14:00:00Z', amount: 5000 } ], addedTime: '2023-03-16T14:00:00Z', status: 'Active' },
810
- { id: 1679059200000, mac: '11:22:33:AA:BB:CC', deviceName: 'Sarah\'s Laptop', userName: 'Sarah Johnson', totalAmount: 2500, payments: [], addedTime: '2023-03-17T16:00:00Z', status: 'Blocked' }
811
  ];
812
  saveData();
813
  showToast('Welcome! Sample data has been loaded.', 'info');
814
  };
815
 
816
- // --- EVENT LISTENERS ---
817
- deviceForm.addEventListener('submit', addNewDevice);
818
- addPaymentForm.addEventListener('submit', addNewPayment);
819
- deviceTable.addEventListener('click', handleTableClick);
820
- paymentModal.addEventListener('click', handleModalClick);
821
- searchInput.addEventListener('input', handleSearch);
822
- exportBtn.addEventListener('click', exportToCSV);
823
-
824
- // --- INITIALIZATION ---
825
  initializeSampleData();
826
- renderDeviceTable();
 
827
  });
828
  </script>
829
  </body>
 
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;
 
21
  body {
22
  font-family: 'Inter', sans-serif;
23
  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
 
24
  }
25
 
26
  .card {
 
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;
 
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); }
 
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>
 
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>
 
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>
 
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');
 
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');
 
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 = '';
430
+ emptyState.classList.toggle('hidden', filteredEntries.length > 0);
 
 
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>