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) -
['MM/DD/YYYY', [activities on that day]]
+ * @typedef {Array.[ , ...]
+ * 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) {