Skip to content

Commit

Permalink
Merge pull request #46 from googleinterns/act-viewer-restructure
Browse files Browse the repository at this point in the history
Restructure activity viewer to see activities better
  • Loading branch information
anan-ya-y authored Jul 16, 2020
2 parents 1780870 + 8d3d4af commit 14a09d0
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 5 deletions.
57 changes: 57 additions & 0 deletions frontend/src/components/Utils/time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Format a timestamp (in milliseconds) into a pretty string with just the time.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like '10:19 AM'.
*/
export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_York') {
const date = new Date(msTimestamp);
const formatOptions = {
hour: 'numeric',
minute: '2-digit',
timeZone: timezone
};
return date.toLocaleTimeString('en-US', formatOptions);;
}

/**
* Format a timestamp (in milliseconds) into a pretty string with just the date.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like 'Monday, January 19, 1970'.
*/
export function timestampToDateFormatted(msTimestamp, timezone='America/New_York') {
const date = new Date(msTimestamp);
const formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: timezone
};
return date.toLocaleDateString('en-US', formatOptions);
}

/**
* Format a timestamp (in milliseconds) into a pretty string.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like
* "Monday, January 19, 1970, 02:48 AM"
*/
export function timestampToFormatted(msTimestamp, timezone = "America/New_York") {
let date = new Date(msTimestamp);
let formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZone: timezone
};
return date.toLocaleString('en-US', formatOptions);
}
58 changes: 58 additions & 0 deletions frontend/src/components/Utils/time.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as utils from './time';

const TZ_CHICAGO = 'America/Chicago';
const TZ_SINGAPORE = 'Asia/Singapore';

test('new york date timestamp format', () => {
// Month parameter is zero indexed so it's actually the 10th month.
const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime();
const expected = 'Saturday, October 3, 2020';
const actual = utils.timestampToDateFormatted(testDate);
expect(actual).toEqual(expected);
});

test('other date timestamp format', () => {
const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime();
const expectedCentral = 'Saturday, August 22, 2020';
const expectedSingapore = 'Sunday, August 23, 2020';
const actualCentral = utils.timestampToDateFormatted(testDate, TZ_CHICAGO);
const actualSingapore = utils.timestampToDateFormatted(testDate, TZ_SINGAPORE);
expect(actualCentral).toEqual(expectedCentral);
expect(actualSingapore).toEqual(expectedSingapore);
})

test('new york time timestamp format', () => {
// Month parameter is zero indexed so it's actually the 10th month.
const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime();
const expected = '10:19 AM';
const actual = utils.timestampToTimeFormatted(testDate);
expect(actual).toEqual(expected);
});

test('other time timestamp format', () => {
const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime();
const expectedCentral = '9:03 PM';
const expectedSingapore = '10:03 AM';
const actualCentral = utils.timestampToTimeFormatted(testDate, TZ_CHICAGO);
const actualSingapore = utils.timestampToTimeFormatted(testDate, TZ_SINGAPORE);
expect(actualCentral).toEqual(expectedCentral);
expect(actualSingapore).toEqual(expectedSingapore);
})

test('new york full timestamp format', () => {
// Month parameter is zero indexed so it's actually the 10th month.
const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime();
const expected = "Saturday, October 3, 2020, 10:19 AM";
const actual = utils.timestampToFormatted(testDate);
expect(actual).toEqual(expected);
});

test('other full timestamp format', () => {
const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime();
const expectedCentral = "Saturday, August 22, 2020, 9:03 PM";
const expectedSingapore = "Sunday, August 23, 2020, 10:03 AM";
const actualCentral = utils.timestampToFormatted(testDate, TZ_CHICAGO);
const actualSingapore = utils.timestampToFormatted(testDate, TZ_SINGAPORE);
expect(actualCentral).toEqual(expectedCentral);
expect(actualSingapore).toEqual(expectedSingapore);
})
36 changes: 36 additions & 0 deletions frontend/src/components/ViewActivities/activity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import Card from 'react-bootstrap/Card';
import * as time from '../Utils/time.js';
import * as DB from '../../constants/database.js'
import '../../styles/activities.css';
import Accordion from 'react-bootstrap/Accordion';

/**
* A single activity.
*
* @param {Object} props This component expects the following props:
* - `activity` The activity to display.
*/
class Activity extends React.Component {
/** @inheritdoc */
render() {
const activity = this.props.activity;
return (
<Accordion defaultActiveKey='1'>
<Card>
<Accordion.Toggle as={Card.Header} eventKey='0' align='center' >
{activity[DB.ACTIVITIES_TITLE]}
</Accordion.Toggle>
<Accordion.Collapse eventKey='0'>
<Card.Body>
<p>Start time: {time.timestampToFormatted(activity[DB.ACTIVITIES_START_TIME])} </p>
<p>End time: {time.timestampToFormatted(activity[DB.ACTIVITIES_END_TIME])} </p>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
);
}
};

export default Activity;
31 changes: 31 additions & 0 deletions frontend/src/components/ViewActivities/activityday.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import Activity from './activity.js';
import * as activityFns from './activityfns.js';
import * as time from '../Utils/time.js'

/**
* One single day of activities.
*
* @param {Object} props This component expects the following props:
* - `activities` The list of activities for "today".
* - `date` The date, formatted as "MM/DD/YYYY".
*/
class ActivityDay extends React.Component {
/** @inheritdoc */
render() {
const sortedActivities = Array.from(this.props.activities)
.sort(activityFns.compareActivities);
let date = new Date(this.props.date);
let id = date.getTime();
return (
<div className='activity-day'>
<h4>{time.timestampToDateFormatted(date.getTime())}</h4>
{sortedActivities.map((activity, index) => (
<Activity activity={activity} key={index} className="activity"/>
))}
</div>
);
}
}

export default ActivityDay;
41 changes: 41 additions & 0 deletions frontend/src/components/ViewActivities/activityfns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as DB from '../../constants/database.js';

/**
* Sort a list of trip activities by date.
* @param {Array} tripActivities Array of activities.
* @returns List of trip activities in the form
* [ ['MM/DD/YYYY', [activities on that day]], ...] in chronological order by date.
*/
export function sortByDate(tripActivities) {
let activities = new Map(); // { MM/DD/YYYY: [activities] }.
for (let activity of tripActivities) {
const activityDate = new Date(activity[DB.ACTIVITIES_START_TIME]);
const dateKey = activityDate.toLocaleDateString()
if (activities.has(dateKey)) {
activities.get(dateKey).add(activity);
} else {
activities.set(dateKey, new Set([activity]));
}
}

// Sort activities by date.
let activitiesSorted = Array.from(activities).sort(compareActivities);

return activitiesSorted;
}

/**
* Put a and b in display order.
* @param {dictionary} a Dictionary representing activity a and its fields.
* @param {dictionary} b Dictionary representing activity b and its fields.
*/
export function compareActivities(a, b) {
if (a[DB.ACTIVITIES_START_TIME] < b[DB.ACTIVITIES_START_TIME]) {
return -1;
} else if (a[DB.ACTIVITIES_START_TIME] > b[DB.ACTIVITIES_START_TIME]) {
return 1;
} else if (a[DB.ACTIVITIES_END_TIME] > b[DB.ACTIVITIES_END_TIME]) {
return 1;
}
return -1;
}
93 changes: 93 additions & 0 deletions frontend/src/components/ViewActivities/activityfns.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as activityFns from './activityfns.js';
import { getActivityList } from './activitylist.js';

const ten = new Date(Date.UTC(2020, 4, 2, 10, 0)); // May 2, 2020 10:00
const eleven = new Date(Date.UTC(2020, 4, 2, 11, 0)); // May 2, 2020 11:00
const elevenThirty = new Date(Date.UTC(2020, 4, 2, 11, 30)); // May 2, 2020 11:30
const twelve = new Date(Date.UTC(2020, 4, 2, 12, 0)); // May 2, 2020 12:00
const one = new Date(Date.UTC(2020, 4, 2, 13, 0)); // May 2, 2020 13:00
const may102pm = new Date(Date.UTC(2020, 4, 10, 14, 0)); // May 10, 2020 14:00
const may014pm = new Date(Date.UTC(2020, 4, 1, 16, 0)); // May 1, 2020 16:00
const may153am = new Date(Date.UTC(2020, 4, 15, 3, 0)); // May 15, 2020 3:00

function createActivity(startTime, endTime){
return {'start_time': startTime, 'end_time': endTime};
}

describe('Same day activity compareActivities', () => {
const tenToTwelve = createActivity(ten, twelve);
const elevenToOne = createActivity(eleven, one);
const elevenToElevenThirty = createActivity(eleven, elevenThirty);
const tenToEleven = createActivity(ten, eleven);
const elevenToTwelve = createActivity(eleven, twelve);

test('Overlapping activities', () => {
expect(activityFns.compareActivities(elevenToOne, tenToTwelve)).toBe(1);
})

test('One activity completely during another', () => {
expect(activityFns.compareActivities(tenToTwelve, elevenToElevenThirty)).toBe(-1);
})

test('Activities with same start time', () => {
expect(activityFns.compareActivities(tenToEleven, tenToTwelve)).toBe(-1);
})

test('Sequential activities', () => {
expect(activityFns.compareActivities(tenToEleven, elevenToTwelve)).toBe(-1);
})

test('Activities with same end time', () => {
expect(activityFns.compareActivities(elevenToTwelve, tenToTwelve)).toBe(1);
})
})

test('compareActivities on different days', () => {
const may10 = createActivity(may102pm, may102pm);
const may15 = createActivity(may153am, may153am);
const may01 = createActivity(may014pm, may014pm);
expect(activityFns.compareActivities(may10, may01)).toBe(1);
expect(activityFns.compareActivities(may15, may01)).toBe(1);
})

describe('sortByDate tests', () => {
const act1 = createActivity(ten, eleven);
const act2 = createActivity(elevenThirty, twelve);
const act3 = createActivity(twelve, one);
const act4 = createActivity(may102pm, may102pm);
const act5 = createActivity(may153am, may153am);

test('sortByDate all same date', () => {
const tripActivities = [act1, act2, act3];

let expected = new Map();
expected.set(ten.toLocaleDateString(), new Set([act1, act2, act3]));
expected = Array.from(expected);

expect(activityFns.sortByDate(tripActivities)).toEqual(expected);
})

test('sortByDate all differentDates', () => {
const tripActivities = [act3, act4, act5];

let expected = new Map();
expected.set(ten.toLocaleDateString(), new Set([act3]));
expected.set(may102pm.toLocaleDateString(), new Set([act4]));
expected.set(may153am.toLocaleDateString(), new Set([act5]));
expected = Array.from(expected);

expect(activityFns.sortByDate(tripActivities)).toEqual(expected);
})

test('sortByDate mixed dates', () => {
const tripActivities = [act3, act4, act1, act5, act2];

let expected = new Map();
expected.set(ten.toLocaleDateString(), new Set([act3, act1, act2]));
expected.set(may102pm.toLocaleDateString(), new Set([act4]));
expected.set(may153am.toLocaleDateString(), new Set([act5]));
expected = Array.from(expected);

expect(activityFns.sortByDate(tripActivities)).toEqual(expected);
})
})
Loading

0 comments on commit 14a09d0

Please sign in to comment.