larxius commited on
Commit
22886c8
·
verified ·
1 Parent(s): 46e63f6

Update frontend/src/pages/SuperAdminPanel.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/SuperAdminPanel.jsx +202 -76
frontend/src/pages/SuperAdminPanel.jsx CHANGED
@@ -129,6 +129,59 @@ const BillingTierCard = ({ tier, onSave }) => {
129
  );
130
  };
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  const SuperAdminPanel = () => {
133
  const { user, refreshAccessToken, loading: authLoading } = useAuth();
134
 
@@ -330,7 +383,9 @@ const SuperAdminPanel = () => {
330
  bookingId: null,
331
  email: '',
332
  meetingDate: '',
 
333
  meetingTime: '',
 
334
  status: 'rescheduled'
335
  });
336
 
@@ -822,13 +877,22 @@ const SuperAdminPanel = () => {
822
  };
823
 
824
  const handleOpenReschedule = (booking) => {
 
 
 
 
 
 
 
825
  setRescheduleModal({
826
  isOpen: true,
827
  bookingId: booking.id,
828
  email: booking.email,
829
- meetingDate: booking.meeting_date || '',
830
- meetingTime: booking.meeting_time || '',
831
- status: 'rescheduled'
 
 
832
  });
833
  };
834
 
@@ -842,14 +906,14 @@ const SuperAdminPanel = () => {
842
  'Content-Type': 'application/json'
843
  },
844
  body: JSON.stringify({
845
- meeting_date: rescheduleModal.meetingDate,
846
- meeting_time: rescheduleModal.meetingTime,
847
  status: rescheduleModal.status
848
  })
849
  });
850
  if (res.ok) {
851
  toast.success("Demo booking rescheduled successfully!");
852
- setRescheduleModal({ isOpen: false, bookingId: null, email: '', meetingDate: '', meetingTime: '', status: 'rescheduled' });
853
  fetchStats();
854
  } else {
855
  toast.error("Failed to reschedule demo booking");
@@ -1598,79 +1662,141 @@ const SuperAdminPanel = () => {
1598
  )}
1599
  </CustomModal>
1600
 
1601
- {/* Reschedule Demo Call Modal */}
1602
- {rescheduleModal.isOpen && (
1603
- <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
1604
- <div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm" onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}></div>
1605
- <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 hide-scrollbar">
1606
- <div className="flex justify-between items-center pb-3 border-b border-slate-100 dark:border-slate-800">
1607
- <h3 className="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
1608
- <span className="material-symbols-outlined text-primary">edit_calendar</span>
1609
- Reschedule Demo Call
1610
- </h3>
1611
- <button onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })} className="text-slate-400 hover:text-slate-600 border-0 bg-transparent cursor-pointer p-1">
1612
- <span className="material-symbols-outlined text-[20px]">close</span>
1613
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1614
  </div>
1615
- <form onSubmit={handleSaveReschedule} className="mt-4 flex flex-col gap-4">
1616
- <div>
1617
- <label className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block mb-1">Lead Email</label>
1618
- <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" />
1619
- </div>
1620
- <div>
1621
- <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">New Meeting Date</label>
1622
- <input
1623
- type="text"
1624
- required
1625
- placeholder="e.g. August 28, 2026"
1626
- value={rescheduleModal.meetingDate}
1627
- onChange={(e) => setRescheduleModal({ ...rescheduleModal, meetingDate: e.target.value })}
1628
- 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"
1629
- />
1630
- </div>
1631
- <div>
1632
- <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">New Meeting Time</label>
1633
- <input
1634
- type="text"
1635
- required
1636
- placeholder="e.g. 04:00 PM"
1637
- value={rescheduleModal.meetingTime}
1638
- onChange={(e) => setRescheduleModal({ ...rescheduleModal, meetingTime: e.target.value })}
1639
- 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"
1640
- />
1641
- </div>
1642
- <div>
1643
- <label className="text-xs font-bold text-slate-700 dark:text-slate-300 block mb-1">Update Status</label>
1644
- <select
1645
- value={rescheduleModal.status}
1646
- onChange={(e) => setRescheduleModal({ ...rescheduleModal, status: e.target.value })}
1647
- 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"
1648
- >
1649
- <option value="rescheduled">Rescheduled</option>
1650
- <option value="pending">Pending</option>
1651
- <option value="completed">Completed / Conducted</option>
1652
- <option value="cancelled">Cancelled / Not Conducted</option>
1653
- </select>
1654
- </div>
1655
- <div className="flex justify-end gap-2 pt-3 border-t border-slate-100 dark:border-slate-800">
1656
- <button
1657
- type="button"
1658
- onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}
1659
- 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"
1660
- >
1661
- Cancel
1662
- </button>
1663
- <button
1664
- type="submit"
1665
- className="px-4 py-2 rounded-lg text-xs font-bold text-white bg-primary hover:brightness-110 shadow-sm cursor-pointer border-0"
1666
- >
1667
- Save & Update
1668
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1669
  </div>
1670
- </form>
1671
  </div>
1672
- </div>
1673
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1674
  </div>
1675
  );
1676
  };
 
129
  );
130
  };
131
 
132
+ // Helper functions for Reschedule Modal Date & Time
133
+ const parseToISODate = (dateStr) => {
134
+ if (!dateStr) return new Date().toISOString().split('T')[0];
135
+ if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr;
136
+ const parsed = new Date(dateStr);
137
+ if (!isNaN(parsed.getTime())) {
138
+ const yyyy = parsed.getFullYear();
139
+ const mm = String(parsed.getMonth() + 1).padStart(2, '0');
140
+ const dd = String(parsed.getDate()).padStart(2, '0');
141
+ return `${yyyy}-${mm}-${dd}`;
142
+ }
143
+ return new Date().toISOString().split('T')[0];
144
+ };
145
+
146
+ const formatToReadableDate = (isoStr) => {
147
+ if (!isoStr) return '';
148
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(isoStr)) return isoStr;
149
+ const [yyyy, mm, dd] = isoStr.split('-');
150
+ const parsed = new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10));
151
+ if (!isNaN(parsed.getTime())) {
152
+ return parsed.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
153
+ }
154
+ return isoStr;
155
+ };
156
+
157
+ const parseToISOTime = (timeStr) => {
158
+ if (!timeStr) return '09:00';
159
+ if (/^\d{2}:\d{2}$/.test(timeStr)) return timeStr;
160
+ const match = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)?/i);
161
+ if (match) {
162
+ let hours = parseInt(match[1], 10);
163
+ const minutes = match[2];
164
+ const ampm = match[3] ? match[3].toUpperCase() : null;
165
+ if (ampm === 'PM' && hours < 12) hours += 12;
166
+ if (ampm === 'AM' && hours === 12) hours = 0;
167
+ return `${String(hours).padStart(2, '0')}:${minutes}`;
168
+ }
169
+ return '09:00';
170
+ };
171
+
172
+ const formatTo12HrTime = (isoTime) => {
173
+ if (!isoTime) return '';
174
+ if (/AM|PM/i.test(isoTime)) return isoTime;
175
+ const [hStr, mStr] = isoTime.split(':');
176
+ if (hStr !== undefined && mStr !== undefined) {
177
+ let h = parseInt(hStr, 10);
178
+ const ampm = h >= 12 ? 'PM' : 'AM';
179
+ h = h % 12 || 12;
180
+ return `${String(h).padStart(2, '0')}:${mStr} ${ampm}`;
181
+ }
182
+ return isoTime;
183
+ };
184
+
185
  const SuperAdminPanel = () => {
186
  const { user, refreshAccessToken, loading: authLoading } = useAuth();
187
 
 
383
  bookingId: null,
384
  email: '',
385
  meetingDate: '',
386
+ isoDate: '',
387
  meetingTime: '',
388
+ isoTime: '',
389
  status: 'rescheduled'
390
  });
391
 
 
877
  };
878
 
879
  const handleOpenReschedule = (booking) => {
880
+ const rawDate = booking.meeting_date || '';
881
+ const rawTime = booking.meeting_time || '';
882
+ const isoDate = parseToISODate(rawDate);
883
+ const isoTime = parseToISOTime(rawTime);
884
+ const formattedDate = formatToReadableDate(isoDate);
885
+ const formattedTime = formatTo12HrTime(isoTime);
886
+
887
  setRescheduleModal({
888
  isOpen: true,
889
  bookingId: booking.id,
890
  email: booking.email,
891
+ meetingDate: formattedDate,
892
+ isoDate: isoDate,
893
+ meetingTime: formattedTime,
894
+ isoTime: isoTime,
895
+ status: booking.status === 'pending' || !booking.status ? 'rescheduled' : booking.status
896
  });
897
  };
898
 
 
906
  'Content-Type': 'application/json'
907
  },
908
  body: JSON.stringify({
909
+ meeting_date: rescheduleModal.meetingDate || formatToReadableDate(rescheduleModal.isoDate),
910
+ meeting_time: rescheduleModal.meetingTime || formatTo12HrTime(rescheduleModal.isoTime),
911
  status: rescheduleModal.status
912
  })
913
  });
914
  if (res.ok) {
915
  toast.success("Demo booking rescheduled successfully!");
916
+ setRescheduleModal({ isOpen: false, bookingId: null, email: '', meetingDate: '', isoDate: '', meetingTime: '', isoTime: '', status: 'rescheduled' });
917
  fetchStats();
918
  } else {
919
  toast.error("Failed to reschedule demo booking");
 
1662
  )}
1663
  </CustomModal>
1664
 
1665
+ {/* Reschedule Demo Call Modal using CustomModal */}
1666
+ <CustomModal
1667
+ isOpen={rescheduleModal.isOpen}
1668
+ onClose={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}
1669
+ title="Reschedule Demo Call"
1670
+ description="Select a new meeting date, time slot, and update booking status."
1671
+ maxWidth="max-w-lg"
1672
+ >
1673
+ <form onSubmit={handleSaveReschedule} className="flex flex-col gap-4 text-left">
1674
+ <div>
1675
+ <label className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider block mb-1">Lead Email</label>
1676
+ <input
1677
+ type="text"
1678
+ disabled
1679
+ value={rescheduleModal.email}
1680
+ className="w-full bg-surface-container border border-outline-variant/60 rounded-lg p-2.5 text-xs text-on-surface-variant font-bold cursor-not-allowed"
1681
+ />
1682
+ </div>
1683
+
1684
+ <div>
1685
+ <div className="flex justify-between items-center mb-1">
1686
+ <label className="text-xs font-bold text-on-surface block">New Meeting Date</label>
1687
+ {rescheduleModal.meetingDate && (
1688
+ <span className="text-[11px] font-bold text-primary bg-primary/10 px-2 py-0.5 rounded border border-primary/20 flex items-center gap-1">
1689
+ <span className="material-symbols-outlined text-[13px]">calendar_today</span>
1690
+ {rescheduleModal.meetingDate}
1691
+ </span>
1692
+ )}
1693
  </div>
1694
+ <input
1695
+ type="date"
1696
+ required
1697
+ value={rescheduleModal.isoDate}
1698
+ onChange={(e) => {
1699
+ const newIso = e.target.value;
1700
+ setRescheduleModal(prev => ({
1701
+ ...prev,
1702
+ isoDate: newIso,
1703
+ meetingDate: formatToReadableDate(newIso)
1704
+ }));
1705
+ }}
1706
+ className="w-full border border-outline-variant rounded-lg p-2.5 text-xs text-on-surface bg-surface-container-lowest focus:border-primary outline-none cursor-pointer"
1707
+ />
1708
+ </div>
1709
+
1710
+ <div>
1711
+ <div className="flex justify-between items-center mb-1">
1712
+ <label className="text-xs font-bold text-on-surface block">New Meeting Time</label>
1713
+ {rescheduleModal.meetingTime && (
1714
+ <span className="text-[11px] font-bold text-primary bg-primary/10 px-2 py-0.5 rounded border border-primary/20 flex items-center gap-1">
1715
+ <span className="material-symbols-outlined text-[13px]">schedule</span>
1716
+ {rescheduleModal.meetingTime}
1717
+ </span>
1718
+ )}
1719
+ </div>
1720
+
1721
+ {/* Time Picker */}
1722
+ <input
1723
+ type="time"
1724
+ required
1725
+ value={rescheduleModal.isoTime}
1726
+ onChange={(e) => {
1727
+ const newIso = e.target.value;
1728
+ setRescheduleModal(prev => ({
1729
+ ...prev,
1730
+ isoTime: newIso,
1731
+ meetingTime: formatTo12HrTime(newIso)
1732
+ }));
1733
+ }}
1734
+ className="w-full border border-outline-variant rounded-lg p-2.5 text-xs text-on-surface bg-surface-container-lowest focus:border-primary outline-none cursor-pointer mb-2"
1735
+ />
1736
+
1737
+ {/* Quick Time Slots */}
1738
+ <div className="flex flex-col gap-1">
1739
+ <span className="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Quick Select Time Slot:</span>
1740
+ <div className="grid grid-cols-4 gap-1.5">
1741
+ {['09:00 AM', '10:00 AM', '11:00 AM', '02:00 PM', '04:00 PM', '05:00 PM', '06:00 PM', '09:30 PM'].map((slot) => {
1742
+ const isSelected = rescheduleModal.meetingTime === slot;
1743
+ return (
1744
+ <button
1745
+ key={slot}
1746
+ type="button"
1747
+ onClick={() => {
1748
+ setRescheduleModal(prev => ({
1749
+ ...prev,
1750
+ meetingTime: slot,
1751
+ isoTime: parseToISOTime(slot)
1752
+ }));
1753
+ }}
1754
+ className={`py-1.5 px-1 text-center text-[10.5px] font-bold border rounded-lg transition-all cursor-pointer ${
1755
+ isSelected
1756
+ ? 'bg-primary text-white border-primary shadow-xs'
1757
+ : 'bg-surface-container-low border-outline-variant/60 text-on-surface-variant hover:bg-surface-container-high'
1758
+ }`}
1759
+ >
1760
+ {slot}
1761
+ </button>
1762
+ );
1763
+ })}
1764
  </div>
1765
+ </div>
1766
  </div>
1767
+
1768
+ <div>
1769
+ <label className="text-xs font-bold text-on-surface block mb-1">Update Booking Status</label>
1770
+ <select
1771
+ value={rescheduleModal.status}
1772
+ onChange={(e) => setRescheduleModal({ ...rescheduleModal, status: e.target.value })}
1773
+ className="w-full border border-outline-variant rounded-lg p-2.5 text-xs text-on-surface bg-surface-container-lowest focus:border-primary outline-none cursor-pointer"
1774
+ >
1775
+ <option value="rescheduled">Rescheduled</option>
1776
+ <option value="pending">Pending</option>
1777
+ <option value="completed">Completed / Conducted</option>
1778
+ <option value="cancelled">Cancelled / Not Conducted</option>
1779
+ </select>
1780
+ </div>
1781
+
1782
+ <div className="flex justify-end gap-2 pt-3 border-t border-outline-variant">
1783
+ <button
1784
+ type="button"
1785
+ onClick={() => setRescheduleModal({ ...rescheduleModal, isOpen: false })}
1786
+ className="px-4 py-2 rounded-lg text-xs font-bold text-on-surface-variant hover:bg-surface-container cursor-pointer bg-transparent border border-outline-variant"
1787
+ >
1788
+ Cancel
1789
+ </button>
1790
+ <button
1791
+ type="submit"
1792
+ className="px-4 py-2 rounded-lg text-xs font-bold text-white bg-primary hover:brightness-110 shadow-sm cursor-pointer border-0 flex items-center gap-1"
1793
+ >
1794
+ <span className="material-symbols-outlined text-[16px]">save</span>
1795
+ Save & Update
1796
+ </button>
1797
+ </div>
1798
+ </form>
1799
+ </CustomModal>
1800
  </div>
1801
  );
1802
  };