Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions frontend/src/components/Utils/time.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as firebase from 'firebase/app';

/**
* Format a timestamp (in milliseconds) into a pretty string with just the time.
* Format a timestamp (in milliseconds) into a pretty string with just the time like
* '10.19 AM'.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like '10:19 AM'.
* @param {int} msTimestamp Timestamp in milliseconds of desired date.
* @param {string} timezone Timezone in which to convert.
* @returns {string} Time formatted into desired pretty string.
*/
export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_York') {
const date = new Date(msTimestamp);
Expand All @@ -18,11 +19,12 @@ export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_Yo
}

/**
* Format a timestamp (in milliseconds) into a pretty string with just the date.
* Format a timestamp (in milliseconds) into a pretty string with just the date, like
* 'Monday, January 19, 1970'.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like 'Monday, January 19, 1970'.
* @param {int} msTimestamp Timestamp in milliseconds of desired date.
* @param {string} timezone Timezone in which to convert.
* @returns {string} Time formatted into desired pretty string.
*/
export function timestampToDateFormatted(msTimestamp, timezone='America/New_York') {
const date = new Date(msTimestamp);
Expand All @@ -36,15 +38,15 @@ export function timestampToDateFormatted(msTimestamp, timezone='America/New_York
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"
/**
* Format a timestamp (in milliseconds) into a pretty string like
* 'Monday, January 19, 1970, 02:48 AM'.
*
* @param {int} msTimestamp Timestamp in milliseconds of desired date.
* @param {string} timezone Timezone in which to convert.
* @returns {string} Time formatted into desired pretty string.
*/
export function timestampToFormatted(msTimestamp, timezone = "America/New_York") {
export function timestampToFormatted(msTimestamp, timezone = 'America/New_York') {
let date = new Date(msTimestamp);
let formatOptions = {
weekday: 'long',
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/Utils/time.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ test('other time timestamp format', () => {
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 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 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);
Expand Down
80 changes: 57 additions & 23 deletions frontend/src/components/ViewActivities/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ import React from 'react';
import * as time from '../Utils/time.js';
import * as DB from '../../constants/database.js'
import '../../styles/activities.css';
import { getField } from './activityfns.js';
import { getField, writeActivity } from './activityfns.js';
import { Accordion, Button, Card, Col, Form, Row } from 'react-bootstrap';

/**
* Returns a dropdown of all the timezones.
* Return a dropdown of all the timezones.
*
* @return {HTML} Dropdown of all the timezones.
*/
function timezonePicker() {
// TODO: Make this dropdown. (#51)
return <div></div>
}

/**
* A single activity.
* React component for a single activity.
*
* @param {Object} props This component expects the following props:
* - `activity` The activity to display.
* @property {Object} props ReactJS props.
* @property {ActivityInfo} props.activity The activity to display.
* (MUST contain 'id' field with database activity id and 'tripId' field.)
*/
class Activity extends React.Component {
/** {@inheritdoc} */
/** @override */
constructor(props) {
super(props);

Expand All @@ -30,6 +33,32 @@ class Activity extends React.Component {
this.setEditActivity = this.setEditActivity.bind(this);
this.finishEditActivity = this.finishEditActivity.bind(this);
this.displayCard = this.displayCard.bind(this);
this.editActivity = this.editActivity.bind(this);

// References.
this.editTitleRef = React.createRef();
this.editStartDateRef = React.createRef();
this.editEndDateRef = React.createRef();
this.editStartTimeRef = React.createRef();
this.editEndTimeRef = React.createRef();
this.editDescriptionRef = React.createRef();
}

/**
* Edit an activity in the database upon form submission.
* TODO: Update times as well! This only does the text field forms (#64).
*/
editActivity() {
let newVals = {};
if (this.editTitleRef.current.value !== '') {
newVals[DB.ACTIVITIES_TITLE] = this.editTitleRef.current.value;
Comment thread
anan-ya-y marked this conversation as resolved.
}
if (this.editDescriptionRef.current.value !== '') {
newVals[DB.ACTIVITIES_DESCRIPTION] = this.editDescriptionRef.current.value;
}
if (Object.keys(newVals).length !== 0) {
writeActivity(this.props.activity.tripId, this.props.activity.id, newVals);
}
}

/**
Expand All @@ -41,10 +70,14 @@ class Activity extends React.Component {

/**
* Set the activity into viewing mode.
*
* @param {event} event The form's event.
*/
finishEditActivity() {
finishEditActivity(event) {
this.setState({editing: false});
}
event.preventDefault();
this.editActivity();
};

/**
* Display the current activity, either in view or display mode.
Expand All @@ -61,42 +94,43 @@ class Activity extends React.Component {
} else { // Edit mode.
return (
// TODO: Save form. (#48)
<Form className="activity-editor" onSubmit={this.finishEditActivity}>
<Form.Group as={Row} controlId="formActivityTitle">
<Form className='activity-editor' onSubmit={this.finishEditActivity}>
<Form.Group as={Row} controlId='formActivityTitle'>
<Col sm={2}><Form.Label>Title:</Form.Label></Col>
<Col><Form.Control type="text" placeholder={activity[DB.ACTIVITIES_TITLE]}/></Col>
<Col><Form.Control type='text' placeholder={activity[DB.ACTIVITIES_TITLE]} ref={this.editTitleRef}/></Col>
</Form.Group>
<Form.Group as={Row} controlId="formActivityStartTime">
<Form.Group as={Row} controlId='formActivityStartTime'>
<Col sm={2}><Form.Label>From:</Form.Label></Col>
<Col sm={4}><Form.Control type="date" label="date"/></Col>
<Col sm={2}><Form.Control type="time" label="time"/></Col>
<Col sm={4}><Form.Control type='date' label='date' ref={this.editStartDateRef}/></Col>
<Col sm={2}><Form.Control type='time' label='time' ref={this.editStartTimeRef}/></Col>
<Col sm={1}>{timezonePicker()}</Col>
</Form.Group>
<Form.Group as={Row} controlId="formActivityEndTime">
<Form.Group as={Row} controlId='formActivityEndTime'>
<Col sm={2}><Form.Label>To:</Form.Label></Col>
<Col sm={4}><Form.Control type="date" label="date"/></Col>
<Col sm={2}><Form.Control type="time" label="time"/></Col>
<Col sm={4}><Form.Control type='date' label='date' ref={this.editEndDateRef}/></Col>
<Col sm={2}><Form.Control type='time' label='time' ref={this.editEndTimeRef}/></Col>
<Col sm={1}>{timezonePicker()}</Col>
</Form.Group>
<Form.Group as={Row} controlId="formActivityDescription">
<Form.Group as={Row} controlId='formActivityDescription'>
<Col sm={2}><Form.Label>Description:</Form.Label></Col>
<Col><Form.Control type="text"
placeholder={getField(activity, DB.ACTIVITIES_DESCRIPTION, "Add some details!") }/>
<Col><Form.Control type='text'
placeholder={getField(activity, DB.ACTIVITIES_DESCRIPTION, 'Add some details!')}
ref={this.editDescriptionRef} />
</Col>
</Form.Group>
<Button type="submit" className="float-right">Done!</Button>
<Button type='submit' className='float-right'>Done!</Button>
</Form>
)
}
}

/** @inheritdoc */
/** @override */
render() {
const activity = this.props.activity;
return (
<Accordion defaultActiveKey='1'>
<Card>
<Accordion.Toggle as={Card.Header} eventKey='0' align='center' >
<Accordion.Toggle as={Card.Header} eventKey='0' align='center'>
{activity[DB.ACTIVITIES_TITLE]}
</Accordion.Toggle>
<Accordion.Collapse eventKey='0' className={'view-activity' + (this.state.editing? ' edit': '')}>
Expand Down
15 changes: 7 additions & 8 deletions frontend/src/components/ViewActivities/activityday.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@ 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".
* React component for a single day of activities.
*
* @property {Object} props ReactJS props.
* @property {ActivityInfo[]} props.activities The list of activities for 'today'.
* @property {string} props.date The date, formatted as 'MM/DD/YYYY'.
*/
class ActivityDay extends React.Component {
/** @inheritdoc */
/** @override */
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"/>
<Activity activity={activity} key={index} className='activity'/>
))}
</div>
);
Expand Down
78 changes: 68 additions & 10 deletions frontend/src/components/ViewActivities/activityfns.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
import * as DB from '../../constants/database.js';
import app from '../Firebase';
import { firestore } from 'firebase';

const db = app.firestore();

/**
* An activity object.
* @typedef {Object} ActivityInfo
* @property {string} id The activity's ID in the database.
* @property {string} tripId The activity's tripId in the database.
* @property {string} title The activity's title.
* @property {long} start_time Number of seconds since epoch of activity's start time.
* @property {long} end_time Number of seconds since epoch of activity's end time.
* @property {string} [description] The activity's description.
*/

/**
* A single activity day. A single instance looks like:
* <pre><code> ['MM/DD/YYYY', [activities on that day]] </code></pre>
* @typedef {Array.<string, ActivityInfo[]>} DayOfActivities
*
*/

/**
* 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.
* @param {ActivityInfo[]} tripActivities Array of activities.
* @return {DayOfActivities[]} List of trip activities in the form
* <pre><code>[ , ...]</code></pre>
* in chronological order by date.
*/
export function sortByDate(tripActivities) {
if (tripActivities === undefined) {
return null;
}
console.log(tripActivities);
let activities = new Map(); // { MM/DD/YYYY: [activities] }.
for (let activity of tripActivities) {
const activityDate = new Date(activity[DB.ACTIVITIES_START_TIME]);
Expand All @@ -19,15 +46,18 @@ export function sortByDate(tripActivities) {
}

// Sort activities by date.
console.log(activities);
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.
* Put <code>a</code> and <code>b</code> in display order.
* This function is a comparator.
* @param {ActivityInfo} a Dictionary representing activity a and its fields.
* @param {ActivityInfo} b Dictionary representing activity b and its fields.
* @return {int} <code>-1</code> if <code>a</code> comes before <code>b</code>, else <code>1</code>.
*/
export function compareActivities(a, b) {
if (a[DB.ACTIVITIES_START_TIME] < b[DB.ACTIVITIES_START_TIME]) {
Expand All @@ -44,14 +74,42 @@ export function compareActivities(a, b) {
/**
* Get the field of field name `fieldName` from `activity` or the default value.
*
* @param {Object} activity
* @param {string} fieldName
* @param defaultValue
* @returns `activity[fieldName]` if possible, else `defaultValue`.
* @param {ActivityInfo} activity The activity from which to get the field.
* @param {string} fieldName Name of field to get.
* @param {*} defaultValue Value if field is not found/is null.
* @return {*} <code>activity[fieldName]</code> if possible, else <code>defaultValue</code>.
*/
export function getField(activity, fieldName, defaultValue) {
if (activity[fieldName] === null || activity[fieldName] === undefined) {
return defaultValue;
}
return activity[fieldName];
}

/**
* Write contents into an activity already existing in the database.
*
* @param {string} tripId Database ID of the trip whose actiivty should be modified.
* @param {string} activityId Database ID of the activity to be modified.
* @param {Object} newValues Dictionary of the new values in <code>{fieldName: newValue}</code> form
* @return {boolean} <code>true</code> if the write was successful, <code>false</code> otherwise.
*/
Comment thread
anan-ya-y marked this conversation as resolved.
export async function writeActivity(tripId, activityId, newValues) {
// todo: check if tripId or activityId is not valid. (#58)
newValues = {
...newValues,
'fillerstamp': firestore.Timestamp.now()
};

const act = db.collection(DB.COLLECTION_TRIPS).doc(tripId)
Comment thread
anan-ya-y marked this conversation as resolved.
.collection(DB.COLLECTION_ACTIVITIES).doc(activityId);

try {
// Note: newValues cannot contain lists. Check documentation for update().
await act.update(newValues);
return true;
} catch (e) {
console.log(e);
return false;
Comment thread
anan-ya-y marked this conversation as resolved.
}
}
6 changes: 3 additions & 3 deletions frontend/src/components/ViewActivities/activityfns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('sortByDate tests', () => {
})

test('getField', () => {
const activity = {field1: "yes"};
expect(activityFns.getField(activity, "field1", "nooo")).toBe("yes");
expect(activityFns.getField(activity, "field2", 4)).toBe(4);
const activity = {field1: 'yes'};
expect(activityFns.getField(activity, 'field1', 'nooo')).toBe('yes');
expect(activityFns.getField(activity, 'field2', 4)).toBe(4);
})
Loading