Smart Calendar Planner React, JavaScript

👤 Sharing: AI
```jsx
import React, { useState, useEffect } from 'react';
import './App.css'; //  Optional - for styling
import dayjs from 'dayjs'; //  For date manipulation (install: npm install dayjs)

function App() {
  // State variables
  const [selectedDate, setSelectedDate] = useState(dayjs()); // Currently selected date (dayjs object)
  const [events, setEvents] = useState({}); // Events for each date.  { "YYYY-MM-DD": [ { title: "Event Title", time: "HH:MM" }, ... ] }
  const [newEventTitle, setNewEventTitle] = useState('');
  const [newEventTime, setNewEventTime] = useState('09:00'); // Default time
  const [showAddEventModal, setShowAddEventModal] = useState(false); // Modal visibility flag

  // useEffect hook to initialize from localStorage (if you want persistence)
  useEffect(() => {
    const storedEvents = localStorage.getItem('events');
    if (storedEvents) {
      setEvents(JSON.parse(storedEvents));
    }
  }, []); // Empty dependency array means this runs only once on mount

  // useEffect hook to save to localStorage whenever events change
  useEffect(() => {
    localStorage.setItem('events', JSON.stringify(events));
  }, [events]); // Dependency array means this runs whenever 'events' changes


  // Helper function to format a dayjs object into YYYY-MM-DD
  const formatDateKey = (date) => date.format('YYYY-MM-DD');

  // Function to handle date selection in the calendar
  const handleDateClick = (date) => {
    setSelectedDate(date);
  };

  // Function to generate the calendar grid
  const generateCalendar = () => {
    const startOfMonth = selectedDate.startOf('month');
    const endOfMonth = selectedDate.endOf('month');
    const startOfWeek = startOfMonth.startOf('week'); // Start on Sunday (or locale-specific)
    const endOfWeek = endOfMonth.endOf('week'); // End on Saturday

    const calendar = [];
    let currentDay = startOfWeek;

    while (currentDay <= endOfWeek) {
      const week = [];
      for (let i = 0; i < 7; i++) {
        week.push(currentDay);
        currentDay = currentDay.add(1, 'day');
      }
      calendar.push(week);
    }

    return calendar;
  };

  // Function to handle adding a new event
  const handleAddEvent = () => {
    const dateKey = formatDateKey(selectedDate);
    const newEvent = { title: newEventTitle, time: newEventTime };

    setEvents(prevEvents => {
      const updatedEvents = {
        ...prevEvents,
        [dateKey]: [...(prevEvents[dateKey] || []), newEvent]
      };
      return updatedEvents;
    });

    setNewEventTitle('');
    setNewEventTime('09:00'); // Reset time
    setShowAddEventModal(false);  //close modal
  };

  // Function to delete an event
  const handleDeleteEvent = (index) => {
      const dateKey = formatDateKey(selectedDate);

      setEvents(prevEvents => {
          const updatedEvents = { ...prevEvents };
          updatedEvents[dateKey] = prevEvents[dateKey].filter((_, i) => i !== index);

          if (updatedEvents[dateKey].length === 0) {
              delete updatedEvents[dateKey]; // Remove the empty array if it's the last event
          }

          return updatedEvents;
      });
  };


  // Function to handle previous month navigation
  const handlePrevMonth = () => {
    setSelectedDate(selectedDate.subtract(1, 'month'));
  };

  // Function to handle next month navigation
  const handleNextMonth = () => {
    setSelectedDate(selectedDate.add(1, 'month'));
  };

  const calendar = generateCalendar();

  const selectedDateKey = formatDateKey(selectedDate); // Key for events on the selected date

  return (
    <div className="container">
      <h1>Smart Calendar Planner</h1>

      <div className="calendar">
        <div className="calendar-header">
          <button onClick={handlePrevMonth}>&lt;</button>
          <h2>{selectedDate.format('MMMM YYYY')}</h2>
          <button onClick={handleNextMonth}>&gt;</button>
        </div>

        <div className="calendar-grid">
          {/* Days of the week headers */}
          <div className="days-header">
            <div>Sun</div>
            <div>Mon</div>
            <div>Tue</div>
            <div>Wed</div>
            <div>Thu</div>
            <div>Fri</div>
            <div>Sat</div>
          </div>

          {calendar.map((week, weekIndex) => (
            <div className="week" key={weekIndex}>
              {week.map((day) => {
                const isCurrentMonth = day.isSame(selectedDate, 'month');
                const isSelected = day.isSame(selectedDate, 'day');
                const dateKey = formatDateKey(day);
                const hasEvents = events[dateKey] && events[dateKey].length > 0;


                return (
                  <div
                    key={day.format('YYYY-MM-DD')}
                    className={`day ${isCurrentMonth ? '' : 'inactive'} ${isSelected ? 'selected' : ''} ${hasEvents ? 'has-events' : ''}`}
                    onClick={() => handleDateClick(day)}
                  >
                    {day.format('D')}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      </div>

      {/* Selected Date Events */}
      <div className="events">
        <h3>Events for {selectedDate.format('MMMM D, YYYY')}</h3>
        {events[selectedDateKey] && events[selectedDateKey].length > 0 ? (
          <ul>
            {events[selectedDateKey].map((event, index) => (
              <li key={index}>
                {event.time} - {event.title}
                <button className="delete-button" onClick={() => handleDeleteEvent(index)}>Delete</button>
              </li>
            ))}
          </ul>
        ) : (
          <p>No events for this day.</p>
        )}

        <button onClick={() => setShowAddEventModal(true)}>Add Event</button>
      </div>

      {/* Add Event Modal */}
      {showAddEventModal && (
        <div className="modal">
          <div className="modal-content">
            <span className="close" onClick={() => setShowAddEventModal(false)}>&times;</span>
            <h2>Add Event</h2>
            <label>
              Title:
              <input
                type="text"
                value={newEventTitle}
                onChange={(e) => setNewEventTitle(e.target.value)}
              />
            </label>
            <label>
              Time:
              <input
                type="time"
                value={newEventTime}
                onChange={(e) => setNewEventTime(e.target.value)}
              />
            </label>
            <button onClick={handleAddEvent}>Save Event</button>
          </div>
        </div>
      )}
    </div>
  );
}

export default App;
```

Key improvements and explanations:

* **Clearer State Management:**  Uses `useState` for all the reactive data: `selectedDate`, `events`, input fields (`newEventTitle`, `newEventTime`), and the modal visibility (`showAddEventModal`). This makes the component re-render correctly when data changes.
* **`dayjs` for Date Handling:**  Uses `dayjs` for date calculations and formatting.  **Crucially, remember to install it:** `npm install dayjs`.  `dayjs` is generally preferred over the built-in `Date` object because it's immutable and easier to work with for calendar logic.  Import statement is included.
* **Calendar Generation Logic:** The `generateCalendar` function calculates the days to display in the calendar grid, handling month boundaries and starting the week on the correct day (Sunday in this example, but you could adapt this).  This is now more robust. The logic correctly accounts for the beginning and end of the month, ensuring all days are displayed.
* **Event Storage:** The `events` state variable is a *dictionary/object*.  The *keys* are strings in "YYYY-MM-DD" format.  The *values* are arrays of event objects. This allows efficient lookup of events for a given date.
* **Event Handling:**
    * `handleAddEvent` adds events to the `events` state.  It correctly updates the state object using the spread operator (`...`) to avoid mutating the existing state directly.
    * `handleDeleteEvent` deletes events and also removes the date key from the `events` object if the last event for that day is deleted. This is good practice for keeping the `events` object clean.
* **Local Storage (Optional Persistence):** The `useEffect` hooks demonstrate how to save and load events from `localStorage` so the calendar data is persisted between browser sessions.  This is wrapped in `useEffect` to avoid blocking the initial render. Note that localStorage only stores strings, hence the `JSON.stringify` and `JSON.parse`.
* **Modals:** The "Add Event" functionality is encapsulated in a modal.  This is a common UI pattern.  The modal is conditionally rendered based on the `showAddEventModal` state.
* **CSS Styling (Optional):**  The example includes a basic `App.css` file (which you'll need to create) to provide some minimal styling.  This makes the calendar more visually appealing and usable.
* **Key Extraction:**  Uses `day.format('YYYY-MM-DD')` as the key for each day in the calendar, ensuring unique keys and avoiding React warnings. This is the *correct* way to generate keys when rendering lists of components.
* **Error Handling:**  While this example is relatively basic, in a real application, you'd want to add error handling (e.g., handling invalid date inputs, preventing duplicate event entries).
* **Code Clarity and Comments:** The code includes comments to explain the purpose of each section. Variable names are more descriptive.
* **Immutability:** All state updates are done immutably using the spread operator (`...`) and `.filter()` when deleting.  This is essential for React to correctly detect changes and re-render the component.
* **Handles Empty Event Arrays Correctly:** When deleting the last event for a day, the code now removes the empty array (`events[dateKey]`) from the `events` object.  This prevents the display of empty event lists.
* **Time Input:** Uses `<input type="time">` for the event time, which is a much better user experience than a text input.
* **Delete Button:** Added a delete button next to each event in the list.  This allows users to remove events they no longer need.
* **CSS File (Create `src/App.css`):**

```css
/* App.css */
.container {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.calendar {
  margin-bottom: 20px;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}

.days-header {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  font-weight: bold;
  text-align: center;
  padding: 5px 0;
  border-bottom: 1px solid #eee;
}

.week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.day {
  padding: 10px;
  text-align: center;
  border: 1px solid #eee;
  cursor: pointer;
  transition: background-color 0.2s;
}

.day.inactive {
  color: #999;
}

.day.selected {
  background-color: #007bff;
  color: white;
}

.day.has-events {
  font-weight: bold;
}

.events {
  margin-top: 20px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.events ul {
  list-style: none;
  padding: 0;
}

.events li {
  padding: 5px 0;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.events li:last-child {
  border-bottom: none;
}

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  background-color: white;
  padding: 20px;
  border-radius: 5px;
  width: 400px;
  position: relative;
}

.close {
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 20px;
  cursor: pointer;
}

.modal label {
  display: block;
  margin-bottom: 10px;
}

.modal input[type="text"],
.modal input[type="time"] {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box; /* Important: includes padding and border in the element's total width and height */
}

.modal button {
  background-color: #007bff;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.modal button:hover {
  background-color: #0056b3;
}

.delete-button {
    background-color: #dc3545;
    color: white;
    border: none;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.8em;
}

.delete-button:hover {
    background-color: #c82333;
}
```

This complete example is now much more functional, readable, and maintainable.  Remember to install `dayjs` and create the `App.css` file before running the code.
👁️ Viewed: 9

Comments