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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +433 -676
index.html CHANGED
@@ -6,7 +6,8 @@
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
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 
10
  <style>
11
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
12
 
@@ -40,33 +41,35 @@
40
  padding: 4px 12px;
41
  border-radius: 20px;
42
  font-size: 12px;
43
- font-weight: 500;
44
  display: inline-block;
 
 
45
  }
46
 
47
  .active-badge {
48
- background-color: #e6f4ea;
49
- color: #0f9d58;
50
  }
51
 
52
  .blocked-badge {
53
- background-color: #fce8e6;
54
- color: #d93025;
55
  }
56
 
57
  .paid-badge {
58
- background-color: #e6f4ea;
59
- color: #0f9d58;
60
  }
61
 
62
  .partial-badge {
63
- background-color: #fef3c7;
64
- color: #b45309;
65
  }
66
 
67
  .unpaid-badge {
68
- background-color: #fce8e6;
69
- color: #d93025;
70
  }
71
 
72
  .form-input {
@@ -75,12 +78,14 @@
75
  padding: 12px 16px;
76
  transition: all 0.3s;
77
  width: 100%;
 
78
  }
79
 
80
  .form-input:focus {
81
  border-color: var(--primary);
82
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
83
  outline: none;
 
84
  }
85
 
86
  .toast {
@@ -103,22 +108,15 @@
103
  opacity: 1;
104
  }
105
 
106
- .toast.success {
107
- background: var(--success);
108
- }
109
-
110
- .toast.error {
111
- background: var(--danger);
112
- }
113
-
114
- .toast.info {
115
- background: var(--info);
116
- }
117
 
118
  .btn {
119
  border-radius: 10px;
120
  padding: 12px 24px;
121
- font-weight: 500;
122
  transition: all 0.3s;
123
  border: none;
124
  cursor: pointer;
@@ -138,20 +136,9 @@
138
  box-shadow: 0 10px 20px rgba(79, 70, 229, 0.3);
139
  }
140
 
141
- .btn-success {
142
- background: var(--success);
143
- color: white;
144
- }
145
-
146
- .btn-warning {
147
- background: var(--warning);
148
- color: white;
149
- }
150
-
151
- .btn-danger {
152
- background: var(--danger);
153
- color: white;
154
- }
155
 
156
  .action-btn {
157
  width: 36px;
@@ -162,40 +149,26 @@
162
  justify-content: center;
163
  cursor: pointer;
164
  transition: all 0.2s;
165
- color: #4b5563;
166
  background: #f1f5f9;
167
  }
168
 
169
  .action-btn:hover {
170
  transform: scale(1.1);
171
- color: var(--primary);
172
- }
173
-
174
- .edit-btn:hover {
175
- background-color: #eef2ff;
176
- }
177
-
178
- .payment-btn:hover {
179
- background-color: #ecfdf5;
180
- color: var(--success);
181
- }
182
-
183
- .block-btn:hover {
184
- background-color: #fef2f2;
185
- color: var(--danger);
186
  }
187
 
188
- .delete-btn:hover {
189
- background-color: #fef2f2;
190
- color: var(--danger);
191
- }
192
 
193
  .mac-address {
194
  font-family: 'Courier New', monospace;
195
- background: #f8fafc;
196
  padding: 4px 8px;
197
  border-radius: 6px;
198
- font-weight: 500;
 
199
  }
200
 
201
  .progress-bar {
@@ -217,7 +190,8 @@
217
  left: 0;
218
  right: 0;
219
  bottom: 0;
220
- background: rgba(0, 0, 0, 0.6);
 
221
  display: flex;
222
  align-items: center;
223
  justify-content: center;
@@ -239,19 +213,20 @@
239
  max-width: 500px;
240
  max-height: 90vh;
241
  overflow-y: auto;
242
- transform: translateY(20px);
243
  opacity: 0;
244
- transition: all 0.3s;
245
  }
246
 
247
  .modal-overlay.active .modal-content {
248
- transform: translateY(0);
249
  opacity: 1;
250
  }
251
 
252
  .payment-item {
253
  display: flex;
254
  justify-content: space-between;
 
255
  padding: 12px 0;
256
  border-bottom: 1px solid #e2e8f0;
257
  }
@@ -259,36 +234,11 @@
259
  .payment-item:last-child {
260
  border-bottom: none;
261
  }
262
-
263
- .tab-button {
264
- padding: 10px 20px;
265
- border-radius: 8px;
266
- cursor: pointer;
267
- transition: all 0.3s;
268
- font-weight: 500;
269
- }
270
-
271
- .tab-button.active {
272
- background: var(--primary);
273
- color: white;
274
- }
275
-
276
- .amount-display {
277
- font-size: 1.25rem;
278
- font-weight: 700;
279
- }
280
-
281
- .payment-status {
282
- padding: 4px 10px;
283
- border-radius: 20px;
284
- font-size: 12px;
285
- font-weight: 500;
286
- }
287
  </style>
288
  </head>
289
  <body class="min-h-screen py-8 px-4">
290
  <!-- Toast Container -->
291
- <div id="toast-container"></div>
292
 
293
  <!-- Payment Modal -->
294
  <div class="modal-overlay" id="payment-modal">
@@ -296,45 +246,45 @@
296
  <div class="p-6">
297
  <div class="flex justify-between items-center mb-4">
298
  <h3 class="text-xl font-bold text-gray-800">Payment Management</h3>
299
- <button class="action-btn close-payment-modal">
300
  <i class="fas fa-times"></i>
301
  </button>
302
  </div>
303
 
304
- <div class="mb-6">
305
  <h4 class="font-medium text-gray-700 mb-2">Device Information</h4>
306
- <div class="grid grid-cols-2 gap-4 mb-4">
307
  <div>
308
  <p class="text-gray-500 text-sm">MAC Address</p>
309
- <p id="payment-mac" class="font-medium">-</p>
310
  </div>
311
  <div>
312
  <p class="text-gray-500 text-sm">Device Name</p>
313
- <p id="payment-device" class="font-medium">-</p>
314
  </div>
315
  <div>
316
  <p class="text-gray-500 text-sm">User Name</p>
317
- <p id="payment-user" class="font-medium">-</p>
318
  </div>
319
  <div>
320
  <p class="text-gray-500 text-sm">Status</p>
321
- <p id="payment-status" class="font-medium">-</p>
322
  </div>
323
  </div>
324
  </div>
325
 
326
- <div class="grid grid-cols-3 gap-4 mb-6">
327
- <div class="bg-gray-50 p-4 rounded-lg">
328
- <p class="text-gray-500 text-sm">Total Amount</p>
329
- <p id="payment-total" class="amount-display">₹0</p>
330
  </div>
331
  <div class="bg-green-50 p-4 rounded-lg">
332
- <p class="text-green-500 text-sm">Paid Amount</p>
333
- <p id="payment-paid" class="amount-display text-green-600">₹0</p>
334
  </div>
335
- <div class="bg-blue-50 p-4 rounded-lg">
336
- <p class="text-blue-500 text-sm">Remaining</p>
337
- <p id="payment-remaining" class="amount-display text-blue-600">₹0</p>
338
  </div>
339
  </div>
340
 
@@ -343,24 +293,26 @@
343
  </div>
344
 
345
  <div class="mb-6">
346
- <h4 class="font-medium text-gray-700 mb-4">Payment History</h4>
347
- <div id="payment-history" class="space-y-2">
348
- <!-- Payment history will be populated here -->
349
- </div>
350
- <div id="no-payments" class="py-4 text-center text-gray-500">
351
- <i class="fas fa-inbox text-3xl mb-2"></i>
352
- <p>No payment history found</p>
 
 
353
  </div>
354
  </div>
355
 
356
  <div>
357
- <h4 class="font-medium text-gray-700 mb-4">Add New Payment</h4>
358
- <div class="flex space-x-3">
359
- <input type="number" id="new-payment-amount" class="form-input flex-1" placeholder="Amount" min="1">
360
- <button id="add-payment-btn" class="btn btn-success">
361
  <i class="fas fa-plus-circle mr-2"></i> Add
362
  </button>
363
- </div>
364
  </div>
365
  </div>
366
  </div>
@@ -371,151 +323,141 @@
371
  <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8">
372
  <div>
373
  <h1 class="text-3xl md:text-4xl font-bold text-gray-800">Network Device Monitor</h1>
374
- <p class="text-gray-600 mt-2">Track, manage, and monitor all network devices</p>
375
  </div>
376
  <div class="flex items-center space-x-4 mt-4 md:mt-0">
377
  <div class="flex items-center bg-white rounded-xl shadow py-2 px-4">
378
  <div class="bg-indigo-100 p-2 rounded-lg mr-3">
379
- <i class="fas fa-user text-indigo-600"></i>
380
  </div>
381
  <div>
382
  <p class="text-sm font-medium text-gray-800">Admin User</p>
383
- <p class="text-xs text-gray-500">Network Admin</p>
384
  </div>
385
  </div>
386
  </div>
387
  </div>
388
 
389
- <div class="flex flex-col lg:flex-row gap-8">
390
  <!-- Left Column - Form and Stats -->
391
- <div class="lg:w-1/3">
392
  <!-- Add Device Form -->
393
- <div class="card p-6 mb-8">
394
- <h2 class="text-xl font-bold text-gray-800 mb-4">Register New Device</h2>
395
  <form id="device-form" class="space-y-4">
396
  <div>
397
- <label class="block text-gray-700 mb-2 font-medium">MAC Address</label>
398
- <input type="text" id="mac-address" class="form-input" placeholder="Any format accepted">
399
- <p class="text-gray-500 text-sm mt-1">We'll automatically format your MAC address</p>
400
  </div>
401
 
402
  <div>
403
- <label class="block text-gray-700 mb-2 font-medium">Device Name</label>
404
- <input type="text" id="device-name" class="form-input" placeholder="e.g. John's iPhone">
405
  </div>
406
 
407
  <div>
408
- <label class="block text-gray-700 mb-2 font-medium">User Name</label>
409
- <input type="text" id="user-name" class="form-input" placeholder="e.g. John Doe">
410
  </div>
411
 
412
  <div class="grid grid-cols-2 gap-4">
413
  <div>
414
- <label class="block text-gray-700 mb-2 font-medium">Total Amount (₹)</label>
415
- <input type="number" id="total-amount" class="form-input" placeholder="e.g. 3000" min="1">
416
  </div>
417
  <div>
418
- <label class="block text-gray-700 mb-2 font-medium">Initial Payment (₹)</label>
419
  <input type="number" id="initial-payment" class="form-input" placeholder="e.g. 1000" min="0">
420
  </div>
421
  </div>
422
 
423
- <button type="submit" class="btn btn-primary w-full">
424
- <i class="fas fa-plus-circle mr-2"></i> Add New Device
425
  </button>
426
  </form>
427
  </div>
428
 
429
  <!-- Financial Summary -->
430
  <div class="card p-6">
431
- <h2 class="text-xl font-bold text-gray-800 mb-4">Financial Summary</h2>
432
  <div class="space-y-4">
433
  <div>
434
  <div class="flex justify-between mb-1">
435
- <span class="text-gray-600">Total Revenue</span>
436
- <span class="font-medium" id="total-revenue">₹0</span>
437
- </div>
438
- <div class="progress-bar">
439
- <div class="progress-fill bg-green-500" id="revenue-bar" style="width: 0%"></div>
440
  </div>
441
  </div>
442
 
443
  <div>
444
  <div class="flex justify-between mb-1">
445
- <span class="text-gray-600">Outstanding Balance</span>
446
- <span class="font-medium" id="outstanding-balance">₹0</span>
447
- </div>
448
- <div class="progress-bar">
449
- <div class="progress-fill bg-yellow-500" id="outstanding-bar" style="width: 0%"></div>
450
  </div>
451
  </div>
452
-
453
- <div>
454
  <div class="flex justify-between mb-1">
455
- <span class="text-gray-600">Average Payment</span>
456
- <span class="font-medium" id="avg-payment">₹0</span>
457
- </div>
458
- <div class="progress-bar">
459
- <div class="progress-fill bg-blue-500" id="avg-payment-bar" style="width: 0%"></div>
460
  </div>
461
  </div>
462
  </div>
463
 
464
- <div class="mt-6 grid grid-cols-2 gap-4">
465
- <div class="bg-green-50 p-4 rounded-lg">
466
- <p class="text-green-500 text-sm">Fully Paid</p>
467
- <p class="text-xl font-bold" id="fully-paid-count">0</p>
 
 
 
 
468
  </div>
469
- <div class="bg-yellow-50 p-4 rounded-lg">
470
- <p class="text-yellow-500 text-sm">Partial Paid</p>
471
- <p class="text-xl font-bold" id="partial-paid-count">0</p>
472
  </div>
473
  </div>
474
  </div>
475
  </div>
476
 
477
  <!-- Right Column - Device Table -->
478
- <div class="lg:w-2/3">
479
  <div class="card p-6">
480
  <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
481
- <h2 class="text-xl font-bold text-gray-800">Registered Devices</h2>
482
  <div class="flex space-x-3 mt-4 md:mt-0 w-full md:w-auto">
483
  <div class="relative w-full md:w-64">
484
- <input type="text" id="search-input" class="form-input pl-10" placeholder="Search devices...">
485
- <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
486
  </div>
487
- <button id="export-btn" class="btn btn-primary">
488
- <i class="fas fa-file-export mr-2"></i> Export
489
  </button>
490
  </div>
491
  </div>
492
 
493
  <div class="overflow-x-auto">
494
- <table class="w-full">
495
  <thead>
496
- <tr class="text-left text-gray-500 text-sm font-medium border-b">
497
- <th class="pb-3">#</th>
498
- <th class="pb-3">MAC Address</th>
499
- <th class="pb-3">Device</th>
500
- <th class="pb-3">User</th>
501
- <th class="pb-3">Total</th>
502
- <th class="pb-3">Paid</th>
503
- <th class="pb-3">Remaining</th>
504
- <th class="pb-3">Status</th>
505
- <th class="pb-3 text-right">Actions</th>
506
  </tr>
507
  </thead>
508
- <tbody id="device-table" class="device-table">
509
  <!-- Device rows will be populated here -->
510
  </tbody>
511
  </table>
512
 
513
- <div id="empty-state" class="py-12 text-center">
514
  <div class="mb-4">
515
- <i class="fas fa-inbox text-4xl text-gray-300"></i>
516
  </div>
517
- <h3 class="text-lg font-medium text-gray-700">No devices registered</h3>
518
- <p class="text-gray-500 mt-1">Add a new device to start monitoring</p>
519
  </div>
520
  </div>
521
  </div>
@@ -524,550 +466,365 @@
524
  </div>
525
 
526
  <script>
527
- // Initialize device data from localStorage or empty array
528
- let devices = JSON.parse(localStorage.getItem('networkDevices')) || [];
 
529
  let currentPaymentDevice = null;
530
-
531
- // DOM elements
 
532
  const deviceForm = document.getElementById('device-form');
533
  const deviceTable = document.getElementById('device-table');
534
  const emptyState = document.getElementById('empty-state');
535
  const paymentModal = document.getElementById('payment-modal');
536
  const searchInput = document.getElementById('search-input');
537
  const exportBtn = document.getElementById('export-btn');
538
-
539
- // Initialize the app
540
- document.addEventListener('DOMContentLoaded', () => {
541
- renderDeviceTable();
542
- updateSummary();
543
- attachEventListeners();
544
- });
545
-
546
- // Event listeners
547
- function attachEventListeners() {
548
- deviceForm.addEventListener('submit', addNewDevice);
549
- document.querySelector('.close-payment-modal').addEventListener('click', closePaymentModal);
550
- document.getElementById('add-payment-btn').addEventListener('click', addNewPayment);
551
- searchInput.addEventListener('input', filterDevices);
552
- exportBtn.addEventListener('click', exportToCSV);
553
- }
554
-
555
- // Add new device
556
- function addNewDevice(e) {
557
- e.preventDefault();
558
-
559
- const macAddress = document.getElementById('mac-address').value.trim();
560
- const deviceName = document.getElementById('device-name').value.trim();
561
- const userName = document.getElementById('user-name').value.trim();
562
- const totalAmount = parseFloat(document.getElementById('total-amount').value);
563
- const initialPayment = parseFloat(document.getElementById('initial-payment').value) || 0;
564
-
565
- // Validation
566
- if (!macAddress || !deviceName || !userName || isNaN(totalAmount)) {
567
- showToast('Please fill all required fields', 'error');
568
- return;
569
- }
570
-
571
- if (totalAmount <= 0) {
572
- showToast('Total amount must be greater than 0', 'error');
573
- return;
574
- }
575
-
576
- if (initialPayment < 0) {
577
- showToast('Initial payment cannot be negative', 'error');
578
- return;
579
- }
580
-
581
- // Create new device object
582
- const newDevice = {
583
- id: Date.now(),
584
- mac: formatMAC(macAddress),
585
- deviceName,
586
- userName,
587
- totalAmount,
588
- payments: [],
589
- addedTime: new Date().toLocaleString(),
590
- status: 'Active'
591
- };
592
-
593
- // Add initial payment if provided
594
- if (initialPayment > 0) {
595
- newDevice.payments.push({
596
- id: Date.now(),
597
- date: new Date().toLocaleString(),
598
- amount: initialPayment
599
- });
600
- }
601
-
602
- // Add to devices array and update storage
603
- devices.push(newDevice);
604
- localStorage.setItem('networkDevices', JSON.stringify(devices));
605
-
606
- // Reset form and update UI
607
- deviceForm.reset();
608
- renderDeviceTable();
609
- updateSummary();
610
-
611
- // Show success message
612
- showToast('Device added successfully!', 'success');
613
- }
614
-
615
- // Format MAC address (flexible)
616
- function formatMAC(mac) {
617
- // Remove any non-alphanumeric characters
618
- let cleanMac = mac.replace(/[^0-9A-Fa-f]/g, '');
619
-
620
- // If we have exactly 12 characters, format with colons
621
- if (cleanMac.length === 12) {
622
- return cleanMac.match(/.{1,2}/g).join(':').toUpperCase();
623
- }
624
 
625
- // Otherwise, return the cleaned version in uppercase
626
- return cleanMac.toUpperCase();
627
- }
628
-
629
- // Render device table
630
- function renderDeviceTable() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  deviceTable.innerHTML = '';
632
-
633
- if (devices.length === 0) {
634
- emptyState.classList.remove('hidden');
635
- return;
636
- }
637
-
638
- emptyState.classList.add('hidden');
639
-
640
- devices.forEach((device, index) => {
641
- // Calculate payment totals
642
- const paidAmount = device.payments.reduce((sum, payment) => sum + payment.amount, 0);
643
  const remainingAmount = device.totalAmount - paidAmount;
644
-
645
- // Determine payment status
646
- let paymentStatus = 'Unpaid';
647
- let statusClass = 'unpaid-badge';
648
-
649
  if (paidAmount >= device.totalAmount) {
650
  paymentStatus = 'Fully Paid';
651
- statusClass = 'paid-badge';
652
  } else if (paidAmount > 0) {
653
- paymentStatus = 'Partial Paid';
654
- statusClass = 'partial-badge';
 
 
 
655
  }
656
 
 
 
657
  const row = document.createElement('tr');
658
- row.className = 'border-b';
659
  row.innerHTML = `
660
- <td class="py-4">${index + 1}</td>
661
- <td class="py-4">
662
- <span class="mac-address">${device.mac}</span>
 
663
  </td>
664
- <td class="py-4 font-medium">${device.deviceName}</td>
665
- <td class="py-4">${device.userName}</td>
666
- <td class="py-4 font-bold">₹${device.totalAmount.toFixed(2)}</td>
667
- <td class="py-4 font-medium text-green-600">₹${paidAmount.toFixed(2)}</td>
668
- <td class="py-4 font-medium ${remainingAmount > 0 ? 'text-red-600' : 'text-green-600'}">
669
- ₹${Math.abs(remainingAmount).toFixed(2)}
 
 
670
  </td>
671
- <td class="py-4">
672
- <span class="payment-status ${statusClass}">
673
- ${paymentStatus}
674
- </span>
 
675
  </td>
676
- <td class="py-4 text-right">
677
- <div class="inline-flex space-x-1">
678
- <button class="action-btn payment-btn" data-id="${device.id}" title="Manage Payments">
679
- <i class="fas fa-rupee-sign"></i>
680
- </button>
681
- <button class="action-btn block-btn" data-id="${device.id}" title="Block Device">
682
- <i class="fas fa-ban"></i>
683
- </button>
684
- <button class="action-btn delete-btn" data-id="${device.id}" title="Delete Device">
685
- <i class="fas fa-trash"></i>
686
  </button>
 
687
  </div>
688
  </td>
689
  `;
690
  deviceTable.appendChild(row);
691
  });
692
-
693
- // Attach event listeners to action buttons
694
- document.querySelectorAll('.payment-btn').forEach(btn => {
695
- btn.addEventListener('click', () => openPaymentModal(btn.dataset.id));
696
- });
697
-
698
- document.querySelectorAll('.block-btn').forEach(btn => {
699
- btn.addEventListener('click', () => blockDevice(btn.dataset.id));
700
- });
701
-
702
- document.querySelectorAll('.delete-btn').forEach(btn => {
703
- btn.addEventListener('click', () => deleteDevice(btn.dataset.id));
704
- });
705
- }
706
-
707
- // Update summary stats
708
- function updateSummary() {
709
- // Payment calculations
710
- const totalRevenue = devices.reduce((sum, device) => {
711
- return sum + device.payments.reduce((deviceSum, payment) => deviceSum + payment.amount, 0);
712
- }, 0);
713
-
714
- const totalOutstanding = devices.reduce((sum, device) => {
715
- const paidAmount = device.payments.reduce((deviceSum, payment) => deviceSum + payment.amount, 0);
716
- return sum + (device.totalAmount - paidAmount);
717
- }, 0);
718
-
719
- // Payment status counts
720
- let fullyPaid = 0;
721
- let partialPaid = 0;
722
-
723
- devices.forEach(device => {
724
- const paidAmount = device.payments.reduce((sum, payment) => sum + payment.amount, 0);
725
- if (paidAmount >= device.totalAmount) {
726
- fullyPaid++;
727
- } else if (paidAmount > 0) {
728
- partialPaid++;
729
- }
730
- });
731
-
732
- // Average payment
733
- const paymentCount = devices.reduce((count, device) => count + device.payments.length, 0);
734
- const avgPayment = paymentCount > 0 ? totalRevenue / paymentCount : 0;
735
-
736
- // Update UI
737
- document.getElementById('total-revenue').textContent = `₹${totalRevenue.toFixed(2)}`;
738
- document.getElementById('outstanding-balance').textContent = `₹${Math.max(0, totalOutstanding).toFixed(2)}`;
739
- document.getElementById('avg-payment').textContent = `₹${avgPayment.toFixed(2)}`;
740
- document.getElementById('fully-paid-count').textContent = fullyPaid;
741
- document.getElementById('partial-paid-count').textContent = partialPaid;
742
-
743
- // Update progress bars
744
- const maxRevenue = Math.max(10000, totalRevenue);
745
- document.getElementById('revenue-bar').style.width = `${(totalRevenue / maxRevenue) * 100}%`;
746
-
747
- const maxOutstanding = Math.max(10000, totalOutstanding);
748
- document.getElementById('outstanding-bar').style.width = `${(totalOutstanding / maxOutstanding) * 100}%`;
749
-
750
- const maxAvg = Math.max(1000, avgPayment);
751
- document.getElementById('avg-payment-bar').style.width = `${(avgPayment / maxAvg) * 100}%`;
752
- }
753
-
754
- // Open payment modal
755
- function openPaymentModal(deviceId) {
756
- const device = devices.find(d => d.id == deviceId);
757
- if (!device) return;
758
-
759
- currentPaymentDevice = device;
760
-
761
- // Calculate payment totals
762
- const paidAmount = device.payments.reduce((sum, payment) => sum + payment.amount, 0);
763
- const remainingAmount = device.totalAmount - paidAmount;
764
- const paymentPercentage = (paidAmount / device.totalAmount) * 100;
765
-
766
- // Update modal content
767
- document.getElementById('payment-mac').textContent = device.mac;
768
- document.getElementById('payment-device').textContent = device.deviceName;
769
- document.getElementById('payment-user').textContent = device.userName;
770
- document.getElementById('payment-status').textContent = device.status;
771
- document.getElementById('payment-total').textContent = `₹${device.totalAmount.toFixed(2)}`;
772
- document.getElementById('payment-paid').textContent = `₹${paidAmount.toFixed(2)}`;
773
- document.getElementById('payment-remaining').textContent = `₹${Math.abs(remainingAmount).toFixed(2)}`;
774
- document.getElementById('payment-progress').style.width = `${paymentPercentage}%`;
775
-
776
- // Update payment history
777
- const paymentHistory = document.getElementById('payment-history');
778
- const noPayments = document.getElementById('no-payments');
779
-
780
- paymentHistory.innerHTML = '';
781
-
782
- if (device.payments.length === 0) {
783
- noPayments.classList.remove('hidden');
784
- } else {
785
- noPayments.classList.add('hidden');
786
- device.payments.forEach(payment => {
787
- const paymentItem = document.createElement('div');
788
- paymentItem.className = 'payment-item';
789
- paymentItem.innerHTML = `
790
- <div>
791
- <div class="font-medium">₹${payment.amount.toFixed(2)}</div>
792
- <div class="text-gray-500 text-sm">${payment.date}</div>
793
- </div>
794
- <div class="text-right">
795
- <div class="text-green-600 font-medium">Payment</div>
796
- <div class="text-gray-500 text-sm">ID: ${payment.id.toString().slice(-6)}</div>
797
- </div>
798
- `;
799
- paymentHistory.appendChild(paymentItem);
800
- });
801
  }
802
-
803
- // Show modal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  paymentModal.classList.add('active');
805
- }
806
-
807
- // Close payment modal
808
- function closePaymentModal() {
809
  paymentModal.classList.remove('active');
810
  currentPaymentDevice = null;
811
- }
812
-
813
- // Add new payment
814
- function addNewPayment() {
 
815
  if (!currentPaymentDevice) return;
816
-
817
  const amountInput = document.getElementById('new-payment-amount');
818
  const amount = parseFloat(amountInput.value);
819
-
 
 
 
820
  if (isNaN(amount) || amount <= 0) {
821
- showToast('Please enter a valid payment amount', 'error');
822
  return;
823
  }
824
-
825
- // Add payment to device
826
- currentPaymentDevice.payments.push({
827
- id: Date.now(),
828
- date: new Date().toLocaleString(),
829
- amount: amount
830
- });
831
-
832
- // Update storage and UI
833
- localStorage.setItem('networkDevices', JSON.stringify(devices));
834
- openPaymentModal(currentPaymentDevice.id);
835
- renderDeviceTable();
836
- updateSummary();
837
-
838
- // Reset input
839
  amountInput.value = '';
840
-
841
- // Show success message
842
  showToast('Payment added successfully!', 'success');
843
- }
844
 
845
- // Block device
846
- function blockDevice(deviceId) {
847
- const device = devices.find(d => d.id == deviceId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  if (!device) return;
849
-
850
  device.status = device.status === 'Active' ? 'Blocked' : 'Active';
851
-
852
- // Update storage and UI
853
- localStorage.setItem('networkDevices', JSON.stringify(devices));
854
- renderDeviceTable();
855
-
856
- // Show notification
857
- const action = device.status === 'Blocked' ? 'blocked' : 'unblocked';
858
- showToast(`Device ${device.mac} has been ${action}`, 'info');
859
- }
860
-
861
- // Delete device
862
- function deleteDevice(deviceId) {
863
- if (!confirm('Are you sure you want to delete this device?')) return;
864
-
865
- devices = devices.filter(d => d.id != deviceId);
866
-
867
- // Update storage and UI
868
- localStorage.setItem('networkDevices', JSON.stringify(devices));
869
- renderDeviceTable();
870
- updateSummary();
871
-
872
- showToast('Device deleted successfully!', 'success');
873
- }
874
-
875
- // Filter devices
876
- function filterDevices() {
877
- const searchTerm = searchInput.value.toLowerCase();
878
-
879
- if (!searchTerm) {
880
- renderDeviceTable();
881
- return;
882
  }
883
-
884
- const filtered = devices.filter(device =>
885
- device.mac.toLowerCase().includes(searchTerm) ||
886
- device.deviceName.toLowerCase().includes(searchTerm) ||
887
- device.userName.toLowerCase().includes(searchTerm)
 
 
 
888
  );
889
-
890
- renderFilteredDevices(filtered);
891
- }
892
-
893
- // Render filtered devices
894
- function renderFilteredDevices(filteredDevices) {
895
- deviceTable.innerHTML = '';
896
-
897
- if (filteredDevices.length === 0) {
898
- emptyState.classList.remove('hidden');
899
- return;
900
- }
901
-
902
- emptyState.classList.add('hidden');
903
-
904
- filteredDevices.forEach((device, index) => {
905
- // Calculate payment totals
906
- const paidAmount = device.payments.reduce((sum, payment) => sum + payment.amount, 0);
907
- const remainingAmount = device.totalAmount - paidAmount;
908
-
909
- // Determine payment status
910
- let paymentStatus = 'Unpaid';
911
- let statusClass = 'unpaid-badge';
912
-
913
- if (paidAmount >= device.totalAmount) {
914
- paymentStatus = 'Fully Paid';
915
- statusClass = 'paid-badge';
916
- } else if (paidAmount > 0) {
917
- paymentStatus = 'Partial Paid';
918
- statusClass = 'partial-badge';
919
- }
920
-
921
- const row = document.createElement('tr');
922
- row.className = 'border-b';
923
- row.innerHTML = `
924
- <td class="py-4">${index + 1}</td>
925
- <td class="py-4">
926
- <span class="mac-address">${device.mac}</span>
927
- </td>
928
- <td class="py-4 font-medium">${device.deviceName}</td>
929
- <td class="py-4">${device.userName}</td>
930
- <td class="py-4 font-bold">₹${device.totalAmount.toFixed(2)}</td>
931
- <td class="py-4 font-medium text-green-600">₹${paidAmount.toFixed(2)}</td>
932
- <td class="py-4 font-medium ${remainingAmount > 0 ? 'text-red-600' : 'text-green-600'}">
933
- ₹${Math.abs(remainingAmount).toFixed(2)}
934
- </td>
935
- <td class="py-4">
936
- <span class="payment-status ${statusClass}">
937
- ${paymentStatus}
938
- </span>
939
- </td>
940
- <td class="py-4 text-right">
941
- <div class="inline-flex space-x-1">
942
- <button class="action-btn payment-btn" data-id="${device.id}" title="Manage Payments">
943
- <i class="fas fa-rupee-sign"></i>
944
- </button>
945
- <button class="action-btn block-btn" data-id="${device.id}" title="Block Device">
946
- <i class="fas fa-ban"></i>
947
- </button>
948
- <button class="action-btn delete-btn" data-id="${device.id}" title="Delete Device">
949
- <i class="fas fa-trash"></i>
950
- </button>
951
- </div>
952
- </td>
953
- `;
954
- deviceTable.appendChild(row);
955
- });
956
-
957
- // Reattach event listeners
958
- document.querySelectorAll('.payment-btn').forEach(btn => {
959
- btn.addEventListener('click', () => openPaymentModal(btn.dataset.id));
960
- });
961
-
962
- document.querySelectorAll('.block-btn').forEach(btn => {
963
- btn.addEventListener('click', () => blockDevice(btn.dataset.id));
964
- });
965
-
966
- document.querySelectorAll('.delete-btn').forEach(btn => {
967
- btn.addEventListener('click', () => deleteDevice(btn.dataset.id));
968
- });
969
- }
970
-
971
- // Export to CSV
972
- function exportToCSV() {
973
  if (devices.length === 0) {
974
- showToast('No devices to export', 'warning');
975
  return;
976
  }
977
-
978
- // Create CSV header
979
- let csvContent = "MAC Address,Device Name,User Name,Total Amount,Paid Amount,Remaining Amount,Status,Added Time\n";
980
-
981
- // Add each device as a row
982
- devices.forEach(device => {
983
- const paidAmount = device.payments.reduce((sum, payment) => sum + payment.amount, 0);
984
- const remainingAmount = device.totalAmount - paidAmount;
 
985
 
986
- csvContent += `"${device.mac}","${device.deviceName}","${device.userName}",${device.totalAmount},${paidAmount},${remainingAmount},"${device.status}","${device.addedTime}"\n`;
 
987
  });
988
-
989
- // Create download link
990
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
991
- const url = URL.createObjectURL(blob);
992
- const link = document.createElement('a');
993
- link.setAttribute('href', url);
994
- link.setAttribute('download', 'network-devices.csv');
995
- link.style.visibility = 'hidden';
996
  document.body.appendChild(link);
997
  link.click();
998
  document.body.removeChild(link);
999
-
1000
- showToast('CSV exported successfully!', 'success');
1001
- }
1002
-
1003
- // Show toast notification
1004
- function showToast(message, type = 'info') {
1005
- const container = document.getElementById('toast-container');
1006
- const toast = document.createElement('div');
1007
- toast.className = `toast ${type}`;
1008
- toast.textContent = message;
1009
- container.appendChild(toast);
1010
-
1011
- // Show toast
1012
- setTimeout(() => {
1013
- toast.classList.add('show');
1014
- }, 10);
1015
-
1016
- // Remove toast after delay
1017
- setTimeout(() => {
1018
- toast.classList.remove('show');
1019
- setTimeout(() => {
1020
- toast.remove();
1021
- }, 400);
1022
- }, 3000);
1023
- }
1024
-
1025
- // Initialize sample data
1026
- function initializeSampleData() {
1027
- if (devices.length === 0) {
1028
- devices = [
1029
- {
1030
- id: 1,
1031
- mac: 'AA:BB:CC:DD:EE:FF',
1032
- deviceName: 'John\'s iPhone',
1033
- userName: 'John Smith',
1034
- totalAmount: 3000,
1035
- payments: [
1036
- { id: 101, date: new Date().toLocaleString(), amount: 1000 },
1037
- { id: 102, date: new Date().toLocaleString(), amount: 500 }
1038
- ],
1039
- addedTime: new Date().toLocaleString(),
1040
- status: 'Active'
1041
- },
1042
- {
1043
- id: 2,
1044
- mac: '112233445566',
1045
- deviceName: 'Office Printer',
1046
- userName: 'Office Admin',
1047
- totalAmount: 5000,
1048
- payments: [
1049
- { id: 201, date: new Date().toLocaleString(), amount: 5000 }
1050
- ],
1051
- addedTime: new Date().toLocaleString(),
1052
- status: 'Active'
1053
- },
1054
- {
1055
- id: 3,
1056
- mac: 'FF-EE-DD-CC-BB-AA',
1057
- deviceName: 'Sarah\'s Laptop',
1058
- userName: 'Sarah Johnson',
1059
- totalAmount: 2500,
1060
- payments: [],
1061
- addedTime: new Date().toLocaleString(),
1062
- status: 'Blocked'
1063
- }
1064
- ];
1065
- localStorage.setItem('networkDevices', JSON.stringify(devices));
1066
- }
1067
- }
1068
-
1069
- // Initialize app
1070
  initializeSampleData();
 
 
1071
  </script>
1072
  </body>
1073
  </html>
 
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
 
 
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 {
 
78
  padding: 12px 16px;
79
  transition: all 0.3s;
80
  width: 100%;
81
+ background-color: #f8fafc;
82
  }
83
 
84
  .form-input:focus {
85
  border-color: var(--primary);
86
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
87
  outline: none;
88
+ background-color: white;
89
  }
90
 
91
  .toast {
 
108
  opacity: 1;
109
  }
110
 
111
+ .toast.success { background: var(--success); }
112
+ .toast.error { background: var(--danger); }
113
+ .toast.info { background: var(--info); }
114
+ .toast.warning { background: var(--warning); }
 
 
 
 
 
 
 
115
 
116
  .btn {
117
  border-radius: 10px;
118
  padding: 12px 24px;
119
+ font-weight: 600;
120
  transition: all 0.3s;
121
  border: none;
122
  cursor: pointer;
 
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;
 
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 {
 
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;
 
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
  }
 
234
  .payment-item:last-child {
235
  border-bottom: none;
236
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  </style>
238
  </head>
239
  <body class="min-h-screen py-8 px-4">
240
  <!-- Toast Container -->
241
+ <div id="toast-container" class="fixed bottom-0 right-0 p-8 space-y-3 z-50"></div>
242
 
243
  <!-- Payment Modal -->
244
  <div class="modal-overlay" id="payment-modal">
 
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
 
 
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>
 
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>
437
  </div>
438
 
439
  <div class="overflow-x-auto">
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>
 
466
  </div>
467
 
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');
494
+ const icons = { success: 'fa-check-circle', error: 'fa-times-circle', info: 'fa-info-circle', warning: 'fa-exclamation-triangle' };
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');
502
+ toast.addEventListener('transitionend', () => toast.remove());
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>
830
  </html>