MS
mawe seo

Interactive Calendar and Event Scheduler

"use client" import React, { useState } from "react"; import { format, addMonths, subMonths, startOfWeek, endOfWeek, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, addDays, subDays, parseISO } from "date fns"; import { FiChevronLeft, FiChevronRight, FiPlus, FiCalendar, FiClock, FiX, FiSearch, FiTag } from "react icons/fi"; const Agenda = () => { const [currentDate, setCurrentDate] = useState(new Date()); const [selectedView, setSelectedView] = useState("month"); const [searchTerm, setSearchTerm] = useState(""); const [selectedTags, setSelectedTags] = useState<string[]>([]); const [tags, setTags] = useState<string[]>(["Work", "Personal", "Meeting", "Important", "Holiday"]); const [newTag, setNewTag] = useState(""); const [events, setEvents] = useState([ { id: 1, title: "Team Meeting", start: "2024 01 15T10:00", end: "2024 01 15T11:00", color: "bg blue 500", tags: ["Work", "Meeting"] }, { id: 2, title: "Project Review", start: "2024 01 15T14:00", end: "2024 01 15T15:30", color: "bg green 500", tags: ["Work", "Important"] }, ]); const [showEventModal, setShowEventModal] = useState(false); const [newEvent, setNewEvent] = useState<{ title: string; start: string; end: string; tags: string[]; }>({ title: "", start: "", end: "", tags: [] }); const views = ["day", "week", "month"]; const handlePrevious = () => { if (selectedView === "month") { setCurrentDate(subMonths(currentDate, 1)); } else if (selectedView === "week") { setCurrentDate(subDays(currentDate, 7)); } else { setCurrentDate(subDays(currentDate, 1)); } }; const handleNext = () => { if (selectedView === "month") { setCurrentDate(addMonths(currentDate, 1)); } else if (selectedView === "week") { setCurrentDate(addDays(currentDate, 7)); } else { setCurrentDate(addDays(currentDate, 1)); } }; const handleAddEvent = () => { if (newEvent.title && newEvent.start && newEvent.end) { setEvents([ ...events, { id: events.length + 1, ...newEvent, color: `bg ${["blue", "green", "red", "purple"][Math.floor(Math.random() * 4)]} 500`, }, ]); setShowEventModal(false); setNewEvent({ title: "", start: "", end: "", tags: [] }); } }; const handleAddTag = () => { if (newTag && !tags.includes(newTag)) { setTags([...tags, newTag]); setNewTag(""); } }; const filteredEvents = events.filter(event => { const matchesSearch = event.title.toLowerCase().includes(searchTerm.toLowerCase()); const matchesTags = selectedTags.length === 0 || selectedTags.some(tag => event.tags && event.tags.includes(tag)); return matchesSearch && matchesTags; }); const MonthView = () => { const start = startOfMonth(currentDate); const end = endOfMonth(currentDate); const days = eachDayOfInterval({ start, end }); return ( <div className="grid grid cols 7 gap 1"> {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => ( <div key={day} className="p 2 text center font semibold text gray 600"> {day} </div> ))} {days.map((day) => ( <div key={day.toString()} className={`min h [100px] p 2 border border gray 200 ${isSameMonth(day, currentDate) ? "bg white" : "bg gray 50"} ${isSameDay(day, new Date()) ? "bg blue 50" : ""}`} > <div className="font medium">{format(day, "d")}</div> <div className="mt 1 space y 1"> {filteredEvents .filter((event) => isSameDay(parseISO(event.start), day)) .map((event) => ( <div key={event.id} className={`${event.color} text white text sm p 1 rounded truncate`} > {event.title} </div> ))} </div> </div> ))} </div> ); }; const WeekView = () => { const start = startOfWeek(currentDate); const end = endOfWeek(currentDate); const days = eachDayOfInterval({ start, end }); return ( <div className="grid grid cols 7 gap 1"> {days.map((day) => ( <div key={day.toString()} className="border border gray 200"> <div className="p 2 text center font semibold bg gray 50"> {format(day, "EEE d")} </div> <div className="min h [600px] p 2 space y 2"> {filteredEvents .filter((event) => isSameDay(parseISO(event.start), day)) .map((event) => ( <div key={event.id} className={`${event.color} text white p 2 rounded cursor pointer`} draggable > <div className="font medium">{event.title}</div> <div className="text sm"> {format(parseISO(event.start), "HH:mm")} {" "} {format(parseISO(event.end), "HH:mm")} </div> </div> ))} </div> </div> ))} </div> ); }; const DayView = () => { const hours = Array.from({ length: 24 }, (_, i) => i); return ( <div className="border border gray 200"> <div className="p 2 text center font semibold bg gray 50"> {format(currentDate, "EEEE, MMMM d")} </div> <div className="relative min h [600px]"> {hours.map((hour) => ( <div key={hour} className="h [60px] border b border gray 200 relative" onClick={() => { setNewEvent({ ...newEvent, start: `${format(currentDate, "yyyy MM dd")}T${String(hour).padStart(2, "0")}:00`, end: `${format(currentDate, "yyyy MM dd")}T${String(hour + 1).padStart(2, "0")}:00`, }); setShowEventModal(true); }} > <div className="absolute left 16 top 0 w 12 text right pr 2 text sm text gray 500"> {String(hour).padStart(2, "0")}:00 </div> </div> ))} {filteredEvents .filter((event) => isSameDay(parseISO(event.start), currentDate)) .map((event) => { const start = parseISO(event.start); const end = parseISO(event.end); const startHour = start.getHours() + start.getMinutes() / 60; const duration = (end.getHours() + end.getMinutes() / 60) startHour; return ( <div key={event.id} className={`absolute left 0 right 0 mx 2 ${event.color} text white p 2 rounded`} style={{ top: `${startHour * 60}px`, height: `${duration * 60}px`, }} > <div className="font medium">{event.title}</div> <div className="text sm"> {format(start, "HH:mm")} {format(end, "HH:mm")} </div> </div> ); })} </div> </div> ); }; return ( <div className="max w full mx auto p 4 flex"> <aside className="w 64 mr 6 flex shrink 0"> <div className="bg white rounded lg shadow p 4 mb 4"> <div className="mb 4"> <div className="flex items center mb 2"> <FiSearch className="w 5 h 5 text gray 400 mr 2" /> <input type="text" placeholder="Search events..." className="w full px 2 py 1 border border gray 300 rounded" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} /> </div> </div> <div> <h3 className="font semibold mb 2 flex items center"> <FiTag className="mr 2" /> Tags </h3> <div className="space y 2"> {tags.map(tag => ( <label key={tag} className="flex items center"> <input type="checkbox" checked={selectedTags.includes(tag)} onChange={(e) => { if (e.target.checked) { setSelectedTags([...selectedTags, tag]); } else { setSelectedTags(selectedTags.filter(t => t !== tag)); } }} className="mr 2" /> {tag} </label> ))} </div> <div className="mt 4"> <div className="flex items center"> <input type="text" placeholder="Add new tag" className="flex 1 px 2 py 1 border border gray 300 rounded l" value={newTag} onChange={(e) => setNewTag(e.target.value)} /> <button onClick={handleAddTag} className="px 3 py 1 bg blue 500 text white rounded r hover:bg blue 600" > Add </button> </div> </div> </div> </div> </aside> <div className="flex 1"> <div className="mb 4 flex items center justify between"> <div className="flex items center space x 4"> <button className="p 2 hover:bg gray 100 rounded full" onClick={handlePrevious} > <FiChevronLeft className="w 5 h 5" /> </button> <h2 className="text xl font semibold"> {format(currentDate, "MMMM yyyy")} </h2> <button className="p 2 hover:bg gray 100 rounded full" onClick={handleNext} > <FiChevronRight className="w 5 h 5" /> </button> </div> <div className="flex items center space x 2"> {views.map((view) => ( <button key={view} className={`px 4 py 2 rounded lg ${selectedView === view ? "bg blue 500 text white" : "bg gray 100"}`} onClick={() => setSelectedView(view)} > {view.charAt(0).toUpperCase() + view.slice(1)} </button> ))} <button className="p 2 bg blue 500 text white rounded full" onClick={() => setShowEventModal(true)} > <FiPlus className="w 5 h 5" /> </button> </div> </div> <div className="bg white rounded lg shadow overflow hidden"> {selectedView === "month" && <MonthView />} {selectedView === "week" && <WeekView />} {selectedView === "day" && <DayView />} </div> </div> {showEventModal && ( <div className="fixed inset 0 bg black bg opacity 50 flex items center justify center"> <div className="bg white rounded lg p 6 w full max w md"> <div className="flex items center justify between mb 4"> <h3 className="text lg font semibold">Add Event</h3> <button className="p 2 hover:bg gray 100 rounded full" onClick={() => setShowEventModal(false)} > <FiX className="w 5 h 5" /> </button> </div> <div className="space y 4"> <div> <label className="block text sm font medium text gray 700 mb 1"> Title </label> <input type="text" className="w full px 3 py 2 border border gray 300 rounded md" value={newEvent.title} onChange={(e) => setNewEvent({ ...newEvent, title: e.target.value }) } /> </div> <div> <label className="block text sm font medium text gray 700 mb 1"> Start Time </label> <input type="datetime local" className="w full px 3 py 2 border border gray 300 rounded md" value={newEvent.start} onChange={(e) => setNewEvent({ ...newEvent, start: e.target.value }) } /> </div> <div> <label className="block text sm font medium text gray 700 mb 1"> End Time </label> <input type="datetime local" className="w full px 3 py 2 border border gray 300 rounded md" value={newEvent.end} onChange={(e) => setNewEvent({ ...newEvent, end: e.target.value }) } /> </div> <div> <label className="block text sm font medium text gray 700 mb 1"> Tags </label> <div className="space y 2"> {tags.map(tag => ( <label key={tag} className="flex items center"> <input type="checkbox" checked={newEvent.tags.includes(tag)} onChange={(e) => { if (e.target.checked) { setNewEvent({ ...newEvent, tags: [...newEvent.tags, tag] }); } else { setNewEvent({ ...newEvent, tags: newEvent.tags.filter(t => t !== tag) }); } }} className="mr 2" /> {tag} </label> ))} </div> </div> <button className="w full py 2 bg blue 500 text white rounded md hover:bg blue 600" onClick={handleAddEvent} > Add Event </button> </div> </div> </div> )} </div> ); }; export default Agenda;

Prompt
Component Preview

About

Create and manage your events effortlessly with our React-based agenda component styled with Tailwind CSS. Includes month, week, and day views, plus tag filtering and event creation features.

Share

Last updated 1 month ago