Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>LaboConnect - Prise de sang en ligne</title> | |
| <link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>💉</text></svg>"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script> | |
| <style> | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%); | |
| } | |
| .card-hover { | |
| transition: all 0.3s ease; | |
| } | |
| .card-hover:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .btn-primary { | |
| background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%); | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary:hover { | |
| background: linear-gradient(90deg, #4338ca 0%, #6d28d9 100%); | |
| transform: scale(1.05); | |
| } | |
| .active-tab { | |
| border-bottom: 3px solid #4f46e5; | |
| color: #4f46e5; | |
| font-weight: 600; | |
| } | |
| .appointment-card { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.4); } | |
| 70% { box-shadow: 0 0 0 10px rgba(79, 70, 229, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(79, 70, 229, 0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm"> | |
| <div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center"> | |
| <i data-feather="droplet" class="text-indigo-600"></i> | |
| </div> | |
| <h1 class="text-2xl font-bold text-gray-800">LaboConnect</h1> | |
| </div> | |
| <div id="authSection" class="flex items-center space-x-4"> | |
| <button id="loginBtn" class="text-indigo-600 font-medium hover:text-indigo-800">Connexion</button> | |
| <button id="registerBtn" class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition">Inscription</button> | |
| </div> | |
| <div id="userSection" class="hidden items-center space-x-4"> | |
| <span id="userName" class="text-gray-700 font-medium"></span> | |
| <button id="logoutBtn" class="text-gray-500 hover:text-gray-700"> | |
| <i data-feather="log-out"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <!-- Auth Forms --> | |
| <div id="authForms" class="max-w-md mx-auto hidden"> | |
| <div class="bg-white rounded-xl shadow-lg p-6 card-hover"> | |
| <div class="flex border-b mb-4"> | |
| <button id="showLogin" class="pb-2 px-4 active-tab">Connexion</button> | |
| <button id="showRegister" class="pb-2 px-4 text-gray-500">Inscription</button> | |
| </div> | |
| <!-- Login Form --> | |
| <form id="loginForm" class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Email</label> | |
| <input type="email" id="loginEmail" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Mot de passe</label> | |
| <input type="password" id="loginPassword" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <button type="submit" class="w-full btn-primary text-white py-2 rounded-lg">Se connecter</button> | |
| </form> | |
| <!-- Register Form --> | |
| <form id="registerForm" class="space-y-4 hidden"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Nom complet</label> | |
| <input type="text" id="registerName" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Email</label> | |
| <input type="email" id="registerEmail" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Mot de passe</label> | |
| <input type="password" id="registerPassword" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <button type="submit" class="w-full btn-primary text-white py-2 rounded-lg">S'inscrire</button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Dashboard --> | |
| <div id="dashboard" class="hidden"> | |
| <!-- Welcome Banner --> | |
| <div class="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl p-6 text-white mb-8"> | |
| <h2 class="text-2xl font-bold mb-2">Bienvenue sur LaboConnect</h2> | |
| <p class="opacity-90">Planifiez vos prises de sang en quelques clics</p> | |
| </div> | |
| <!-- Navigation Tabs --> | |
| <div class="flex border-b mb-6"> | |
| <button id="tabNewAppointment" class="pb-3 px-6 active-tab">Nouveau rendez-vous</button> | |
| <button id="tabMyAppointments" class="pb-3 px-6 text-gray-500">Mes rendez-vous</button> | |
| </div> | |
| <!-- New Appointment Form --> | |
| <div id="newAppointmentSection"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div class="bg-white rounded-xl shadow-lg p-6 card-hover"> | |
| <h3 class="text-xl font-semibold mb-4 text-gray-800">Prendre un rendez-vous</h3> | |
| <form id="appointmentForm" class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Laboratoire</label> | |
| <select id="laboratorySelect" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| <option value="">Sélectionnez un laboratoire</option> | |
| <option value="Labo Central Paris">Labo Central Paris</option> | |
| <option value="BioTest Marseille">BioTest Marseille</option> | |
| <option value="MediLab Lyon">MediLab Lyon</option> | |
| <option value="AnalyseLab Bordeaux">AnalyseLab Bordeaux</option> | |
| <option value="SangTest Lille">SangTest Lille</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Date du rendez-vous</label> | |
| <input type="date" id="appointmentDate" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Heure du rendez-vous</label> | |
| <select id="appointmentTime" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| <option value="">Sélectionnez une heure</option> | |
| <option value="08:00">08:00</option> | |
| <option value="09:00">09:00</option> | |
| <option value="10:00">10:00</option> | |
| <option value="11:00">11:00</option> | |
| <option value="14:00">14:00</option> | |
| <option value="15:00">15:00</option> | |
| <option value="16:00">16:00</option> | |
| <option value="17:00">17:00</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Type d'analyse</label> | |
| <select id="analysisType" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-300" required> | |
| <option value="">Sélectionnez un type</option> | |
| <option value="Hémogramme">Hémogramme</option> | |
| <option value="Cholestérol">Cholestérol</option> | |
| <option value="Glycémie">Glycémie</option> | |
| <option value="Bilan thyroïdien">Bilan thyroïdien</option> | |
| <option value="Vitamine D">Vitamine D</option> | |
| <option value="Ferritine">Ferritine</option> | |
| </select> | |
| </div> | |
| <button type="submit" class="w-full btn-primary text-white py-3 rounded-lg mt-4 pulse"> | |
| Prendre rendez-vous | |
| </button> | |
| </form> | |
| </div> | |
| <!-- Laboratory Info --> | |
| <div class="bg-white rounded-xl shadow-lg p-6 card-hover"> | |
| <h3 class="text-xl font-semibold mb-4 text-gray-800">Nos laboratoires partenaires</h3> | |
| <div class="space-y-4"> | |
| <div class="flex items-start space-x-3"> | |
| <div class="bg-indigo-100 p-2 rounded-lg"> | |
| <i data-feather="map-pin" class="text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-gray-800">Labo Central Paris</h4> | |
| <p class="text-sm text-gray-600">123 Rue de la Santé, 75013 Paris</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-3"> | |
| <div class="bg-indigo-100 p-2 rounded-lg"> | |
| <i data-feather="map-pin" class="text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-gray-800">BioTest Marseille</h4> | |
| <p class="text-sm text-gray-600">45 Avenue des Laboratoires, 13008 Marseille</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-3"> | |
| <div class="bg-indigo-100 p-2 rounded-lg"> | |
| <i data-feather="map-pin" class="text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-gray-800">MediLab Lyon</h4> | |
| <p class="text-sm text-gray-600">78 Boulevard des Sciences, 69003 Lyon</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 pt-4 border-t"> | |
| <h4 class="font-semibold text-gray-800 mb-2">Pourquoi choisir nos laboratoires ?</h4> | |
| <ul class="text-sm text-gray-600 space-y-1"> | |
| <li class="flex items-center"> | |
| <i data-feather="check-circle" class="text-green-500 mr-2 w-4 h-4"></i> | |
| Résultats sous 24h | |
| </li> | |
| <li class="flex items-center"> | |
| <i data-feather="check-circle" class="text-green-500 mr-2 w-4 h-4"></i> | |
| Personnel qualifié | |
| </li> | |
| <li class="flex items-center"> | |
| <i data-feather="check-circle" class="text-green-500 mr-2 w-4 h-4"></i> | |
| Équipements modernes | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- My Appointments Section --> | |
| <div id="myAppointmentsSection" class="hidden"> | |
| <div class="bg-white rounded-xl shadow-lg p-6"> | |
| <h3 class="text-xl font-semibold mb-4 text-gray-800">Mes rendez-vous</h3> | |
| <div id="appointmentsList" class="space-y-4"> | |
| <!-- Appointments will be dynamically added here --> | |
| <div class="text-center py-12 hidden" id="noAppointmentsMessage"> | |
| <i data-feather="calendar" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i> | |
| <p class="text-gray-500">Vous n'avez pas encore de rendez-vous</p> | |
| <button id="bookFirstAppointment" class="mt-4 text-indigo-600 font-medium">Prendre votre premier rendez-vous</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Notification --> | |
| <div id="notification" class="fixed top-4 right-4 bg-white shadow-lg rounded-lg p-4 border-l-4 border-green-500 hidden z-50"> | |
| <div class="flex items-start"> | |
| <i data-feather="check-circle" class="text-green-500 mr-2 mt-1"></i> | |
| <div> | |
| <h4 class="font-medium text-gray-800">Succès</h4> | |
| <p class="text-sm text-gray-600" id="notificationMessage"></p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Feather Icons | |
| feather.replace(); | |
| // DOM Elements | |
| const authForms = document.getElementById('authForms'); | |
| const dashboard = document.getElementById('dashboard'); | |
| const loginBtn = document.getElementById('loginBtn'); | |
| const registerBtn = document.getElementById('registerBtn'); | |
| const logoutBtn = document.getElementById('logoutBtn'); | |
| const showLogin = document.getElementById('showLogin'); | |
| const showRegister = document.getElementById('showRegister'); | |
| const loginForm = document.getElementById('loginForm'); | |
| const registerForm = document.getElementById('registerForm'); | |
| const appointmentForm = document.getElementById('appointmentForm'); | |
| const tabNewAppointment = document.getElementById('tabNewAppointment'); | |
| const tabMyAppointments = document.getElementById('tabMyAppointments'); | |
| const newAppointmentSection = document.getElementById('newAppointmentSection'); | |
| const myAppointmentsSection = document.getElementById('myAppointmentsSection'); | |
| const appointmentsList = document.getElementById('appointmentsList'); | |
| const noAppointmentsMessage = document.getElementById('noAppointmentsMessage'); | |
| const bookFirstAppointment = document.getElementById('bookFirstAppointment'); | |
| const notification = document.getElementById('notification'); | |
| const notificationMessage = document.getElementById('notificationMessage'); | |
| const authSection = document.getElementById('authSection'); | |
| const userSection = document.getElementById('userSection'); | |
| const userName = document.getElementById('userName'); | |
| // State | |
| let currentUser = null; | |
| let appointments = JSON.parse(localStorage.getItem('appointments')) || []; | |
| // Show notification | |
| function showNotification(message) { | |
| notificationMessage.textContent = message; | |
| notification.classList.remove('hidden'); | |
| setTimeout(() => { | |
| notification.classList.add('hidden'); | |
| }, 3000); | |
| } | |
| // Switch between login and register forms | |
| showLogin.addEventListener('click', () => { | |
| loginForm.classList.remove('hidden'); | |
| registerForm.classList.add('hidden'); | |
| showLogin.classList.add('active-tab', 'text-gray-800'); | |
| showLogin.classList.remove('text-gray-500'); | |
| showRegister.classList.remove('active-tab', 'text-gray-800'); | |
| showRegister.classList.add('text-gray-500'); | |
| }); | |
| showRegister.addEventListener('click', () => { | |
| registerForm.classList.remove('hidden'); | |
| loginForm.classList.add('hidden'); | |
| showRegister.classList.add('active-tab', 'text-gray-800'); | |
| showRegister.classList.remove('text-gray-500'); | |
| showLogin.classList.remove('active-tab', 'text-gray-800'); | |
| showLogin.classList.add('text-gray-500'); | |
| }); | |
| // Switch tabs | |
| tabNewAppointment.addEventListener('click', () => { | |
| newAppointmentSection.classList.remove('hidden'); | |
| myAppointmentsSection.classList.add('hidden'); | |
| tabNewAppointment.classList.add('active-tab', 'text-gray-800'); | |
| tabNewAppointment.classList.remove('text-gray-500'); | |
| tabMyAppointments.classList.remove('active-tab', 'text-gray-800'); | |
| tabMyAppointments.classList.add('text-gray-500'); | |
| }); | |
| tabMyAppointments.addEventListener('click', () => { | |
| newAppointmentSection.classList.add('hidden'); | |
| myAppointmentsSection.classList.remove('hidden'); | |
| tabMyAppointments.classList.add('active-tab', 'text-gray-800'); | |
| tabMyAppointments.classList.remove('text-gray-500'); | |
| tabNewAppointment.classList.remove('active-tab', 'text-gray-800'); | |
| tabNewAppointment.classList.add('text-gray-500'); | |
| renderAppointments(); | |
| }); | |
| // Book first appointment button | |
| bookFirstAppointment.addEventListener('click', () => { | |
| tabNewAppointment.click(); | |
| }); | |
| // User authentication | |
| loginBtn.addEventListener('click', () => { | |
| authForms.classList.remove('hidden'); | |
| showLogin.click(); | |
| }); | |
| registerBtn.addEventListener('click', () => { | |
| authForms.classList.remove('hidden'); | |
| showRegister.click(); | |
| }); | |
| logoutBtn.addEventListener('click', () => { | |
| currentUser = null; | |
| localStorage.removeItem('currentUser'); | |
| authSection.classList.remove('hidden'); | |
| userSection.classList.add('hidden'); | |
| dashboard.classList.add('hidden'); | |
| authForms.classList.remove('hidden'); | |
| showNotification('Vous avez été déconnecté'); | |
| }); | |
| // Login form submission | |
| loginForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const email = document.getElementById('loginEmail').value; | |
| const password = document.getElementById('loginPassword').value; | |
| // Mock authentication | |
| const users = JSON.parse(localStorage.getItem('users')) || []; | |
| const user = users.find(u => u.email === email && u.password === password); | |
| if (user) { | |
| currentUser = user; | |
| localStorage.setItem('currentUser', JSON.stringify(user)); | |
| showDashboard(); | |
| showNotification('Connexion réussie !'); | |
| } else { | |
| showNotification('Identifiants incorrects'); | |
| } | |
| }); | |
| // Register form submission | |
| registerForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const name = document.getElementById('registerName').value; | |
| const email = document.getElementById('registerEmail').value; | |
| const password = document.getElementById('registerPassword').value; | |
| // Check if user already exists | |
| const users = JSON.parse(localStorage.getItem('users')) || []; | |
| if (users.some(u => u.email === email)) { | |
| showNotification('Cet email est déjà utilisé'); | |
| return; | |
| } | |
| // Create new user | |
| const newUser = { name, email, password }; | |
| users.push(newUser); | |
| localStorage.setItem('users', JSON.stringify(users)); | |
| currentUser = newUser; | |
| localStorage.setItem('currentUser', JSON.stringify(newUser)); | |
| showDashboard(); | |
| showNotification('Inscription réussie ! Bienvenue sur LaboConnect'); | |
| }); | |
| // Appointment form submission | |
| appointmentForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| if (!currentUser) return; | |
| const laboratory = document.getElementById('laboratorySelect').value; | |
| const date = document.getElementById('appointmentDate').value; | |
| const time = document.getElementById('appointmentTime').value; | |
| const analysis = document.getElementById('analysisType').value; | |
| const appointment = { | |
| id: Date.now(), | |
| userId: currentUser.email, | |
| laboratory, | |
| date, | |
| time, | |
| analysis, | |
| status: 'confirmé' | |
| }; | |
| appointments.push(appointment); | |
| localStorage.setItem('appointments', JSON.stringify(appointments)); | |
| showNotification('Rendez-vous pris avec succès !'); | |
| appointmentForm.reset(); | |
| renderAppointments(); | |
| }); | |
| // Render appointments | |
| function renderAppointments() { | |
| if (!currentUser) return; | |
| const userAppointments = appointments.filter(a => a.userId === currentUser.email); | |
| if (userAppointments.length === 0) { | |
| noAppointmentsMessage.classList.remove('hidden'); | |
| appointmentsList.innerHTML = ''; | |
| appointmentsList.appendChild(noAppointmentsMessage); | |
| return; | |
| } | |
| noAppointmentsMessage.classList.add('hidden'); | |
| appointmentsList.innerHTML = ''; | |
| userAppointments.forEach(appointment => { | |
| const appointmentEl = document.createElement('div'); | |
| appointmentEl.className = 'border rounded-lg p-4 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 appointment-card'; | |
| const dateObj = new Date(appointment.date); | |
| const formattedDate = dateObj.toLocaleDateString('fr-FR', { | |
| weekday: 'long', | |
| day: 'numeric', | |
| month: 'long' | |
| }); | |
| appointmentEl.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <div class="bg-indigo-100 p-2 rounded-lg"> | |
| <i data-feather="calendar" class="text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-gray-800">${appointment.analysis}</h4> | |
| <p class="text-sm text-gray-600">${formattedDate} à ${appointment.time}</p> | |
| <p class="text-sm text-gray-600">${appointment.laboratory}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span class="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium">Confirmé</span> | |
| <button onclick="cancelAppointment(${appointment.id})" class="text-red-500 hover:text-red-700"> | |
| <i data-feather="x-circle"></i> | |
| </button> | |
| </div> | |
| `; | |
| appointmentsList.appendChild(appointmentEl); | |
| }); | |
| feather.replace(); | |
| } | |
| // Cancel appointment | |
| window.cancelAppointment = function(id) { | |
| if (confirm('Êtes-vous sûr de vouloir annuler ce rendez-vous ?')) { | |
| appointments = appointments.filter(a => a.id !== id); | |
| localStorage.setItem('appointments', JSON.stringify(appointments)); | |
| renderAppointments(); | |
| showNotification('Rendez-vous annulé'); | |
| } | |
| }; | |
| // Show dashboard | |
| function showDashboard() { | |
| authForms.classList.add('hidden'); | |
| dashboard.classList.remove('hidden'); | |
| authSection.classList.add('hidden'); | |
| userSection.classList.remove('hidden'); | |
| userName.textContent = currentUser.name; | |
| renderAppointments(); | |
| } | |
| // Check if user is already logged in | |
| function checkAuthStatus() { | |
| const storedUser = localStorage.getItem('currentUser'); | |
| if (storedUser) { | |
| currentUser = JSON.parse(storedUser); | |
| showDashboard(); | |
| } else { | |
| authForms.classList.remove('hidden'); | |
| } | |
| } | |
| // Set minimum date for appointment | |
| function setMinDate() { | |
| const today = new Date(); | |
| const formattedDate = today.toISOString().split('T')[0]; | |
| document.getElementById('appointmentDate').min = formattedDate; | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| checkAuthStatus(); | |
| setMinDate(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |