PK µ3ÐZÝ©_ _ index.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const rootElement = document.getElementById('root'); if (!rootElement) { throw new Error("Could not find root element to mount to"); } const root = ReactDOM.createRoot(rootElement); root.render( ); PK µ3ÐZÂùhVØ Ø metadata.json { "name": "Clinic Appointment System", "description": "A user-friendly system for booking medical clinic appointments, featuring dynamic doctor selection, simulated notifications, and AI-powered health tips." } PK µ3ÐZK͹] ] index.html Clinic Appointment System
PK µ3ÐZù“   App.tsx import React, { useState, useEffect, useCallback } from 'react'; import { Header } from './components/Header'; import { BookingForm } from './components/BookingForm'; import { ConfirmationModal } from './components/ConfirmationModal'; import { HealthTipSection } from './components/HealthTipSection'; import { fetchHealthTipFromGemini } from './services/geminiService'; import type { BookingConfirmation } from './types'; const App: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [bookingDetailsForModal, setBookingDetailsForModal] = useState(null); const [healthTip, setHealthTip] = useState('Loading health tip...'); const [isLoadingTip, setIsLoadingTip] = useState(true); const [errorTip, setErrorTip] = useState(null); const handleBookingSuccess = (details: BookingConfirmation) => { setBookingDetailsForModal(details); setIsModalOpen(true); }; const handleCloseModal = () => { setIsModalOpen(false); setBookingDetailsForModal(null); }; const loadHealthTip = useCallback(async () => { setIsLoadingTip(true); setErrorTip(null); try { const tip = await fetchHealthTipFromGemini(); setHealthTip(tip); } catch (error) { console.error("Failed to fetch health tip:", error); setHealthTip("Could not load health tip at this moment."); setErrorTip("Failed to load tip. Please try again."); } finally { setIsLoadingTip(false); } }, []); useEffect(() => { loadHealthTip(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Load tip on initial mount return (

Book Your Appointment

{bookingDetailsForModal && ( )}

© {new Date().getFullYear()} Clinic Appointment System. All rights reserved.

Powered by Modern Technology

); }; export default App; PK µ3ÐZE®ˆdŸ Ÿ  types.ts export interface Doctor { id: string; name: string; specialty: string; availableTimeSlots: string[]; // e.g., ["09:00", "09:30"] } export interface AppointmentFormData { patientName: string; patientEmail: string; patientPhone: string; doctorId: string; date: string; time: string; reason: string; } export interface BookingConfirmation extends AppointmentFormData { doctorName: string; } PK µ3ÐZÑû¾ŒÚ Ú constants.ts import type { Doctor } from './types'; export const DOCTORS: Doctor[] = [ { id: 'doc1', name: 'Dr. Alice Smith', specialty: 'Cardiology', availableTimeSlots: ['09:00', '09:30', '10:00', '10:30', '11:00'], }, { id: 'doc2', name: 'Dr. Bob Johnson', specialty: 'Pediatrics', availableTimeSlots: ['13:00', '13:30', '14:00', '14:30', '15:00'], }, { id: 'doc3', name: 'Dr. Carol Williams', specialty: 'Neurology', availableTimeSlots: ['09:00', '10:00', '11:00', '14:00', '15:00', '16:00'], }, { id: 'doc4', name: 'Dr. David Brown', specialty: 'Dermatology', availableTimeSlots: ['08:30', '09:30', '10:30', '11:30', '13:30', '14:30'], }, ]; // General time slots, can be filtered by doctor's availability export const GENERAL_TIME_SLOTS: string[] = [ '08:00', '08:30', '09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00' ]; PK µ3ÐZ components/PK µ3ÐZû;’   components/Header.tsx import React from 'react'; import { CalendarDaysIcon } from './icons/CalendarDaysIcon'; export const Header: React.FC = () => { return (

Clinic Appointments

Clinic Logo
); }; PK µ3ÐZˆÃçØ! !  components/BookingForm.tsx import React, { useState, useEffect, useCallback } from 'react'; import type { AppointmentFormData, BookingConfirmation, Doctor } from '../types'; import { DOCTORS } from '../constants'; import { TextInput } from './inputs/TextInput'; import { SelectInput } from './inputs/SelectInput'; import { Button } from './ui/Button'; import { UserIcon } from './icons/UserIcon'; import { EnvelopeIcon } from './icons/EnvelopeIcon'; import { PhoneIcon } from './icons/PhoneIcon'; import { BriefcaseIcon } from './icons/BriefcaseIcon'; import { CalendarIcon } from './icons/CalendarIcon'; import { ClockIcon } from './icons/ClockIcon'; import { PencilSquareIcon } from './icons/PencilSquareIcon'; import { sendBookingConfirmationEmail, sendBookingConfirmationWhatsApp } from '../services/notificationService'; interface BookingFormProps { onBookingSuccess: (details: BookingConfirmation) => void; } export const BookingForm: React.FC = ({ onBookingSuccess }) => { const [formData, setFormData] = useState({ patientName: '', patientEmail: '', patientPhone: '', doctorId: '', date: '', time: '', reason: '', }); const [availableTimes, setAvailableTimes] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [errors, setErrors] = useState>({}); const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); if (errors[name as keyof AppointmentFormData]) { setErrors(prev => ({ ...prev, [name]: undefined })); } }; const selectedDoctor = DOCTORS.find(doc => doc.id === formData.doctorId); useEffect(() => { if (selectedDoctor) { setAvailableTimes(selectedDoctor.availableTimeSlots); // Reset time if current time is not available for new doctor if (formData.time && !selectedDoctor.availableTimeSlots.includes(formData.time)) { setFormData(prev => ({ ...prev, time: '' })); } } else { setAvailableTimes([]); } }, [formData.doctorId, selectedDoctor, formData.time]); const validateForm = useCallback(() => { const newErrors: Partial = {}; if (!formData.patientName.trim()) newErrors.patientName = 'Patient name is required.'; if (!formData.patientEmail.trim()) { newErrors.patientEmail = 'Patient email is required.'; } else if (!/\S+@\S+\.\S+/.test(formData.patientEmail)) { newErrors.patientEmail = 'Email address is invalid.'; } if (!formData.patientPhone.trim()) { newErrors.patientPhone = 'Patient phone is required.'; } else if (!/^\+?[1-9]\d{1,14}$/.test(formData.patientPhone.replace(/\s+/g, ''))) { // Basic E.164-like format newErrors.patientPhone = 'Phone number is invalid.'; } if (!formData.doctorId) newErrors.doctorId = 'Please select a doctor.'; if (!formData.date) { newErrors.date = 'Please select a date.'; } else { const today = new Date(); today.setHours(0,0,0,0); // Compare dates only if (new Date(formData.date) < today) { newErrors.date = 'Cannot book appointments for past dates.'; } } if (!formData.time) newErrors.time = 'Please select a time.'; if (!formData.reason.trim()) newErrors.reason = 'Reason for visit is required.'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }, [formData]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) return; setIsSubmitting(true); // Simulate API call await new Promise(resolve => setTimeout(resolve, 1500)); const doctor = DOCTORS.find(doc => doc.id === formData.doctorId); if (doctor) { const bookingConfirmation: BookingConfirmation = { ...formData, doctorName: doctor.name, }; // Simulate sending notifications sendBookingConfirmationEmail(bookingConfirmation); sendBookingConfirmationWhatsApp(bookingConfirmation); onBookingSuccess(bookingConfirmation); // Reset form setFormData({ patientName: '', patientEmail: '', patientPhone: '', doctorId: '', date: '', time: '', reason: '', }); setAvailableTimes([]); } setIsSubmitting(false); }; const todayDateString = new Date().toISOString().split('T')[0]; return (
} error={errors.patientName} required /> } error={errors.patientEmail} required />
} error={errors.patientPhone} required />
({ value: doc.id, label: `${doc.name} (${doc.specialty})` }))} icon={} defaultOptionLabel="-- Choose a Doctor --" error={errors.doctorId} required /> } min={todayDateString} error={errors.date} required disabled={!formData.doctorId} /> ({ value: slot, label: slot }))} icon={} defaultOptionLabel="-- Choose a Time --" error={errors.time} required disabled={!formData.doctorId || !formData.date || availableTimes.length === 0} emptyOptionsMessage={formData.doctorId && formData.date && availableTimes.length === 0 ? "No slots available for this doctor/date" : "Select doctor and date first"} />