-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from googleinterns/act-viewer-restructure
Restructure activity viewer to see activities better
- Loading branch information
Showing
10 changed files
with
442 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
93
frontend/src/components/ViewActivities/activityfns.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}) | ||
}) |
Oops, something went wrong.