Restructure: move all files from Public HTML/ to root for Hostinger deployment

This commit is contained in:
Krikorios
2026-02-22 02:37:32 +02:00
parent 515080774a
commit e3a74b2d50
103 changed files with 62 additions and 147 deletions
+930
View File
@@ -0,0 +1,930 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow, noarchive">
<title>Availability - MSPE Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
<link rel="stylesheet" href="/admin/css/admin.css">
<style>
.availability-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
.add-slot-form {
background: white;
border-radius: 10px;
box-shadow: var(--shadow-sm);
padding: 1.5rem;
height: fit-content;
}
.add-slot-form h3 {
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
font-weight: 600;
color: var(--gray-700);
margin-bottom: 0.5rem;
font-size: 0.875rem;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-family: var(--font-main);
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.quick-slots {
margin-bottom: 1.5rem;
}
.quick-slots label {
font-weight: 600;
margin-bottom: 0.75rem;
display: block;
font-size: 0.875rem;
}
.quick-slots-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
.quick-slot-btn {
padding: 0.5rem;
border: 1px solid var(--gray-300);
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
transition: all 0.3s ease;
}
.quick-slot-btn:hover {
border-color: var(--primary);
color: var(--primary);
}
.bulk-actions {
display: flex;
gap: 0.75rem;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--gray-200);
}
.bulk-actions button {
flex: 1;
}
.slots-list {
background: white;
border-radius: 10px;
box-shadow: var(--shadow-sm);
}
.slots-list-header {
padding: 1.5rem;
border-bottom: 1px solid var(--gray-200);
display: flex;
justify-content: space-between;
align-items: center;
}
.slots-list-header h3 {
margin: 0;
}
.slots-filters {
display: flex;
gap: 1rem;
}
.slot-date-filter {
display: flex;
align-items: center;
gap: 0.5rem;
}
.slots-table {
width: 100%;
border-collapse: collapse;
}
.slots-table th {
background: var(--gray-100);
padding: 1rem;
text-align: left;
font-weight: 600;
color: var(--gray-600);
font-size: 0.875rem;
}
.slots-table td {
padding: 1rem;
border-bottom: 1px solid var(--gray-200);
font-size: 0.875rem;
}
.slots-table tr:hover {
background: var(--gray-50);
}
.slot-time {
font-weight: 600;
color: var(--primary);
}
.slot-capacity {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.slot-capacity i {
font-size: 0.75rem;
color: var(--gray-400);
}
.action-btn {
padding: 0.375rem 0.75rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
margin-right: 0.5rem;
transition: all 0.3s ease;
}
.action-btn.delete {
background: #fee2e2;
color: #dc2626;
}
.action-btn.delete:hover {
background: #fecaca;
}
.action-btn.toggle {
background: #e0f2fe;
color: #0369a1;
}
.action-btn.toggle:hover {
background: #bae6fd;
}
.empty-state {
padding: 4rem 2rem;
text-align: center;
color: var(--gray-500);
}
.empty-state i {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.3;
}
.upcoming-section {
background: var(--dark);
border-radius: 10px;
padding: 1.5rem;
margin-top: 2rem;
color: white;
}
.upcoming-section h4 {
margin-bottom: 1rem;
}
.upcoming-bookings {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.upcoming-item {
background: rgba(255,255,255,0.05);
border-radius: 8px;
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.upcoming-item-date {
background: var(--primary);
border-radius: 6px;
padding: 0.5rem 0.75rem;
text-align: center;
min-width: 60px;
}
.upcoming-item-date .day {
font-size: 1.25rem;
font-weight: 700;
}
.upcoming-item-date .month {
font-size: 0.75rem;
text-transform: uppercase;
}
@media (max-width: 1024px) {
.availability-grid {
grid-template-columns: 1fr;
}
.quick-slots-grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<a href="/admin/dashboard.html" class="sidebar-logo">
<i class="fas fa-cube"></i>
<span>MSPE</span>
</a>
<button class="sidebar-toggle" id="sidebar-toggle">
<i class="fas fa-bars"></i>
</button>
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<span class="nav-section-title">Main</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/dashboard.html" class="nav-link">
<i class="fas fa-th-large"></i>
<span>Dashboard</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Content</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/pages.html" class="nav-link">
<i class="fas fa-file-alt"></i>
<span>Pages</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/services.html" class="nav-link">
<i class="fas fa-concierge-bell"></i>
<span>Services</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/portfolio.html" class="nav-link">
<i class="fas fa-briefcase"></i>
<span>Portfolio</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/news.html" class="nav-link">
<i class="fas fa-newspaper"></i>
<span>News & Events</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/testimonials.html" class="nav-link">
<i class="fas fa-quote-right"></i>
<span>Testimonials</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/team.html" class="nav-link">
<i class="fas fa-users"></i>
<span>Team</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Media</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/media.html" class="nav-link">
<i class="fas fa-images"></i>
<span>Media Library</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Inquiries</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/messages.html" class="nav-link">
<i class="fas fa-envelope"></i>
<span>Messages</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/subscribers.html" class="nav-link">
<i class="fas fa-bell"></i>
<span>Subscribers</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/bookings.html" class="nav-link">
<i class="fas fa-calendar-check"></i>
<span>Bookings</span>
</a>
</li>
<li class="nav-item active">
<a href="/admin/availability.html" class="nav-link">
<i class="fas fa-clock"></i>
<span>Availability</span>
</a>
</li>
</ul>
</div>
<div class="nav-section">
<span class="nav-section-title">Settings</span>
<ul class="nav-menu">
<li class="nav-item">
<a href="/admin/settings.html" class="nav-link">
<i class="fas fa-cog"></i>
<span>Site Settings</span>
</a>
</li>
<li class="nav-item">
<a href="/admin/users.html" class="nav-link">
<i class="fas fa-user-shield"></i>
<span>Admin Users</span>
</a>
</li>
</ul>
</div>
</nav>
<div class="sidebar-footer">
<a href="/index.html" class="view-site-btn" target="_blank">
<i class="fas fa-external-link-alt"></i>
<span>View Website</span>
</a>
</div>
</aside>
<main class="main-content">
<header class="admin-header">
<div class="header-left">
<button class="mobile-toggle" id="mobile-toggle">
<i class="fas fa-bars"></i>
</button>
<h1 class="page-title">Manage Availability</h1>
</div>
<div class="header-right">
<div class="header-user">
<div class="user-avatar">
<img src="https://ui-avatars.com/api/?name=Admin&background=0ea5e9&color=fff" alt="Admin">
</div>
</div>
</div>
</header>
<div class="content-wrapper">
<div class="availability-grid">
<div class="add-slot-form">
<h3><i class="fas fa-plus-circle" style="color: var(--primary); margin-right: 0.5rem;"></i> Add Time Slot</h3>
<form id="add-slot-form">
<div class="form-group">
<label>Date *</label>
<input type="date" name="date" required>
</div>
<div class="form-group">
<label>Time *</label>
<input type="time" name="time" required>
</div>
<div class="form-group">
<label>Capacity (number of clients)</label>
<input type="number" name="capacity" value="1" min="1" max="10">
</div>
<div class="form-group">
<label>Notes</label>
<textarea name="notes" rows="3" placeholder="Optional notes about this slot..."></textarea>
</div>
<div class="quick-slots">
<label>Quick Add Times</label>
<div class="quick-slots-grid">
<button type="button" class="quick-slot-btn" onclick="addQuickTime('09:00')">9:00 AM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('10:00')">10:00 AM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('11:00')">11:00 AM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('13:00')">1:00 PM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('14:00')">2:00 PM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('15:00')">3:00 PM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('16:00')">4:00 PM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('17:00')">5:00 PM</button>
<button type="button" class="quick-slot-btn" onclick="addQuickTime('18:00')">6:00 PM</button>
</div>
</div>
<div class="bulk-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i> Add Slot
</button>
<button type="button" class="btn btn-secondary" onclick="generateWeekSlots()">
<i class="fas fa-calendar-week"></i> Generate Week
</button>
</div>
</form>
<!-- Block Time Section -->
<div style="margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--gray-200);">
<h3 style="margin-bottom: 1rem;"><i class="fas fa-ban" style="color: #ef4444; margin-right: 0.5rem;"></i> Block Time</h3>
<p style="font-size: 0.875rem; color: var(--gray-500); margin-bottom: 1rem;">Block a time slot to make it unavailable for public booking (vacation, personal time, etc.)</p>
<form id="block-time-form">
<div class="form-group">
<label>Date *</label>
<input type="date" name="block_date" required>
</div>
<div class="form-group">
<label>Time *</label>
<input type="time" name="block_time" required>
</div>
<div class="form-group">
<label>Reason (internal note)</label>
<input type="text" name="block_reason" placeholder="e.g., Personal appointment">
</div>
<button type="submit" class="btn btn-secondary" style="width: 100%; background: #fee2e2; color: #dc2626; border-color: #ef4444;">
<i class="fas fa-ban"></i> Block This Time
</button>
</form>
</div>
<div class="upcoming-section" id="upcoming-section" style="display: none;">
<h4><i class="fas fa-clock"></i> Upcoming Bookings</h4>
<div class="upcoming-bookings" id="upcoming-bookings"></div>
</div>
</div>
<div class="slots-list">
<div class="slots-list-header">
<h3>Availability Slots</h3>
<div class="slots-filters">
<div class="slot-date-filter">
<label>From:</label>
<input type="date" id="filter-date-from" style="padding: 0.5rem;">
</div>
<div class="slot-date-filter">
<label>To:</label>
<input type="date" id="filter-date-to" style="padding: 0.5rem;">
</div>
</div>
</div>
<!-- Blocked Times Section -->
<div id="blocked-times-section" style="display: none; background: #fef2f2; border-bottom: 1px solid #fecaca;">
<div style="padding: 1rem 1.5rem; display: flex; justify-content: space-between; align-items: center;">
<h4 style="margin: 0; color: #dc2626;"><i class="fas fa-ban" style="margin-right: 0.5rem;"></i> Blocked Times</h4>
</div>
<div id="blocked-times-list" style="padding: 0 1.5rem 1rem;"></div>
</div>
<div class="slots-table-container">
<table class="slots-table">
<thead>
<tr>
<th>Date</th>
<th>Time</th>
<th>Capacity</th>
<th>Status</th>
<th>Notes</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="slots-table-body">
<tr>
<td colspan="6">
<div class="empty-state">
<i class="fas fa-calendar-alt"></i>
<p>Loading availability slots...</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="/admin/js/admin.js"></script>
<script>
let currentSlots = [];
let blockedTimes = [];
document.addEventListener('DOMContentLoaded', function() {
loadSlots();
loadBlockedTimes();
loadUpcomingBookings();
setupEventListeners();
setDefaultDate();
});
function setupEventListeners() {
document.getElementById('add-slot-form').addEventListener('submit', handleAddSlot);
document.getElementById('block-time-form').addEventListener('submit', handleBlockTime);
document.getElementById('filter-date-from').addEventListener('change', filterSlots);
document.getElementById('filter-date-to').addEventListener('change', filterSlots);
}
function setDefaultDate() {
const today = new Date().toISOString().split('T')[0];
document.querySelector('input[name="date"]').value = today;
document.querySelector('input[name="date"]').min = today;
document.querySelector('input[name="block_date"]').value = today;
document.querySelector('input[name="block_date"]').min = today;
}
function addQuickTime(time) {
document.querySelector('input[name="time"]').value = time;
}
async function handleBlockTime(e) {
e.preventDefault();
const form = e.target;
const blockData = {
date: form.block_date.value,
time: form.block_time.value,
reason: form.block_reason.value || 'No reason provided'
};
if (!blockData.date || !blockData.time) {
MSPE.showNotification('Please select date and time', 'error');
return;
}
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Blocking...';
try {
const response = await MSPE.API.post('bookings.php?action=block-time', blockData);
MSPE.showNotification('Time blocked successfully!', 'success');
form.reset();
setDefaultDate();
loadBlockedTimes();
} catch (error) {
console.error('Error blocking time:', error);
MSPE.showNotification('Failed to block time', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
}
}
async function loadBlockedTimes() {
try {
const response = await MSPE.API.get('bookings.php?action=blocked-times');
blockedTimes = response.data || [];
renderBlockedTimes();
} catch (error) {
console.error('Error loading blocked times:', error);
}
}
function renderBlockedTimes() {
const section = document.getElementById('blocked-times-section');
const container = document.getElementById('blocked-times-list');
const today = new Date().toISOString().split('T')[0];
const futureBlocked = blockedTimes.filter(b => b.date >= today);
if (futureBlocked.length === 0) {
section.style.display = 'none';
return;
}
section.style.display = 'block';
container.innerHTML = futureBlocked.map(blocked => {
const date = new Date(blocked.date);
const formattedDate = date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
const formattedTime = new Date(`${blocked.date}T${blocked.time}`).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
return `
<div style="display: flex; align-items: center; justify-content: space-between; background: white; padding: 0.75rem 1rem; border-radius: 8px; margin-bottom: 0.5rem; border: 1px solid #fecaca;">
<div>
<span style="font-weight: 600; color: #dc2626;">${formattedDate}</span>
<span style="color: #64748b; margin: 0 0.5rem;">at</span>
<span style="font-weight: 600;">${formattedTime}</span>
${blocked.reason ? `<span style="color: #64748b; font-size: 0.875rem; margin-left: 0.5rem;">(${blocked.reason})</span>` : ''}
</div>
<button onclick="unblockTime('${blocked.id}')" style="background: none; border: none; color: #dc2626; cursor: pointer; padding: 0.25rem;">
<i class="fas fa-times"></i>
</button>
</div>
`;
}).join('');
}
async function unblockTime(id) {
if (!await MSPE.confirmDialog('Remove this blocked time?')) {
return;
}
try {
await MSPE.API.delete(`bookings.php?id=${id}&type=blocked`);
MSPE.showNotification('Time unblocked successfully!', 'success');
loadBlockedTimes();
} catch (error) {
console.error('Error unblocking time:', error);
MSPE.showNotification('Failed to unblock time', 'error');
}
}
async function handleAddSlot(e) {
e.preventDefault();
const form = e.target;
const data = new FormData(form);
const slotData = Object.fromEntries(data);
if (!slotData.date || !slotData.time) {
MSPE.showNotification('Please select date and time', 'error');
return;
}
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Adding...';
try {
const response = await MSPE.API.post('bookings.php?action=create-slot', slotData);
MSPE.showNotification('Time slot added successfully!', 'success');
form.reset();
setDefaultDate();
loadSlots();
} catch (error) {
console.error('Error adding slot:', error);
MSPE.showNotification('Failed to add time slot', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
}
}
async function generateWeekSlots() {
const today = new Date();
const slotsToAdd = [];
const times = ['09:00', '10:00', '11:00', '13:00', '14:00', '15:00', '16:00', '17:00'];
for (let i = 1; i <= 7; i++) {
const date = new Date(today);
date.setDate(date.getDate() + i);
const dateStr = date.toISOString().split('T')[0];
times.forEach(time => {
slotsToAdd.push({
date: dateStr,
time: time,
capacity: 1
});
});
}
if (!await MSPE.confirmDialog(`This will add ${slotsToAdd.length} time slots for the next 7 days. Continue?`)) {
return;
}
let added = 0;
let failed = 0;
for (const slot of slotsToAdd) {
try {
await MSPE.API.post('bookings.php?action=create-slot', slot);
added++;
} catch (error) {
failed++;
}
}
if (added > 0) {
MSPE.showNotification(`${added} time slots added successfully!`, 'success');
}
if (failed > 0) {
MSPE.showNotification(`${failed} slots failed to add`, 'error');
}
loadSlots();
}
async function loadSlots() {
try {
const result = await MSPE.API.get('bookings.php');
currentSlots = result.data || [];
renderSlots();
} catch (error) {
console.error('Error loading slots:', error);
}
}
function renderSlots() {
const tbody = document.getElementById('slots-table-body');
const dateFrom = document.getElementById('filter-date-from').value;
const dateTo = document.getElementById('filter-date-to').value;
let filteredSlots = [...currentSlots];
if (dateFrom) {
filteredSlots = filteredSlots.filter(s => s.date >= dateFrom);
}
if (dateTo) {
filteredSlots = filteredSlots.filter(s => s.date <= dateTo);
}
if (filteredSlots.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6">
<div class="empty-state">
<i class="fas fa-calendar-times"></i>
<p>No availability slots found</p>
<p style="font-size: 0.875rem; margin-top: 0.5rem;">Add time slots using the form on the left</p>
</div>
</td>
</tr>
`;
return;
}
filteredSlots.sort((a, b) => {
return new Date(`${a.date}T${a.time}`) - new Date(`${b.date}T${b.time}`);
});
tbody.innerHTML = filteredSlots.map(slot => {
const date = new Date(slot.date);
const formattedDate = date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
const formattedTime = new Date(`${slot.date}T${slot.time}`).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
const isAvailable = slot.is_available !== false;
return `
<tr>
<td>
<div style="font-weight: 600;">${formattedDate}</div>
</td>
<td>
<span class="slot-time">${formattedTime}</span>
</td>
<td>
<span class="slot-capacity">
<i class="fas fa-users"></i> ${slot.capacity}
</span>
</td>
<td>
<span class="status-badge ${isAvailable ? 'status-confirmed' : 'status-cancelled'}">
${isAvailable ? 'Available' : 'Disabled'}
</span>
</td>
<td>
<span style="color: var(--gray-500);">${slot.notes || '-'}</span>
</td>
<td>
<button class="action-btn toggle" onclick="toggleSlot('${slot.id}')">
<i class="fas fa-${isAvailable ? 'eye-slash' : 'eye'}"></i>
</button>
<button class="action-btn delete" onclick="deleteSlot('${slot.id}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`;
}).join('');
}
function filterSlots() {
renderSlots();
}
async function toggleSlot(id) {
const slot = currentSlots.find(s => s.id === id);
if (!slot) return;
const newStatus = slot.is_available === false ? true : false;
if (!await MSPE.confirmDialog(`Are you sure you want to ${newStatus ? 'enable' : 'disable'} this slot?`)) {
return;
}
try {
await MSPE.API.put(`bookings.php?id=${id}`, { is_available: newStatus });
MSPE.showNotification(`Slot ${newStatus ? 'enabled' : 'disabled'} successfully!`, 'success');
loadSlots();
} catch (error) {
console.error('Error toggling slot:', error);
MSPE.showNotification('Failed to update slot', 'error');
}
}
async function deleteSlot(id) {
if (!await MSPE.confirmDialog('Are you sure you want to delete this availability slot?')) {
return;
}
try {
await MSPE.API.delete(`bookings.php?id=${id}`);
MSPE.showNotification('Slot deleted successfully!', 'success');
loadSlots();
} catch (error) {
console.error('Error deleting slot:', error);
MSPE.showNotification('Failed to delete slot', 'error');
}
}
async function loadUpcomingBookings() {
try {
const response = await MSPE.API.get('bookings.php?action=upcoming');
const bookings = response.data || [];
if (bookings.length === 0) {
document.getElementById('upcoming-section').style.display = 'none';
return;
}
document.getElementById('upcoming-section').style.display = 'block';
const container = document.getElementById('upcoming-bookings');
container.innerHTML = bookings.slice(0, 5).map(booking => {
const date = new Date(booking.booking_date);
const day = date.getDate();
const month = date.toLocaleDateString('en-US', { month: 'short' });
const formattedTime = new Date(`${booking.booking_date}T${booking.booking_time}`).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
return `
<div class="upcoming-item">
<div class="upcoming-item-date">
<div class="day">${day}</div>
<div class="month">${month}</div>
</div>
<div style="flex: 1;">
<div style="font-weight: 600;">${booking.first_name} ${booking.last_name}</div>
<div style="font-size: 0.875rem; opacity: 0.8;">${formattedTime}</div>
</div>
<i class="fas fa-chevron-right" style="color: var(--gray-400);"></i>
</div>
`;
}).join('');
} catch (error) {
console.error('Error loading upcoming bookings:', error);
}
}
</script>
</body>
</html>