Spaces:
Running
Running
Make the username: Epiliz password: Epilat2110 - Follow Up Deployment
Browse files- index.html +304 -830
index.html
CHANGED
@@ -3,167 +3,101 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<title>Laser Hair Removal
|
7 |
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
9 |
<style>
|
10 |
-
/* Custom styles that can't be done with Tailwind */
|
11 |
-
.calendar-day:hover {
|
12 |
-
background-color: #f3f4f6;
|
13 |
-
cursor: pointer;
|
14 |
-
}
|
15 |
-
.calendar-day.has-appointments {
|
16 |
-
background-color: #e5e7eb;
|
17 |
-
}
|
18 |
-
.appointment-item:hover {
|
19 |
-
background-color: #f3f4f6;
|
20 |
-
}
|
21 |
-
#calendar {
|
22 |
-
min-height: 500px;
|
23 |
-
}
|
24 |
.fade-in {
|
25 |
animation: fadeIn 0.3s ease-in-out;
|
26 |
}
|
27 |
@keyframes fadeIn {
|
28 |
-
from { opacity: 0; }
|
29 |
-
to { opacity: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
</style>
|
32 |
</head>
|
33 |
-
<body class="bg-gray-
|
34 |
-
<!-- Login
|
35 |
-
<div id="login-
|
36 |
-
<div class="bg-white p-8 rounded-lg shadow-
|
37 |
-
<div class="text-center mb-
|
38 |
-
<
|
39 |
-
<
|
40 |
-
<p class="text-gray-600">Staff Login</p>
|
41 |
</div>
|
42 |
-
<form id="login-form" class="space-y-
|
43 |
<div>
|
44 |
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
45 |
<input type="text" id="username" name="username" required
|
46 |
-
|
47 |
</div>
|
48 |
<div>
|
49 |
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
50 |
<input type="password" id="password" name="password" required
|
51 |
-
|
52 |
</div>
|
53 |
-
<
|
|
|
54 |
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">
|
55 |
-
|
56 |
-
|
|
|
57 |
</form>
|
|
|
58 |
</div>
|
59 |
</div>
|
60 |
|
61 |
-
<!-- Main App
|
62 |
-
<div id="app-container" class="hidden">
|
63 |
-
<!-- Header -->
|
64 |
-
<
|
65 |
-
<
|
66 |
-
<
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
<button id="logout-btn" class="flex items-center text-gray-600 hover:text-purple-600">
|
72 |
-
<i class="fas fa-sign-out-alt mr-1"></i>
|
73 |
-
<span>Logout</span>
|
74 |
-
</button>
|
75 |
-
</div>
|
76 |
-
</div>
|
77 |
-
</header>
|
78 |
|
79 |
-
<!--
|
80 |
-
<
|
81 |
-
|
82 |
-
|
83 |
-
<nav class="-mb-px flex space-x-8">
|
84 |
-
<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">
|
85 |
-
<i class="fas fa-calendar-alt mr-2"></i>Calendar
|
86 |
-
</button>
|
87 |
-
<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">
|
88 |
-
<i class="fas fa-list mr-2"></i>Appointments
|
89 |
-
</button>
|
90 |
-
<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">
|
91 |
-
<i class="fas fa-plus-circle mr-2"></i>New Appointment
|
92 |
-
</button>
|
93 |
-
</nav>
|
94 |
</div>
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
<div class="flex justify-between items-center mb-6">
|
99 |
-
<h2 class="text-xl font-semibold text-gray-800">Appointment Calendar</h2>
|
100 |
-
<div class="flex items-center space-x-4">
|
101 |
-
<button id="prev-month" class="p-2 rounded-full hover:bg-gray-200">
|
102 |
-
<i class="fas fa-chevron-left"></i>
|
103 |
-
</button>
|
104 |
-
<span id="current-month" class="font-medium">July 2023</span>
|
105 |
-
<button id="next-month" class="p-2 rounded-full hover:bg-gray-200">
|
106 |
-
<i class="fas fa-chevron-right"></i>
|
107 |
-
</button>
|
108 |
-
<button id="today-btn" class="px-3 py-1 bg-gray-200 rounded-md text-sm hover:bg-gray-300">
|
109 |
-
Today
|
110 |
-
</button>
|
111 |
-
</div>
|
112 |
</div>
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
</div>
|
125 |
-
|
126 |
-
<!-- Calendar Grid -->
|
127 |
-
<div id="calendar" class="grid grid-cols-7 grid-rows-5 border border-gray-200 bg-white">
|
128 |
-
<!-- Calendar days will be dynamically inserted here -->
|
129 |
-
</div>
|
130 |
-
</div>
|
131 |
-
|
132 |
-
<!-- Day Appointments Modal -->
|
133 |
-
<div id="day-appointments-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden">
|
134 |
-
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[80vh] overflow-y-auto">
|
135 |
-
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
136 |
-
<h3 id="modal-day-title" class="text-lg font-semibold text-gray-800">Appointments for July 15, 2023</h3>
|
137 |
-
<button id="close-day-modal" class="text-gray-500 hover:text-gray-700">
|
138 |
-
<i class="fas fa-times"></i>
|
139 |
-
</button>
|
140 |
-
</div>
|
141 |
-
<div id="day-appointments-list" class="p-4 space-y-3">
|
142 |
-
<!-- Appointments will be inserted here -->
|
143 |
-
</div>
|
144 |
-
<div class="px-6 py-3 border-t border-gray-200 flex justify-end">
|
145 |
-
<button id="add-appointment-for-day" class="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
146 |
-
Add Appointment
|
147 |
-
</button>
|
148 |
-
</div>
|
149 |
-
</div>
|
150 |
-
</div>
|
151 |
-
</div>
|
152 |
|
153 |
-
|
154 |
-
|
|
|
|
|
155 |
<div class="flex justify-between items-center mb-6">
|
156 |
-
<h2 class="text-
|
157 |
-
<
|
158 |
-
<
|
159 |
-
|
160 |
-
class="pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
161 |
-
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
|
162 |
-
</div>
|
163 |
-
<button id="export-csv" class="px-3 py-1 bg-gray-200 rounded-md text-sm hover:bg-gray-300 flex items-center">
|
164 |
-
<i class="fas fa-file-export mr-2"></i> Export CSV
|
165 |
-
</button>
|
166 |
-
</div>
|
167 |
</div>
|
168 |
|
169 |
<div class="bg-white rounded-lg shadow overflow-hidden">
|
@@ -171,776 +105,316 @@
|
|
171 |
<table class="min-w-full divide-y divide-gray-200">
|
172 |
<thead class="bg-gray-50">
|
173 |
<tr>
|
174 |
-
<th
|
175 |
-
<th
|
176 |
-
<th
|
177 |
-
<th
|
178 |
-
<th
|
179 |
</tr>
|
180 |
</thead>
|
181 |
-
<tbody id="appointments-
|
182 |
-
<!-- Appointments will be
|
183 |
</tbody>
|
184 |
</table>
|
185 |
</div>
|
186 |
</div>
|
|
|
|
|
|
|
|
|
187 |
</div>
|
188 |
|
189 |
-
<!--
|
190 |
-
<div id="
|
191 |
-
<div class="
|
192 |
-
<
|
193 |
-
<
|
194 |
-
</
|
195 |
-
<
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
202 |
-
</div>
|
203 |
-
<div>
|
204 |
-
<label for="client-phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
|
205 |
-
<input type="tel" id="client-phone" name="client-phone" required
|
206 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
207 |
-
</div>
|
208 |
-
<div>
|
209 |
-
<label for="client-email" class="block text-sm font-medium text-gray-700 mb-1">Email (Optional)</label>
|
210 |
-
<input type="email" id="client-email" name="client-email"
|
211 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
212 |
-
</div>
|
213 |
-
<div>
|
214 |
-
<label for="service-package" class="block text-sm font-medium text-gray-700 mb-1">Service Package *</label>
|
215 |
-
<select id="service-package" name="service-package" required
|
216 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
217 |
-
<option value="">Select a package</option>
|
218 |
-
<option value="Full Body">Full Body</option>
|
219 |
-
<option value="Legs Only">Legs Only</option>
|
220 |
-
<option value="Underarms">Underarms</option>
|
221 |
-
<option value="Bikini Line">Bikini Line</option>
|
222 |
-
<option value="Brazilian">Brazilian</option>
|
223 |
-
<option value="Face">Face</option>
|
224 |
-
<option value="Back">Back</option>
|
225 |
-
<option value="Chest">Chest</option>
|
226 |
-
</select>
|
227 |
-
</div>
|
228 |
-
<div>
|
229 |
-
<label for="appointment-date" class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
|
230 |
-
<input type="date" id="appointment-date" name="appointment-date" required
|
231 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
232 |
-
</div>
|
233 |
-
<div>
|
234 |
-
<label for="appointment-time" class="block text-sm font-medium text-gray-700 mb-1">Time *</label>
|
235 |
-
<input type="time" id="appointment-time" name="appointment-time" required
|
236 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
237 |
-
</div>
|
238 |
-
</div>
|
239 |
<div>
|
240 |
-
<label for="
|
241 |
-
<
|
242 |
-
|
243 |
-
</div>
|
244 |
-
<div class="flex justify-end space-x-4">
|
245 |
-
<button type="reset" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
|
246 |
-
Clear
|
247 |
-
</button>
|
248 |
-
<button type="submit" class="px-6 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
249 |
-
Schedule Appointment
|
250 |
-
</button>
|
251 |
</div>
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
<!-- Edit Appointment Modal -->
|
258 |
-
<div id="edit-appointment-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden">
|
259 |
-
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[80vh] overflow-y-auto">
|
260 |
-
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
261 |
-
<h3 class="text-lg font-semibold text-gray-800">Edit Appointment</h3>
|
262 |
-
<button id="close-edit-modal" class="text-gray-500 hover:text-gray-700">
|
263 |
-
<i class="fas fa-times"></i>
|
264 |
-
</button>
|
265 |
-
</div>
|
266 |
-
<div class="p-6">
|
267 |
-
<form id="edit-appointment-form" class="space-y-6">
|
268 |
-
<input type="hidden" id="edit-appointment-id">
|
269 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
270 |
-
<div>
|
271 |
-
<label for="edit-client-name" class="block text-sm font-medium text-gray-700 mb-1">Client Full Name *</label>
|
272 |
-
<input type="text" id="edit-client-name" name="edit-client-name" required
|
273 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
274 |
-
</div>
|
275 |
-
<div>
|
276 |
-
<label for="edit-client-phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
|
277 |
-
<input type="tel" id="edit-client-phone" name="edit-client-phone" required
|
278 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
279 |
-
</div>
|
280 |
-
<div>
|
281 |
-
<label for="edit-client-email" class="block text-sm font-medium text-gray-700 mb-1">Email (Optional)</label>
|
282 |
-
<input type="email" id="edit-client-email" name="edit-client-email"
|
283 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
284 |
-
</div>
|
285 |
-
<div>
|
286 |
-
<label for="edit-service-package" class="block text-sm font-medium text-gray-700 mb-1">Service Package *</label>
|
287 |
-
<select id="edit-service-package" name="edit-service-package" required
|
288 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
289 |
-
<option value="">Select a package</option>
|
290 |
-
<option value="Full Body">Full Body</option>
|
291 |
-
<option value="Legs Only">Legs Only</option>
|
292 |
-
<option value="Underarms">Underarms</option>
|
293 |
-
<option value="Bikini Line">Bikini Line</option>
|
294 |
-
<option value="Brazilian">Brazilian</option>
|
295 |
-
<option value="Face">Face</option>
|
296 |
-
<option value="Back">Back</option>
|
297 |
-
<option value="Chest">Chest</option>
|
298 |
-
</select>
|
299 |
-
</div>
|
300 |
-
<div>
|
301 |
-
<label for="edit-appointment-date" class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
|
302 |
-
<input type="date" id="edit-appointment-date" name="edit-appointment-date" required
|
303 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
304 |
-
</div>
|
305 |
-
<div>
|
306 |
-
<label for="edit-appointment-time" class="block text-sm font-medium text-gray-700 mb-1">Time *</label>
|
307 |
-
<input type="time" id="edit-appointment-time" name="edit-appointment-time" required
|
308 |
-
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
309 |
-
</div>
|
310 |
</div>
|
311 |
<div>
|
312 |
-
<label for="
|
313 |
-
<
|
314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
315 |
</div>
|
316 |
-
<div
|
317 |
-
<
|
318 |
-
|
319 |
-
|
320 |
-
<button type="submit" class="px-6 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
321 |
-
Update Appointment
|
322 |
-
</button>
|
323 |
-
<button type="button" id="delete-appointment" class="px-6 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
|
324 |
-
Delete
|
325 |
-
</button>
|
326 |
</div>
|
327 |
-
</
|
328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
</div>
|
330 |
</div>
|
331 |
-
</
|
332 |
</div>
|
333 |
|
334 |
<script>
|
335 |
-
// Mock data for demonstration
|
336 |
-
let appointments = [
|
337 |
-
{
|
338 |
-
id: 1,
|
339 |
-
clientName: "Sarah Johnson",
|
340 |
-
phone: "555-123-4567",
|
341 |
-
email: "[email protected]",
|
342 |
-
service: "Full Body",
|
343 |
-
date: "2023-07-15",
|
344 |
-
time: "10:00",
|
345 |
-
notes: "First session, sensitive skin"
|
346 |
-
},
|
347 |
-
{
|
348 |
-
id: 2,
|
349 |
-
clientName: "Michael Chen",
|
350 |
-
phone: "555-987-6543",
|
351 |
-
email: "[email protected]",
|
352 |
-
service: "Back",
|
353 |
-
date: "2023-07-15",
|
354 |
-
time: "14:30",
|
355 |
-
notes: "Follow-up session"
|
356 |
-
},
|
357 |
-
{
|
358 |
-
id: 3,
|
359 |
-
clientName: "Emily Rodriguez",
|
360 |
-
phone: "555-456-7890",
|
361 |
-
email: "",
|
362 |
-
service: "Legs Only",
|
363 |
-
date: "2023-07-18",
|
364 |
-
time: "11:15",
|
365 |
-
notes: ""
|
366 |
-
},
|
367 |
-
{
|
368 |
-
id: 4,
|
369 |
-
clientName: "David Kim",
|
370 |
-
phone: "555-789-0123",
|
371 |
-
email: "[email protected]",
|
372 |
-
service: "Underarms",
|
373 |
-
date: "2023-07-20",
|
374 |
-
time: "09:00",
|
375 |
-
notes: "Allergic to aloe vera"
|
376 |
-
},
|
377 |
-
{
|
378 |
-
id: 5,
|
379 |
-
clientName: "Jessica Williams",
|
380 |
-
phone: "555-234-5678",
|
381 |
-
email: "[email protected]",
|
382 |
-
service: "Brazilian",
|
383 |
-
date: "2023-07-22",
|
384 |
-
time: "13:45",
|
385 |
-
notes: "Prefer cold gel"
|
386 |
-
}
|
387 |
-
];
|
388 |
-
|
389 |
-
// Current user session
|
390 |
-
let currentUser = null;
|
391 |
-
|
392 |
-
// Current month and year for calendar
|
393 |
-
let currentDate = new Date();
|
394 |
-
let currentMonth = currentDate.getMonth();
|
395 |
-
let currentYear = currentDate.getFullYear();
|
396 |
-
|
397 |
// DOM Elements
|
398 |
-
const
|
399 |
const appContainer = document.getElementById('app-container');
|
400 |
const loginForm = document.getElementById('login-form');
|
401 |
-
const
|
402 |
-
|
403 |
-
|
404 |
-
const
|
405 |
-
const
|
406 |
-
|
407 |
-
|
408 |
-
const
|
409 |
-
const
|
410 |
-
const
|
411 |
-
const
|
412 |
-
const
|
413 |
-
|
414 |
-
|
415 |
-
const
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
// New appointment form elements
|
420 |
-
const newAppointmentForm = document.getElementById('new-appointment-form');
|
421 |
-
|
422 |
-
// Modal elements
|
423 |
-
const dayAppointmentsModal = document.getElementById('day-appointments-modal');
|
424 |
-
const modalDayTitle = document.getElementById('modal-day-title');
|
425 |
-
const dayAppointmentsList = document.getElementById('day-appointments-list');
|
426 |
-
const closeDayModal = document.getElementById('close-day-modal');
|
427 |
-
const addAppointmentForDay = document.getElementById('add-appointment-for-day');
|
428 |
-
|
429 |
-
const editAppointmentModal = document.getElementById('edit-appointment-modal');
|
430 |
-
const editAppointmentForm = document.getElementById('edit-appointment-form');
|
431 |
-
const closeEditModal = document.getElementById('close-edit-modal');
|
432 |
-
const cancelEdit = document.getElementById('cancel-edit');
|
433 |
-
const deleteAppointmentBtn = document.getElementById('delete-appointment');
|
434 |
-
|
435 |
-
// Event Listeners
|
436 |
-
document.addEventListener('DOMContentLoaded', () => {
|
437 |
-
// Initialize the app
|
438 |
-
if (localStorage.getItem('laserSalonUser')) {
|
439 |
-
currentUser = JSON.parse(localStorage.getItem('laserSalonUser'));
|
440 |
-
loginScreen.classList.add('hidden');
|
441 |
-
appContainer.classList.remove('hidden');
|
442 |
-
renderCalendar();
|
443 |
-
renderAppointmentsList();
|
444 |
-
}
|
445 |
-
});
|
446 |
-
|
447 |
-
// Login/Logout
|
448 |
-
loginForm.addEventListener('submit', (e) => {
|
449 |
e.preventDefault();
|
450 |
const username = document.getElementById('username').value;
|
451 |
const password = document.getElementById('password').value;
|
452 |
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
localStorage.setItem('laserSalonUser', JSON.stringify(currentUser));
|
457 |
-
loginScreen.classList.add('hidden');
|
458 |
appContainer.classList.remove('hidden');
|
459 |
-
|
460 |
-
renderAppointmentsList();
|
461 |
} else {
|
462 |
-
|
463 |
-
|
464 |
-
});
|
465 |
-
|
466 |
-
logoutBtn.addEventListener('click', () => {
|
467 |
-
currentUser = null;
|
468 |
-
localStorage.removeItem('laserSalonUser');
|
469 |
-
loginScreen.classList.remove('hidden');
|
470 |
-
appContainer.classList.add('hidden');
|
471 |
-
loginForm.reset();
|
472 |
-
});
|
473 |
-
|
474 |
-
// Tab switching
|
475 |
-
tabButtons.forEach(button => {
|
476 |
-
button.addEventListener('click', () => {
|
477 |
-
// Remove active class from all buttons
|
478 |
-
tabButtons.forEach(btn => {
|
479 |
-
btn.classList.remove('active', 'border-purple-500', 'text-purple-600');
|
480 |
-
btn.classList.add('border-transparent', 'text-gray-500');
|
481 |
-
});
|
482 |
-
|
483 |
-
// Add active class to clicked button
|
484 |
-
button.classList.add('active', 'border-purple-500', 'text-purple-600');
|
485 |
-
button.classList.remove('border-transparent', 'text-gray-500');
|
486 |
-
|
487 |
-
// Hide all tab contents
|
488 |
-
tabContents.forEach(content => {
|
489 |
-
content.classList.add('hidden');
|
490 |
-
});
|
491 |
-
|
492 |
-
// Show the corresponding tab content
|
493 |
-
const tabId = button.id.replace('-tab', '-content');
|
494 |
-
document.getElementById(tabId).classList.remove('hidden');
|
495 |
-
});
|
496 |
-
});
|
497 |
-
|
498 |
-
// Calendar navigation
|
499 |
-
prevMonthBtn.addEventListener('click', () => {
|
500 |
-
currentMonth--;
|
501 |
-
if (currentMonth < 0) {
|
502 |
-
currentMonth = 11;
|
503 |
-
currentYear--;
|
504 |
-
}
|
505 |
-
renderCalendar();
|
506 |
-
});
|
507 |
-
|
508 |
-
nextMonthBtn.addEventListener('click', () => {
|
509 |
-
currentMonth++;
|
510 |
-
if (currentMonth > 11) {
|
511 |
-
currentMonth = 0;
|
512 |
-
currentYear++;
|
513 |
}
|
514 |
-
renderCalendar();
|
515 |
-
});
|
516 |
-
|
517 |
-
todayBtn.addEventListener('click', () => {
|
518 |
-
currentDate = new Date();
|
519 |
-
currentMonth = currentDate.getMonth();
|
520 |
-
currentYear = currentDate.getFullYear();
|
521 |
-
renderCalendar();
|
522 |
});
|
523 |
|
524 |
-
//
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
closeEditModal.addEventListener('click', () => {
|
530 |
-
editAppointmentModal.classList.add('hidden');
|
531 |
});
|
532 |
|
533 |
-
|
534 |
-
|
|
|
535 |
});
|
536 |
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
const date = new Date(day);
|
541 |
-
const formattedDate = formatDateForInput(date);
|
542 |
-
|
543 |
-
// Switch to new appointment tab
|
544 |
-
tabButtons.forEach(btn => btn.classList.remove('active', 'border-purple-500', 'text-purple-600'));
|
545 |
-
document.getElementById('new-appointment-tab').classList.add('active', 'border-purple-500', 'text-purple-600');
|
546 |
-
tabContents.forEach(content => content.classList.add('hidden'));
|
547 |
-
document.getElementById('new-appointment-content').classList.remove('hidden');
|
548 |
-
|
549 |
-
// Set the date in the form
|
550 |
-
document.getElementById('appointment-date').value = formattedDate;
|
551 |
-
|
552 |
-
// Close the modal
|
553 |
-
dayAppointmentsModal.classList.add('hidden');
|
554 |
});
|
555 |
|
556 |
-
|
557 |
-
newAppointmentForm.addEventListener('submit', (e) => {
|
558 |
e.preventDefault();
|
559 |
-
|
560 |
-
const newAppointment = {
|
561 |
-
id: appointments.length > 0 ? Math.max(...appointments.map(a => a.id)) + 1 : 1,
|
562 |
-
clientName: document.getElementById('client-name').value,
|
563 |
-
phone: document.getElementById('client-phone').value,
|
564 |
-
email: document.getElementById('client-email').value,
|
565 |
-
service: document.getElementById('service-package').value,
|
566 |
-
date: document.getElementById('appointment-date').value,
|
567 |
-
time: document.getElementById('appointment-time').value,
|
568 |
-
notes: document.getElementById('appointment-notes').value
|
569 |
-
};
|
570 |
-
|
571 |
-
appointments.push(newAppointment);
|
572 |
-
newAppointmentForm.reset();
|
573 |
-
|
574 |
-
// Show success message
|
575 |
-
alert('Appointment scheduled successfully!');
|
576 |
-
|
577 |
-
// Update views
|
578 |
-
renderCalendar();
|
579 |
-
renderAppointmentsList();
|
580 |
-
|
581 |
-
// Switch to appointments tab
|
582 |
-
tabButtons.forEach(btn => btn.classList.remove('active', 'border-purple-500', 'text-purple-600'));
|
583 |
-
document.getElementById('appointments-tab').classList.add('active', 'border-purple-500', 'text-purple-600');
|
584 |
-
tabContents.forEach(content => content.classList.add('hidden'));
|
585 |
-
document.getElementById('appointments-content').classList.remove('hidden');
|
586 |
});
|
587 |
|
588 |
-
|
589 |
-
|
590 |
-
|
|
|
|
|
591 |
});
|
592 |
|
593 |
-
//
|
594 |
-
|
595 |
-
|
596 |
});
|
597 |
|
598 |
-
//
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
renderCalendar();
|
605 |
-
renderAppointmentsList();
|
606 |
}
|
607 |
-
|
|
|
608 |
|
609 |
-
//
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
if (
|
617 |
-
|
618 |
-
id: appointmentId,
|
619 |
-
clientName: document.getElementById('edit-client-name').value,
|
620 |
-
phone: document.getElementById('edit-client-phone').value,
|
621 |
-
email: document.getElementById('edit-client-email').value,
|
622 |
-
service: document.getElementById('edit-service-package').value,
|
623 |
-
date: document.getElementById('edit-appointment-date').value,
|
624 |
-
time: document.getElementById('edit-appointment-time').value,
|
625 |
-
notes: document.getElementById('edit-appointment-notes').value
|
626 |
-
};
|
627 |
-
|
628 |
-
editAppointmentModal.classList.add('hidden');
|
629 |
-
renderCalendar();
|
630 |
-
renderAppointmentsList();
|
631 |
}
|
632 |
-
}
|
633 |
|
634 |
-
//
|
635 |
-
function
|
636 |
-
//
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
if (i < firstDayOfWeek) {
|
661 |
-
// Days from previous month
|
662 |
-
const prevDay = prevMonthDays - (firstDayOfWeek - i - 1);
|
663 |
-
dayElement.innerHTML = `<div class="text-right text-gray-400">${prevDay}</div>`;
|
664 |
-
dayElement.classList.add('text-gray-400');
|
665 |
-
} else if (dayCount > daysInMonth) {
|
666 |
-
// Days from next month
|
667 |
-
dayElement.innerHTML = `<div class="text-right text-gray-400">${nextMonthDay}</div>`;
|
668 |
-
dayElement.classList.add('text-gray-400');
|
669 |
-
nextMonthDay++;
|
670 |
-
} else {
|
671 |
-
// Current month days
|
672 |
-
dayElement.innerHTML = `<div class="text-right font-medium">${dayCount}</div>`;
|
673 |
-
|
674 |
-
// Highlight today
|
675 |
-
const today = new Date();
|
676 |
-
if (dayCount === today.getDate() && currentMonth === today.getMonth() && currentYear === today.getFullYear()) {
|
677 |
-
dayElement.classList.add('bg-purple-100');
|
678 |
-
}
|
679 |
-
|
680 |
-
// Check for appointments on this day
|
681 |
-
const formattedDate = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(dayCount).padStart(2, '0')}`;
|
682 |
-
const dayAppointments = appointments.filter(a => a.date === formattedDate);
|
683 |
-
|
684 |
-
if (dayAppointments.length > 0) {
|
685 |
-
dayElement.classList.add('has-appointments');
|
686 |
-
|
687 |
-
// Add click event to show appointments for the day
|
688 |
-
dayElement.addEventListener('click', () => {
|
689 |
-
showDayAppointments(formattedDate, dayCount);
|
690 |
-
});
|
691 |
-
|
692 |
-
// Display appointment count
|
693 |
-
const appointmentCount = document.createElement('div');
|
694 |
-
appointmentCount.className = 'text-xs mt-1 text-purple-600 font-medium';
|
695 |
-
appointmentCount.textContent = `${dayAppointments.length} appointment${dayAppointments.length !== 1 ? 's' : ''}`;
|
696 |
-
dayElement.appendChild(appointmentCount);
|
697 |
-
|
698 |
-
// Display first 2 appointments (if space allows)
|
699 |
-
for (let j = 0; j < Math.min(dayAppointments.length, 2); j++) {
|
700 |
-
const appointment = dayAppointments[j];
|
701 |
-
const appointmentElement = document.createElement('div');
|
702 |
-
appointmentElement.className = 'text-xs mt-1 truncate bg-purple-50 px-1 py-0.5 rounded';
|
703 |
-
appointmentElement.textContent = `${appointment.time} - ${appointment.clientName.split(' ')[0]}`;
|
704 |
-
appointmentElement.title = `${appointment.time} - ${appointment.clientName}: ${appointment.service}`;
|
705 |
-
dayElement.appendChild(appointmentElement);
|
706 |
-
}
|
707 |
-
|
708 |
-
if (dayAppointments.length > 2) {
|
709 |
-
const moreElement = document.createElement('div');
|
710 |
-
moreElement.className = 'text-xs mt-1 text-gray-500';
|
711 |
-
moreElement.textContent = `+${dayAppointments.length - 2} more`;
|
712 |
-
dayElement.appendChild(moreElement);
|
713 |
-
}
|
714 |
-
} else {
|
715 |
-
// Add click event to add new appointment
|
716 |
-
dayElement.addEventListener('click', () => {
|
717 |
-
showDayAppointments(formattedDate, dayCount);
|
718 |
-
});
|
719 |
-
}
|
720 |
-
|
721 |
-
dayCount++;
|
722 |
}
|
723 |
-
|
724 |
-
calendar.appendChild(dayElement);
|
725 |
-
}
|
726 |
-
}
|
727 |
|
728 |
-
|
729 |
-
const formattedDate = new Date(date);
|
730 |
-
const dayName = formattedDate.toLocaleDateString('en-US', { weekday: 'long' });
|
731 |
-
const monthName = formattedDate.toLocaleDateString('en-US', { month: 'long' });
|
732 |
-
|
733 |
-
modalDayTitle.textContent = `Appointments for ${dayName}, ${monthName} ${day}, ${currentYear}`;
|
734 |
-
|
735 |
-
// Filter appointments for this day
|
736 |
-
const dayAppointments = appointments.filter(a => a.date === date);
|
737 |
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
if (dayAppointments.length === 0) {
|
742 |
-
dayAppointmentsList.innerHTML = '<p class="text-gray-500 text-center py-4">No appointments scheduled for this day.</p>';
|
743 |
} else {
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
const
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
</div>
|
763 |
-
</
|
764 |
-
<
|
765 |
-
<
|
766 |
-
|
767 |
-
|
768 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
769 |
`;
|
770 |
|
771 |
-
|
772 |
});
|
773 |
|
774 |
-
// Add event listeners to
|
775 |
-
document.querySelectorAll('.
|
776 |
-
|
777 |
-
const
|
778 |
-
|
779 |
-
e.stopPropagation();
|
780 |
});
|
781 |
});
|
782 |
}
|
783 |
-
|
784 |
-
// Show the modal
|
785 |
-
dayAppointmentsModal.classList.remove('hidden');
|
786 |
}
|
787 |
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
a.clientName.toLowerCase().includes(searchTerm) ||
|
799 |
-
a.phone.includes(searchTerm) ||
|
800 |
-
(a.email && a.email.toLowerCase().includes(searchTerm))
|
801 |
-
);
|
802 |
}
|
|
|
|
|
|
|
|
|
|
|
803 |
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
return dateA - dateB;
|
809 |
-
});
|
810 |
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
</td>
|
817 |
-
`;
|
818 |
-
appointmentsTableBody.appendChild(row);
|
819 |
return;
|
820 |
}
|
821 |
|
822 |
-
//
|
823 |
-
|
824 |
-
|
825 |
-
|
826 |
-
|
827 |
-
|
828 |
-
const row = document.createElement('tr');
|
829 |
-
row.className = 'hover:bg-gray-50';
|
830 |
-
row.innerHTML = `
|
831 |
-
<td class="px-6 py-4 whitespace-nowrap">
|
832 |
-
<div class="flex items-center">
|
833 |
-
<div>
|
834 |
-
<div class="font-medium text-gray-900">${appointment.clientName}</div>
|
835 |
-
</div>
|
836 |
-
</div>
|
837 |
-
</td>
|
838 |
-
<td class="px-6 py-4 whitespace-nowrap">
|
839 |
-
<div class="text-gray-900">${appointment.service}</div>
|
840 |
-
</td>
|
841 |
-
<td class="px-6 py-4 whitespace-nowrap">
|
842 |
-
<div class="text-gray-900">${formattedDate}</div>
|
843 |
-
<div class="text-gray-500">${formattedTime}</div>
|
844 |
-
</td>
|
845 |
-
<td class="px-6 py-4 whitespace-nowrap">
|
846 |
-
<div class="text-gray-900">${appointment.phone}</div>
|
847 |
-
${appointment.email ? `<div class="text-gray-500">${appointment.email}</div>` : ''}
|
848 |
-
</td>
|
849 |
-
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
850 |
-
<button class="edit-appointment text-purple-600 hover:text-purple-900 mr-3" data-id="${appointment.id}">
|
851 |
-
<i class="fas fa-edit"></i> Edit
|
852 |
-
</button>
|
853 |
-
<button class="delete-appointment text-red-600 hover:text-red-900" data-id="${appointment.id}">
|
854 |
-
<i class="fas fa-trash-alt"></i> Delete
|
855 |
-
</button>
|
856 |
-
</td>
|
857 |
-
`;
|
858 |
-
|
859 |
-
appointmentsTableBody.appendChild(row);
|
860 |
-
});
|
861 |
-
|
862 |
-
// Add event listeners to edit and delete buttons
|
863 |
-
document.querySelectorAll('.edit-appointment').forEach(button => {
|
864 |
-
button.addEventListener('click', () => {
|
865 |
-
const appointmentId = parseInt(button.getAttribute('data-id'));
|
866 |
-
showEditAppointmentModal(appointmentId);
|
867 |
-
});
|
868 |
});
|
869 |
|
870 |
-
|
871 |
-
|
872 |
-
|
873 |
-
|
874 |
-
appointments = appointments.filter(a => a.id !== appointmentId);
|
875 |
-
renderCalendar();
|
876 |
-
renderAppointmentsList();
|
877 |
-
}
|
878 |
-
});
|
879 |
-
});
|
880 |
-
}
|
881 |
-
|
882 |
-
function showEditAppointmentModal(appointmentId) {
|
883 |
-
const appointment = appointments.find(a => a.id === appointmentId);
|
884 |
-
if (!appointment) return;
|
885 |
-
|
886 |
-
// Fill the form with appointment data
|
887 |
-
document.getElementById('edit-appointment-id').value = appointment.id;
|
888 |
-
document.getElementById('edit-client-name').value = appointment.clientName;
|
889 |
-
document.getElementById('edit-client-phone').value = appointment.phone;
|
890 |
-
document.getElementById('edit-client-email').value = appointment.email || '';
|
891 |
-
document.getElementById('edit-service-package').value = appointment.service;
|
892 |
-
document.getElementById('edit-appointment-date').value = appointment.date;
|
893 |
-
document.getElementById('edit-appointment-time').value = appointment.time;
|
894 |
-
document.getElementById('edit-appointment-notes').value = appointment.notes || '';
|
895 |
|
896 |
-
//
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
const year = date.getFullYear();
|
903 |
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
904 |
-
const day = String(date.getDate()).padStart(2, '0');
|
905 |
-
return `${year}-${month}-${day}`;
|
906 |
-
}
|
907 |
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
});
|
915 |
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
// Add each appointment
|
920 |
-
sortedAppointments.forEach(appointment => {
|
921 |
-
const row = [
|
922 |
-
`"${appointment.clientName}"`,
|
923 |
-
`"${appointment.phone}"`,
|
924 |
-
`"${appointment.email || ''}"`,
|
925 |
-
`"${appointment.service}"`,
|
926 |
-
`"${appointment.date}"`,
|
927 |
-
`"${appointment.time}"`,
|
928 |
-
`"${appointment.notes || ''}"`
|
929 |
-
];
|
930 |
-
csv += row.join(',') + '\n';
|
931 |
-
});
|
932 |
-
|
933 |
-
// Create download link
|
934 |
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
935 |
-
const url = URL.createObjectURL(blob);
|
936 |
-
const link = document.createElement('a');
|
937 |
-
link.setAttribute('href', url);
|
938 |
-
link.setAttribute('download', `laser_salon_appointments_${new Date().toISOString().slice(0, 10)}.csv`);
|
939 |
-
link.style.visibility = 'hidden';
|
940 |
-
document.body.appendChild(link);
|
941 |
-
link.click();
|
942 |
-
document.body.removeChild(link);
|
943 |
-
}
|
944 |
</script>
|
945 |
<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>
|
946 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Epiliz - Laser Hair Removal</title>
|
7 |
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
9 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
.fade-in {
|
11 |
animation: fadeIn 0.3s ease-in-out;
|
12 |
}
|
13 |
@keyframes fadeIn {
|
14 |
+
from { opacity: 0; transform: translateY(10px); }
|
15 |
+
to { opacity: 1; transform: translateY(0); }
|
16 |
+
}
|
17 |
+
.sidebar {
|
18 |
+
transition: all 0.3s ease;
|
19 |
+
}
|
20 |
+
@media (max-width: 768px) {
|
21 |
+
.sidebar {
|
22 |
+
transform: translateX(-100%);
|
23 |
+
}
|
24 |
+
.sidebar.open {
|
25 |
+
transform: translateX(0);
|
26 |
+
}
|
27 |
}
|
28 |
</style>
|
29 |
</head>
|
30 |
+
<body class="bg-gray-50 font-sans">
|
31 |
+
<!-- Login Page -->
|
32 |
+
<div id="login-page" class="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-100 to-pink-100">
|
33 |
+
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
|
34 |
+
<div class="text-center mb-8">
|
35 |
+
<h1 class="text-3xl font-bold text-purple-800">Epiliz</h1>
|
36 |
+
<p class="text-gray-600 mt-2">Laser Hair Removal Management</p>
|
|
|
37 |
</div>
|
38 |
+
<form id="login-form" class="space-y-6">
|
39 |
<div>
|
40 |
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
41 |
<input type="text" id="username" name="username" required
|
42 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
43 |
</div>
|
44 |
<div>
|
45 |
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
46 |
<input type="password" id="password" name="password" required
|
47 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
48 |
</div>
|
49 |
+
<div>
|
50 |
+
<button type="submit"
|
51 |
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">
|
52 |
+
Sign In
|
53 |
+
</button>
|
54 |
+
</div>
|
55 |
</form>
|
56 |
+
<div id="login-error" class="mt-4 text-red-500 text-sm hidden"></div>
|
57 |
</div>
|
58 |
</div>
|
59 |
|
60 |
+
<!-- Main App (hidden initially) -->
|
61 |
+
<div id="app-container" class="hidden min-h-screen">
|
62 |
+
<!-- Mobile Header -->
|
63 |
+
<div class="md:hidden bg-purple-700 text-white p-4 flex justify-between items-center">
|
64 |
+
<button id="menu-toggle" class="text-white focus:outline-none">
|
65 |
+
<i class="fas fa-bars text-xl"></i>
|
66 |
+
</button>
|
67 |
+
<h1 class="text-xl font-bold">Epiliz</h1>
|
68 |
+
<div class="w-6"></div> <!-- Spacer for alignment -->
|
69 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
|
71 |
+
<!-- Sidebar -->
|
72 |
+
<div id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-purple-800 text-white transform md:translate-x-0 z-10">
|
73 |
+
<div class="p-4 flex items-center border-b border-purple-700">
|
74 |
+
<h1 class="text-xl font-bold">Epiliz</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
</div>
|
76 |
+
<nav class="mt-6">
|
77 |
+
<div class="px-4 py-3 bg-purple-900">
|
78 |
+
<span class="text-sm font-semibold">Menu</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
</div>
|
80 |
+
<a href="#" id="dashboard-link" class="block px-4 py-3 mt-2 text-white hover:bg-purple-700 transition duration-200">
|
81 |
+
<i class="fas fa-calendar-alt mr-3"></i> Appointments
|
82 |
+
</a>
|
83 |
+
<a href="#" id="add-appointment-link" class="block px-4 py-3 mt-2 text-white hover:bg-purple-700 transition duration-200">
|
84 |
+
<i class="fas fa-plus-circle mr-3"></i> Add Appointment
|
85 |
+
</a>
|
86 |
+
<a href="#" id="logout-link" class="block px-4 py-3 mt-2 text-white hover:bg-purple-700 transition duration-200">
|
87 |
+
<i class="fas fa-sign-out-alt mr-3"></i> Logout
|
88 |
+
</a>
|
89 |
+
</nav>
|
90 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
+
<!-- Main Content -->
|
93 |
+
<div class="md:ml-64 transition-all duration-300">
|
94 |
+
<!-- Dashboard -->
|
95 |
+
<div id="dashboard" class="p-6 fade-in">
|
96 |
<div class="flex justify-between items-center mb-6">
|
97 |
+
<h2 class="text-2xl font-bold text-gray-800">Appointments</h2>
|
98 |
+
<button id="add-appointment-btn" class="bg-purple-600 text-white px-4 py-2 rounded-md hover:bg-purple-700 transition duration-200 md:hidden">
|
99 |
+
<i class="fas fa-plus mr-2"></i> Add
|
100 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
</div>
|
102 |
|
103 |
<div class="bg-white rounded-lg shadow overflow-hidden">
|
|
|
105 |
<table class="min-w-full divide-y divide-gray-200">
|
106 |
<thead class="bg-gray-50">
|
107 |
<tr>
|
108 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
|
109 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
110 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Treatment Area</th>
|
111 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact</th>
|
112 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
113 |
</tr>
|
114 |
</thead>
|
115 |
+
<tbody id="appointments-list" class="bg-white divide-y divide-gray-200">
|
116 |
+
<!-- Appointments will be loaded here -->
|
117 |
</tbody>
|
118 |
</table>
|
119 |
</div>
|
120 |
</div>
|
121 |
+
<div id="no-appointments" class="mt-8 text-center text-gray-500 hidden">
|
122 |
+
<i class="fas fa-calendar-times text-4xl mb-4"></i>
|
123 |
+
<p class="text-lg">No appointments scheduled</p>
|
124 |
+
</div>
|
125 |
</div>
|
126 |
|
127 |
+
<!-- Add Appointment Form -->
|
128 |
+
<div id="add-appointment" class="p-6 hidden fade-in">
|
129 |
+
<div class="flex items-center mb-6">
|
130 |
+
<button id="back-to-dashboard" class="mr-4 text-purple-600 hover:text-purple-800">
|
131 |
+
<i class="fas fa-arrow-left text-xl"></i>
|
132 |
+
</button>
|
133 |
+
<h2 class="text-2xl font-bold text-gray-800">Add New Appointment</h2>
|
134 |
+
</div>
|
135 |
+
|
136 |
+
<div class="bg-white rounded-lg shadow p-6 max-w-2xl mx-auto">
|
137 |
+
<form id="appointment-form">
|
138 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
<div>
|
140 |
+
<label for="client-name" class="block text-sm font-medium text-gray-700 mb-1">Client Name*</label>
|
141 |
+
<input type="text" id="client-name" name="client-name" required
|
142 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
</div>
|
144 |
+
<div>
|
145 |
+
<label for="appointment-date" class="block text-sm font-medium text-gray-700 mb-1">Appointment Date*</label>
|
146 |
+
<input type="datetime-local" id="appointment-date" name="appointment-date" required
|
147 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
</div>
|
149 |
<div>
|
150 |
+
<label for="treatment-area" class="block text-sm font-medium text-gray-700 mb-1">Treatment Area*</label>
|
151 |
+
<select id="treatment-area" name="treatment-area" required
|
152 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
153 |
+
<option value="">Select area</option>
|
154 |
+
<option value="Face">Face</option>
|
155 |
+
<option value="Underarms">Underarms</option>
|
156 |
+
<option value="Arms">Arms</option>
|
157 |
+
<option value="Legs">Legs</option>
|
158 |
+
<option value="Bikini">Bikini</option>
|
159 |
+
<option value="Back">Back</option>
|
160 |
+
<option value="Chest">Chest</option>
|
161 |
+
<option value="Brazilian">Brazilian</option>
|
162 |
+
</select>
|
163 |
</div>
|
164 |
+
<div>
|
165 |
+
<label for="contact-info" class="block text-sm font-medium text-gray-700 mb-1">Contact Info</label>
|
166 |
+
<input type="text" id="contact-info" name="contact-info"
|
167 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
</div>
|
169 |
+
</div>
|
170 |
+
<div class="mt-8">
|
171 |
+
<button type="submit"
|
172 |
+
class="w-full md:w-auto bg-purple-600 text-white py-2 px-6 rounded-md hover:bg-purple-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2">
|
173 |
+
Save Appointment
|
174 |
+
</button>
|
175 |
+
</div>
|
176 |
+
</form>
|
177 |
+
<div id="form-error" class="mt-4 text-red-500 text-sm hidden"></div>
|
178 |
+
<div id="form-success" class="mt-4 text-green-500 text-sm hidden"></div>
|
179 |
</div>
|
180 |
</div>
|
181 |
+
</div>
|
182 |
</div>
|
183 |
|
184 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
// DOM Elements
|
186 |
+
const loginPage = document.getElementById('login-page');
|
187 |
const appContainer = document.getElementById('app-container');
|
188 |
const loginForm = document.getElementById('login-form');
|
189 |
+
const loginError = document.getElementById('login-error');
|
190 |
+
const menuToggle = document.getElementById('menu-toggle');
|
191 |
+
const sidebar = document.getElementById('sidebar');
|
192 |
+
const dashboardLink = document.getElementById('dashboard-link');
|
193 |
+
const addAppointmentLink = document.getElementById('add-appointment-link');
|
194 |
+
const logoutLink = document.getElementById('logout-link');
|
195 |
+
const dashboard = document.getElementById('dashboard');
|
196 |
+
const addAppointment = document.getElementById('add-appointment');
|
197 |
+
const backToDashboard = document.getElementById('back-to-dashboard');
|
198 |
+
const addAppointmentBtn = document.getElementById('add-appointment-btn');
|
199 |
+
const appointmentsList = document.getElementById('appointments-list');
|
200 |
+
const noAppointments = document.getElementById('no-appointments');
|
201 |
+
const appointmentForm = document.getElementById('appointment-form');
|
202 |
+
const formError = document.getElementById('form-error');
|
203 |
+
const formSuccess = document.getElementById('form-success');
|
204 |
+
|
205 |
+
// Login
|
206 |
+
loginForm.addEventListener('submit', function(e) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
e.preventDefault();
|
208 |
const username = document.getElementById('username').value;
|
209 |
const password = document.getElementById('password').value;
|
210 |
|
211 |
+
if (username === 'Epiliz' && password === 'Epilat2110') {
|
212 |
+
loginError.classList.add('hidden');
|
213 |
+
loginPage.classList.add('hidden');
|
|
|
|
|
214 |
appContainer.classList.remove('hidden');
|
215 |
+
loadAppointments();
|
|
|
216 |
} else {
|
217 |
+
loginError.textContent = 'Invalid username or password';
|
218 |
+
loginError.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
220 |
});
|
221 |
|
222 |
+
// Navigation
|
223 |
+
dashboardLink.addEventListener('click', function(e) {
|
224 |
+
e.preventDefault();
|
225 |
+
showDashboard();
|
|
|
|
|
|
|
226 |
});
|
227 |
|
228 |
+
addAppointmentLink.addEventListener('click', function(e) {
|
229 |
+
e.preventDefault();
|
230 |
+
showAddAppointment();
|
231 |
});
|
232 |
|
233 |
+
addAppointmentBtn.addEventListener('click', function(e) {
|
234 |
+
e.preventDefault();
|
235 |
+
showAddAppointment();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
236 |
});
|
237 |
|
238 |
+
backToDashboard.addEventListener('click', function(e) {
|
|
|
239 |
e.preventDefault();
|
240 |
+
showDashboard();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
});
|
242 |
|
243 |
+
logoutLink.addEventListener('click', function(e) {
|
244 |
+
e.preventDefault();
|
245 |
+
appContainer.classList.add('hidden');
|
246 |
+
loginPage.classList.remove('hidden');
|
247 |
+
loginForm.reset();
|
248 |
});
|
249 |
|
250 |
+
// Mobile menu toggle
|
251 |
+
menuToggle.addEventListener('click', function() {
|
252 |
+
sidebar.classList.toggle('open');
|
253 |
});
|
254 |
|
255 |
+
// Show dashboard view
|
256 |
+
function showDashboard() {
|
257 |
+
dashboard.classList.remove('hidden');
|
258 |
+
addAppointment.classList.add('hidden');
|
259 |
+
if (window.innerWidth < 768) {
|
260 |
+
sidebar.classList.remove('open');
|
|
|
|
|
261 |
}
|
262 |
+
loadAppointments();
|
263 |
+
}
|
264 |
|
265 |
+
// Show add appointment view
|
266 |
+
function showAddAppointment() {
|
267 |
+
dashboard.classList.add('hidden');
|
268 |
+
addAppointment.classList.remove('hidden');
|
269 |
+
appointmentForm.reset();
|
270 |
+
formError.classList.add('hidden');
|
271 |
+
formSuccess.classList.add('hidden');
|
272 |
+
if (window.innerWidth < 768) {
|
273 |
+
sidebar.classList.remove('open');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
}
|
275 |
+
}
|
276 |
|
277 |
+
// Load appointments from database
|
278 |
+
function loadAppointments() {
|
279 |
+
// In a real app, this would fetch from your PHP backend
|
280 |
+
// For demo purposes, we'll use mock data
|
281 |
+
const mockAppointments = [
|
282 |
+
{
|
283 |
+
id: 1,
|
284 |
+
client_name: "Sarah Johnson",
|
285 |
+
appointment_date: "2023-06-15 14:30:00",
|
286 |
+
treatment_area: "Legs",
|
287 |
+
contact_info: "sarah@example.com"
|
288 |
+
},
|
289 |
+
{
|
290 |
+
id: 2,
|
291 |
+
client_name: "Michael Chen",
|
292 |
+
appointment_date: "2023-06-16 10:00:00",
|
293 |
+
treatment_area: "Back",
|
294 |
+
contact_info: "555-123-4567"
|
295 |
+
},
|
296 |
+
{
|
297 |
+
id: 3,
|
298 |
+
client_name: "Emma Williams",
|
299 |
+
appointment_date: "2023-06-17 15:45:00",
|
300 |
+
treatment_area: "Bikini",
|
301 |
+
contact_info: "emma.[email protected]"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
}
|
303 |
+
];
|
|
|
|
|
|
|
304 |
|
305 |
+
appointmentsList.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
|
307 |
+
if (mockAppointments.length === 0) {
|
308 |
+
noAppointments.classList.remove('hidden');
|
|
|
|
|
|
|
309 |
} else {
|
310 |
+
noAppointments.classList.add('hidden');
|
311 |
+
mockAppointments.forEach(appointment => {
|
312 |
+
const row = document.createElement('tr');
|
313 |
+
row.className = 'hover:bg-gray-50';
|
314 |
+
|
315 |
+
const formattedDate = new Date(appointment.appointment_date).toLocaleString('en-US', {
|
316 |
+
month: 'short',
|
317 |
+
day: 'numeric',
|
318 |
+
year: 'numeric',
|
319 |
+
hour: '2-digit',
|
320 |
+
minute: '2-digit'
|
321 |
+
});
|
322 |
+
|
323 |
+
row.innerHTML = `
|
324 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
325 |
+
<div class="font-medium text-gray-900">${appointment.client_name}</div>
|
326 |
+
</td>
|
327 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
328 |
+
<div class="text-gray-700">${formattedDate}</div>
|
329 |
+
</td>
|
330 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
331 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-purple-100 text-purple-800">
|
332 |
+
${appointment.treatment_area}
|
333 |
+
</span>
|
334 |
+
</td>
|
335 |
+
<td class="px-6 py-4 whitespace-nowrap text-gray-700">
|
336 |
+
${appointment.contact_info || 'N/A'}
|
337 |
+
</td>
|
338 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
339 |
+
<button class="text-red-600 hover:text-red-900 delete-btn" data-id="${appointment.id}">
|
340 |
+
<i class="fas fa-trash-alt"></i>
|
341 |
+
</button>
|
342 |
+
</td>
|
343 |
`;
|
344 |
|
345 |
+
appointmentsList.appendChild(row);
|
346 |
});
|
347 |
|
348 |
+
// Add event listeners to delete buttons
|
349 |
+
document.querySelectorAll('.delete-btn').forEach(btn => {
|
350 |
+
btn.addEventListener('click', function() {
|
351 |
+
const id = this.getAttribute('data-id');
|
352 |
+
deleteAppointment(id);
|
|
|
353 |
});
|
354 |
});
|
355 |
}
|
|
|
|
|
|
|
356 |
}
|
357 |
|
358 |
+
// Delete appointment
|
359 |
+
function deleteAppointment(id) {
|
360 |
+
if (confirm('Are you sure you want to delete this appointment?')) {
|
361 |
+
// In a real app, this would send a request to your PHP backend
|
362 |
+
console.log(`Deleting appointment with ID: ${id}`);
|
363 |
+
|
364 |
+
// Simulate successful deletion
|
365 |
+
setTimeout(() => {
|
366 |
+
loadAppointments();
|
367 |
+
}, 300);
|
|
|
|
|
|
|
|
|
368 |
}
|
369 |
+
}
|
370 |
+
|
371 |
+
// Add new appointment
|
372 |
+
appointmentForm.addEventListener('submit', function(e) {
|
373 |
+
e.preventDefault();
|
374 |
|
375 |
+
const clientName = document.getElementById('client-name').value;
|
376 |
+
const appointmentDate = document.getElementById('appointment-date').value;
|
377 |
+
const treatmentArea = document.getElementById('treatment-area').value;
|
378 |
+
const contactInfo = document.getElementById('contact-info').value;
|
|
|
|
|
379 |
|
380 |
+
// Simple validation
|
381 |
+
if (!clientName || !appointmentDate || !treatmentArea) {
|
382 |
+
formError.textContent = 'Please fill in all required fields';
|
383 |
+
formError.classList.remove('hidden');
|
384 |
+
formSuccess.classList.add('hidden');
|
|
|
|
|
|
|
385 |
return;
|
386 |
}
|
387 |
|
388 |
+
// In a real app, this would send data to your PHP backend
|
389 |
+
console.log('Saving appointment:', {
|
390 |
+
clientName,
|
391 |
+
appointmentDate,
|
392 |
+
treatmentArea,
|
393 |
+
contactInfo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
});
|
395 |
|
396 |
+
// Simulate successful save
|
397 |
+
formError.classList.add('hidden');
|
398 |
+
formSuccess.textContent = 'Appointment saved successfully!';
|
399 |
+
formSuccess.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
|
401 |
+
// Reset form and show success message
|
402 |
+
setTimeout(() => {
|
403 |
+
appointmentForm.reset();
|
404 |
+
showDashboard();
|
405 |
+
}, 1500);
|
406 |
+
});
|
|
|
|
|
|
|
|
|
|
|
407 |
|
408 |
+
// Initialize
|
409 |
+
document.addEventListener('DOMContentLoaded', function() {
|
410 |
+
// Set min date for appointment date input to today
|
411 |
+
const today = new Date();
|
412 |
+
const tomorrow = new Date(today);
|
413 |
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
414 |
|
415 |
+
const minDate = tomorrow.toISOString().slice(0, 16);
|
416 |
+
document.getElementById('appointment-date').min = minDate;
|
417 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 |
</script>
|
419 |
<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>
|
420 |
</html>
|