826 lines
30 KiB
JavaScript
826 lines
30 KiB
JavaScript
// public/admin.js (for admin.html)
|
|
|
|
// Global variables
|
|
let currentAdminDate = new Date();
|
|
let currentAdminMonth = currentAdminDate.getMonth();
|
|
let currentAdminYear = currentAdminDate.getFullYear();
|
|
let selectedAdminDate = null;
|
|
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
|
|
// Generate all possible time slots in chronological order (8am to 5pm)
|
|
const timeSlotsData = [
|
|
'8:00am', '9:00am', '10:00am', '11:00am', '12:00pm',
|
|
'1:00pm', '2:00pm', '3:00pm', '4:00pm', '5:00pm'
|
|
];
|
|
|
|
// Helper function to convert time string to minutes since midnight
|
|
function timeToMinutes(timeStr) {
|
|
const match = timeStr.match(/([0-9]+)(?::([0-9]+))?(am|pm)/i);
|
|
if (!match) return 0;
|
|
|
|
let hours = parseInt(match[1]);
|
|
const minutes = parseInt(match[2] || '0');
|
|
const meridian = match[3].toLowerCase();
|
|
|
|
if (meridian === 'pm' && hours !== 12) {
|
|
hours += 12;
|
|
} else if (meridian === 'am' && hours === 12) {
|
|
hours = 0;
|
|
}
|
|
|
|
return hours * 60 + minutes;
|
|
}
|
|
|
|
// Helper function to ensure consistent time format
|
|
function formatTimeSlot(hour) {
|
|
const meridian = hour >= 12 ? 'pm' : 'am';
|
|
const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour);
|
|
return `${displayHour}:00${meridian}`;
|
|
}
|
|
|
|
// Render the admin calendar with available dates highlighted
|
|
async function renderAdminCalendar(month, year) {
|
|
const adminCalendarDates = document.getElementById('adminCalendarDates');
|
|
const adminMonthYear = document.getElementById('adminMonthYear');
|
|
if (!adminCalendarDates || !adminMonthYear) {
|
|
console.error('Required calendar elements not found');
|
|
return;
|
|
}
|
|
|
|
// Clear existing calendar
|
|
adminCalendarDates.innerHTML = '';
|
|
adminMonthYear.textContent = `${months[month]} ${year}`;
|
|
|
|
// Get current UID and availability
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) {
|
|
// If no UID selected, just show empty calendar
|
|
return;
|
|
}
|
|
|
|
const firstDay = new Date(year, month, 1).getDay();
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
const today = new Date();
|
|
const availability = await fetchAvailability();
|
|
|
|
// Start of today (midnight)
|
|
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
|
|
// Create blank cells for days before the first day of the month
|
|
for (let i = 0; i < firstDay; i++) {
|
|
const blank = document.createElement('div');
|
|
blank.classList.add('date-item', 'p-2', 'rounded-pill', 'text-center', 'text-muted');
|
|
adminCalendarDates.appendChild(blank);
|
|
}
|
|
|
|
// Clear any existing selected date
|
|
const existingSelected = adminCalendarDates.querySelector('.selected');
|
|
if (existingSelected) {
|
|
existingSelected.classList.remove('selected');
|
|
}
|
|
|
|
// Populate the calendar with days
|
|
let todayElement = null;
|
|
for (let i = 1; i <= daysInMonth; i++) {
|
|
const day = document.createElement('div');
|
|
day.classList.add('date-item', 'p-2', 'rounded-pill', 'text-center');
|
|
day.textContent = i;
|
|
const date = new Date(year, month, i);
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
|
|
// Check if date is in the past
|
|
const isPastDate = date < startOfToday;
|
|
|
|
if (date.toDateString() === today.toDateString()) {
|
|
day.classList.add('current');
|
|
todayElement = day;
|
|
}
|
|
if (availability[dateStr]) {
|
|
day.classList.add('available');
|
|
}
|
|
if (isPastDate) {
|
|
day.classList.add('disabled');
|
|
} else {
|
|
day.addEventListener('click', () => selectAdminDate(date, firstDay));
|
|
}
|
|
adminCalendarDates.appendChild(day);
|
|
}
|
|
|
|
// If a date was previously selected, re-select it
|
|
if (selectedAdminDate &&
|
|
selectedAdminDate.getMonth() === month &&
|
|
selectedAdminDate.getFullYear() === year) {
|
|
selectAdminDate(selectedAdminDate, firstDay);
|
|
}
|
|
}
|
|
|
|
// Handle admin date selection
|
|
async function selectAdminDate(date, firstDay) {
|
|
selectedAdminDate = date;
|
|
const calendarDates = document.getElementById('adminCalendarDates');
|
|
if (!calendarDates) return;
|
|
|
|
// Remove selected class from all dates
|
|
const allDates = calendarDates.querySelectorAll('.date-item');
|
|
allDates.forEach(item => item.classList.remove('selected'));
|
|
|
|
// Add selected class to the clicked date
|
|
const selectedDay = Array.from(allDates).find(item => {
|
|
if (!item.textContent) return false;
|
|
const dayText = item.textContent;
|
|
const itemDate = new Date(currentAdminYear, currentAdminMonth, parseInt(dayText));
|
|
return itemDate.toDateString() === date.toDateString();
|
|
});
|
|
if (selectedDay) selectedDay.classList.add('selected');
|
|
const selectedDateDisplay = document.getElementById('selectedDateDisplay');
|
|
if (selectedDateDisplay) {
|
|
selectedDateDisplay.textContent = `Selected Date: ${date.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}`;
|
|
}
|
|
|
|
// Update time slots for the selected date
|
|
await updateTimeSlots(date);
|
|
}
|
|
|
|
// Update time slots based on selected date
|
|
async function updateTimeSlots(date) {
|
|
const availability = await fetchAvailability();
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
let availableTimes = [];
|
|
|
|
// Handle case where availability might be undefined or null
|
|
if (availability && availability[dateStr]) {
|
|
if (typeof availability[dateStr] === 'string') {
|
|
availableTimes = availability[dateStr].split(',').map(t => t.trim());
|
|
} else if (Array.isArray(availability[dateStr])) {
|
|
availableTimes = availability[dateStr].map(t => t.trim());
|
|
}
|
|
}
|
|
|
|
// Sort available times chronologically
|
|
availableTimes.sort((a, b) => timeToMinutes(a) - timeToMinutes(b));
|
|
|
|
const timeSlots = document.getElementById('timeSlots');
|
|
if (!timeSlots) return;
|
|
|
|
timeSlots.innerHTML = '';
|
|
|
|
// Get current time in minutes for comparison
|
|
const now = new Date();
|
|
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
const isToday = date.toDateString() === now.toDateString();
|
|
|
|
// Create a map of all time slots with their status
|
|
const allSlots = new Map();
|
|
timeSlotsData.forEach(time => {
|
|
const timeInMinutes = timeToMinutes(time);
|
|
// Skip times that are in the past on the current day
|
|
if (isToday && timeInMinutes <= currentMinutes) {
|
|
return;
|
|
}
|
|
allSlots.set(time, availableTimes.includes(time));
|
|
});
|
|
|
|
// Sort all slots chronologically
|
|
const sortedSlots = Array.from(allSlots.entries())
|
|
.sort((a, b) => timeToMinutes(a[0]) - timeToMinutes(b[0]));
|
|
|
|
// Display all slots in chronological order
|
|
sortedSlots.forEach(([time, isAvailable]) => {
|
|
const button = document.createElement('button');
|
|
button.classList.add('time-slot', 'btn', 'rounded-pill', 'w-100', 'mb-2', 'py-2');
|
|
button.textContent = time;
|
|
|
|
if (isAvailable) {
|
|
button.classList.add('btn-primary', 'available');
|
|
button.addEventListener('click', () => {
|
|
button.classList.remove('btn-primary', 'available');
|
|
button.classList.add('btn-outline-secondary');
|
|
removeTime(dateStr, time);
|
|
});
|
|
} else {
|
|
button.classList.add('btn-outline-secondary');
|
|
button.addEventListener('click', () => {
|
|
button.classList.remove('btn-outline-secondary');
|
|
button.classList.add('btn-primary', 'available');
|
|
addSingleTime(dateStr, time);
|
|
});
|
|
}
|
|
|
|
timeSlots.appendChild(button);
|
|
});
|
|
}
|
|
|
|
// Fetch available dates from the API
|
|
async function fetchAvailability() {
|
|
try {
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) return {};
|
|
|
|
const response = await fetch(`/api/availability/${selectedUid}`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const availability = await response.json();
|
|
return availability;
|
|
} catch (error) {
|
|
console.error('Error fetching availability:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Helper function to update calendar UI after availability changes
|
|
async function updateAvailabilityUI() {
|
|
const availability = await fetchAvailability();
|
|
if (selectedAdminDate) {
|
|
await updateTimeSlots(selectedAdminDate);
|
|
}
|
|
|
|
const calendarDates = document.getElementById('adminCalendarDates');
|
|
if (calendarDates) {
|
|
const allDateElements = calendarDates.querySelectorAll('.date-item');
|
|
allDateElements.forEach(dateElement => {
|
|
if (!dateElement.textContent) return; // Skip empty cells
|
|
const day = parseInt(dateElement.textContent);
|
|
const date = new Date(currentAdminYear, currentAdminMonth, day);
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
|
|
if (availability[dateStr]) {
|
|
dateElement.classList.add('available');
|
|
} else {
|
|
dateElement.classList.remove('available');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// UID management functions
|
|
async function loadUids() {
|
|
try {
|
|
const response = await fetch('/api/uids');
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || 'Failed to fetch UIDs');
|
|
}
|
|
const uids = await response.json();
|
|
|
|
const select = document.getElementById('uidSelect');
|
|
select.innerHTML = '<option value="">Select a Schedule UID...</option>';
|
|
|
|
uids.forEach(uidObj => {
|
|
const option = document.createElement('option');
|
|
option.value = uidObj.uid;
|
|
option.textContent = uidObj.uid;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading UIDs:', error);
|
|
}
|
|
}
|
|
|
|
async function createUid(uid) {
|
|
try {
|
|
const response = await fetch('/api/uids', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ uid })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || 'Failed to create UID');
|
|
}
|
|
|
|
// Reload UIDs and select the new one
|
|
await loadUids();
|
|
|
|
// Select the new UID and trigger change event
|
|
const select = document.getElementById('uidSelect');
|
|
select.value = uid;
|
|
select.dispatchEvent(new Event('change'));
|
|
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error creating UID:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function deleteUid(uid) {
|
|
try {
|
|
const response = await fetch(`/api/uids/${uid}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (!response.ok) throw new Error('Failed to delete UID');
|
|
await loadUids();
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error deleting UID:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Add a single time for the selected date
|
|
async function addSingleTime(dateStr, time) {
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) {
|
|
alert('Please select a UID first');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const availability = await fetchAvailability();
|
|
let times = [];
|
|
if (availability && availability[dateStr]) {
|
|
if (typeof availability[dateStr] === 'string') {
|
|
times = availability[dateStr].split(',').map(t => t.trim());
|
|
} else if (Array.isArray(availability[dateStr])) {
|
|
times = [...availability[dateStr]];
|
|
}
|
|
}
|
|
times.push(time);
|
|
|
|
const response = await fetch(`/api/availability/${selectedUid}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ date: dateStr, times: [...new Set(times)] }) // Remove duplicates
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log(result.message);
|
|
await updateAvailabilityUI();
|
|
} catch (error) {
|
|
console.error('Error adding time slot:', error);
|
|
alert('Failed to add time slot. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Remove a single time for the selected date
|
|
async function removeTime(dateStr, time) {
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) {
|
|
alert('Please select a UID first');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const availability = await fetchAvailability();
|
|
let times = [];
|
|
if (availability && availability[dateStr]) {
|
|
if (typeof availability[dateStr] === 'string') {
|
|
times = availability[dateStr].split(',').filter(t => t.trim() !== time);
|
|
} else if (Array.isArray(availability[dateStr])) {
|
|
times = availability[dateStr].filter(t => t.trim() !== time);
|
|
}
|
|
}
|
|
|
|
const response = await fetch(`/api/availability/${selectedUid}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ date: dateStr, times })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
await updateAvailabilityUI();
|
|
} catch (error) {
|
|
console.error('Error removing time slot:', error);
|
|
alert('Failed to remove time slot. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Helper function to convert time string to minutes since midnight
|
|
function timeToMinutes(timeStr) {
|
|
const match = timeStr.match(/([0-9]+)(?::([0-9]+))?(am|pm)/i);
|
|
if (!match) return 0;
|
|
|
|
let [_, hours, minutes, period] = match;
|
|
hours = parseInt(hours);
|
|
minutes = minutes ? parseInt(minutes) : 0;
|
|
period = period.toLowerCase();
|
|
|
|
if (period === 'pm' && hours !== 12) hours += 12;
|
|
if (period === 'am' && hours === 12) hours = 0;
|
|
|
|
return hours * 60 + minutes;
|
|
}
|
|
|
|
// Helper function to ensure consistent time format
|
|
function formatTimeSlot(hour) {
|
|
const period = hour < 12 ? 'am' : 'pm';
|
|
const displayHour = hour === 0 ? 12 : (hour > 12 ? hour - 12 : hour);
|
|
return `${displayHour}:00${period}`;
|
|
}
|
|
|
|
// Handle admin date selection
|
|
async function selectAdminDate(date, firstDay) {
|
|
// Check if the date is in the past
|
|
const today = new Date();
|
|
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
if (date < startOfToday) {
|
|
console.warn('Attempted to select a past date');
|
|
return;
|
|
}
|
|
|
|
// Update selected date and time slots
|
|
selectedAdminDate = date;
|
|
await updateTimeSlots(date);
|
|
const dateItems = document.querySelectorAll('#adminCalendarDates .date-item');
|
|
dateItems.forEach(item => item.classList.remove('selected')); // Ensure only one date is selected
|
|
// Find the exact date item for the clicked date
|
|
const selectedDay = Array.from(dateItems).find(item => {
|
|
const dayText = item.textContent;
|
|
const itemDate = new Date(currentAdminYear, currentAdminMonth, parseInt(dayText));
|
|
return itemDate.toDateString() === date.toDateString();
|
|
});
|
|
if (selectedDay) selectedDay.classList.add('selected');
|
|
const selectedDateDisplay = document.getElementById('selectedDateDisplay');
|
|
if (selectedDateDisplay) {
|
|
selectedDateDisplay.textContent = `Selected Date: ${date.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}`;
|
|
}
|
|
}
|
|
|
|
// Update time slots based on selected date
|
|
async function updateTimeSlots(date) {
|
|
|
|
const availability = await fetchAvailability();
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
let availableTimes = [];
|
|
|
|
// Handle case where availability might be undefined or null
|
|
if (availability && availability[dateStr]) {
|
|
if (typeof availability[dateStr] === 'string') {
|
|
availableTimes = availability[dateStr].split(',').map(t => t.trim());
|
|
} else if (Array.isArray(availability[dateStr])) {
|
|
availableTimes = availability[dateStr].map(t => t.trim());
|
|
}
|
|
}
|
|
|
|
// Sort available times chronologically
|
|
availableTimes.sort((a, b) => timeToMinutes(a) - timeToMinutes(b));
|
|
|
|
const timeSlots = document.getElementById('timeSlots');
|
|
if (!timeSlots) return;
|
|
|
|
timeSlots.innerHTML = '';
|
|
|
|
// Get current time in minutes for comparison
|
|
const now = new Date();
|
|
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
const isToday = date.toDateString() === now.toDateString();
|
|
|
|
// Create a map of all time slots with their status
|
|
const allSlots = new Map();
|
|
timeSlotsData.forEach(time => {
|
|
const timeInMinutes = timeToMinutes(time);
|
|
// Skip times that are in the past on the current day
|
|
if (isToday && timeInMinutes <= currentMinutes) {
|
|
return;
|
|
}
|
|
allSlots.set(time, availableTimes.includes(time));
|
|
});
|
|
|
|
// Sort all slots chronologically
|
|
const sortedSlots = Array.from(allSlots.entries())
|
|
.sort((a, b) => timeToMinutes(a[0]) - timeToMinutes(b[0]));
|
|
|
|
// Display all slots in chronological order
|
|
sortedSlots.forEach(([time, isAvailable]) => {
|
|
const button = document.createElement('button');
|
|
button.classList.add('time-slot', 'btn', 'rounded-pill', 'w-100', 'mb-2', 'py-2');
|
|
button.textContent = time;
|
|
|
|
if (isAvailable) {
|
|
button.classList.add('btn-primary', 'available');
|
|
button.addEventListener('click', () => {
|
|
button.classList.remove('btn-primary', 'available');
|
|
button.classList.add('btn-outline-secondary');
|
|
removeTime(dateStr, time);
|
|
});
|
|
} else {
|
|
button.classList.add('btn-outline-secondary');
|
|
button.addEventListener('click', () => {
|
|
button.classList.remove('btn-outline-secondary');
|
|
button.classList.add('btn-primary', 'available');
|
|
addSingleTime(dateStr, time);
|
|
});
|
|
}
|
|
|
|
timeSlots.appendChild(button);
|
|
});
|
|
}
|
|
|
|
// Fetch available dates from the API
|
|
async function fetchAvailability() {
|
|
try {
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) return {};
|
|
|
|
const response = await fetch(`/api/availability/${selectedUid}`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const availability = await response.json();
|
|
return availability;
|
|
} catch (error) {
|
|
console.error('Error fetching availability:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Helper function to update calendar UI after availability changes
|
|
async function updateAvailabilityUI() {
|
|
const availability = await fetchAvailability();
|
|
if (selectedAdminDate) {
|
|
await updateTimeSlots(selectedAdminDate);
|
|
}
|
|
|
|
const calendarDates = document.getElementById('adminCalendarDates');
|
|
if (calendarDates) {
|
|
const allDateElements = calendarDates.querySelectorAll('.date-item');
|
|
allDateElements.forEach(dateElement => {
|
|
if (!dateElement.textContent) return; // Skip empty cells
|
|
const day = parseInt(dateElement.textContent);
|
|
const date = new Date(currentAdminYear, currentAdminMonth, day);
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
|
|
if (availability[dateStr]) {
|
|
dateElement.classList.add('available');
|
|
} else {
|
|
dateElement.classList.remove('available');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Load existing UIDs
|
|
await loadUids();
|
|
|
|
// Handle UID selection
|
|
const uidSelect = document.getElementById('uidSelect');
|
|
const deleteUidBtn = document.getElementById('deleteUid');
|
|
const uidUrl = document.getElementById('uidUrl');
|
|
|
|
uidSelect.addEventListener('change', async () => {
|
|
const selectedUid = uidSelect.value;
|
|
deleteUidBtn.disabled = !selectedUid;
|
|
flushDatabaseBtn.disabled = !selectedUid;
|
|
|
|
// Clear current selections
|
|
selectedAdminDate = null;
|
|
const selectedDateElem = document.getElementById('selectedDateDisplay');
|
|
const timeSlotsElem = document.getElementById('timeSlots');
|
|
const calendarDatesElem = document.getElementById('adminCalendarDates');
|
|
const adminMonthYear = document.getElementById('adminMonthYear');
|
|
|
|
// Clear all UI elements
|
|
if (selectedDateElem) selectedDateElem.textContent = '';
|
|
if (timeSlotsElem) timeSlotsElem.innerHTML = '';
|
|
if (calendarDatesElem) calendarDatesElem.innerHTML = '';
|
|
if (adminMonthYear) adminMonthYear.textContent = '';
|
|
|
|
if (selectedUid) {
|
|
const url = `${window.location.origin}/${selectedUid}`;
|
|
uidUrl.textContent = url;
|
|
uidUrl.href = url;
|
|
// Refresh calendar with new UID's data
|
|
await renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
// Show availability for current date
|
|
if (selectedAdminDate) {
|
|
await updateTimeSlots(selectedAdminDate);
|
|
}
|
|
} else {
|
|
uidUrl.textContent = 'Select a UID first';
|
|
uidUrl.href = '#';
|
|
// Add placeholder text
|
|
if (selectedDateElem) selectedDateElem.textContent = 'Please select a UID to view the calendar';
|
|
await renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
}
|
|
});
|
|
|
|
// Handle new UID creation
|
|
const newUidInput = document.getElementById('newUid');
|
|
const createUidBtn = document.getElementById('createUid');
|
|
|
|
createUidBtn.addEventListener('click', async () => {
|
|
const uid = newUidInput.value.trim().toLowerCase();
|
|
|
|
if (!/^[a-z0-9-]+$/.test(uid)) {
|
|
alert('UID can only contain lowercase letters, numbers, and hyphens');
|
|
return;
|
|
}
|
|
|
|
if (await createUid(uid)) {
|
|
newUidInput.value = '';
|
|
} else {
|
|
alert('Failed to create UID. Please try again.');
|
|
}
|
|
});
|
|
|
|
// Handle UID deletion
|
|
deleteUidBtn.addEventListener('click', async () => {
|
|
const uid = uidSelect.value;
|
|
if (!uid) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete UID: ${uid}?\nThis will remove all associated availability data.`)) {
|
|
return;
|
|
}
|
|
|
|
if (await deleteUid(uid)) {
|
|
// Clear selection and trigger change event
|
|
uidSelect.value = '';
|
|
uidSelect.dispatchEvent(new Event('change'));
|
|
} else {
|
|
alert('Failed to delete UID. Please try again.');
|
|
}
|
|
});
|
|
|
|
// Initialize UI elements
|
|
const adminCalendarDates = document.getElementById('adminCalendarDates');
|
|
const adminMonthYear = document.getElementById('adminMonthYear');
|
|
const adminPrevMonthBtn = document.getElementById('adminPrevMonth');
|
|
const adminNextMonthBtn = document.getElementById('adminNextMonth');
|
|
const selectedDateDisplay = document.getElementById('selectedDateDisplay');
|
|
const timeSlots = document.getElementById('timeSlots');
|
|
const flushDatabaseBtn = document.getElementById('flushDatabase');
|
|
const resetDatabaseBtn = document.getElementById('resetDatabase');
|
|
|
|
// Handle database reset
|
|
resetDatabaseBtn.addEventListener('click', async () => {
|
|
if (!confirm('⚠️ WARNING: This will permanently delete ALL UIDs and their associated availability data. This action cannot be undone.\n\nAre you absolutely sure you want to proceed?')) {
|
|
return;
|
|
}
|
|
|
|
// Double confirm for dangerous action
|
|
if (!confirm('Please confirm one more time that you want to reset the entire database. All data will be lost.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/reset', {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || 'Failed to reset database');
|
|
}
|
|
|
|
// Reload the page to reset all state
|
|
window.location.reload();
|
|
} catch (error) {
|
|
console.error('Error resetting database:', error);
|
|
alert('Failed to reset database. Please try again.');
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if a date has availability
|
|
async function checkAvailabilityForDate(date) {
|
|
const availability = await fetchAvailability();
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
return !!availability[dateStr];
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Flush (delete) all entries from the database
|
|
flushDatabaseBtn.addEventListener('click', async () => {
|
|
const selectedUid = document.getElementById('uidSelect').value;
|
|
if (!selectedUid) {
|
|
alert('Please select a UID first');
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`Are you sure you want to delete ALL availability entries for UID: ${selectedUid}?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log(`Attempting to delete all availability for UID: ${selectedUid}...`);
|
|
const response = await fetch(`/api/availability/${selectedUid}/flush`, {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('Delete availability response:', result);
|
|
|
|
// Refresh data
|
|
await fetchAvailability(); // Refresh availability data
|
|
|
|
// Reset UI
|
|
selectedAdminDate = null; // Clear selected date
|
|
selectedDateDisplay.textContent = '';
|
|
timeSlots.innerHTML = ''; // Clear time slots
|
|
await renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
} catch (error) {
|
|
console.error('Error deleting availability:', error);
|
|
alert('Failed to delete availability. Please try again.');
|
|
}
|
|
});
|
|
|
|
// Navigate to previous month (admin)
|
|
adminPrevMonthBtn.addEventListener('click', () => {
|
|
currentAdminMonth--;
|
|
if (currentAdminMonth < 0) {
|
|
currentAdminMonth = 11;
|
|
currentAdminYear--;
|
|
}
|
|
renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
});
|
|
|
|
// Navigate to next month (admin)
|
|
adminNextMonthBtn.addEventListener('click', () => {
|
|
currentAdminMonth++;
|
|
if (currentAdminMonth > 11) {
|
|
currentAdminMonth = 0;
|
|
currentAdminYear++;
|
|
}
|
|
renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
});
|
|
|
|
// Handle global database flush
|
|
const flushGlobalDatabaseBtn = document.getElementById('flushGlobalDatabase');
|
|
if (flushGlobalDatabaseBtn) {
|
|
flushGlobalDatabaseBtn.addEventListener('click', async () => {
|
|
if (!confirm('WARNING: This will delete ALL UIDs and their availability data. This action cannot be undone. Are you sure you want to proceed?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/flush-global', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log('Global database flush response:', result);
|
|
alert(result.message || 'Database flushed successfully!');
|
|
|
|
// Reset everything
|
|
selectedAdminDate = null;
|
|
const selectedDateElem = document.getElementById('selectedDateDisplay');
|
|
const timeSlotsElem = document.getElementById('timeSlots');
|
|
const calendarDatesElem = document.getElementById('adminCalendarDates');
|
|
if (selectedDateElem) selectedDateElem.textContent = '';
|
|
if (timeSlotsElem) timeSlotsElem.innerHTML = '';
|
|
if (calendarDatesElem) calendarDatesElem.innerHTML = '';
|
|
|
|
// Reload UIDs
|
|
await loadUids();
|
|
|
|
// Update UID URL display
|
|
const uidUrl = document.getElementById('uidUrl');
|
|
if (uidUrl) uidUrl.textContent = 'Select a UID first';
|
|
|
|
// Disable delete button
|
|
const deleteUidBtn = document.getElementById('deleteUid');
|
|
if (deleteUidBtn) deleteUidBtn.disabled = true;
|
|
} catch (error) {
|
|
console.error('Error flushing database:', error);
|
|
alert('Failed to flush database. Please try again.');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial render with current date
|
|
renderAdminCalendar(currentAdminMonth, currentAdminYear);
|
|
if (currentAdminDate.getDate() <= new Date(currentAdminYear, currentAdminMonth, 0).getDate()) {
|
|
const hasAvailability = await checkAvailabilityForDate(currentAdminDate);
|
|
if (hasAvailability) {
|
|
selectAdminDate(currentAdminDate, new Date(currentAdminYear, currentAdminMonth, 1).getDay());
|
|
}
|
|
}
|
|
}); |