Unverified Commit 8391d710 authored by Zhou (Link)  Fang's avatar Zhou (Link) Fang Committed by GitHub
Browse files

Improve past events display - #6 (#199)


Signed-off-by: Yi Liu's avatarYi Liu <yi.liu@eclipse-foundation.org>
Co-authored-by: Yi Liu's avatarYi Liu <yi.liu@eclipse-foundation.org>
parent 67488be1
import 'promise-polyfill/src/polyfill';
import 'unfetch/polyfill';
import 'abortcontroller-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import EventsDataFetcher from './components/EventsDataFetcher';
import Wrapper from './components/Wrapper';
const App = () => (
<div className="container">
<h1>Upcoming Events</h1>
<EventsDataFetcher />
<h1>Events</h1>
<Wrapper />
</div>
);
......
import React from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const CustomSearch = ({ setSearchValue }) => {
const CustomSearch = ({ setTriggerSearchValue }) => {
const [searchValue, setSearchValue] = useState("")
const handleSearchOnChange = (e) => {
setSearchValue(e.target.value)
}
useEffect(() => {
if (searchValue.length > 2) {
setTriggerSearchValue(searchValue)
}
if (searchValue.length === 0) {
setTriggerSearchValue(searchValue)
}
})
return (
<div className="inner-addon left-addon">
<i className="fa fa-search"></i>
......@@ -21,7 +32,7 @@ const CustomSearch = ({ setSearchValue }) => {
}
CustomSearch.propTypes = {
setSearchValue: PropTypes.func.isRequired,
setTriggerSearchValue: PropTypes.func.isRequired,
}
......
......@@ -50,5 +50,4 @@ EventDetails.propTypes = {
event: PropTypes.object.isRequired,
}
export default EventCard
\ No newline at end of file
......@@ -98,10 +98,6 @@ export const EVENT_TYPES = [
id: "ee",
name: "Other interesting Events"
},
{
id: "unknown",
name: "Unknown"
},
]
export const EVENT_ATTENDANCE_TYPE = [
......@@ -119,35 +115,6 @@ export const EVENT_ATTENDANCE_TYPE = [
}
]
export function checkEventWorkingGroups(events, filter) {
for (let i=0; i<events.length; i++) {
if (events[i].publish_to.includes(filter.id)) { // as long as find one event has the working group
return true
}
}
}
export function checkEventTypes(events, filter) {
for (let i=0; i<events.length; i++) {
if (events[i].type == filter.id) { // as long as find one event is of the type
return true
}
}
}
export function checkFilterHasEvents(filters, filterType, events) {
switch(filterType) {
case "WORKINGGROUPS":
return filters.filter(el => checkEventWorkingGroups(events, el))
case "EVENTTYPE":
return filters.filter(el => checkEventTypes(events, el))
default:
return
}
}
export function getSelectedItems(checkedItems) {
let selected = []
if (checkedItems) {
......@@ -160,37 +127,6 @@ export function getSelectedItems(checkedItems) {
}
}
export function getEventsByWorkingGroups(checkedItems, events) {
let checked = getSelectedItems(checkedItems)
if (checked && checked.length > 0) {
let result = events.filter( el => checked.some(item => el.publish_to.includes(item)) )
return result
} else return events
}
export function getEventsByType(checkedItems, events) {
let checked = getSelectedItems(checkedItems)
if (checked && checked.length > 0) {
let result = events.filter( el => checked.includes(el.type) )
return result
} else return events
}
export function getSearchedEvents(events, searchValue) {
if (searchValue && searchValue != '') {
let result = events.filter(el => el.title.toLowerCase().includes(searchValue.toLowerCase()))
return result
} else {
return events
}
}
export function getFilteredEvents(events, searchValue, checkedWorkingGroups, checkedTypes) {
let selectedByWorkingGroups = getEventsByWorkingGroups(checkedWorkingGroups, events)
let selectedByTypes = getEventsByType(checkedTypes, selectedByWorkingGroups)
return getSearchedEvents(selectedByTypes, searchValue)
}
export function hasAddress(event) {
if (event.address && (event.address.city || event.address.country)) {
return true
......@@ -235,6 +171,14 @@ export function generateTimes(startDate, endDate, locale = []) {
}
}
export function checkDatePast(inputDate) {
var today = new Date();
var input_date = new Date(inputDate);
if (today < input_date) {
return false;
} else return true
}
export function alphaOrder(array) {
if (array) {
......@@ -242,3 +186,25 @@ export function alphaOrder(array) {
}
}
export function hasSelectedItems(items) {
let selectedItems = getSelectedItems(items)
if (selectedItems && selectedItems.length > 0) {
return selectedItems
} else return false
}
export function getUrl(page, searchParas, groupParas, typeParas) {
let url = `https://newsroom.eclipse.org/api/events?&page=${page}&pagesize=10&options[orderby][field_event_date]=custom`
for (let i=0; i<groupParas.length; i++) {
url = url + "&parameters[publish_to][]=" + groupParas[i]
}
for (let j=0; j<typeParas.length; j++) {
url = url + "&parameters[type][]=" + typeParas[j]
}
if (searchParas) {
url = url + "&parameters[search]=" + searchParas
}
return url
}
import React, { useState } from 'react';
import EventCard from './EventCard';
import CustomSearch from './CustomSearch';
import CheckboxFilters from './CheckboxFilters';
import { getFilteredEvents } from './EventHelpers';
import Legend from './Legend';
import PropTypes from 'prop-types';
const Events = ({ events }) => {
const [searchValue, setSearchValue] = useState('')
const [checkedWorkingGroups, setCheckedWorkingGroups] = useState({})
const [checkedTypes, setCheckedTypes] = useState({})
return (
<div className="container">
<div className="row margin-bottom-20">
<div className="col-md-6">
{/* Filters will be here */}
<CustomSearch searchValue={searchValue} setSearchValue={setSearchValue} />
<CheckboxFilters checkedTypes={checkedTypes} setCheckedTypes={setCheckedTypes} events={events} />
<CheckboxFilters checkedWorkingGroups={checkedWorkingGroups} setCheckedWorkingGroups={setCheckedWorkingGroups} events={events} />
<a className="btn btn-primary" href="https://newsroom.eclipse.org/node/add/events">Submit Your Event</a>
<Legend />
</div>
<div className="col-md-18 event-list-wrapper">
{getFilteredEvents(events, searchValue, checkedWorkingGroups, checkedTypes).map((event) => (
<div className="col-md-12 max-min-width" key={event.id}>
<EventCard event={event} />
</div>
))}
</div>
</div>
</div>
)
}
Events.propTypes = {
events: PropTypes.array.isRequired,
}
export default Events;
import React, { useState, useEffect } from 'react';
import Events from './Events';
import Loading from './Loading';
import { alertTypes } from './AlertsEnum';
import Alerts from './Alerts';
// Testing purpose, to be deleted
// To test, modify the events in the file, and pass testEventData into <Events events={testEventData} />
// import { testEventData } from './TestEventsData';
const EventsDataFetcher = () => {
// Based off https://reactjs.org/docs/faq-ajax.html
const [error, setError] = useState(null)
const [isLoaded, setIsLoaded] = useState(false)
const [events, setEvents] = useState([])
// Note: the empty deps array [] means
// this useEffect will run once
// similar to componentDidMount()
useEffect(() => {
fetch('https://newsroom.eclipse.org/api/events?parameters[upcoming_only]=1&&options[orderby][field_event_date]=ASC')
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true)
setEvents(result.events)
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true)
setError(error)
}
)
}, [])
if (error) {
return (
<Alerts alertType={alertTypes.ERROR} message={error.message} />
)
} else if (!isLoaded) {
return <Loading />
} else {
return (
<Events events={events} />
)
}
}
export default EventsDataFetcher
\ No newline at end of file
......@@ -2,7 +2,7 @@ import React from 'react';
const Loading = () => {
return (
<div><i className="fa fa-spinner fa-spin fa-3x fa-fw"></i></div>
<div style={{marginLeft: "auto", marginRight: "auto"}}><i className="fa fa-spinner fa-spin fa-3x fa-fw"></i></div>
)
}
......
import React, { useState } from 'react';
import CustomSearch from './CustomSearch';
import EventsCheckboxFilters from './pastAndUpcomingEvents/EventsCheckboxFilters';
import EventsDataFetcher from './pastAndUpcomingEvents/EventsDataFetcher';
import Legend from "./Legend";
const Wrapper = () => {
const [triggerSearchValue, setTriggerSearchValue] = useState("")
const [checkedWorkingGroups, setCheckedWorkingGroups] = useState({})
const [checkedTypes, setCheckedTypes] = useState({})
const [upcomingReachEnd, setUpcomingReachEnd] = useState(false)
return (
<>
<div className="container">
<div className="row margin-bottom-20">
<div className="col-md-6">
<CustomSearch triggerSearchValue={triggerSearchValue} setTriggerSearchValue={setTriggerSearchValue} />
<EventsCheckboxFilters
checkedTypes={checkedTypes}
setCheckedTypes={setCheckedTypes}
/>
<EventsCheckboxFilters
checkedWorkingGroups={checkedWorkingGroups}
setCheckedWorkingGroups={setCheckedWorkingGroups}
/>
<a className="btn btn-primary" href="https://newsroom.eclipse.org/node/add/events">Submit Your Event</a>
<Legend />
</div>
<div className="col-md-18 event-list-wrapper">
<EventsDataFetcher
eventTime="upcoming"
searchValue={triggerSearchValue}
checkedWorkingGroups={checkedWorkingGroups}
checkedTypes={checkedTypes}
reachEnd={upcomingReachEnd}
setReachEnd={setUpcomingReachEnd}
/>
</div>
</div>
</div>
</>
)
}
export default Wrapper
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import EventCard from '../EventCard';
import Loading from '../Loading';
import PropTypes from 'prop-types';
import { checkDatePast } from '../EventHelpers';
const EventLists = ({ events, isFetchingMore, fetchMore, reachEnd }) => {
if (events.length === 0 && !isFetchingMore) {
return (
<p style={{ marginLeft: 'auto', marginRight: 'auto' }}>
We have reached the end of results for the current filters
</p>
);
}
const [upcomingEvents, setUpcomingEvents] = useState([]);
const [pastEvents, setPastEvents] = useState([]);
const [noMoreUpcoming, setNoMoreUpcoming] = useState(false);
const filterEvents = () => {
// get past event list
const thePastEvents = events.filter((eventItem) =>
checkDatePast(eventItem['end-date'])
);
setPastEvents(thePastEvents);
// get upcoming event list
const theUpcomingEvents = events.filter(
(eventItem) => !checkDatePast(eventItem['end-date'])
);
setUpcomingEvents(theUpcomingEvents);
};
const checkNoMoreUpcoming = () => {
// if the last event in the list is a past event,
// then there should be no more upcoming events,
// then we will set noMoreUpcoming to true to show the past event list.
const lastEventDate = events[events.length - 1]['end-date'];
const isLastEventPast = checkDatePast(lastEventDate);
if (isLastEventPast) {
setNoMoreUpcoming(true);
}
};
const renderPastEvents = () => {
return pastEvents.map((eventItem) => (
<div className="col-md-12 max-min-width event-past" key={eventItem.id}>
<EventCard event={eventItem} />
</div>
));
};
const renderUpcomingEvents = () => {
if (upcomingEvents.length > 0) {
return upcomingEvents.map((eventItem) => (
<div className="col-md-12 max-min-width" key={eventItem.id}>
<EventCard event={eventItem} />
</div>
));
}
return <p>No upcoming event scheduled for this type(s) for now.</p>;
};
useEffect(() => {
filterEvents();
checkNoMoreUpcoming();
}, [events]);
return (
<>
<div className="upcoming-event-list">
<h2>Upcoming Events</h2>
{renderUpcomingEvents()}
</div>
{noMoreUpcoming ? (
<div className="past-event-list">
<h2>Past Events</h2>
{renderPastEvents()}
</div>
) : null}
<div className="event-load-more">
{reachEnd ? (
<p className="margin-5">
We have reached the end of results for the current filters
</p>
) : isFetchingMore ? (
<Loading />
) : (
!isFetchingMore && (
<button
className="btn btn-primary margin-top-10"
onClick={fetchMore}
>
Load More
</button>
)
)}
</div>
</>
);
};
EventLists.propTypes = {
events: PropTypes.array.isRequired,
isFetchingMore: PropTypes.bool.isRequired,
fetchMore: PropTypes.func.isRequired,
reachEnd: PropTypes.bool.isRequired,
};
export default EventLists;
import React, { useState } from 'react';
import Checkbox from './Checkbox';
import { EVENT_TYPES, WORKING_GROUPS, checkFilterHasEvents, alphaOrder } from './EventHelpers';
import Checkbox from '../Checkbox';
import { EVENT_TYPES, WORKING_GROUPS, alphaOrder } from '../EventHelpers';
import PropTypes from 'prop-types';
const CheckboxFilters = ({ checkedTypes, setCheckedTypes, checkedWorkingGroups, setCheckedWorkingGroups, events }) => {
const EventsCheckboxFilters = ({
checkedTypes,
setCheckedTypes,
checkedWorkingGroups,
setCheckedWorkingGroups
}) => {
const determineInitialState = () => {
return window.innerWidth > 991
......@@ -40,7 +45,6 @@ const CheckboxFilters = ({ checkedTypes, setCheckedTypes, checkedWorkingGroups,
})
}
}
}
const toggleTypes = () => {
......@@ -51,25 +55,25 @@ const CheckboxFilters = ({ checkedTypes, setCheckedTypes, checkedWorkingGroups,
setShowWorkingGroups(!showWorkingGroups)
}
const WorkingGroups = () => {
if (checkedWorkingGroups && setCheckedWorkingGroups) {
function renderFilterComponent(checkedFilter, filterCheckFunc, filterShowingState, filterShowingFunc, filterDataArray, filterTypeName) {
if (checkedFilter && filterCheckFunc) {
return (
<>
<button
onClick={toggleWorkingGroups}
onClick={filterShowingFunc}
className="event-filter-title"
>
CATEGORIES
{ filterTypeName }
<i className="fa fa-angle-down event-filter-expandable-icon" aria-hidden="true"></i>
</button>
{ showWorkingGroups &&
{ filterShowingState &&
<ul className="event-filter-checkbox-list">
{ alphaOrder(checkFilterHasEvents(WORKING_GROUPS, "WORKINGGROUPS", events)).map(item => (
{ filterDataArray.map(item => (
<li key={item.id}>
<label key={item.id}>
<Checkbox
name={item.id}
checked={checkedWorkingGroups[item.id]}
checked={checkedFilter[item.id]}
onChange={handleChange}
/>
{item.name}
......@@ -84,51 +88,20 @@ const CheckboxFilters = ({ checkedTypes, setCheckedTypes, checkedWorkingGroups,
}
const EventTypes = () => {
if (checkedTypes && setCheckedTypes) {
return (
<>
<button
onClick={toggleTypes}
className="event-filter-title"
>
EVENT TYPE
<i className="fa fa-angle-down event-filter-expandable-icon" aria-hidden="true"></i>
</button>
{ showTypes &&
<ul className="event-filter-checkbox-list">
{ alphaOrder(checkFilterHasEvents(EVENT_TYPES, "EVENTTYPE", events)).map(item => (
<li key={item.id}>
<label key={item.id}>
<Checkbox
name={item.id}
checked={checkedTypes[item.id]}
onChange={handleChange}
/>
{item.name}
</label>
</li>
)) }
</ul>
}
</>
)
}
}
return (
<div className="margin-bottom-10">
{WorkingGroups()}
{EventTypes()}
{renderFilterComponent(checkedTypes, setCheckedTypes, showTypes, toggleTypes, alphaOrder(EVENT_TYPES), "EVENT TYPE")}
{renderFilterComponent(checkedWorkingGroups, setCheckedWorkingGroups, showWorkingGroups, toggleWorkingGroups, alphaOrder(WORKING_GROUPS), "CATEGORIES")}
</div>
)
}
CheckboxFilters.propTypes = {
EventsCheckboxFilters.propTypes = {
checkedTypes: PropTypes.object,
setCheckedTypes: PropTypes.func,
checkedWorkingGroups: PropTypes.object,
setCheckedWorkingGroups: PropTypes.func,
setCheckedWorkingGroups: PropTypes.func
}
export default CheckboxFilters
\ No newline at end of file
export default EventsCheckboxFilters
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { alertTypes } from '../AlertsEnum';
import Alerts from '../Alerts';
import EventLists from './EventLists';
import { hasSelectedItems, getUrl } from '../EventHelpers';
import PropTypes from 'prop-types';
import Loading from '../Loading';
const EventsDataFetcher = ({ searchValue, checkedWorkingGroups, checkedTypes, reachEnd, setReachEnd }) => {
const [error, setError] = useState(null)
const [isFetchingMore, setIsFetchingMore] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const [isLoading, setIsLoading] = useState(true)
const [eventsData, setEventsData] = useState([])