Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Update frontend/src/pages/SuperAdminPanel.jsx
Browse files
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 601 |
});
|
| 602 |
if (res.ok) {
|
| 603 |
-
toast.success(
|
| 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 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
|
| 1082 |
-
|
| 1083 |
-
|
| 1084 |
-
)}
|
| 1085 |
-
|
| 1086 |
-
|
| 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 |
};
|