awesome-app / index.html
Cezarxil's picture
Make the username: epiliz ; password: culoareafericiri - Follow Up Deployment
ec6c815 verified
raw
history blame
52.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laser Hair Removal Salon Scheduler</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles that can't be done with Tailwind */
.calendar-day:hover {
background-color: #f3f4f6;
cursor: pointer;
}
.calendar-day.has-appointments {
background-color: #e5e7eb;
}
.appointment-item:hover {
background-color: #f3f4f6;
}
#calendar {
min-height: 500px;
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<!-- Login Screen (shown by default) -->
<div id="login-screen" class="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-50 z-50">
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
<div class="text-center mb-6">
<i class="fas fa-spa text-4xl text-purple-600 mb-2"></i>
<h1 class="text-2xl font-bold text-gray-800">Laser Elegance Salon</h1>
<p class="text-gray-600">Staff Login</p>
</div>
<form id="login-form" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<button type="submit"
class="w-full bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2">
Login
</button>
</form>
</div>
</div>
<!-- Main App Container (hidden by default) -->
<div id="app-container" class="hidden">
<!-- Header -->
<header class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fas fa-spa text-2xl text-purple-600"></i>
<h1 class="text-xl font-bold text-gray-800">Laser Elegance Scheduler</h1>
</div>
<div class="flex items-center space-x-4">
<button id="logout-btn" class="flex items-center text-gray-600 hover:text-purple-600">
<i class="fas fa-sign-out-alt mr-1"></i>
<span>Logout</span>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<!-- Tabs Navigation -->
<div class="border-b border-gray-200 mb-6">
<nav class="-mb-px flex space-x-8">
<button id="calendar-tab" class="tab-button active border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
<i class="fas fa-calendar-alt mr-2"></i>Calendar
</button>
<button id="appointments-tab" class="tab-button border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
<i class="fas fa-list mr-2"></i>Appointments
</button>
<button id="new-appointment-tab" class="tab-button border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
<i class="fas fa-plus-circle mr-2"></i>New Appointment
</button>
</nav>
</div>
<!-- Calendar Tab Content -->
<div id="calendar-content" class="tab-content fade-in">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Appointment Calendar</h2>
<div class="flex items-center space-x-4">
<button id="prev-month" class="p-2 rounded-full hover:bg-gray-200">
<i class="fas fa-chevron-left"></i>
</button>
<span id="current-month" class="font-medium">July 2023</span>
<button id="next-month" class="p-2 rounded-full hover:bg-gray-200">
<i class="fas fa-chevron-right"></i>
</button>
<button id="today-btn" class="px-3 py-1 bg-gray-200 rounded-md text-sm hover:bg-gray-300">
Today
</button>
</div>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden">
<!-- Calendar Header (Days of Week) -->
<div class="grid grid-cols-7 bg-gray-100 border-b border-gray-200">
<div class="py-2 text-center font-medium text-sm text-gray-600">Sun</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Mon</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Tue</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Wed</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Thu</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Fri</div>
<div class="py-2 text-center font-medium text-sm text-gray-600">Sat</div>
</div>
<!-- Calendar Grid -->
<div id="calendar" class="grid grid-cols-7 grid-rows-5 border border-gray-200 bg-white">
<!-- Calendar days will be dynamically inserted here -->
</div>
</div>
<!-- Day Appointments Modal -->
<div id="day-appointments-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[80vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<h3 id="modal-day-title" class="text-lg font-semibold text-gray-800">Appointments for July 15, 2023</h3>
<button id="close-day-modal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div id="day-appointments-list" class="p-4 space-y-3">
<!-- Appointments will be inserted here -->
</div>
<div class="px-6 py-3 border-t border-gray-200 flex justify-end">
<button id="add-appointment-for-day" class="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
Add Appointment
</button>
</div>
</div>
</div>
</div>
<!-- Appointments List Tab Content -->
<div id="appointments-content" class="tab-content hidden fade-in">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Upcoming Appointments</h2>
<div class="flex items-center space-x-4">
<div class="relative">
<input type="text" id="search-appointments" placeholder="Search clients..."
class="pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<button id="export-csv" class="px-3 py-1 bg-gray-200 rounded-md text-sm hover:bg-gray-300 flex items-center">
<i class="fas fa-file-export mr-2"></i> Export CSV
</button>
</div>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Service</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date & Time</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact</th>
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="appointments-table-body" class="bg-white divide-y divide-gray-200">
<!-- Appointments will be inserted here -->
</tbody>
</table>
</div>
</div>
</div>
<!-- New Appointment Tab Content -->
<div id="new-appointment-content" class="tab-content hidden fade-in">
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-xl font-semibold text-gray-800">New Appointment</h2>
</div>
<div class="p-6">
<form id="new-appointment-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="client-name" class="block text-sm font-medium text-gray-700 mb-1">Client Full Name *</label>
<input type="text" id="client-name" name="client-name" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="client-phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
<input type="tel" id="client-phone" name="client-phone" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="client-email" class="block text-sm font-medium text-gray-700 mb-1">Email (Optional)</label>
<input type="email" id="client-email" name="client-email"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="service-package" class="block text-sm font-medium text-gray-700 mb-1">Service Package *</label>
<select id="service-package" name="service-package" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
<option value="">Select a package</option>
<option value="Full Body">Full Body</option>
<option value="Legs Only">Legs Only</option>
<option value="Underarms">Underarms</option>
<option value="Bikini Line">Bikini Line</option>
<option value="Brazilian">Brazilian</option>
<option value="Face">Face</option>
<option value="Back">Back</option>
<option value="Chest">Chest</option>
</select>
</div>
<div>
<label for="appointment-date" class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
<input type="date" id="appointment-date" name="appointment-date" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="appointment-time" class="block text-sm font-medium text-gray-700 mb-1">Time *</label>
<input type="time" id="appointment-time" name="appointment-time" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
</div>
<div>
<label for="appointment-notes" class="block text-sm font-medium text-gray-700 mb-1">Notes (Optional)</label>
<textarea id="appointment-notes" name="appointment-notes" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
</div>
<div class="flex justify-end space-x-4">
<button type="reset" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
Clear
</button>
<button type="submit" class="px-6 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
Schedule Appointment
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Appointment Modal -->
<div id="edit-appointment-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[80vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-semibold text-gray-800">Edit Appointment</h3>
<button id="close-edit-modal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<form id="edit-appointment-form" class="space-y-6">
<input type="hidden" id="edit-appointment-id">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="edit-client-name" class="block text-sm font-medium text-gray-700 mb-1">Client Full Name *</label>
<input type="text" id="edit-client-name" name="edit-client-name" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="edit-client-phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
<input type="tel" id="edit-client-phone" name="edit-client-phone" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="edit-client-email" class="block text-sm font-medium text-gray-700 mb-1">Email (Optional)</label>
<input type="email" id="edit-client-email" name="edit-client-email"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="edit-service-package" class="block text-sm font-medium text-gray-700 mb-1">Service Package *</label>
<select id="edit-service-package" name="edit-service-package" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
<option value="">Select a package</option>
<option value="Full Body">Full Body</option>
<option value="Legs Only">Legs Only</option>
<option value="Underarms">Underarms</option>
<option value="Bikini Line">Bikini Line</option>
<option value="Brazilian">Brazilian</option>
<option value="Face">Face</option>
<option value="Back">Back</option>
<option value="Chest">Chest</option>
</select>
</div>
<div>
<label for="edit-appointment-date" class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
<input type="date" id="edit-appointment-date" name="edit-appointment-date" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label for="edit-appointment-time" class="block text-sm font-medium text-gray-700 mb-1">Time *</label>
<input type="time" id="edit-appointment-time" name="edit-appointment-time" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
</div>
<div>
<label for="edit-appointment-notes" class="block text-sm font-medium text-gray-700 mb-1">Notes (Optional)</label>
<textarea id="edit-appointment-notes" name="edit-appointment-notes" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
</div>
<div class="flex justify-end space-x-4">
<button type="button" id="cancel-edit" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
Cancel
</button>
<button type="submit" class="px-6 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
Update Appointment
</button>
<button type="button" id="delete-appointment" class="px-6 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
Delete
</button>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
<script>
// Mock data for demonstration
let appointments = [
{
id: 1,
clientName: "Sarah Johnson",
phone: "555-123-4567",
email: "[email protected]",
service: "Full Body",
date: "2023-07-15",
time: "10:00",
notes: "First session, sensitive skin"
},
{
id: 2,
clientName: "Michael Chen",
phone: "555-987-6543",
email: "[email protected]",
service: "Back",
date: "2023-07-15",
time: "14:30",
notes: "Follow-up session"
},
{
id: 3,
clientName: "Emily Rodriguez",
phone: "555-456-7890",
email: "",
service: "Legs Only",
date: "2023-07-18",
time: "11:15",
notes: ""
},
{
id: 4,
clientName: "David Kim",
phone: "555-789-0123",
email: "[email protected]",
service: "Underarms",
date: "2023-07-20",
time: "09:00",
notes: "Allergic to aloe vera"
},
{
id: 5,
clientName: "Jessica Williams",
phone: "555-234-5678",
email: "[email protected]",
service: "Brazilian",
date: "2023-07-22",
time: "13:45",
notes: "Prefer cold gel"
}
];
// Current user session
let currentUser = null;
// Current month and year for calendar
let currentDate = new Date();
let currentMonth = currentDate.getMonth();
let currentYear = currentDate.getFullYear();
// DOM Elements
const loginScreen = document.getElementById('login-screen');
const appContainer = document.getElementById('app-container');
const loginForm = document.getElementById('login-form');
const logoutBtn = document.getElementById('logout-btn');
// Tab elements
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
// Calendar elements
const calendar = document.getElementById('calendar');
const currentMonthDisplay = document.getElementById('current-month');
const prevMonthBtn = document.getElementById('prev-month');
const nextMonthBtn = document.getElementById('next-month');
const todayBtn = document.getElementById('today-btn');
// Appointments list elements
const appointmentsTableBody = document.getElementById('appointments-table-body');
const searchAppointments = document.getElementById('search-appointments');
const exportCsvBtn = document.getElementById('export-csv');
// New appointment form elements
const newAppointmentForm = document.getElementById('new-appointment-form');
// Modal elements
const dayAppointmentsModal = document.getElementById('day-appointments-modal');
const modalDayTitle = document.getElementById('modal-day-title');
const dayAppointmentsList = document.getElementById('day-appointments-list');
const closeDayModal = document.getElementById('close-day-modal');
const addAppointmentForDay = document.getElementById('add-appointment-for-day');
const editAppointmentModal = document.getElementById('edit-appointment-modal');
const editAppointmentForm = document.getElementById('edit-appointment-form');
const closeEditModal = document.getElementById('close-edit-modal');
const cancelEdit = document.getElementById('cancel-edit');
const deleteAppointmentBtn = document.getElementById('delete-appointment');
// Event Listeners
document.addEventListener('DOMContentLoaded', () => {
// Initialize the app
if (localStorage.getItem('laserSalonUser')) {
currentUser = JSON.parse(localStorage.getItem('laserSalonUser'));
loginScreen.classList.add('hidden');
appContainer.classList.remove('hidden');
renderCalendar();
renderAppointmentsList();
}
});
// Login/Logout
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// Simple validation (in a real app, this would be a backend call)
if (username === 'epiliz' && password === 'culoareafericiri') {
currentUser = { username: 'admin', name: 'Admin User' };
localStorage.setItem('laserSalonUser', JSON.stringify(currentUser));
loginScreen.classList.add('hidden');
appContainer.classList.remove('hidden');
renderCalendar();
renderAppointmentsList();
} else {
alert('Invalid credentials. Try epiliz/culoareafericiri');
}
});
logoutBtn.addEventListener('click', () => {
currentUser = null;
localStorage.removeItem('laserSalonUser');
loginScreen.classList.remove('hidden');
appContainer.classList.add('hidden');
loginForm.reset();
});
// Tab switching
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Remove active class from all buttons
tabButtons.forEach(btn => {
btn.classList.remove('active', 'border-purple-500', 'text-purple-600');
btn.classList.add('border-transparent', 'text-gray-500');
});
// Add active class to clicked button
button.classList.add('active', 'border-purple-500', 'text-purple-600');
button.classList.remove('border-transparent', 'text-gray-500');
// Hide all tab contents
tabContents.forEach(content => {
content.classList.add('hidden');
});
// Show the corresponding tab content
const tabId = button.id.replace('-tab', '-content');
document.getElementById(tabId).classList.remove('hidden');
});
});
// Calendar navigation
prevMonthBtn.addEventListener('click', () => {
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar();
});
nextMonthBtn.addEventListener('click', () => {
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar();
});
todayBtn.addEventListener('click', () => {
currentDate = new Date();
currentMonth = currentDate.getMonth();
currentYear = currentDate.getFullYear();
renderCalendar();
});
// Close modals
closeDayModal.addEventListener('click', () => {
dayAppointmentsModal.classList.add('hidden');
});
closeEditModal.addEventListener('click', () => {
editAppointmentModal.classList.add('hidden');
});
cancelEdit.addEventListener('click', () => {
editAppointmentModal.classList.add('hidden');
});
// Add appointment for specific day
addAppointmentForDay.addEventListener('click', () => {
const day = modalDayTitle.textContent.match(/(\w+ \d{1,2}, \d{4})/)[0];
const date = new Date(day);
const formattedDate = formatDateForInput(date);
// Switch to new appointment tab
tabButtons.forEach(btn => btn.classList.remove('active', 'border-purple-500', 'text-purple-600'));
document.getElementById('new-appointment-tab').classList.add('active', 'border-purple-500', 'text-purple-600');
tabContents.forEach(content => content.classList.add('hidden'));
document.getElementById('new-appointment-content').classList.remove('hidden');
// Set the date in the form
document.getElementById('appointment-date').value = formattedDate;
// Close the modal
dayAppointmentsModal.classList.add('hidden');
});
// New appointment form
newAppointmentForm.addEventListener('submit', (e) => {
e.preventDefault();
const newAppointment = {
id: appointments.length > 0 ? Math.max(...appointments.map(a => a.id)) + 1 : 1,
clientName: document.getElementById('client-name').value,
phone: document.getElementById('client-phone').value,
email: document.getElementById('client-email').value,
service: document.getElementById('service-package').value,
date: document.getElementById('appointment-date').value,
time: document.getElementById('appointment-time').value,
notes: document.getElementById('appointment-notes').value
};
appointments.push(newAppointment);
newAppointmentForm.reset();
// Show success message
alert('Appointment scheduled successfully!');
// Update views
renderCalendar();
renderAppointmentsList();
// Switch to appointments tab
tabButtons.forEach(btn => btn.classList.remove('active', 'border-purple-500', 'text-purple-600'));
document.getElementById('appointments-tab').classList.add('active', 'border-purple-500', 'text-purple-600');
tabContents.forEach(content => content.classList.add('hidden'));
document.getElementById('appointments-content').classList.remove('hidden');
});
// Search appointments
searchAppointments.addEventListener('input', () => {
renderAppointmentsList();
});
// Export to CSV
exportCsvBtn.addEventListener('click', () => {
exportToCsv();
});
// Delete appointment
deleteAppointmentBtn.addEventListener('click', () => {
const appointmentId = parseInt(document.getElementById('edit-appointment-id').value);
if (confirm('Are you sure you want to delete this appointment?')) {
appointments = appointments.filter(a => a.id !== appointmentId);
editAppointmentModal.classList.add('hidden');
renderCalendar();
renderAppointmentsList();
}
});
// Edit appointment form
editAppointmentForm.addEventListener('submit', (e) => {
e.preventDefault();
const appointmentId = parseInt(document.getElementById('edit-appointment-id').value);
const appointmentIndex = appointments.findIndex(a => a.id === appointmentId);
if (appointmentIndex !== -1) {
appointments[appointmentIndex] = {
id: appointmentId,
clientName: document.getElementById('edit-client-name').value,
phone: document.getElementById('edit-client-phone').value,
email: document.getElementById('edit-client-email').value,
service: document.getElementById('edit-service-package').value,
date: document.getElementById('edit-appointment-date').value,
time: document.getElementById('edit-appointment-time').value,
notes: document.getElementById('edit-appointment-notes').value
};
editAppointmentModal.classList.add('hidden');
renderCalendar();
renderAppointmentsList();
}
});
// Functions
function renderCalendar() {
// Update month/year display
currentMonthDisplay.textContent = new Date(currentYear, currentMonth).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
// Clear the calendar
calendar.innerHTML = '';
// Get first day of month and total days in month
const firstDay = new Date(currentYear, currentMonth, 1);
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
// Get day of week for first day (0 = Sunday, 6 = Saturday)
const firstDayOfWeek = firstDay.getDay();
// Get days from previous month to display
const prevMonthDays = new Date(currentYear, currentMonth, 0).getDate();
// Create calendar grid
let dayCount = 1;
let nextMonthDay = 1;
for (let i = 0; i < 35; i++) { // 5 rows x 7 days
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day p-2 border border-gray-200 h-24 overflow-y-auto';
if (i < firstDayOfWeek) {
// Days from previous month
const prevDay = prevMonthDays - (firstDayOfWeek - i - 1);
dayElement.innerHTML = `<div class="text-right text-gray-400">${prevDay}</div>`;
dayElement.classList.add('text-gray-400');
} else if (dayCount > daysInMonth) {
// Days from next month
dayElement.innerHTML = `<div class="text-right text-gray-400">${nextMonthDay}</div>`;
dayElement.classList.add('text-gray-400');
nextMonthDay++;
} else {
// Current month days
dayElement.innerHTML = `<div class="text-right font-medium">${dayCount}</div>`;
// Highlight today
const today = new Date();
if (dayCount === today.getDate() && currentMonth === today.getMonth() && currentYear === today.getFullYear()) {
dayElement.classList.add('bg-purple-100');
}
// Check for appointments on this day
const formattedDate = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(dayCount).padStart(2, '0')}`;
const dayAppointments = appointments.filter(a => a.date === formattedDate);
if (dayAppointments.length > 0) {
dayElement.classList.add('has-appointments');
// Add click event to show appointments for the day
dayElement.addEventListener('click', () => {
showDayAppointments(formattedDate, dayCount);
});
// Display appointment count
const appointmentCount = document.createElement('div');
appointmentCount.className = 'text-xs mt-1 text-purple-600 font-medium';
appointmentCount.textContent = `${dayAppointments.length} appointment${dayAppointments.length !== 1 ? 's' : ''}`;
dayElement.appendChild(appointmentCount);
// Display first 2 appointments (if space allows)
for (let j = 0; j < Math.min(dayAppointments.length, 2); j++) {
const appointment = dayAppointments[j];
const appointmentElement = document.createElement('div');
appointmentElement.className = 'text-xs mt-1 truncate bg-purple-50 px-1 py-0.5 rounded';
appointmentElement.textContent = `${appointment.time} - ${appointment.clientName.split(' ')[0]}`;
appointmentElement.title = `${appointment.time} - ${appointment.clientName}: ${appointment.service}`;
dayElement.appendChild(appointmentElement);
}
if (dayAppointments.length > 2) {
const moreElement = document.createElement('div');
moreElement.className = 'text-xs mt-1 text-gray-500';
moreElement.textContent = `+${dayAppointments.length - 2} more`;
dayElement.appendChild(moreElement);
}
} else {
// Add click event to add new appointment
dayElement.addEventListener('click', () => {
showDayAppointments(formattedDate, dayCount);
});
}
dayCount++;
}
calendar.appendChild(dayElement);
}
}
function showDayAppointments(date, day) {
const formattedDate = new Date(date);
const dayName = formattedDate.toLocaleDateString('en-US', { weekday: 'long' });
const monthName = formattedDate.toLocaleDateString('en-US', { month: 'long' });
modalDayTitle.textContent = `Appointments for ${dayName}, ${monthName} ${day}, ${currentYear}`;
// Filter appointments for this day
const dayAppointments = appointments.filter(a => a.date === date);
// Clear previous appointments
dayAppointmentsList.innerHTML = '';
if (dayAppointments.length === 0) {
dayAppointmentsList.innerHTML = '<p class="text-gray-500 text-center py-4">No appointments scheduled for this day.</p>';
} else {
// Sort appointments by time
dayAppointments.sort((a, b) => a.time.localeCompare(b.time));
// Display each appointment
dayAppointments.forEach(appointment => {
const appointmentElement = document.createElement('div');
appointmentElement.className = 'appointment-item bg-white border border-gray-200 rounded-md p-3';
appointmentElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-gray-800">${appointment.clientName}</h4>
<p class="text-sm text-gray-600">${appointment.service}${appointment.time}</p>
${appointment.notes ? `<p class="text-xs text-gray-500 mt-1">${appointment.notes}</p>` : ''}
</div>
<div class="flex space-x-2">
<button class="edit-appointment text-purple-600 hover:text-purple-800" data-id="${appointment.id}">
<i class="fas fa-edit"></i>
</button>
</div>
</div>
<div class="mt-2 flex items-center text-xs text-gray-500">
<i class="fas fa-phone-alt mr-1"></i>
<span class="mr-3">${appointment.phone}</span>
${appointment.email ? `<i class="fas fa-envelope mr-1"></i><span>${appointment.email}</span>` : ''}
</div>
`;
dayAppointmentsList.appendChild(appointmentElement);
});
// Add event listeners to edit buttons
document.querySelectorAll('.edit-appointment').forEach(button => {
button.addEventListener('click', (e) => {
const appointmentId = parseInt(button.getAttribute('data-id'));
showEditAppointmentModal(appointmentId);
e.stopPropagation();
});
});
}
// Show the modal
dayAppointmentsModal.classList.remove('hidden');
}
function renderAppointmentsList() {
// Clear previous appointments
appointmentsTableBody.innerHTML = '';
// Filter appointments based on search
const searchTerm = searchAppointments.value.toLowerCase();
let filteredAppointments = [...appointments];
if (searchTerm) {
filteredAppointments = appointments.filter(a =>
a.clientName.toLowerCase().includes(searchTerm) ||
a.phone.includes(searchTerm) ||
(a.email && a.email.toLowerCase().includes(searchTerm))
);
}
// Sort appointments by date and time (soonest first)
filteredAppointments.sort((a, b) => {
const dateA = new Date(`${a.date}T${a.time}`);
const dateB = new Date(`${b.date}T${b.time}`);
return dateA - dateB;
});
if (filteredAppointments.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
No appointments found. Try a different search term or create a new appointment.
</td>
`;
appointmentsTableBody.appendChild(row);
return;
}
// Display each appointment
filteredAppointments.forEach(appointment => {
const appointmentDate = new Date(`${appointment.date}T${appointment.time}`);
const formattedDate = appointmentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
const formattedTime = appointmentDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div>
<div class="font-medium text-gray-900">${appointment.clientName}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-gray-900">${appointment.service}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-gray-900">${formattedDate}</div>
<div class="text-gray-500">${formattedTime}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-gray-900">${appointment.phone}</div>
${appointment.email ? `<div class="text-gray-500">${appointment.email}</div>` : ''}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button class="edit-appointment text-purple-600 hover:text-purple-900 mr-3" data-id="${appointment.id}">
<i class="fas fa-edit"></i> Edit
</button>
<button class="delete-appointment text-red-600 hover:text-red-900" data-id="${appointment.id}">
<i class="fas fa-trash-alt"></i> Delete
</button>
</td>
`;
appointmentsTableBody.appendChild(row);
});
// Add event listeners to edit and delete buttons
document.querySelectorAll('.edit-appointment').forEach(button => {
button.addEventListener('click', () => {
const appointmentId = parseInt(button.getAttribute('data-id'));
showEditAppointmentModal(appointmentId);
});
});
document.querySelectorAll('.delete-appointment').forEach(button => {
button.addEventListener('click', () => {
const appointmentId = parseInt(button.getAttribute('data-id'));
if (confirm('Are you sure you want to delete this appointment?')) {
appointments = appointments.filter(a => a.id !== appointmentId);
renderCalendar();
renderAppointmentsList();
}
});
});
}
function showEditAppointmentModal(appointmentId) {
const appointment = appointments.find(a => a.id === appointmentId);
if (!appointment) return;
// Fill the form with appointment data
document.getElementById('edit-appointment-id').value = appointment.id;
document.getElementById('edit-client-name').value = appointment.clientName;
document.getElementById('edit-client-phone').value = appointment.phone;
document.getElementById('edit-client-email').value = appointment.email || '';
document.getElementById('edit-service-package').value = appointment.service;
document.getElementById('edit-appointment-date').value = appointment.date;
document.getElementById('edit-appointment-time').value = appointment.time;
document.getElementById('edit-appointment-notes').value = appointment.notes || '';
// Show the modal
dayAppointmentsModal.classList.add('hidden');
editAppointmentModal.classList.remove('hidden');
}
function formatDateForInput(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function exportToCsv() {
// Sort appointments by date
const sortedAppointments = [...appointments].sort((a, b) => {
const dateA = new Date(`${a.date}T${a.time}`);
const dateB = new Date(`${b.date}T${b.time}`);
return dateA - dateB;
});
// CSV header
let csv = 'Client Name,Phone,Email,Service,Date,Time,Notes\n';
// Add each appointment
sortedAppointments.forEach(appointment => {
const row = [
`"${appointment.clientName}"`,
`"${appointment.phone}"`,
`"${appointment.email || ''}"`,
`"${appointment.service}"`,
`"${appointment.date}"`,
`"${appointment.time}"`,
`"${appointment.notes || ''}"`
];
csv += row.join(',') + '\n';
});
// Create download link
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', `laser_salon_appointments_${new Date().toISOString().slice(0, 10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Cezarxil/awesome-app" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>