Spaces:
Running
Running
| // Sidebar Toggle Functionality | |
| const sidebar = document.getElementById('sidebar'); | |
| const sidebarToggle = document.getElementById('sidebarToggle'); | |
| const mobileMenuBtn = document.getElementById('mobileMenuBtn'); | |
| let isSidebarCollapsed = false; | |
| function toggleSidebar() { | |
| isSidebarCollapsed = !isSidebarCollapsed; | |
| if (window.innerWidth >= 1024) { | |
| // Desktop behavior | |
| if (isSidebarCollapsed) { | |
| sidebar.classList.add('collapsed'); | |
| sidebar.style.width = '5rem'; | |
| } else { | |
| sidebar.classList.remove('collapsed'); | |
| sidebar.style.width = '16rem'; | |
| } | |
| } else { | |
| // Mobile behavior - slide in/out | |
| sidebar.classList.toggle('open'); | |
| // Create or toggle overlay | |
| let overlay = document.querySelector('.sidebar-overlay'); | |
| if (!overlay) { | |
| overlay = document.createElement('div'); | |
| overlay.className = 'sidebar-overlay'; | |
| document.body.appendChild(overlay); | |
| overlay.addEventListener('click', toggleSidebar); | |
| } | |
| overlay.classList.toggle('open'); | |
| } | |
| // Refresh Lucide icons after transition | |
| setTimeout(() => { | |
| lucide.createIcons(); | |
| }, 300); | |
| } | |
| sidebarToggle.addEventListener('click', toggleSidebar); | |
| mobileMenuBtn.addEventListener('click', toggleSidebar); | |
| // Patient Type Toggle | |
| const existingPatientBtn = document.getElementById('existingPatientBtn'); | |
| const newPatientBtn = document.getElementById('newPatientBtn'); | |
| const existingPatientForm = document.getElementById('existingPatientForm'); | |
| const newPatientForm = document.getElementById('newPatientForm'); | |
| existingPatientBtn.addEventListener('click', () => { | |
| existingPatientBtn.classList.add('bg-white', 'text-slate-800', 'shadow-sm'); | |
| existingPatientBtn.classList.remove('text-slate-600'); | |
| newPatientBtn.classList.remove('bg-white', 'text-slate-800', 'shadow-sm'); | |
| newPatientBtn.classList.add('text-slate-600'); | |
| existingPatientForm.classList.remove('hidden'); | |
| newPatientForm.classList.add('hidden'); | |
| }); | |
| newPatientBtn.addEventListener('click', () => { | |
| newPatientBtn.classList.add('bg-white', 'text-slate-800', 'shadow-sm'); | |
| newPatientBtn.classList.remove('text-slate-600'); | |
| existingPatientBtn.classList.remove('bg-white', 'text-slate-800', 'shadow-sm'); | |
| existingPatientBtn.classList.add('text-slate-600'); | |
| newPatientForm.classList.remove('hidden'); | |
| existingPatientForm.classList.add('hidden'); | |
| }); | |
| // Time Slot Generation | |
| const timeSlotsContainer = document.getElementById('timeSlotsContainer'); | |
| const appointmentDate = document.getElementById('appointmentDate'); | |
| const selectedDateDisplay = document.getElementById('selectedDateDisplay'); | |
| const confirmBtn = document.getElementById('confirmAppointmentBtn'); | |
| // Generate time slots (9 AM to 5 PM, 30 min intervals) | |
| function generateTimeSlots() { | |
| const slots = []; | |
| const startHour = 9; | |
| const endHour = 17; | |
| for (let hour = startHour; hour < endHour; hour++) { | |
| for (let min of [0, 30]) { | |
| const time = `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`; | |
| // Randomly make some slots unavailable for realism | |
| const isAvailable = Math.random() > 0.3; | |
| slots.push({ time, isAvailable }); | |
| } | |
| } | |
| return slots; | |
| } | |
| function renderTimeSlots(date) { | |
| if (!date) { | |
| timeSlotsContainer.innerHTML = ` | |
| <div class="col-span-2 text-center py-8 text-slate-400"> | |
| <i data-lucide="calendar-days" class="w-12 h-12 mx-auto mb-3 opacity-50"></i> | |
| <p class="text-sm">Please select a date to view available times</p> | |
| </div> | |
| `; | |
| lucide.createIcons(); | |
| confirmBtn.disabled = true; | |
| return; | |
| } | |
| // Show loading state briefly | |
| timeSlotsContainer.innerHTML = '<div class="col-span-2 text-center py-4"><div class="loading-spinner w-6 h-6 border-2 border-teal-600 border-t-transparent rounded-full animate-spin mx-auto"></div></div>'; | |
| setTimeout(() => { | |
| const slots = generateTimeSlots(); | |
| let html = ''; | |
| slots.forEach(slot => { | |
| const statusClass = slot.isAvailable ? 'available bg-white border-slate-200 text-slate-700 hover:border-teal-400' : 'disabled'; | |
| const clickHandler = slot.isAvailable ? `onclick="selectTimeSlot(this, '${slot.time}')"` : ''; | |
| html += ` | |
| <button type="button" ${clickHandler} | |
| class="time-slot ${statusClass} px-3 py-2 rounded-lg border text-sm font-medium transition-all" | |
| ${!slot.isAvailable ? 'disabled' : ''}> | |
| ${formatTime(slot.time)} | |
| </button> | |
| `; | |
| }); | |
| timeSlotsContainer.innerHTML = html; | |
| // Update date display | |
| const dateObj = new Date(date); | |
| selectedDateDisplay.textContent = dateObj.toLocaleDateString('en-US', { | |
| weekday: 'short', | |
| month: 'short', | |
| day: 'numeric' | |
| }); | |
| lucide.createIcons(); | |
| }, 300); | |
| } | |
| function formatTime(time) { | |
| const [hours, minutes] = time.split(':'); | |
| const hour = parseInt(hours); | |
| const ampm = hour >= 12 ? 'PM' : 'AM'; | |
| const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour); | |
| return `${displayHour}:${minutes} ${ampm}`; | |
| } | |
| let selectedTime = null; | |
| function selectTimeSlot(element, time) { | |
| // Remove selected class from all slots | |
| document.querySelectorAll('.time-slot.available').forEach(slot => { | |
| slot.classList.remove('selected'); | |
| }); | |
| // Add selected class to clicked slot | |
| element.classList.add('selected'); | |
| selectedTime = time; | |
| // Update time display in header | |
| const timeDisplay = document.getElementById('selectedTimeDisplay'); | |
| if (timeDisplay) { | |
| timeDisplay.textContent = formatTime(time); | |
| timeDisplay.classList.remove('text-slate-400'); | |
| timeDisplay.classList.add('text-teal-600', 'font-medium'); | |
| } | |
| // Enable confirm button | |
| confirmBtn.disabled = false; | |
| confirmBtn.textContent = `Confirm at ${formatTime(time)}`; | |
| } | |
| // Date change handler | |
| appointmentDate.addEventListener('change', (e) => { | |
| renderTimeSlots(e.target.value); | |
| // Auto-scroll to right panel time slots on date selection | |
| if (e.target.value && window.innerWidth >= 1280) { | |
| const timePanel = document.querySelector('.xl\\:col-span-1'); | |
| if (timePanel) { | |
| timePanel.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| } | |
| } | |
| }); | |
| // Type of Visit Selection | |
| const visitTypeButtons = document.querySelectorAll('.visit-type-btn'); | |
| let selectedVisitType = 'walk_in'; | |
| visitTypeButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| // Remove selected state from all buttons | |
| visitTypeButtons.forEach(btn => { | |
| btn.setAttribute('data-selected', 'false'); | |
| }); | |
| // Add selected state to clicked button | |
| button.setAttribute('data-selected', 'true'); | |
| selectedVisitType = button.getAttribute('data-value'); | |
| }); | |
| }); | |
| // Initialize with today's date | |
| const today = new Date().toISOString().split('T')[0]; | |
| appointmentDate.value = today; | |
| appointmentDate.min = today; | |
| renderTimeSlots(today); | |
| // Physician change simulation | |
| const physicianSelect = document.getElementById('physicianSelect'); | |
| physicianSelect.addEventListener('change', () => { | |
| if (appointmentDate.value) { | |
| // Re-render slots with slight delay to simulate fetching new availability | |
| renderTimeSlots(appointmentDate.value); | |
| } | |
| }); | |
| // Confirm button handler | |
| confirmBtn.addEventListener('click', () => { | |
| if (selectedTime && appointmentDate.value) { | |
| // Show success animation | |
| confirmBtn.innerHTML = '<i data-lucide="check" class="w-5 h-5 inline mr-2"></i> Appointment Saved!'; | |
| confirmBtn.classList.remove('bg-teal-600', 'hover:bg-teal-700'); | |
| confirmBtn.classList.add('bg-green-600', 'hover:bg-green-700'); | |
| lucide.createIcons(); | |
| // Reset after 2 seconds | |
| setTimeout(() => { | |
| confirmBtn.textContent = 'Confirm Appointment'; | |
| confirmBtn.classList.add('bg-teal-600', 'hover:bg-teal-700'); | |
| confirmBtn.classList.remove('bg-green-600', 'hover:bg-green-700'); | |
| confirmBtn.disabled = true; | |
| // Clear selections | |
| document.querySelectorAll('.time-slot.selected').forEach(slot => { | |
| slot.classList.remove('selected'); | |
| }); | |
| selectedTime = null; | |
| // Reset time display in header | |
| const timeDisplay = document.getElementById('selectedTimeDisplay'); | |
| if (timeDisplay) { | |
| timeDisplay.textContent = '--:--'; | |
| timeDisplay.classList.remove('text-teal-600', 'font-medium'); | |
| timeDisplay.classList.add('text-slate-400'); | |
| } | |
| }, 2000); | |
| } | |
| }); | |
| // Responsive handling | |
| window.addEventListener('resize', () => { | |
| if (window.innerWidth >= 1024) { | |
| // Reset to visible on desktop | |
| sidebar.classList.remove('open'); | |
| const overlay = document.querySelector('.sidebar-overlay'); | |
| if (overlay) overlay.classList.remove('open'); | |
| } else { | |
| // Reset collapsed state on mobile | |
| if (isSidebarCollapsed) { | |
| isSidebarCollapsed = false; | |
| sidebar.classList.remove('collapsed'); | |
| sidebar.style.width = '16rem'; | |
| } | |
| } | |
| }); | |
| // Initialize Lucide icons on load | |
| document.addEventListener('DOMContentLoaded', () => { | |
| lucide.createIcons(); | |
| }); |