Spaces:
Sleeping
Sleeping
| import { useState } from 'react'; | |
| import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, Check, X } from 'lucide-react'; | |
| const SmartCalendar = ({ availability = [], bookings = [], onToggleDate }) => { | |
| const [currentDate, setCurrentDate] = useState(new Date()); | |
| const daysInMonth = (year, month) => new Date(year, month + 1, 0).getDate(); | |
| const firstDayOfMonth = (year, month) => new Date(year, month, 1).getDay(); | |
| const year = currentDate.getFullYear(); | |
| const month = currentDate.getMonth(); | |
| const monthNames = [ | |
| "January", "February", "March", "April", "May", "June", | |
| "July", "August", "September", "October", "November", "December" | |
| ]; | |
| const prevMonth = () => setCurrentDate(new Date(year, month - 1, 1)); | |
| const nextMonth = () => setCurrentDate(new Date(year, month + 1, 1)); | |
| const days = []; | |
| const totalDays = daysInMonth(year, month); | |
| const startDay = firstDayOfMonth(year, month); | |
| // Padding for previous month | |
| for (let i = 0; i < startDay; i++) { | |
| days.push({ day: null, fullDate: null }); | |
| } | |
| // Current month days | |
| for (let i = 1; i <= totalDays; i++) { | |
| const fullDate = new Date(year, month, i).toISOString().split('T')[0]; | |
| const isAvailable = availability.includes(fullDate); | |
| const dayBookings = bookings.filter(b => b.date.startsWith(fullDate)); | |
| days.push({ day: i, fullDate, isAvailable, bookings: dayBookings }); | |
| } | |
| return ( | |
| <div className="bg-white rounded-[2rem] border border-gray-100 shadow-sm overflow-hidden animate-in fade-in duration-500"> | |
| <div className="p-6 bg-gray-50/50 border-b border-gray-50 flex items-center justify-between"> | |
| <div className="flex items-center space-x-3"> | |
| <div className="bg-blue-600 p-2 rounded-xl text-white"> | |
| <CalendarIcon size={18} /> | |
| </div> | |
| <h3 className="font-black text-gray-900 tracking-tight">{monthNames[month]} {year}</h3> | |
| </div> | |
| <div className="flex items-center space-x-2"> | |
| <button onClick={prevMonth} className="p-2 hover:bg-gray-200 rounded-xl transition text-gray-400 hover:text-gray-900"> | |
| <ChevronLeft size={20} /> | |
| </button> | |
| <button onClick={nextMonth} className="p-2 hover:bg-gray-200 rounded-xl transition text-gray-400 hover:text-gray-900"> | |
| <ChevronRight size={20} /> | |
| </button> | |
| </div> | |
| </div> | |
| <div className="p-4"> | |
| <div className="grid grid-cols-7 gap-1 mb-4"> | |
| {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(d => ( | |
| <div key={d} className="text-center text-[10px] font-black text-gray-400 uppercase tracking-widest py-2"> | |
| {d} | |
| </div> | |
| ))} | |
| {days.map((d, i) => ( | |
| <div | |
| key={i} | |
| onClick={() => d.fullDate && onToggleDate(d.fullDate)} | |
| className={` | |
| relative aspect-square flex flex-col items-center justify-center rounded-2xl cursor-pointer transition-all duration-300 group | |
| ${!d.day ? 'pointer-events-none' : 'hover:scale-95'} | |
| ${d.isAvailable ? 'bg-blue-50 text-blue-700 border border-blue-100' : 'text-gray-900 hover:bg-gray-50'} | |
| ${d.bookings.length > 0 ? 'ring-2 ring-indigo-600 ring-offset-2' : ''} | |
| `} | |
| > | |
| <span className="text-sm font-bold z-10">{d.day}</span> | |
| {d.day && ( | |
| <> | |
| <div className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| {d.isAvailable ? <X size={10} className="text-red-400" /> : <Check size={10} className="text-green-400" />} | |
| </div> | |
| {d.bookings.length > 0 && ( | |
| <div className="absolute bottom-2 w-1.5 h-1.5 bg-indigo-600 rounded-full shadow-lg shadow-indigo-100"></div> | |
| )} | |
| {d.isAvailable && !d.bookings.length && ( | |
| <div className="absolute inset-0 bg-blue-100/50 rounded-2xl animate-pulse"></div> | |
| )} | |
| </> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| <div className="mt-6 pt-6 border-t border-gray-50 flex items-center justify-between px-2"> | |
| <div className="flex items-center space-x-4"> | |
| <div className="flex items-center space-x-2"> | |
| <div className="w-3 h-3 bg-blue-50 border border-blue-100 rounded-sm"></div> | |
| <span className="text-[10px] font-black uppercase text-gray-400 tracking-wider">Available</span> | |
| </div> | |
| <div className="flex items-center space-x-2"> | |
| <div className="w-3 h-3 bg-indigo-600 rounded-full"></div> | |
| <span className="text-[10px] font-black uppercase text-gray-400 tracking-wider">Booked</span> | |
| </div> | |
| </div> | |
| <p className="text-[9px] font-bold text-gray-300 italic italic">Click a date to toggle</p> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default SmartCalendar; | |