diff --git a/web/calendar/calendar.react.js b/web/calendar/calendar.react.js index 94a93e8a4..6ecb31892 100644 --- a/web/calendar/calendar.react.js +++ b/web/calendar/calendar.react.js @@ -1,281 +1,281 @@ // @flow import { faFilter } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import dateFormat from 'dateformat'; import invariant from 'invariant'; -import PropTypes from 'prop-types'; import * as React from 'react'; import { updateCalendarQueryActionTypes, updateCalendarQuery, } from 'lib/actions/entry-actions'; import { currentDaysToEntries } from 'lib/selectors/thread-selectors'; import { - entryInfoPropType, type EntryInfo, type CalendarQuery, type CalendarQueryUpdateResult, type CalendarQueryUpdateStartingPayload, } from 'lib/types/entry-types'; -import type { DispatchActionPromise } from 'lib/utils/action-utils'; +import { + type DispatchActionPromise, + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils'; import { getDate, dateString, startDateForYearAndMonth, endDateForYearAndMonth, } from 'lib/utils/date-utils'; -import { connect } from 'lib/utils/redux-utils'; -import { - type AppState, - type NavInfo, - navInfoPropType, -} from '../redux/redux-setup'; +import { type NavInfo } from '../redux/redux-setup'; +import { useSelector } from '../redux/redux-utils'; import { yearAssertingSelector, monthAssertingSelector, webCalendarQuery, } from '../selectors/nav-selectors'; import { canonicalURLFromReduxState } from '../url-utils'; import css from './calendar.css'; import Day from './day.react'; import FilterPanel from './filter-panel.react'; -type Props = { - setModal: (modal: ?React.Node) => void, - url: string, - // Redux state - year: number, - month: number, // 1-indexed - daysToEntries: { [dayString: string]: EntryInfo[] }, - navInfo: NavInfo, - currentCalendarQuery: () => CalendarQuery, - // Redux dispatch functions - dispatchActionPromise: DispatchActionPromise, - // async functions that hit server APIs - updateCalendarQuery: ( +type BaseProps = {| + +setModal: (modal: ?React.Node) => void, + +url: string, +|}; +type Props = {| + ...BaseProps, + +year: number, + +month: number, + +daysToEntries: { [dayString: string]: EntryInfo[] }, + +navInfo: NavInfo, + +currentCalendarQuery: () => CalendarQuery, + +dispatchActionPromise: DispatchActionPromise, + +updateCalendarQuery: ( calendarQuery: CalendarQuery, reduxAlreadyUpdated?: boolean, ) => Promise, -}; +|}; type State = {| filterPanelOpen: boolean, |}; class Calendar extends React.PureComponent { - static propTypes = { - setModal: PropTypes.func.isRequired, - url: PropTypes.string.isRequired, - year: PropTypes.number.isRequired, - month: PropTypes.number.isRequired, - daysToEntries: PropTypes.objectOf(PropTypes.arrayOf(entryInfoPropType)) - .isRequired, - navInfo: navInfoPropType.isRequired, - currentCalendarQuery: PropTypes.func.isRequired, - dispatchActionPromise: PropTypes.func.isRequired, - updateCalendarQuery: PropTypes.func.isRequired, - }; state: State = { filterPanelOpen: false, }; getDate( dayOfMonth: number, monthInput: ?number = undefined, yearInput: ?number = undefined, ) { return getDate( yearInput ? yearInput : this.props.year, monthInput ? monthInput : this.props.month, dayOfMonth, ); } prevMonthDates() { const { year, month } = this.props; const lastMonthDate = getDate(year, month - 1, 1); const prevYear = lastMonthDate.getFullYear(); const prevMonth = lastMonthDate.getMonth() + 1; return { startDate: startDateForYearAndMonth(prevYear, prevMonth), endDate: endDateForYearAndMonth(prevYear, prevMonth), }; } nextMonthDates() { const { year, month } = this.props; const nextMonthDate = getDate(year, month + 1, 1); const nextYear = nextMonthDate.getFullYear(); const nextMonth = nextMonthDate.getMonth() + 1; return { startDate: startDateForYearAndMonth(nextYear, nextMonth), endDate: endDateForYearAndMonth(nextYear, nextMonth), }; } render() { const { year, month } = this.props; const monthName = dateFormat(getDate(year, month, 1), 'mmmm'); const prevURL = canonicalURLFromReduxState( { ...this.props.navInfo, ...this.prevMonthDates() }, this.props.url, ); const nextURL = canonicalURLFromReduxState( { ...this.props.navInfo, ...this.nextMonthDates() }, this.props.url, ); const lastDayOfMonth = this.getDate(0, this.props.month + 1); const totalDaysInMonth = lastDayOfMonth.getDate(); const firstDayToPrint = 1 - this.getDate(1).getDay(); const lastDayToPrint = totalDaysInMonth + 6 - lastDayOfMonth.getDay(); const rows = []; let columns = []; let week = 1; let tabIndex = 1; for ( let curDayOfMonth = firstDayToPrint; curDayOfMonth <= lastDayToPrint; curDayOfMonth++ ) { if (curDayOfMonth < 1 || curDayOfMonth > totalDaysInMonth) { columns.push(); } else { const dayString = dateString( this.props.year, this.props.month, curDayOfMonth, ); const entries = this.props.daysToEntries[dayString]; invariant( entries, 'the currentDaysToEntries selector should make sure all dayStrings ' + `in the current range have entries, but ${dayString} did not`, ); columns.push( , ); tabIndex += entries.length; } if (columns.length === 7) { rows.push({columns}); columns = []; } } let filterPanel = null; let calendarContentStyle = null; let filterButtonStyle = null; if (this.state.filterPanelOpen) { filterPanel = ; calendarContentStyle = { marginLeft: '300px' }; filterButtonStyle = { backgroundColor: 'rgba(0,0,0,0.67)' }; } return (
{filterPanel}
Filters

<
{' '} {monthName} {year}{' '}
>

{rows}
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
); } toggleFilters = (event: SyntheticEvent) => { event.preventDefault(); this.setState({ filterPanelOpen: !this.state.filterPanelOpen }); }; onClickPrevURL = (event: SyntheticEvent) => { event.preventDefault(); const currentCalendarQuery = this.props.currentCalendarQuery(); const newCalendarQuery = { ...currentCalendarQuery, ...this.prevMonthDates(), }; this.props.dispatchActionPromise( updateCalendarQueryActionTypes, this.props.updateCalendarQuery(newCalendarQuery, true), undefined, ({ calendarQuery: newCalendarQuery }: CalendarQueryUpdateStartingPayload), ); }; onClickNextURL = (event: SyntheticEvent) => { event.preventDefault(); const currentCalendarQuery = this.props.currentCalendarQuery(); const newCalendarQuery = { ...currentCalendarQuery, ...this.nextMonthDates(), }; this.props.dispatchActionPromise( updateCalendarQueryActionTypes, this.props.updateCalendarQuery(newCalendarQuery, true), undefined, ({ calendarQuery: newCalendarQuery }: CalendarQueryUpdateStartingPayload), ); }; } -export default connect( - (state: AppState) => ({ - year: yearAssertingSelector(state), - month: monthAssertingSelector(state), - daysToEntries: currentDaysToEntries(state), - navInfo: state.navInfo, - currentCalendarQuery: webCalendarQuery(state), - }), - { updateCalendarQuery }, -)(Calendar); +export default React.memo(function ConnectedCalendar( + props: BaseProps, +) { + const year = useSelector(yearAssertingSelector); + const month = useSelector(monthAssertingSelector); + const daysToEntries = useSelector(currentDaysToEntries); + const navInfo = useSelector((state) => state.navInfo); + const currentCalendarQuery = useSelector(webCalendarQuery); + const callUpdateCalendarQuery = useServerCall(updateCalendarQuery); + const dispatchActionPromise = useDispatchActionPromise(); + + return ( + + ); +});