radgrad/radgrad2

View on GitHub
archive/page-tracking/page-tracking/PageTrackingScoreboardWidget.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import React, { useState } from 'react';
import _ from 'lodash';
import { Grid, Table, Header, Button } from 'semantic-ui-react';
import DatePicker from 'react-datepicker';
import { useRouteMatch } from 'react-router-dom';
import moment from 'moment';
import { PageInterestsDailySnapshots } from '../../../../api/page-tracking/PageInterestsDailySnapshotCollection';
import { PageInterestsDailySnapshot } from '../../../../typings/radgrad';
import { aggregateDailySnapshots, getCategory, getUrlCategory, AggregatedDailySnapshot, parseName } from './utilities/page-tracking';
import { IPageInterestsCategoryTypes } from '../../../../api/page-tracking/PageInterestsCategoryTypes';
import PageTrackingWidgetMessage from './PageTrackingWidgetMessage';
import RadGradAlert from '../../../app/imports/ui/utilities/RadGradAlert';

interface PageTrackingScoreboardWidgetProps {
  pageInterestsDailySnapshots: PageInterestsDailySnapshot[];
}

const PageTrackingScoreboardWidget: React.FC<PageTrackingScoreboardWidgetProps> = ({ pageInterestsDailySnapshots }) => {
  const match = useRouteMatch();
  const urlCategory: IPageInterestsCategoryTypes = getUrlCategory(match);
  // See page-tracking-general.ts to see urlCategory vs category
  const category = getCategory(urlCategory);

  const aggregatedDailySnapshot: AggregatedDailySnapshot = aggregateDailySnapshots(pageInterestsDailySnapshots);

  /* ######################### Table State ######################### */
  const [data, setData] = useState<AggregatedDailySnapshot>(aggregatedDailySnapshot);
  const [dataBeforeFilter] = useState<AggregatedDailySnapshot>(data);
  const [column, setColumn] = useState<'name' | 'views'>(undefined);
  const [direction, setDirection] = useState<'ascending' | 'descending'>(undefined);
  /* ######################### Date Picker State ######################### */
  const [startDate, setStartDate] = useState<Date>(undefined);
  const [endDate, setEndDate] = useState<Date>(undefined);

  /* ######################### Styles ######################### */
  const tableStyle: React.CSSProperties = { maxHeight: '400px', overflowY: 'auto' };
  const marginBottomStyle: React.CSSProperties = { marginBottom: '5px' };

  /* ######################### Event Handlers ######################### */
  const handleSort = (e, clickedColumn) => {
    if (column !== clickedColumn) {
      setColumn(clickedColumn);
      const newData: AggregatedDailySnapshot = { ...data, [category]: _.sortBy(data[category], [clickedColumn]) };
      setData(newData);
      setDirection('ascending');
      return;
    }

    // Create new data by first creating a slice of data[category] so we do not mutate state directly,
    // and then reverse that slice. Then rebuild a new data object by combining old data with the new reversed data.
    const newData: AggregatedDailySnapshot = { ...data, [category]: data[category].slice().reverse() };
    setData(newData);
    setDirection(direction === 'ascending' ? 'descending' : 'ascending');
  };

  const handleFilter = () => {
    if (startDate === undefined || endDate === undefined) {
      RadGradAlert.failure('Date Selection Required', 'A Start and End Date selection is required.');
      return;
    }
    const filteredDailySnapshots: PageInterestsDailySnapshot[] = PageInterestsDailySnapshots.find({
      timestamp: {
        $gte: startDate,
        $lte: moment(endDate).endOf('day').toDate(),
      },
    }).fetch();
    const filteredAggregatedDailySnapshots: AggregatedDailySnapshot = aggregateDailySnapshots(filteredDailySnapshots);
    // Handle sort to main sort properties (ascending/descending for a clicked column) when we filter data
    if (column !== undefined) {
      const newData: AggregatedDailySnapshot = {
        ...filteredAggregatedDailySnapshots,
        [category]: _.sortBy(filteredAggregatedDailySnapshots[category], [column]),
      };
      setData(newData);
    } else {
      setData(filteredAggregatedDailySnapshots);
    }
  };

  const handleClear = () => {
    setStartDate(undefined);
    setEndDate(undefined);
    setData(dataBeforeFilter);
  };

  return (
    <Grid columns={2}>
      {/* Table View */}
      <Grid.Column width={11}>
        <div style={tableStyle}>
          <Table celled striped sortable>
            <Table.Header>
              <Table.Row>
                <Table.HeaderCell sorted={column === 'name' ? direction : undefined} onClick={(e) => handleSort(e, 'name')}>
                  Name
                </Table.HeaderCell>
                <Table.HeaderCell sorted={column === 'views' ? direction : undefined} onClick={(e) => handleSort(e, 'views')}>
                  Page Views
                </Table.HeaderCell>
              </Table.Row>
            </Table.Header>
            <Table.Body>
              {data[category].length > 0 ? (
                <React.Fragment>
                  {data[category].map((snapshot) => (
                    <Table.Row key={`${category}-${snapshot.name}:${snapshot.views}`}>
                      <Table.Cell width={10}>{parseName(urlCategory, snapshot.name)}</Table.Cell>
                      <Table.Cell width={6}>{snapshot.views}</Table.Cell>
                    </Table.Row>
                  ))}
                </React.Fragment>
              ) : undefined}
            </Table.Body>
          </Table>
        </div>
        <PageTrackingWidgetMessage />
      </Grid.Column>

      {/* Date Filter */}
      <Grid.Column width={5}>
        <Header>FILTER BY DATE</Header>
        <Grid.Row style={marginBottomStyle}>
          <Button size="mini" onClick={handleFilter}>
            Filter
          </Button>
          <Button size="mini" onClick={handleClear}>
            Clear
          </Button>
        </Grid.Row>
        <Grid.Row>
          <DatePicker selectsStart showMonthDropdown showYearDropdown onChange={(date) => setStartDate(date)} placeholderText="Start Date" selected={startDate} startDate={startDate} endDate={endDate} maxDate={endDate || new Date()} />
          <DatePicker selectsEnd showMonthDropdown showYearDropdown onChange={(date) => setEndDate(date)} placeholderText="End Date" selected={endDate} startDate={startDate} endDate={endDate} minDate={startDate} maxDate={new Date()} />
        </Grid.Row>
      </Grid.Column>
    </Grid>
  );
};

export default PageTrackingScoreboardWidget;