awesome-app / index.html
Cezarxil's picture
the buttons dont work - Follow Up Deployment
c92446c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Appointments</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 scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Animation for notifications */
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.notification {
animation: slideIn 0.3s forwards, fadeOut 0.5s 2.5s forwards;
}
/* Custom checkbox */
.custom-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #4f46e5;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
}
.custom-checkbox:checked {
background-color: #4f46e5;
}
.custom-checkbox:checked::after {
content: '\2713';
font-size: 14px;
color: white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body class="bg-gray-100 min-h-screen font-sans">
<div class="container mx-auto px-4 py-6 max-w-md">
<!-- Header -->
<header class="flex justify-between items-center mb-8">
<h1 class="text-2xl font-bold text-indigo-700">
<i class="fas fa-calendar-check mr-2"></i> Appointments
</h1>
<div class="flex space-x-2">
<button id="calendarViewBtn" class="bg-gray-200 text-gray-700 p-2 rounded-full hover:bg-gray-300 transition">
<i class="fas fa-calendar-alt"></i>
</button>
<button id="addBtn" class="bg-indigo-600 text-white p-2 rounded-full hover:bg-indigo-700 transition">
<i class="fas fa-plus"></i>
</button>
</div>
</header>
<!-- Calendar View -->
<div id="calendarView" class="hidden mb-6 bg-white rounded-lg shadow p-4">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium" id="calendarMonthYear">Month Year</h3>
<div class="flex space-x-2">
<button id="prevMonth" class="p-1 text-gray-500 hover:text-gray-700">
<i class="fas fa-chevron-left"></i>
</button>
<button id="todayBtn" class="px-2 py-1 text-sm bg-gray-100 rounded hover:bg-gray-200">
Today
</button>
<button id="nextMonth" class="p-1 text-gray-500 hover:text-gray-700">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<div class="grid grid-cols-7 gap-1 mb-2">
<div class="text-center font-medium text-sm py-1">Sun</div>
<div class="text-center font-medium text-sm py-1">Mon</div>
<div class="text-center font-medium text-sm py-1">Tue</div>
<div class="text-center font-medium text-sm py-1">Wed</div>
<div class="text-center font-medium text-sm py-1">Thu</div>
<div class="text-center font-medium text-sm py-1">Fri</div>
<div class="text-center font-medium text-sm py-1">Sat</div>
</div>
<div id="calendarDays" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be generated here -->
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-white p-4 rounded-lg shadow">
<p class="text-gray-500 text-sm">Today</p>
<h3 class="text-xl font-bold" id="todayCount">0</h3>
</div>
<div class="bg-white p-4 rounded-lg shadow">
<p class="text-gray-500 text-sm">Upcoming</p>
<h3 class="text-xl font-bold" id="upcomingCount">0</h3>
</div>
</div>
<!-- Filter Tabs -->
<div class="flex mb-4 bg-white rounded-lg shadow overflow-hidden">
<button class="filter-tab flex-1 py-2 px-4 text-center font-medium" data-filter="all">All</button>
<button class="filter-tab flex-1 py-2 px-4 text-center font-medium" data-filter="upcoming">Upcoming</button>
<button class="filter-tab flex-1 py-2 px-4 text-center font-medium" data-filter="completed">Completed</button>
</div>
<!-- Search -->
<div class="relative mb-6">
<input type="text" id="searchInput" placeholder="Search clients..."
class="w-full p-3 pl-10 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<i class="fas fa-search absolute left-3 top-3.5 text-gray-400"></i>
</div>
<!-- Appointments List -->
<div id="appointmentsList" class="space-y-3 max-h-[60vh] overflow-y-auto">
<!-- Appointments will be loaded here -->
</div>
<!-- Empty State -->
<div id="emptyState" class="text-center py-10 hidden">
<i class="fas fa-calendar-times text-4xl text-gray-300 mb-4"></i>
<h3 class="text-lg font-medium text-gray-500">No appointments found</h3>
<p class="text-gray-400">Add a new appointment by clicking the + button</p>
</div>
</div>
<!-- Add/Edit Appointment Modal -->
<div id="appointmentModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-medium" id="modalTitle">Add New Appointment</h3>
<button id="closeModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<form id="appointmentForm" class="p-4 space-y-4">
<input type="hidden" id="appointmentId">
<div>
<label for="clientName" class="block text-sm font-medium text-gray-700 mb-1">Client Name</label>
<input type="text" id="clientName" required
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="clientPhone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
<input type="tel" id="clientPhone"
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="serviceType" class="block text-sm font-medium text-gray-700 mb-1">Service</label>
<select id="serviceType" required
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="">Select a service</option>
<option value="FULL BODY">FULL BODY</option>
<option value="MAXI">MAXI</option>
<option value="MINI">MINI</option>
<option value="INGHINAL">INGHINAL</option>
<option value="AXILE">AXILE</option>
<option value="FACIAL">FACIAL</option>
</select>
</div>
<div>
<label for="appointmentDate" class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="appointmentDate" required
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="appointmentTime" class="block text-sm font-medium text-gray-700 mb-1">Time</label>
<input type="time" id="appointmentTime" required
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="appointmentNotes" class="block text-sm font-medium text-gray-700 mb-1">Notes</label>
<textarea id="appointmentNotes" rows="3"
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
</div>
<div class="flex items-center">
<input type="checkbox" id="isCompleted" class="custom-checkbox mr-2">
<label for="isCompleted" class="text-sm font-medium text-gray-700">Completed</label>
</div>
<div class="flex space-x-3 pt-2">
<button type="submit" class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition">
Save
</button>
<button type="button" id="cancelBtn" class="flex-1 bg-gray-200 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-300 transition">
Cancel
</button>
</div>
</form>
</div>
</div>
<!-- Notification -->
<div id="notification" class="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg hidden notification">
<span id="notificationText">Appointment saved successfully!</span>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const addBtn = document.getElementById('addBtn');
const appointmentModal = document.getElementById('appointmentModal');
const closeModal = document.getElementById('closeModal');
const cancelBtn = document.getElementById('cancelBtn');
const appointmentForm = document.getElementById('appointmentForm');
const appointmentsList = document.getElementById('appointmentsList');
const emptyState = document.getElementById('emptyState');
const searchInput = document.getElementById('searchInput');
const filterTabs = document.querySelectorAll('.filter-tab');
const todayCountEl = document.getElementById('todayCount');
const upcomingCountEl = document.getElementById('upcomingCount');
const notification = document.getElementById('notification');
// Current filter state
let currentFilter = 'all';
let currentSearch = '';
// Initialize the app
initApp();
// Event Listeners
addBtn.addEventListener('click', openAddModal);
calendarViewBtn.addEventListener('click', toggleCalendarView);
prevMonth.addEventListener('click', () => {
currentDate.setMonth(currentDate.getMonth() - 1);
renderCalendar();
});
nextMonth.addEventListener('click', () => {
currentDate.setMonth(currentDate.getMonth() + 1);
renderCalendar();
});
todayBtn.addEventListener('click', () => {
currentDate = new Date();
renderCalendar();
});
closeModal.addEventListener('click', closeModalFunc);
cancelBtn.addEventListener('click', closeModalFunc);
appointmentForm.addEventListener('submit', saveAppointment);
searchInput.addEventListener('input', function(e) {
currentSearch = e.target.value.toLowerCase();
renderAppointments();
});
filterTabs.forEach(tab => {
tab.addEventListener('click', function() {
filterTabs.forEach(t => t.classList.remove('bg-indigo-600', 'text-white'));
this.classList.add('bg-indigo-600', 'text-white');
currentFilter = this.dataset.filter;
renderAppointments();
});
});
// Set first tab as active
filterTabs[0].classList.add('bg-indigo-600', 'text-white');
// Calendar variables
const calendarViewBtn = document.getElementById('calendarViewBtn');
const calendarView = document.getElementById('calendarView');
const prevMonth = document.getElementById('prevMonth');
const nextMonth = document.getElementById('nextMonth');
const todayBtn = document.getElementById('todayBtn');
let currentDate = new Date();
let currentView = 'list'; // 'list' or 'calendar'
// Functions
function initApp() {
// Initialize calendar
renderCalendar();
// Check if appointments exist in localStorage
if (!localStorage.getItem('appointments')) {
// Add some sample data if empty
const sampleAppointments = [
{
id: Date.now().toString(),
clientName: 'John Doe',
clientPhone: '555-1234',
date: getFormattedDate(new Date()),
time: '10:00',
notes: 'First consultation',
completed: false,
createdAt: new Date().toISOString()
},
{
id: (Date.now() + 1).toString(),
clientName: 'Jane Smith',
clientPhone: '555-5678',
date: getFormattedDate(new Date(new Date().setDate(new Date().getDate() + 1))),
time: '14:30',
notes: 'Follow-up appointment',
completed: false,
createdAt: new Date().toISOString()
}
];
localStorage.setItem('appointments', JSON.stringify(sampleAppointments));
}
renderAppointments();
updateStats();
}
function openAddModal() {
document.getElementById('modalTitle').textContent = 'Add New Appointment';
document.getElementById('appointmentId').value = '';
document.getElementById('appointmentForm').reset();
document.getElementById('serviceType').value = '';
document.getElementById('isCompleted').checked = false;
// Set default date to today
const today = new Date();
document.getElementById('appointmentDate').value = getFormattedDate(today);
// Set default time to next hour
const nextHour = today.getHours() + 1;
document.getElementById('appointmentTime').value = `${nextHour.toString().padStart(2, '0')}:00`;
appointmentModal.classList.remove('hidden');
}
function openEditModal(appointment) {
document.getElementById('modalTitle').textContent = 'Edit Appointment';
document.getElementById('appointmentId').value = appointment.id;
document.getElementById('clientName').value = appointment.clientName;
document.getElementById('clientPhone').value = appointment.clientPhone;
document.getElementById('serviceType').value = appointment.serviceType || '';
document.getElementById('appointmentDate').value = appointment.date;
document.getElementById('appointmentTime').value = appointment.time;
document.getElementById('appointmentNotes').value = appointment.notes;
document.getElementById('isCompleted').checked = appointment.completed;
appointmentModal.classList.remove('hidden');
}
function closeModalFunc() {
appointmentModal.classList.add('hidden');
}
function saveAppointment(e) {
e.preventDefault();
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const appointmentId = document.getElementById('appointmentId').value;
const appointment = {
id: appointmentId || Date.now().toString(),
clientName: document.getElementById('clientName').value,
clientPhone: document.getElementById('clientPhone').value,
serviceType: document.getElementById('serviceType').value,
date: document.getElementById('appointmentDate').value,
time: document.getElementById('appointmentTime').value,
notes: document.getElementById('appointmentNotes').value,
completed: document.getElementById('isCompleted').checked,
createdAt: appointmentId ?
(appointments.find(a => a.id === appointmentId)?.createdAt || new Date().toISOString()) :
new Date().toISOString()
};
if (appointmentId) {
// Update existing appointment
const index = appointments.findIndex(a => a.id === appointmentId);
if (index !== -1) {
appointments[index] = appointment;
}
} else {
// Add new appointment
appointments.push(appointment);
}
localStorage.setItem('appointments', JSON.stringify(appointments));
closeModalFunc();
renderAppointments();
updateStats();
// Show notification
showNotification('Appointment saved successfully!');
}
function deleteAppointment(id) {
if (confirm('Are you sure you want to delete this appointment?')) {
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const updatedAppointments = appointments.filter(a => a.id !== id);
localStorage.setItem('appointments', JSON.stringify(updatedAppointments));
renderAppointments();
updateStats();
// Show notification
showNotification('Appointment deleted!');
}
}
function toggleCompleted(id) {
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const appointment = appointments.find(a => a.id === id);
if (appointment) {
appointment.completed = !appointment.completed;
localStorage.setItem('appointments', JSON.stringify(appointments));
renderAppointments();
updateStats();
// Show notification
showNotification(`Appointment marked as ${appointment.completed ? 'completed' : 'pending'}!`);
}
}
function renderAppointments() {
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
// Filter appointments based on current filter and search
let filteredAppointments = appointments.filter(appointment => {
// Apply search filter
const matchesSearch = currentSearch === '' ||
appointment.clientName.toLowerCase().includes(currentSearch) ||
appointment.clientPhone.includes(currentSearch) ||
appointment.notes.toLowerCase().includes(currentSearch);
// Apply status filter
let matchesFilter = true;
if (currentFilter === 'upcoming') {
matchesFilter = !appointment.completed;
} else if (currentFilter === 'completed') {
matchesFilter = appointment.completed;
}
return matchesSearch && matchesFilter;
});
// Sort appointments by date and time (upcoming 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;
});
// Clear the list
appointmentsList.innerHTML = '';
if (filteredAppointments.length === 0) {
emptyState.classList.remove('hidden');
} else {
emptyState.classList.add('hidden');
filteredAppointments.forEach(appointment => {
const appointmentDate = new Date(`${appointment.date}T${appointment.time}`);
const isToday = isSameDay(new Date(), appointmentDate);
const isPast = appointmentDate < new Date() && !isToday;
const appointmentEl = document.createElement('div');
appointmentEl.className = `bg-white rounded-lg shadow p-4 ${appointment.completed ? 'opacity-70' : ''} ${isPast && !appointment.completed ? 'border-l-4 border-red-500' : ''}`;
appointmentEl.innerHTML = `
<div class="flex justify-between items-start mb-2">
<div>
<h3 class="font-bold text-lg ${appointment.completed ? 'line-through' : ''}">${appointment.clientName}</h3>
<p class="text-gray-500 text-sm">${formatDate(appointment.date)} at ${appointment.time}</p>
</div>
<div class="flex space-x-2">
<button class="toggle-complete p-1 text-${appointment.completed ? 'green' : 'gray'}-500" data-id="${appointment.id}">
<i class="fas fa-${appointment.completed ? 'check-circle' : 'circle'}"></i>
</button>
<button class="edit-btn p-1 text-blue-500" data-id="${appointment.id}">
<i class="fas fa-edit"></i>
</button>
<button class="delete-btn p-1 text-red-500" data-id="${appointment.id}">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
${appointment.clientPhone ? `<p class="text-gray-600 mb-1"><i class="fas fa-phone mr-2"></i>${appointment.clientPhone}</p>` : ''}
${appointment.serviceType ? `<p class="text-gray-600 mb-1"><i class="fas fa-cut mr-2"></i>${appointment.serviceType}</p>` : ''}
${appointment.notes ? `<p class="text-gray-600"><i class="fas fa-sticky-note mr-2"></i>${appointment.notes}</p>` : ''}
${isToday ? `<span class="inline-block mt-2 px-2 py-1 bg-indigo-100 text-indigo-800 text-xs rounded-full">Today</span>` : ''}
`;
appointmentsList.appendChild(appointmentEl);
});
// Add event listeners to dynamically created buttons
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const appointment = appointments.find(a => a.id === id);
if (appointment) {
openEditModal(appointment);
}
});
});
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
deleteAppointment(id);
});
});
document.querySelectorAll('.toggle-complete').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
toggleCompleted(id);
});
});
}
}
function toggleCalendarView() {
currentView = currentView === 'list' ? 'calendar' : 'list';
if (currentView === 'calendar') {
appointmentsList.classList.add('hidden');
calendarView.classList.remove('hidden');
calendarViewBtn.classList.remove('bg-gray-200');
calendarViewBtn.classList.add('bg-indigo-600', 'text-white');
renderCalendar();
} else {
appointmentsList.classList.remove('hidden');
calendarView.classList.add('hidden');
calendarViewBtn.classList.remove('bg-indigo-600', 'text-white');
calendarViewBtn.classList.add('bg-gray-200');
}
}
function renderCalendar() {
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
// Update month/year display
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
document.getElementById('calendarMonthYear').textContent =
`${monthNames[month]} ${year}`;
// Get first day of month and total days in month
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Get appointments for this month
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const monthAppointments = appointments.filter(appt => {
const apptDate = new Date(appt.date);
return apptDate.getFullYear() === year && apptDate.getMonth() === month;
});
// Generate calendar days
const calendarDays = document.getElementById('calendarDays');
calendarDays.innerHTML = '';
// Add empty cells for days before first day of month
for (let i = 0; i < firstDay; i++) {
const emptyCell = document.createElement('div');
emptyCell.className = 'h-16 p-1 border border-gray-100 bg-gray-50';
calendarDays.appendChild(emptyCell);
}
// Add day cells
for (let day = 1; day <= daysInMonth; day++) {
const dayCell = document.createElement('div');
dayCell.className = 'h-16 p-1 border border-gray-200 overflow-y-auto';
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const dayAppointments = monthAppointments.filter(appt => appt.date === dateStr);
// Highlight today
const today = new Date();
if (year === today.getFullYear() && month === today.getMonth() && day === today.getDate()) {
dayCell.classList.add('bg-indigo-50');
}
// Day number
const dayNumber = document.createElement('div');
dayNumber.className = 'text-right text-sm font-medium mb-1';
dayNumber.textContent = day;
dayCell.appendChild(dayNumber);
// Appointments for this day
dayAppointments.forEach(appt => {
const apptEl = document.createElement('div');
apptEl.className = `text-xs p-1 mb-1 rounded truncate ${appt.completed ? 'bg-green-100 text-green-800' : 'bg-indigo-100 text-indigo-800'}`;
apptEl.title = `${appt.clientName} - ${appt.time}`;
apptEl.textContent = `${appt.time} ${appt.clientName}`;
dayCell.appendChild(apptEl);
});
calendarDays.appendChild(dayCell);
}
}
function updateStats() {
const appointments = JSON.parse(localStorage.getItem('appointments')) || [];
const today = new Date().toISOString().split('T')[0];
const todayAppointments = appointments.filter(a => a.date === today && !a.completed).length;
const upcomingAppointments = appointments.filter(a => {
const appointmentDate = new Date(`${a.date}T${a.time}`);
return !a.completed && (new Date(a.date) > new Date() ||
(isSameDay(new Date(), appointmentDate) && appointmentDate > new Date()));
}).length;
todayCountEl.textContent = todayAppointments;
upcomingCountEl.textContent = upcomingAppointments;
}
function showNotification(message) {
notificationText.textContent = message;
notification.classList.remove('hidden');
// Hide after 3 seconds
setTimeout(() => {
notification.classList.add('hidden');
}, 3000);
}
// Helper functions
function getFormattedDate(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 formatDate(dateString) {
const options = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
return new Date(dateString).toLocaleDateString(undefined, options);
}
function isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
});
</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>