SU-SWS/stanford_profile_helper

View on GitHub
modules/stanford_events/pdb_components/mini-calendar/src/index.js

Summary

Maintainability
B
6 hrs
Test Coverage
import ReactDOM from 'react-dom';
import React, {useState, useEffect} from 'react';
import {Popover} from "@mui/material";
import Calendar from 'react-calendar';
import qs from 'qs';
import axios from 'axios';
import NavigationButton from "./components/NavigationButton";
import EventCard from "./components/EventCard";
import './styles.scss';

ReactDOM.render(
  <React.StrictMode>
    <App/>
  </React.StrictMode>,
  document.getElementById('event-mini-cal')
);

function App() {
  const apiUrl = '/jsonapi/node/stanford_event';
  const currentDate = new Date();

  const [anchorEl, setAnchorEl] = useState(null);
  const [chosenDate, setChosenDate] = useState('');
  const [events, setEvents] = useState([]);
  const [fetchedUrls, setFetchedUrls] = useState([]);
  const [minDate, setMinDate] = useState(currentDate);
  const [maxDate, setMaxDate] = useState(currentDate);

  useEffect(() => {
    fetchMonthEvents(currentDate.getMonth(), currentDate.getFullYear());

    const params = {
      filter: {status: 1},
      sort: {date: {path: 'su_event_date_time.value', direction: 'ASC'}},
      page: {limit: 1},
    };

    const minUrl = apiUrl + '?' + qs.stringify(params, {encodeValuesOnly: true});
    params.sort.date.direction = 'DESC';
    const maxUrl = apiUrl + '?' + qs.stringify(params, {encodeValuesOnly: true});

    axios.get(minUrl).then(res => {
      if (res.data.data.length >= 1) {
        const min = new Date(res.data.data[0].attributes.su_event_date_time.value)
        // Set the minimum date to the first of the month for the given event.
        min.setDate(1);
        setMinDate(min);
      }
    })
    axios.get(maxUrl).then(res => {
      if (res.data.data.length >= 1) {
        setMaxDate(new Date(res.data.data[0].attributes.su_event_date_time.value));
      }
    })
  }, []);

  /**
   * Call the JSON API and get all the events between the start and end dates.
   *
   * @param url
   *   API URL.
   * @param startDate
   *   Start timestamp for filtering.
   * @param endDate
   *   End timestamp for filtering.
   * @returns {Promise<void>}
   */
  async function getEvents(url, startDate, endDate) {
    const filters = {status: 1};
    if (startDate !== undefined) {
      filters.startDate = {
        condition: {
          path: 'su_event_date_time.value',
          operator: '>=',
          value: startDate
        }
      }
    }
    if (endDate !== undefined) {
      filters.endDate = {
        condition: {
          path: 'su_event_date_time.value',
          operator: '<',
          value: endDate
        }
      }
    }

    // Append the query string to the url.
    if (url.indexOf('?') === -1) {
      url += '?' + qs.stringify({
        filter: filters,
        sort: 'su_event_date_time.value',
      }, {encodeValuesOnly: true});
    }

    // We've already fetched this url, we don't want to fetch it again.
    if (fetchedUrls.indexOf(url) >= 0) {
      return;
    }
    // Make sure we don't fetch this url again in the future.
    setFetchedUrls(previousFetched => [...previousFetched, url]);

    // Fetch the url data, set the state and follow any paginated urls.
    await axios.get(url).then(res => {
      // Set the events state after sorting by the start date & time.
      setEvents(events => [...events, ...res.data.data.map(item => item.attributes)]);

      // Follow the api data to gather all the events possible.
      if (typeof res.data.links.next === 'object') {
        getEvents(decodeURI(res.data.links.next.href));
      }
    })
  }

  /**
   * Fetch events from the api for the given month and year.
   * @param month
   *   Month of the year.
   * @param year
   *   Four digit year.
   */
  const fetchMonthEvents = (month, year) => {
    const firstDay = new Date(year, month, 1).getTime() / 1000;
    const lastDay = new Date(year, month + 1, 1).getTime() / 1000;
    getEvents(apiUrl, firstDay, lastDay);
  }

  /**
   * Grab all events that occur on the given date string.
   *
   * @param date
   *   Date string.
   *
   * @returns array
   *   Array of events.
   */
  const getEventsForDate = (date) => {
    const givenDate = new Date(date);
    return events.filter((event) => {
      const eventDate = new Date(event.su_event_date_time.value);
      // Compare only the date, not the time.
      return givenDate.toLocaleDateString() === eventDate.toLocaleDateString();
    });
  }

  return (
    <div className="events-mini-calender">
      <Calendar
        minDetail="month"
        minDate={minDate}
        maxDate={maxDate}
        nextLabel={<NavigationButton/>}
        next2Label={<NavigationButton double/>}
        prevLabel={<NavigationButton previous/>}
        prev2Label={<NavigationButton previous double/>}
        navigationLabel={({label}) => <span aria-live="polite">{label}</span>}
        tileDisabled={({date, view}) => view === 'month' && !getEventsForDate(date).length}
        onActiveStartDateChange={({activeStartDate})=> fetchMonthEvents(activeStartDate.getMonth(), activeStartDate.getFullYear())}
        onClickDay={(value, event) => {
          setAnchorEl(event.currentTarget);
          setChosenDate(value.toLocaleDateString())
        }}
        tileContent={({date}) => {
          return date.toLocaleDateString() === currentDate.toLocaleDateString() ?
            <span className="visually-hidden"> (Current day)</span> : null
        }}
      />
      <Popover
        className="calender-popover"
        open={Boolean(anchorEl)}
        anchorEl={anchorEl}
        onClose={() => setAnchorEl(null)}
        anchorOrigin={{
          vertical: 'bottom',
          horizontal: 'left',
        }}
        PaperProps={{role: 'dialog', 'aria-label': 'List of Events'}}
      >
        <button
          className="far fa-window-close close-button"
          onClick={() => setAnchorEl(null)}
        >
          <span className="visually-hidden">Close</span>
        </button>

        <ul className="popover-list">
          {getEventsForDate(chosenDate).map(event =>
            <EventCard key={event.drupal_internal__nid} {...event}/>
          )}
        </ul>
      </Popover>
    </div>
  );
}