diff --git a/frontend/src/components/Utils/time.js b/frontend/src/components/Utils/time.js index fe354c26..8140ea53 100644 --- a/frontend/src/components/Utils/time.js +++ b/frontend/src/components/Utils/time.js @@ -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); @@ -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); @@ -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', diff --git a/frontend/src/components/Utils/time.test.js b/frontend/src/components/Utils/time.test.js index 73098453..b02a9102 100644 --- a/frontend/src/components/Utils/time.test.js +++ b/frontend/src/components/Utils/time.test.js @@ -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); diff --git a/frontend/src/components/ViewActivities/activity.js b/frontend/src/components/ViewActivities/activity.js index 0aee66e3..862102ec 100644 --- a/frontend/src/components/ViewActivities/activity.js +++ b/frontend/src/components/ViewActivities/activity.js @@ -2,11 +2,13 @@ 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) @@ -14,13 +16,14 @@ function timezonePicker() { } /** - * 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); @@ -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; + } + 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); + } } /** @@ -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. @@ -61,42 +94,43 @@ class Activity extends React.Component { } else { // Edit mode. return ( // TODO: Save form. (#48) -
- + + Title: - + - + From: - - + + {timezonePicker()} - + To: - - + + {timezonePicker()} - + Description: - + - + ) } } - /** @inheritdoc */ + /** @override */ render() { const activity = this.props.activity; return ( - + {activity[DB.ACTIVITIES_TITLE]} diff --git a/frontend/src/components/ViewActivities/activityday.js b/frontend/src/components/ViewActivities/activityday.js index e4ca85cb..58b221ca 100644 --- a/frontend/src/components/ViewActivities/activityday.js +++ b/frontend/src/components/ViewActivities/activityday.js @@ -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 (

{time.timestampToDateFormatted(date.getTime())}

{sortedActivities.map((activity, index) => ( - + ))}
); diff --git a/frontend/src/components/ViewActivities/activityfns.js b/frontend/src/components/ViewActivities/activityfns.js index ba75365e..c77c6282 100644 --- a/frontend/src/components/ViewActivities/activityfns.js +++ b/frontend/src/components/ViewActivities/activityfns.js @@ -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: + *
 ['MM/DD/YYYY', [activities on that day]] 
+ * @typedef {Array.} 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 + *
[ , ...]
+ * 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]); @@ -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 a and b 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} -1 if a comes before b, else 1. */ export function compareActivities(a, b) { if (a[DB.ACTIVITIES_START_TIME] < b[DB.ACTIVITIES_START_TIME]) { @@ -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 {*} activity[fieldName] if possible, else defaultValue. */ 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 {fieldName: newValue} form + * @return {boolean} true if the write was successful, false otherwise. + */ +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) + .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; + } } \ No newline at end of file diff --git a/frontend/src/components/ViewActivities/activityfns.test.js b/frontend/src/components/ViewActivities/activityfns.test.js index 8cd69333..99d82ade 100644 --- a/frontend/src/components/ViewActivities/activityfns.test.js +++ b/frontend/src/components/ViewActivities/activityfns.test.js @@ -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); }) \ No newline at end of file diff --git a/frontend/src/components/ViewActivities/activitylist.js b/frontend/src/components/ViewActivities/activitylist.js index fd34e098..068c0312 100644 --- a/frontend/src/components/ViewActivities/activitylist.js +++ b/frontend/src/components/ViewActivities/activitylist.js @@ -7,23 +7,25 @@ import app from '../Firebase'; const db = app.firestore(); - /** * Gets the list of activities from the server. * * @param {string} tripId The trip ID. + * @return {ActivityInfo[]} The list of trip activities. */ export async function getActivityList(tripId) { - let tripActivities = []; - - return db.collection(DB.COLLECTION_TRIPS).doc(tripId) - .collection(DB.COLLECTION_ACTIVITIES).get() - .then(querySnapshot => { - querySnapshot.forEach(doc => { - let data = doc.data(); - data['id'] = doc.id; - - // TODO: if start date != end date, split into 2 days. (#37) + let tripActivities = []; + + return db.collection(DB.COLLECTION_TRIPS).doc(tripId) + .collection(DB.COLLECTION_ACTIVITIES).get() + .then(querySnapshot => { + querySnapshot.forEach(doc => { + console.log(doc.data()); + let data = doc.data(); + data['id'] = doc.id; + data['tripId'] = tripId; + + // TODO: if start date != end date, split into 2 days. (#37) // Eliminate nanoseconds, convert to milliseconds. data[DB.ACTIVITIES_START_TIME] = @@ -32,28 +34,26 @@ export async function getActivityList(tripId) { data[DB.ACTIVITIES_END_TIME]['seconds'] * 1000; tripActivities.push(data); - }) - }).catch(error => { - console.log("It seems that an error has occured."); - tripActivities = null; - }).then( () => {return tripActivities} ); + }); + return tripActivities; + }); } /** - * The list of activities. + * React component for the list of activities. * - * @param {Object} props This component expects the following props: - * - `tripId` {string} The trip's ID. + * @param {Object} props ReactJS props. + * @param {string} props.tripId The trip's ID. */ class ActivityList extends React.Component { - /** @inheritdoc */ + /** @override */ constructor(props) { super(props); this.state = { days : [] }; } /** - * @inheritdoc + * @override * * Get sorted list of activities from the database. * @@ -71,9 +71,10 @@ class ActivityList extends React.Component { return; } this.setState({days: activityFns.sortByDate(tripActivities)}); + console.log(this.state.days); } - /** @inheritdoc */ + /** @override */ render() { if (this.state === null) { return (
); } if (this.state.days === null) {