larxius commited on
Commit
0eacd51
·
verified ·
1 Parent(s): 399f08f

Update frontend/src/pages/SuperAdminPanel.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/SuperAdminPanel.jsx +221 -25
frontend/src/pages/SuperAdminPanel.jsx CHANGED
@@ -589,7 +589,16 @@ const SuperAdminPanel = () => {
589
  return <div className="text-on-surface text-center mt-20 font-bold">Access Denied. You do not have LarShield Management permissions.</div>;
590
  }
591
 
592
- const handleCompleteBooking = async (bookingId) => {
 
 
 
 
 
 
 
 
 
593
  try {
594
  const res = await fetch(`/api/demo/bookings/${bookingId}`, {
595
  method: 'PUT',
@@ -597,10 +606,10 @@ const SuperAdminPanel = () => {
597
  'Authorization': `Bearer ${localStorage.getItem('wss_token')}`,
598
  'Content-Type': 'application/json'
599
  },
600
- body: JSON.stringify({ status: 'completed' })
601
  });
602
  if (res.ok) {
603
- toast.success("Booking marked as completed");
604
  fetchStats();
605
  } else {
606
  toast.error("Failed to update booking status");
@@ -610,6 +619,64 @@ const SuperAdminPanel = () => {
610
  }
611
  };
612
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  return (
614
  <div className="w-full text-on-surface animate-fade-in">
615
  {/* Support Engineer Information Banner */}
@@ -1063,28 +1130,83 @@ const SuperAdminPanel = () => {
1063
  </tr>
1064
  </thead>
1065
  <tbody className="divide-y divide-outline-variant">
1066
- {demoBookings.map(b => (
1067
- <tr key={b.id} className="hover:bg-surface-container-lowest transition-colors">
1068
- <td className="px-md py-sm font-bold text-on-surface text-[13px]">{b.email}</td>
1069
- <td className="px-md py-sm text-on-surface-variant text-[13px]">{b.company_size.replace('Company Size: ', '')}</td>
1070
- <td className="px-md py-sm text-on-surface-variant text-[13px]">
1071
- {b.meeting_date} <br/>
1072
- <span className="text-[11px] font-bold text-primary">{b.meeting_time}</span>
1073
- </td>
1074
- <td className="px-md py-sm">
1075
- <span className={`px-2 py-0.5 rounded text-[11px] font-bold uppercase tracking-wider ${b.status === 'completed' ? 'bg-green-500/10 text-green-600' : 'bg-orange-500/10 text-orange-600'}`}>
1076
- {b.status}
1077
- </span>
1078
- </td>
1079
- <td className="px-md py-sm">
1080
- {b.status !== 'completed' && (
1081
- <button onClick={() => handleCompleteBooking(b.id)} className="bg-primary text-white border-0 py-1 px-3 rounded font-bold cursor-pointer text-[12px] hover:brightness-110 active:scale-95 transition-all">
1082
- Mark Complete
1083
- </button>
1084
- )}
1085
- </td>
1086
- </tr>
1087
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1088
  </tbody>
1089
  </table>
1090
  </div>
@@ -1287,6 +1409,80 @@ const SuperAdminPanel = () => {
1287
  </div>
1288
  )}
1289
  </CustomModal>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1290
  </div>
1291
  );
1292
  };
 
589
  return <div className="text-on-surface text-center mt-20 font-bold">Access Denied. You do not have LarShield Management permissions.</div>;
590
  }
591
 
592
+ const [rescheduleModal, setRescheduleModal] = useState({
593
+ isOpen: false,
594
+ bookingId: null,
595
+ email: '',
596
+ meetingDate: '',
597
+ meetingTime: '',
598
+ status: 'rescheduled'
599
+ });
600
+
601
+ const handleUpdateBookingStatus = async (bookingId, status) => {
602
  try {
603
  const res = await fetch(`/api/demo/bookings/${bookingId}`, {
604
  method: 'PUT',
 
606
  'Authorization': `Bearer ${localStorage.getItem('wss_token')}`,
607
  'Content-Type': 'application/json'
608
  },
609
+ body: JSON.stringify({ status })
610
  });
611
  if (res.ok) {
612
+ toast.success(`Booking status updated to ${status}`);
613
  fetchStats();
614
  } else {
615
  toast.error("Failed to update booking status");
 
619
  }
620
  };
621
 
622
+ const handleOpenReschedule = (booking) => {
623
+ setRescheduleModal({
624
+ isOpen: true,
625
+ bookingId: booking.id,
626
+ email: booking.email,
627
+ meetingDate: booking.meeting_date || '',
628
+ meetingTime: booking.meeting_time || '',
629
+ status: 'rescheduled'
630
+ });
631
+ };
632
+
633
+ const handleSaveReschedule = async (e) => {
634
+ e.preventDefault();
635
+ try {
636
+ const res = await fetch(`/api/demo/bookings/${rescheduleModal.bookingId}`, {
637
+ method: 'PUT',
638
+ headers: {
639
+ 'Authorization': `Bearer ${localStorage.getItem('wss_token')}`,
640
+ 'Content-Type': 'application/json'
641
+ },
642
+ body: JSON.stringify({
643
+ meeting_date: rescheduleModal.meetingDate,
644
+ meeting_time: rescheduleModal.meetingTime,
645
+ status: rescheduleModal.status
646
+ })
647
+ });
648
+ if (res.ok) {
649
+ toast.success("Demo booking rescheduled successfully!");
650
+ setRescheduleModal({ isOpen: false, bookingId: null, email: '', meetingDate: '', meetingTime: '', status: 'rescheduled' });
651
+ fetchStats();
652
+ } else {
653
+ toast.error("Failed to reschedule demo booking");
654
+ }
655
+ } catch (err) {
656
+ toast.error("Error rescheduling booking");
657
+ }
658
+ };
659
+
660
+ const handleDeleteBooking = async (bookingId) => {
661
+ if (!window.confirm("Are you sure you want to delete this demo booking lead?")) return;
662
+ try {
663
+ const res = await fetch(`/api/demo/bookings/${bookingId}`, {
664
+ method: 'DELETE',
665
+ headers: {
666
+ 'Authorization': `Bearer ${localStorage.getItem('wss_token')}`
667
+ }
668
+ });
669
+ if (res.ok) {
670
+ toast.success("Demo lead deleted successfully");
671
+ fetchStats();
672
+ } else {
673
+ toast.error("Failed to delete demo lead");
674
+ }
675
+ } catch (err) {
676
+ toast.error("Error deleting lead");
677
+ }
678
+ };
679
+
680
  return (
681
  <div className="w-full text-on-surface animate-fade-in">
682
  {/* Support Engineer Information Banner */}
 
1130
  </tr>
1131
  </thead>
1132
  <tbody className="divide-y divide-outline-variant">
1133
+ {demoBookings.map(b => {
1134
+ const statusStyle =
1135
+ b.status === 'completed'
1136
+ ? 'bg-green-500/10 text-green-600 border-green-500/30'
1137
+ : b.status === 'cancelled'
1138
+ ? 'bg-red-500/10 text-red-600 border-red-500/30'
1139
+ : b.status === 'rescheduled'
1140
+ ? 'bg-blue-500/10 text-blue-600 border-blue-500/30'
1141
+ : 'bg-orange-500/10 text-orange-600 border-orange-500/30';
1142
+
1143
+ const statusLabel =
1144
+ b.status === 'completed' ? 'Completed / Conducted' :
1145
+ b.status === 'cancelled' ? 'Cancelled / Not Conducted' :
1146
+ b.status === 'rescheduled' ? 'Rescheduled' : 'Pending';
1147
+
1148
+ return (
1149
+ <tr key={b.id} className="hover:bg-surface-container-lowest transition-colors">
1150
+ <td className="px-md py-sm font-bold text-on-surface text-[13px]">{b.email}</td>
1151
+ <td className="px-md py-sm text-on-surface-variant text-[13px]">{b.company_size?.replace('Company Size: ', '')}</td>
1152
+ <td className="px-md py-sm text-on-surface-variant text-[13px]">
1153
+ {b.meeting_date} <br/>
1154
+ <span className="text-[11px] font-bold text-primary">{b.meeting_time}</span>
1155
+ </td>
1156
+ <td className="px-md py-sm">
1157
+ <span className={`px-2.5 py-1 rounded-full text-[11px] font-bold uppercase tracking-wider border ${statusStyle}`}>
1158
+ {statusLabel}
1159
+ </span>
1160
+ </td>
1161
+ <td className="px-md py-sm">
1162
+ <div className="flex items-center gap-1.5 flex-wrap">
1163
+ {/* Complete Button */}
1164
+ {b.status !== 'completed' && (
1165
+ <button
1166
+ onClick={() => handleUpdateBookingStatus(b.id, 'completed')}
1167
+ className="bg-green-600 text-white border-0 py-1 px-2.5 rounded font-bold cursor-pointer text-[11px] hover:bg-green-700 transition-all flex items-center gap-1 shadow-xs"
1168
+ title="Mark Demo as Completed"
1169
+ >
1170
+ <span className="material-symbols-outlined text-[14px]">check_circle</span>
1171
+ Complete
1172
+ </button>
1173
+ )}
1174
+
1175
+ {/* Cancel / Couldn't Conduct Button */}
1176
+ {b.status !== 'cancelled' && (
1177
+ <button
1178
+ onClick={() => handleUpdateBookingStatus(b.id, 'cancelled')}
1179
+ className="bg-red-500/10 text-red-600 hover:bg-red-600 hover:text-white border border-red-500/30 py-1 px-2.5 rounded font-bold cursor-pointer text-[11px] transition-all flex items-center gap-1"
1180
+ title="Mark as Cancelled or Couldn't Show Demo"
1181
+ >
1182
+ <span className="material-symbols-outlined text-[14px]">cancel</span>
1183
+ Cancelled
1184
+ </button>
1185
+ )}
1186
+
1187
+ {/* Reschedule Button */}
1188
+ <button
1189
+ onClick={() => handleOpenReschedule(b)}
1190
+ className="bg-blue-500/10 text-blue-600 hover:bg-blue-600 hover:text-white border border-blue-500/30 py-1 px-2.5 rounded font-bold cursor-pointer text-[11px] transition-all flex items-center gap-1"
1191
+ title="Reschedule Date & Time"
1192
+ >
1193
+ <span className="material-symbols-outlined text-[14px]">edit_calendar</span>
1194
+ Reschedule
1195
+ </button>
1196
+
1197
+ {/* Delete Lead Button */}
1198
+ <button
1199
+ onClick={() => handleDeleteBooking(b.id)}
1200
+ className="text-on-surface-variant hover:text-red-600 border-0 bg-transparent p-1 cursor-pointer transition-colors"
1201
+ title="Delete Lead"
1202
+ >
1203
+ <span className="material-symbols-outlined text-[16px]">delete</span>
1204
+ </button>
1205
+ </div>
1206
+ </td>
1207
+ </tr>
1208
+ );
1209
+ })}
1210
  </tbody>
1211
  </table>
1212
  </div>
 
1409
  </div>
1410
  )}
1411
  </CustomModal>
1412
+
1413
+ {/* Reschedule Demo Call Modal */}
1414
+ {rescheduleModal.isOpen && (
1415
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
1416
+ <div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm" onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}></div>
1417
+ <div className="relative bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-slate-800 shadow-2xl max-w-md w-full p-6 z-10 animate-slide-up text-left font-sans">
1418
+ <div className="flex justify-between items-center pb-3 border-b border-slate-100 dark:border-slate-800">
1419
+ <h3 className="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
1420
+ <span className="material-symbols-outlined text-primary">edit_calendar</span>
1421
+ Reschedule Demo Call
1422
+ </h3>
1423
+ <button onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })} className="text-slate-400 hover:text-slate-600 border-0 bg-transparent cursor-pointer p-1">
1424
+ <span className="material-symbols-outlined text-[20px]">close</span>
1425
+ </button>
1426
+ </div>
1427
+ <form onSubmit={handleSaveReschedule} className="mt-4 flex flex-col gap-4">
1428
+ <div>
1429
+ <label className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block mb-1">Lead Email</label>
1430
+ <input type="text" disabled value={rescheduleModal.email} className="w-full bg-slate-100 border border-slate-200 rounded-lg p-2.5 text-xs text-slate-700 font-bold" />
1431
+ </div>
1432
+ <div>
1433
+ <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">New Meeting Date</label>
1434
+ <input
1435
+ type="text"
1436
+ required
1437
+ placeholder="e.g. August 28, 2026"
1438
+ value={rescheduleModal.meetingDate}
1439
+ onChange={(e) => setRescheduleModal({ ...rescheduleModal, meetingDate: e.target.value })}
1440
+ className="w-full border border-slate-300 rounded-lg p-2.5 text-xs text-slate-900 dark:text-white dark:bg-slate-800 focus:outline-none focus:border-primary"
1441
+ />
1442
+ </div>
1443
+ <div>
1444
+ <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">New Meeting Time</label>
1445
+ <input
1446
+ type="text"
1447
+ required
1448
+ placeholder="e.g. 04:00 PM"
1449
+ value={rescheduleModal.meetingTime}
1450
+ onChange={(e) => setRescheduleModal({ ...rescheduleModal, meetingTime: e.target.value })}
1451
+ className="w-full border border-slate-300 rounded-lg p-2.5 text-xs text-slate-900 dark:text-white dark:bg-slate-800 focus:outline-none focus:border-primary"
1452
+ />
1453
+ </div>
1454
+ <div>
1455
+ <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">Update Status</label>
1456
+ <select
1457
+ value={rescheduleModal.status}
1458
+ onChange={(e) => setRescheduleModal({ ...rescheduleModal, status: e.target.value })}
1459
+ className="w-full border border-slate-300 rounded-lg p-2.5 text-xs text-slate-900 dark:text-white dark:bg-slate-800 focus:outline-none focus:border-primary cursor-pointer"
1460
+ >
1461
+ <option value="rescheduled">Rescheduled</option>
1462
+ <option value="pending">Pending</option>
1463
+ <option value="completed">Completed / Conducted</option>
1464
+ <option value="cancelled">Cancelled / Not Conducted</option>
1465
+ </select>
1466
+ </div>
1467
+ <div className="flex justify-end gap-2 pt-3 border-t border-slate-100 dark:border-slate-800">
1468
+ <button
1469
+ type="button"
1470
+ onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}
1471
+ className="px-4 py-2 rounded-lg text-xs font-bold text-slate-600 border border-slate-300 hover:bg-slate-100 cursor-pointer bg-white"
1472
+ >
1473
+ Cancel
1474
+ </button>
1475
+ <button
1476
+ type="submit"
1477
+ className="px-4 py-2 rounded-lg text-xs font-bold text-white bg-primary hover:brightness-110 shadow-sm cursor-pointer border-0"
1478
+ >
1479
+ Save & Update
1480
+ </button>
1481
+ </div>
1482
+ </form>
1483
+ </div>
1484
+ </div>
1485
+ )}
1486
  </div>
1487
  );
1488
  };