Spaces:
Running
Running
Make the username: epiliz ; password: culoareafericiri - Follow Up Deployment
Browse files- index.html +857 -447
index.html
CHANGED
@@ -2,535 +2,945 @@
|
|
2 |
<html lang="en">
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0
|
6 |
-
<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 |
-
/* Custom
|
11 |
-
|
12 |
-
|
|
|
13 |
}
|
14 |
-
|
15 |
-
background: #
|
16 |
}
|
17 |
-
|
18 |
-
background: #
|
19 |
-
border-radius: 4px;
|
20 |
}
|
21 |
-
|
22 |
-
|
23 |
}
|
24 |
-
|
25 |
-
/* Animation for notifications */
|
26 |
-
@keyframes fadeIn {
|
27 |
-
from { opacity: 0; transform: translateY(20px); }
|
28 |
-
to { opacity: 1; transform: translateY(0); }
|
29 |
-
}
|
30 |
-
|
31 |
.fade-in {
|
32 |
-
animation: fadeIn 0.3s ease-out
|
|
|
|
|
|
|
|
|
33 |
}
|
34 |
</style>
|
35 |
</head>
|
36 |
-
<body class="bg-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
<!-- Header -->
|
39 |
-
<header class="
|
40 |
-
<div class="flex items-center
|
41 |
-
<div class="
|
42 |
-
<i class="fas fa-spa text-
|
|
|
43 |
</div>
|
44 |
-
<
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
<button id="notificationBtn" class="bg-pink-200 p-3 rounded-full text-pink-600 hover:bg-pink-300 transition">
|
49 |
-
<i class="fas fa-bell"></i>
|
50 |
-
<span id="notificationCount" class="absolute -top-1 -right-1 bg-pink-600 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center hidden">0</span>
|
51 |
</button>
|
52 |
-
<div id="notificationDropdown" class="hidden absolute right-0 mt-2 w-64 bg-white rounded-md shadow-lg z-10 border border-pink-200">
|
53 |
-
<div class="p-4 border-b border-pink-100">
|
54 |
-
<h3 class="font-semibold text-pink-800">Notifications</h3>
|
55 |
-
</div>
|
56 |
-
<div id="notificationList" class="max-h-60 overflow-y-auto">
|
57 |
-
<!-- Notifications will appear here -->
|
58 |
-
</div>
|
59 |
-
</div>
|
60 |
-
</div>
|
61 |
-
<div class="bg-pink-200 p-3 rounded-full">
|
62 |
-
<i class="fas fa-user text-pink-600"></i>
|
63 |
</div>
|
64 |
</div>
|
65 |
</header>
|
66 |
|
67 |
<!-- Main Content -->
|
68 |
-
<
|
69 |
-
<!--
|
70 |
-
<div class="
|
71 |
-
<
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
<
|
79 |
-
<
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
<
|
91 |
-
|
92 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
</div>
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
</div>
|
99 |
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
<option value="" disabled selected>Select a time</option>
|
104 |
-
<option value="09:00">09:00 AM</option>
|
105 |
-
<option value="10:00">10:00 AM</option>
|
106 |
-
<option value="11:00">11:00 AM</option>
|
107 |
-
<option value="12:00">12:00 PM</option>
|
108 |
-
<option value="13:00">01:00 PM</option>
|
109 |
-
<option value="14:00">02:00 PM</option>
|
110 |
-
<option value="15:00">03:00 PM</option>
|
111 |
-
<option value="16:00">04:00 PM</option>
|
112 |
-
</select>
|
113 |
</div>
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
</div>
|
119 |
-
|
120 |
-
<button type="submit" class="w-full bg-pink-600 hover:bg-pink-700 text-white font-medium py-2 px-4 rounded-md transition duration-300">
|
121 |
-
Schedule Appointment
|
122 |
-
</button>
|
123 |
-
</form>
|
124 |
</div>
|
125 |
|
126 |
-
<!-- Appointments List -->
|
127 |
-
<div class="
|
128 |
-
<div class="
|
129 |
-
<
|
130 |
-
|
131 |
<div class="relative">
|
132 |
-
<input type="text" id="
|
133 |
-
|
|
|
134 |
</div>
|
|
|
|
|
|
|
135 |
</div>
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
|
|
|
|
140 |
<tr>
|
141 |
-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-
|
142 |
-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-
|
143 |
-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-
|
144 |
-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-
|
145 |
-
<th scope="col" class="px-6 py-3 text-
|
146 |
</tr>
|
147 |
</thead>
|
148 |
-
<tbody id="
|
149 |
-
<!-- Appointments will be
|
150 |
</tbody>
|
151 |
</table>
|
152 |
</div>
|
153 |
-
|
154 |
-
<div id="noAppointments" class="text-center py-8 text-pink-500">
|
155 |
-
<i class="fas fa-calendar-alt text-4xl mb-4"></i>
|
156 |
-
<p class="text-lg">No appointments scheduled yet</p>
|
157 |
-
</div>
|
158 |
</div>
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
<
|
166 |
-
<div class="text-center font-medium text-pink-700 py-2">Tue</div>
|
167 |
-
<div class="text-center font-medium text-pink-700 py-2">Wed</div>
|
168 |
-
<div class="text-center font-medium text-pink-700 py-2">Thu</div>
|
169 |
-
<div class="text-center font-medium text-pink-700 py-2">Fri</div>
|
170 |
-
<div class="text-center font-medium text-pink-700 py-2">Sat</div>
|
171 |
</div>
|
172 |
-
<div
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
</div>
|
175 |
</div>
|
176 |
</div>
|
177 |
-
</div>
|
178 |
-
</div>
|
179 |
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
<
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
</div>
|
196 |
</div>
|
197 |
-
</
|
198 |
</div>
|
199 |
|
200 |
<script>
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
// Initialize the app
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
|
225 |
-
//
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
clientPhone,
|
240 |
-
serviceType,
|
241 |
-
date: appointmentDate,
|
242 |
-
time: appointmentTime,
|
243 |
-
notes,
|
244 |
-
status: 'Scheduled',
|
245 |
-
createdAt: new Date().toISOString()
|
246 |
-
};
|
247 |
|
248 |
-
|
249 |
-
|
|
|
|
|
250 |
|
251 |
-
//
|
252 |
-
const
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
};
|
258 |
|
259 |
-
|
260 |
-
saveNotifications();
|
261 |
-
|
262 |
-
// Reset form
|
263 |
-
appointmentForm.reset();
|
264 |
-
|
265 |
-
// Update UI
|
266 |
-
renderAppointments();
|
267 |
renderCalendar();
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
|
|
|
|
274 |
|
275 |
-
//
|
276 |
-
|
277 |
-
renderAppointments();
|
278 |
-
});
|
279 |
|
280 |
-
//
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
// Mark notifications as read when dropdown is opened
|
285 |
-
notifications = notifications.map(notif => ({...notif, read: true}));
|
286 |
-
saveNotifications();
|
287 |
-
updateNotificationCount();
|
288 |
-
renderNotifications();
|
289 |
-
});
|
290 |
|
291 |
-
//
|
292 |
-
|
293 |
-
successModal.classList.add('hidden');
|
294 |
-
});
|
295 |
|
296 |
-
//
|
297 |
-
|
298 |
-
if (!notificationBtn.contains(e.target) && !notificationDropdown.contains(e.target)) {
|
299 |
-
notificationDropdown.classList.add('hidden');
|
300 |
-
}
|
301 |
-
});
|
302 |
|
303 |
-
//
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
appointment.serviceType.toLowerCase().includes(searchTerm) ||
|
311 |
-
appointment.clientPhone.includes(searchTerm)
|
312 |
-
);
|
313 |
-
});
|
314 |
-
|
315 |
-
if (filteredAppointments.length === 0) {
|
316 |
-
noAppointments.classList.remove('hidden');
|
317 |
-
appointmentsList.innerHTML = '';
|
318 |
-
return;
|
319 |
-
}
|
320 |
-
|
321 |
-
noAppointments.classList.add('hidden');
|
322 |
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
});
|
332 |
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
}[appointment.status] || 'bg-gray-100 text-gray-800';
|
339 |
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
<i class="fas fa-check"></i>
|
368 |
-
</button>
|
369 |
-
<button onclick="cancelAppointment(${appointment.id})" class="text-red-600 hover:text-red-900">
|
370 |
-
<i class="fas fa-times"></i>
|
371 |
-
</button>
|
372 |
-
</td>
|
373 |
-
</tr>
|
374 |
-
`;
|
375 |
-
})
|
376 |
-
.join('');
|
377 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
});
|
393 |
-
|
394 |
-
calendarGrid.innerHTML = '';
|
395 |
-
|
396 |
-
// Add empty cells for days before the first day of the month
|
397 |
-
for (let i = 0; i < firstDay; i++) {
|
398 |
-
calendarGrid.innerHTML += `<div class="h-20 border border-pink-100 bg-pink-50 rounded"></div>`;
|
399 |
-
}
|
400 |
|
401 |
-
//
|
402 |
-
|
403 |
-
const
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
`).join('')}
|
417 |
</div>
|
418 |
</div>
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
function renderNotifications() {
|
424 |
-
if (notifications.length === 0) {
|
425 |
-
notificationList.innerHTML = `
|
426 |
-
<div class="p-4 text-center text-pink-500">
|
427 |
-
<i class="fas fa-bell-slash text-2xl mb-2"></i>
|
428 |
-
<p>No notifications yet</p>
|
429 |
</div>
|
430 |
`;
|
431 |
-
|
432 |
-
|
|
|
433 |
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
minute: '2-digit'
|
441 |
});
|
442 |
-
|
443 |
-
return `
|
444 |
-
<div class="p-3 border-b border-pink-100 ${notification.read ? 'bg-white' : 'bg-pink-50'}">
|
445 |
-
<div class="flex justify-between">
|
446 |
-
<p class="text-sm ${notification.read ? 'text-pink-700' : 'font-semibold text-pink-900'}">${notification.message}</p>
|
447 |
-
<span class="text-xs text-pink-500">${formattedDate}</span>
|
448 |
-
</div>
|
449 |
-
</div>
|
450 |
-
`;
|
451 |
-
}).join('');
|
452 |
}
|
453 |
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
notificationCount.classList.add('hidden');
|
462 |
-
}
|
463 |
-
}
|
464 |
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
successModal.classList.remove('hidden');
|
469 |
-
}
|
470 |
|
471 |
-
|
472 |
-
|
|
|
|
|
|
|
|
|
473 |
}
|
474 |
|
475 |
-
|
476 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
}
|
478 |
|
479 |
-
//
|
480 |
-
|
481 |
-
const
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
// Add notification
|
512 |
-
const newNotification = {
|
513 |
-
id: Date.now(),
|
514 |
-
message: `Appointment cancelled for ${appointment.clientName}`,
|
515 |
-
date: new Date().toISOString(),
|
516 |
-
read: false
|
517 |
-
};
|
518 |
-
|
519 |
-
notifications.unshift(newNotification);
|
520 |
-
saveNotifications();
|
521 |
-
updateNotificationCount();
|
522 |
-
renderNotifications();
|
523 |
-
|
524 |
-
showModal('Appointment Cancelled!', `The appointment for ${appointment.clientName} has been cancelled.`);
|
525 |
-
}
|
526 |
-
};
|
527 |
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
534 |
</script>
|
535 |
<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>
|
536 |
</html>
|
|
|
2 |
<html lang="en">
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Laser Hair Removal Salon Scheduler</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 |
+
/* 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-100 font-sans">
|
34 |
+
<!-- Login Screen (shown by default) -->
|
35 |
+
<div id="login-screen" class="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-50 z-50">
|
36 |
+
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
|
37 |
+
<div class="text-center mb-6">
|
38 |
+
<i class="fas fa-spa text-4xl text-purple-600 mb-2"></i>
|
39 |
+
<h1 class="text-2xl font-bold text-gray-800">Laser Elegance Salon</h1>
|
40 |
+
<p class="text-gray-600">Staff Login</p>
|
41 |
+
</div>
|
42 |
+
<form id="login-form" class="space-y-4">
|
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 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
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 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
|
52 |
+
</div>
|
53 |
+
<button type="submit"
|
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 |
+
Login
|
56 |
+
</button>
|
57 |
+
</form>
|
58 |
+
</div>
|
59 |
+
</div>
|
60 |
+
|
61 |
+
<!-- Main App Container (hidden by default) -->
|
62 |
+
<div id="app-container" class="hidden">
|
63 |
<!-- Header -->
|
64 |
+
<header class="bg-white shadow-sm">
|
65 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
|
66 |
+
<div class="flex items-center space-x-2">
|
67 |
+
<i class="fas fa-spa text-2xl text-purple-600"></i>
|
68 |
+
<h1 class="text-xl font-bold text-gray-800">Laser Elegance Scheduler</h1>
|
69 |
</div>
|
70 |
+
<div class="flex items-center space-x-4">
|
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 |
<!-- Main Content -->
|
80 |
+
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
81 |
+
<!-- Tabs Navigation -->
|
82 |
+
<div class="border-b border-gray-200 mb-6">
|
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 |
+
<!-- Calendar Tab Content -->
|
97 |
+
<div id="calendar-content" class="tab-content fade-in">
|
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 |
+
<div class="bg-white rounded-lg shadow overflow-hidden">
|
115 |
+
<!-- Calendar Header (Days of Week) -->
|
116 |
+
<div class="grid grid-cols-7 bg-gray-100 border-b border-gray-200">
|
117 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Sun</div>
|
118 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Mon</div>
|
119 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Tue</div>
|
120 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Wed</div>
|
121 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Thu</div>
|
122 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Fri</div>
|
123 |
+
<div class="py-2 text-center font-medium text-sm text-gray-600">Sat</div>
|
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 |
+
<!-- Appointments List Tab Content -->
|
154 |
+
<div id="appointments-content" class="tab-content hidden fade-in">
|
155 |
+
<div class="flex justify-between items-center mb-6">
|
156 |
+
<h2 class="text-xl font-semibold text-gray-800">Upcoming Appointments</h2>
|
157 |
+
<div class="flex items-center space-x-4">
|
158 |
<div class="relative">
|
159 |
+
<input type="text" id="search-appointments" placeholder="Search clients..."
|
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">
|
170 |
+
<div class="overflow-x-auto">
|
171 |
+
<table class="min-w-full divide-y divide-gray-200">
|
172 |
+
<thead class="bg-gray-50">
|
173 |
<tr>
|
174 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
|
175 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Service</th>
|
176 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date & Time</th>
|
177 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact</th>
|
178 |
+
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
179 |
</tr>
|
180 |
</thead>
|
181 |
+
<tbody id="appointments-table-body" class="bg-white divide-y divide-gray-200">
|
182 |
+
<!-- Appointments will be inserted here -->
|
183 |
</tbody>
|
184 |
</table>
|
185 |
</div>
|
|
|
|
|
|
|
|
|
|
|
186 |
</div>
|
187 |
+
</div>
|
188 |
+
|
189 |
+
<!-- New Appointment Tab Content -->
|
190 |
+
<div id="new-appointment-content" class="tab-content hidden fade-in">
|
191 |
+
<div class="bg-white rounded-lg shadow overflow-hidden">
|
192 |
+
<div class="px-6 py-4 border-b border-gray-200">
|
193 |
+
<h2 class="text-xl font-semibold text-gray-800">New Appointment</h2>
|
|
|
|
|
|
|
|
|
|
|
194 |
</div>
|
195 |
+
<div class="p-6">
|
196 |
+
<form id="new-appointment-form" class="space-y-6">
|
197 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
198 |
+
<div>
|
199 |
+
<label for="client-name" class="block text-sm font-medium text-gray-700 mb-1">Client Full Name *</label>
|
200 |
+
<input type="text" id="client-name" name="client-name" required
|
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="appointment-notes" class="block text-sm font-medium text-gray-700 mb-1">Notes (Optional)</label>
|
241 |
+
<textarea id="appointment-notes" name="appointment-notes" rows="3"
|
242 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
|
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 |
+
</form>
|
253 |
</div>
|
254 |
</div>
|
255 |
</div>
|
|
|
|
|
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="edit-appointment-notes" class="block text-sm font-medium text-gray-700 mb-1">Notes (Optional)</label>
|
313 |
+
<textarea id="edit-appointment-notes" name="edit-appointment-notes" rows="3"
|
314 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
|
315 |
+
</div>
|
316 |
+
<div class="flex justify-end space-x-4">
|
317 |
+
<button type="button" id="cancel-edit" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
|
318 |
+
Cancel
|
319 |
+
</button>
|
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 |
+
</form>
|
328 |
+
</div>
|
329 |
</div>
|
330 |
</div>
|
331 |
+
</main>
|
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: "sarah.[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: "michael.[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 loginScreen = document.getElementById('login-screen');
|
399 |
+
const appContainer = document.getElementById('app-container');
|
400 |
+
const loginForm = document.getElementById('login-form');
|
401 |
+
const logoutBtn = document.getElementById('logout-btn');
|
402 |
+
|
403 |
+
// Tab elements
|
404 |
+
const tabButtons = document.querySelectorAll('.tab-button');
|
405 |
+
const tabContents = document.querySelectorAll('.tab-content');
|
406 |
+
|
407 |
+
// Calendar elements
|
408 |
+
const calendar = document.getElementById('calendar');
|
409 |
+
const currentMonthDisplay = document.getElementById('current-month');
|
410 |
+
const prevMonthBtn = document.getElementById('prev-month');
|
411 |
+
const nextMonthBtn = document.getElementById('next-month');
|
412 |
+
const todayBtn = document.getElementById('today-btn');
|
413 |
+
|
414 |
+
// Appointments list elements
|
415 |
+
const appointmentsTableBody = document.getElementById('appointments-table-body');
|
416 |
+
const searchAppointments = document.getElementById('search-appointments');
|
417 |
+
const exportCsvBtn = document.getElementById('export-csv');
|
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 |
+
// Simple validation (in a real app, this would be a backend call)
|
454 |
+
if (username === 'epiliz' && password === 'culoareafericiri') {
|
455 |
+
currentUser = { username: 'admin', name: 'Admin User' };
|
456 |
+
localStorage.setItem('laserSalonUser', JSON.stringify(currentUser));
|
457 |
+
loginScreen.classList.add('hidden');
|
458 |
+
appContainer.classList.remove('hidden');
|
459 |
+
renderCalendar();
|
460 |
+
renderAppointmentsList();
|
461 |
+
} else {
|
462 |
+
alert('Invalid credentials. Try epiliz/culoareafericiri');
|
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 |
+
// Close modals
|
525 |
+
closeDayModal.addEventListener('click', () => {
|
526 |
+
dayAppointmentsModal.classList.add('hidden');
|
527 |
+
});
|
528 |
+
|
529 |
+
closeEditModal.addEventListener('click', () => {
|
530 |
+
editAppointmentModal.classList.add('hidden');
|
531 |
+
});
|
532 |
+
|
533 |
+
cancelEdit.addEventListener('click', () => {
|
534 |
+
editAppointmentModal.classList.add('hidden');
|
535 |
+
});
|
536 |
+
|
537 |
+
// Add appointment for specific day
|
538 |
+
addAppointmentForDay.addEventListener('click', () => {
|
539 |
+
const day = modalDayTitle.textContent.match(/(\w+ \d{1,2}, \d{4})/)[0];
|
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 |
+
// New appointment form
|
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 |
+
// Search appointments
|
589 |
+
searchAppointments.addEventListener('input', () => {
|
590 |
+
renderAppointmentsList();
|
591 |
+
});
|
592 |
+
|
593 |
+
// Export to CSV
|
594 |
+
exportCsvBtn.addEventListener('click', () => {
|
595 |
+
exportToCsv();
|
596 |
+
});
|
597 |
+
|
598 |
+
// Delete appointment
|
599 |
+
deleteAppointmentBtn.addEventListener('click', () => {
|
600 |
+
const appointmentId = parseInt(document.getElementById('edit-appointment-id').value);
|
601 |
+
if (confirm('Are you sure you want to delete this appointment?')) {
|
602 |
+
appointments = appointments.filter(a => a.id !== appointmentId);
|
603 |
+
editAppointmentModal.classList.add('hidden');
|
604 |
+
renderCalendar();
|
605 |
+
renderAppointmentsList();
|
606 |
+
}
|
607 |
+
});
|
608 |
+
|
609 |
+
// Edit appointment form
|
610 |
+
editAppointmentForm.addEventListener('submit', (e) => {
|
611 |
+
e.preventDefault();
|
612 |
+
|
613 |
+
const appointmentId = parseInt(document.getElementById('edit-appointment-id').value);
|
614 |
+
const appointmentIndex = appointments.findIndex(a => a.id === appointmentId);
|
615 |
+
|
616 |
+
if (appointmentIndex !== -1) {
|
617 |
+
appointments[appointmentIndex] = {
|
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 |
+
// Functions
|
635 |
+
function renderCalendar() {
|
636 |
+
// Update month/year display
|
637 |
+
currentMonthDisplay.textContent = new Date(currentYear, currentMonth).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
638 |
|
639 |
+
// Clear the calendar
|
640 |
+
calendar.innerHTML = '';
|
|
|
|
|
641 |
|
642 |
+
// Get first day of month and total days in month
|
643 |
+
const firstDay = new Date(currentYear, currentMonth, 1);
|
644 |
+
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
645 |
|
646 |
+
// Get day of week for first day (0 = Sunday, 6 = Saturday)
|
647 |
+
const firstDayOfWeek = firstDay.getDay();
|
|
|
|
|
648 |
|
649 |
+
// Get days from previous month to display
|
650 |
+
const prevMonthDays = new Date(currentYear, currentMonth, 0).getDate();
|
|
|
|
|
|
|
|
|
651 |
|
652 |
+
// Create calendar grid
|
653 |
+
let dayCount = 1;
|
654 |
+
let nextMonthDay = 1;
|
655 |
+
|
656 |
+
for (let i = 0; i < 35; i++) { // 5 rows x 7 days
|
657 |
+
const dayElement = document.createElement('div');
|
658 |
+
dayElement.className = 'calendar-day p-2 border border-gray-200 h-24 overflow-y-auto';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
function showDayAppointments(date, day) {
|
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 |
+
// Clear previous appointments
|
739 |
+
dayAppointmentsList.innerHTML = '';
|
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 |
+
// Sort appointments by time
|
745 |
+
dayAppointments.sort((a, b) => a.time.localeCompare(b.time));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 |
|
747 |
+
// Display each appointment
|
748 |
+
dayAppointments.forEach(appointment => {
|
749 |
+
const appointmentElement = document.createElement('div');
|
750 |
+
appointmentElement.className = 'appointment-item bg-white border border-gray-200 rounded-md p-3';
|
751 |
+
appointmentElement.innerHTML = `
|
752 |
+
<div class="flex justify-between items-start">
|
753 |
+
<div>
|
754 |
+
<h4 class="font-medium text-gray-800">${appointment.clientName}</h4>
|
755 |
+
<p class="text-sm text-gray-600">${appointment.service} • ${appointment.time}</p>
|
756 |
+
${appointment.notes ? `<p class="text-xs text-gray-500 mt-1">${appointment.notes}</p>` : ''}
|
757 |
+
</div>
|
758 |
+
<div class="flex space-x-2">
|
759 |
+
<button class="edit-appointment text-purple-600 hover:text-purple-800" data-id="${appointment.id}">
|
760 |
+
<i class="fas fa-edit"></i>
|
761 |
+
</button>
|
|
|
762 |
</div>
|
763 |
</div>
|
764 |
+
<div class="mt-2 flex items-center text-xs text-gray-500">
|
765 |
+
<i class="fas fa-phone-alt mr-1"></i>
|
766 |
+
<span class="mr-3">${appointment.phone}</span>
|
767 |
+
${appointment.email ? `<i class="fas fa-envelope mr-1"></i><span>${appointment.email}</span>` : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
768 |
</div>
|
769 |
`;
|
770 |
+
|
771 |
+
dayAppointmentsList.appendChild(appointmentElement);
|
772 |
+
});
|
773 |
|
774 |
+
// Add event listeners to edit buttons
|
775 |
+
document.querySelectorAll('.edit-appointment').forEach(button => {
|
776 |
+
button.addEventListener('click', (e) => {
|
777 |
+
const appointmentId = parseInt(button.getAttribute('data-id'));
|
778 |
+
showEditAppointmentModal(appointmentId);
|
779 |
+
e.stopPropagation();
|
|
|
780 |
});
|
781 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
782 |
}
|
783 |
|
784 |
+
// Show the modal
|
785 |
+
dayAppointmentsModal.classList.remove('hidden');
|
786 |
+
}
|
787 |
+
|
788 |
+
function renderAppointmentsList() {
|
789 |
+
// Clear previous appointments
|
790 |
+
appointmentsTableBody.innerHTML = '';
|
|
|
|
|
|
|
791 |
|
792 |
+
// Filter appointments based on search
|
793 |
+
const searchTerm = searchAppointments.value.toLowerCase();
|
794 |
+
let filteredAppointments = [...appointments];
|
|
|
|
|
795 |
|
796 |
+
if (searchTerm) {
|
797 |
+
filteredAppointments = appointments.filter(a =>
|
798 |
+
a.clientName.toLowerCase().includes(searchTerm) ||
|
799 |
+
a.phone.includes(searchTerm) ||
|
800 |
+
(a.email && a.email.toLowerCase().includes(searchTerm))
|
801 |
+
);
|
802 |
}
|
803 |
|
804 |
+
// Sort appointments by date and time (soonest first)
|
805 |
+
filteredAppointments.sort((a, b) => {
|
806 |
+
const dateA = new Date(`${a.date}T${a.time}`);
|
807 |
+
const dateB = new Date(`${b.date}T${b.time}`);
|
808 |
+
return dateA - dateB;
|
809 |
+
});
|
810 |
+
|
811 |
+
if (filteredAppointments.length === 0) {
|
812 |
+
const row = document.createElement('tr');
|
813 |
+
row.innerHTML = `
|
814 |
+
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
|
815 |
+
No appointments found. Try a different search term or create a new appointment.
|
816 |
+
</td>
|
817 |
+
`;
|
818 |
+
appointmentsTableBody.appendChild(row);
|
819 |
+
return;
|
820 |
}
|
821 |
|
822 |
+
// Display each appointment
|
823 |
+
filteredAppointments.forEach(appointment => {
|
824 |
+
const appointmentDate = new Date(`${appointment.date}T${appointment.time}`);
|
825 |
+
const formattedDate = appointmentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
826 |
+
const formattedTime = appointmentDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
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 |
+
document.querySelectorAll('.delete-appointment').forEach(button => {
|
871 |
+
button.addEventListener('click', () => {
|
872 |
+
const appointmentId = parseInt(button.getAttribute('data-id'));
|
873 |
+
if (confirm('Are you sure you want to delete this appointment?')) {
|
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 |
+
// Show the modal
|
897 |
+
dayAppointmentsModal.classList.add('hidden');
|
898 |
+
editAppointmentModal.classList.remove('hidden');
|
899 |
+
}
|
900 |
+
|
901 |
+
function formatDateForInput(date) {
|
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 |
+
function exportToCsv() {
|
909 |
+
// Sort appointments by date
|
910 |
+
const sortedAppointments = [...appointments].sort((a, b) => {
|
911 |
+
const dateA = new Date(`${a.date}T${a.time}`);
|
912 |
+
const dateB = new Date(`${b.date}T${b.time}`);
|
913 |
+
return dateA - dateB;
|
914 |
+
});
|
915 |
+
|
916 |
+
// CSV header
|
917 |
+
let csv = 'Client Name,Phone,Email,Service,Date,Time,Notes\n';
|
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>
|