diff --git a/src/components/FeedPortal/components/NewsFeed.jsx b/src/components/FeedPortal/components/NewsFeed.jsx
index 6ae0494a..22ed6682 100644
--- a/src/components/FeedPortal/components/NewsFeed.jsx
+++ b/src/components/FeedPortal/components/NewsFeed.jsx
@@ -14,28 +14,28 @@ const NewsFeed = ({ type, loading, data }) => {
${project ? `${project.team_name.toUpperCase()}` : "ALL"} NEWS
`;
-
const renderNewsfeedItems = items => items.map(
item => {
return FeedItemContainer({
component: NewsfeedItems[item.type],
item,
key: item.id,
- })
+ });
}
);
- const renderStandups = standups => standups.map(
- standup => (
- standup.submitted_at
- ? FeedItemContainer({
- item: { standup, type: "NewsfeedStandup", user: standup.member, timestamp: standup.submitted_at },
- key: standup.id,
- component: NewsfeedItems.NewsfeedStandup,
- })
- : null
- ),
- );
+ const renderStandups = standups => {
+ return (
+ FeedItemContainer({
+ item: {
+ standups, type: "NewsfeedStandup",
+ user: standups[standups.length-1].member,
+ timestamp: standups[standups.length-1].submitted_at },
+ key: standups[standups.length-1].id,
+ component: NewsfeedItems.NewsfeedStandup,
+ })
+ );
+ };
const renderFeed = ({ user, project }) => {
let dataToRender = (
@@ -91,6 +91,8 @@ const getNewsfeed = (project_id) => {
worked_on
working_on
blocked_on
+ is_expired
+ expiration
member {
id
username
diff --git a/src/components/FeedPortal/components/NewsfeedStandup.jsx b/src/components/FeedPortal/components/NewsfeedStandup.jsx
index 799ff645..e3b3d3ea 100644
--- a/src/components/FeedPortal/components/NewsfeedStandup.jsx
+++ b/src/components/FeedPortal/components/NewsfeedStandup.jsx
@@ -1,57 +1,61 @@
-import * as React from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
+
import "./NewsfeedStandup.css";
+import FeedItemContainer from "./FeedItem";
+import StandupDetail from "./StandupDetail";
+import StandupSummary from "./StandupSummary";
-const sentimentMap = {
- red: 'Trouble Ahead!',
- yellow: 'Nervous',
- green: 'Great!',
-};
-
-const responseLabelMap = {
- progress_sentiment: 'Health Status',
- worked_on: 'Worked on',
- working_on: 'Working on',
- blocked_on: 'Blocked on',
-}
+class NewsfeedStandup extends React.Component {
+
+ static propTypes = {
+ standups: PropTypes.array.isRequired,
+ timestamp: PropTypes.number.isRequired,
+ type: PropTypes.string.isRequired,
+ user: PropTypes.object.isRequired,
+ };
-const classNameSelector = (item, data) => {
- let className = "team-standup-answer";
- if (item === "progress_sentiment") {
- className += ` team-standup-status--${data}`;
+ constructor(props) {
+ super(props);
+
+ const { standups } = props;
+ this.state = {
+ standups: standups,
+ selected_standup: {},
+ };
+
+ this.updateSelectedStandup = this.updateSelectedStandup.bind(this);
}
- return className;
-};
-const renderResponses = standupFields => Object.keys(standupFields).map(
- standupField => {
- const fieldValue = standupFields[standupField];
- const className = classNameSelector(standupField, fieldValue);
+ updateSelectedStandup= (standup) => {
+ this.setState({ selected_standup: { ...standup } });
+ }
+
+ renderResponses = () => {
return (
-
-
{responseLabelMap[standupField]} :
-
- {
- standupField === "progress_sentiment"
- ? sentimentMap[fieldValue]
- : fieldValue
- }
+
+
-
+
);
- },
-);
-
-const NewsfeedStandup = ({
- standup: {
- progress_sentiment,
- worked_on,
- working_on,
- blocked_on,
- },
-}) => (
-
- {renderResponses({ progress_sentiment, worked_on, working_on, blocked_on })}
-
- );
+ };
+
+ render = () => {
+ return (
+
+ { this.renderResponses() }
+
+ );
+ }
+
+}
export default NewsfeedStandup;
diff --git a/src/components/FeedPortal/components/NewsfeedStandup.scss b/src/components/FeedPortal/components/NewsfeedStandup.scss
index 103c55c6..95b9c9f9 100644
--- a/src/components/FeedPortal/components/NewsfeedStandup.scss
+++ b/src/components/FeedPortal/components/NewsfeedStandup.scss
@@ -1,14 +1,30 @@
@import '../../../styles/abstracts/_variables.scss';
-
/*
==========================
CONTAINER
==========================
*/
-.team-standup-card-container {
- background-color: $palest-grey;
- padding: 40px;
+.team-standup-container {
+ display: grid;
+ grid-template-columns: 25% 72%;
+ grid-gap: 1rem;
+}
+
+.team-standup-summary {
+ background-color: white;
+ display: table-cell; // Force height of all children to that of largest child
+ grid-column: 1;
+ grid-row: 1;
+ padding: .25rem;
+}
+
+.team-standup-detail {
+ background-color: white;
+ display: table-cell; // Force height of all children to that of largest child
+ grid-column: 2;
+ grid-row: 1;
+ padding: .25rem;
}
/*
==========================
@@ -24,12 +40,21 @@ data object
.team-standup-label {
grid-area: l;
- border-right: 1px solid $grey;
display: block;
- padding-bottom: 30px;
font-size: 14px;
- color: $med-grey;
- text-transform: uppercase;
+ color: #9e9e9e;
+}
+
+.team-standup-label--bordered {
+ border-right: 1px solid #9e9e9e;
+}
+
+.team-standup-label--padtop {
+ padding-top: 1rem;
+}
+
+.team-standup-label--padded {
+ padding-bottom: 30px;
}
.team-standup-answer {
@@ -39,6 +64,13 @@ data object
color: $dark-grey;
padding-bottom: 30px;
}
+
+.team-standup-id {
+ display: block;
+ font-size: 12px;
+ color: $health-green;
+ padding-left: 0;
+}
/*
==========================
COLORED STATUS
diff --git a/src/components/FeedPortal/components/StandupCompleted.jsx b/src/components/FeedPortal/components/StandupCompleted.jsx
new file mode 100644
index 00000000..8d0515b4
--- /dev/null
+++ b/src/components/FeedPortal/components/StandupCompleted.jsx
@@ -0,0 +1,95 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+
+import "./NewsfeedStandup.css";
+
+const INITIAL_LIST_LIMIT = 3;
+
+class StandupCompleted extends React.Component {
+
+ static propTypes = {
+ sortedStandups: PropTypes.array.isRequired,
+ newStandupSelected: PropTypes.func.isRequired,
+ updateSelectedStandup: PropTypes.func.isRequired,
+ }
+
+ constructor(props) {
+ super(props);
+ this.defaultDisplayListCount = INITIAL_LIST_LIMIT;
+ this.state = {
+ listDisplayLimit: this.defaultDisplayListCount,
+ scrollText: "More..."
+ };
+ }
+
+ filterStandups = (standup, standupIndex, displayCount) => {
+ return standupIndex !== 0 && standup.submitted_at && displayCount <= this.state.listDisplayLimit;
+ }
+
+ renderMore = (sortedStandups) => {
+ const completedStandupCount = sortedStandups.reduce((count, standup, standupIndex) => {
+ if (this.filterStandups(standup, standupIndex, 0)) {
+ count += 1;
+ }
+ return count;
+ }, 0);
+
+ return (
+
+ );
+ }
+
+ render = () => {
+ const { sortedStandups, newStandupSelected, updateSelectedStandup } = this.props;
+ let displayCount = 1;
+ const incrementDisplayCount = () => {
+ displayCount += 1;
+ };
+
+ return (
+
+
+ Completed Standups
+
+ { sortedStandups.length > 0
+ ? sortedStandups.map( (standup, standupIndex) => (
+ this.filterStandups(standup, standupIndex, displayCount)
+ ? {
+ newStandupSelected(e, standup, updateSelectedStandup);
+ } }>
+ { new Date(standup.submitted_at).toLocaleDateString()} - { standup.member.username }
+ { incrementDisplayCount() }
+
+ : null
+ ))
+ : No standups
+ }
+ { this.renderMore(sortedStandups) }
+
+ );
+ };
+
+}
+
+export default StandupCompleted;
diff --git a/src/components/FeedPortal/components/StandupDetail.jsx b/src/components/FeedPortal/components/StandupDetail.jsx
new file mode 100644
index 00000000..7a88aa39
--- /dev/null
+++ b/src/components/FeedPortal/components/StandupDetail.jsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import "./NewsfeedStandup.css";
+
+const sentimentMap = {
+ red: 'Trouble Ahead!',
+ yellow: 'Nervous',
+ green: 'Great!',
+};
+
+const responseLabelMap = {
+ progress_sentiment: 'Health Status',
+ worked_on: 'Worked on',
+ working_on: 'Working on',
+ blocked_on: 'Blocked on',
+};
+
+const classNameSelector = (item, data) => {
+ let className = "team-standup-answer";
+ if (item === "progress_sentiment") {
+ className += ` team-standup-status--${data}`;
+ }
+ return className;
+};
+
+const renderResponses = standupFields => Object.keys(standupFields).map(
+ standupField => {
+ const fieldValue = standupFields[standupField];
+ const className = classNameSelector(standupField, fieldValue);
+ return (
+
+
+
{responseLabelMap[standupField]} :
+
+ {
+ standupField === "progress_sentiment"
+ ? sentimentMap[fieldValue]
+ : fieldValue
+ }
+
+
+
+ );
+ });
+
+const StandupDetail = ({
+ standup: {
+ progress_sentiment,
+ worked_on,
+ working_on,
+ blocked_on,
+ },
+}) => (
+
+ {renderResponses({ progress_sentiment, worked_on, working_on, blocked_on })}
+
+ );
+
+export default StandupDetail;
diff --git a/src/components/FeedPortal/components/StandupPending.jsx b/src/components/FeedPortal/components/StandupPending.jsx
new file mode 100644
index 00000000..577fe0cc
--- /dev/null
+++ b/src/components/FeedPortal/components/StandupPending.jsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+import { Link } from "react-router-dom"
+
+import "./NewsfeedStandup.css";
+
+const StandupPending = ({ sortedStandups }) => {
+ // Extract the most recent uncompleted standup regardless of its expiration
+ const pendingStandup = sortedStandups.reduce( (standups, currentStandup) => {
+ if (!currentStandup.submitted_at) {
+ standups.push(currentStandup)
+ }
+ return standups;
+ }, [])[0];
+ const standupId = `${ new Date(pendingStandup.expiration).toLocaleDateString() } - ${ pendingStandup.member.username }`;
+ return (
+
+
+ Pending Standup
+
+ { !pendingStandup.submitted_at && pendingStandup.expiration > Date.now()
+ ?
+
+ { pendingStandup
+ ? standupId
+ : "No Standup Available" }
+
+
+ : No pending standup
+ }
+
+ );
+
+}
+
+export default StandupPending;
diff --git a/src/components/FeedPortal/components/StandupRecent.jsx b/src/components/FeedPortal/components/StandupRecent.jsx
new file mode 100644
index 00000000..003c9d27
--- /dev/null
+++ b/src/components/FeedPortal/components/StandupRecent.jsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+
+import "./NewsfeedStandup.css";
+
+const StandupRecent = ({ sortedStandups, newStandupSelected, updateSelectedStandup }) => {
+ const mostRecentStandup = sortedStandups[0];
+ return (
+
+
+ Most Recent Standup
+
+ { mostRecentStandup.submitted_at
+ ? {
+ newStandupSelected(e, mostRecentStandup, updateSelectedStandup);
+ } }>
+ { new Date(mostRecentStandup.submitted_at).toLocaleDateString() } - { mostRecentStandup.member.username }
+
+ : No completed standups
+ }
+
+ );
+}
+
+export default StandupRecent;
diff --git a/src/components/FeedPortal/components/StandupSummary.jsx b/src/components/FeedPortal/components/StandupSummary.jsx
new file mode 100644
index 00000000..006b9961
--- /dev/null
+++ b/src/components/FeedPortal/components/StandupSummary.jsx
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+
+import "./NewsfeedStandup.css";
+import StandupCompleted from './StandupCompleted';
+import StandupPending from './StandupPending';
+import StandupRecent from './StandupRecent';
+
+class StandupSummary extends React.Component {
+
+ static propTypes = {
+ standups: PropTypes.array.isRequired,
+ updateSelectedStandup: PropTypes.func.isRequired,
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ displayCount: 0,
+ };
+
+ // Force the most recently completed standup to be displayed in the detail panel
+ this.sortedStandups = props.standups
+ .slice() // Copy the array of standups so we don't modify props
+ .sort( (a, b) => (b.submitted_at - a.submitted_at) );
+ props.updateSelectedStandup(this.sortedStandups[0]);
+ }
+
+ // Update the selected standup in the containers state to force its display
+ // in the detail component
+ newStandupSelected = (e, mostRecentStandup, updateSelectedStandup) => {
+ updateSelectedStandup(mostRecentStandup);
+ }
+
+ renderResponses = (props) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ render = () => {
+ return (
+
+ { this.renderResponses(this.props) }
+
+ );
+ };
+
+}
+
+export default StandupSummary;
diff --git a/src/components/ProjectShowcase/components/ExternalLinks.jsx b/src/components/ProjectShowcase/components/ExternalLinks.jsx
index 71e9225c..0e45bbab 100644
--- a/src/components/ProjectShowcase/components/ExternalLinks.jsx
+++ b/src/components/ProjectShowcase/components/ExternalLinks.jsx
@@ -125,6 +125,7 @@ class ExternalLinks extends React.Component {
Live Link
diff --git a/src/components/Ticketbox/components/Success.jsx b/src/components/Ticketbox/components/Success.jsx
index 69f23016..be7f0676 100644
--- a/src/components/Ticketbox/components/Success.jsx
+++ b/src/components/Ticketbox/components/Success.jsx
@@ -5,7 +5,7 @@ const Success = ({ category, url }) => (
Success!
{url
- ? View {category} Issue on Github
+ ?
View {category} Issue on Github
:
Please wait a few days for us to review and get back to you.
diff --git a/src/components/UserProfile/UserSideBar.jsx b/src/components/UserProfile/UserSideBar.jsx
index 6a93d079..212cb1b0 100644
--- a/src/components/UserProfile/UserSideBar.jsx
+++ b/src/components/UserProfile/UserSideBar.jsx
@@ -8,7 +8,7 @@ const Links = ({ user: { username } }) => (
links
-
+
diff --git a/src/fragmentTypes.json b/src/fragmentTypes.json
index c278b1e3..083531ff 100644
--- a/src/fragmentTypes.json
+++ b/src/fragmentTypes.json
@@ -1 +1 @@
-{"__schema":{"types":[{"kind":"INTERFACE","name":"ProjectBase","possibleTypes":[{"name":"CohortProject"},{"name":"Project"}]},{"kind":"INTERFACE","name":"SkillInterface","possibleTypes":[{"name":"Skill"},{"name":"AcquiredSkill"},{"name":"DesiredSkill"}]},{"kind":"INTERFACE","name":"UserBase","possibleTypes":[{"name":"User"},{"name":"CohortMember"},{"name":"InactiveUser"},{"name":"ProjectMember"}]},{"kind":"INTERFACE","name":"NewsfeedItem","possibleTypes":[{"name":"GithubActivityIssue"},{"name":"GithubActivityPullRequest"},{"name":"NewsfeedVoyage"},{"name":"NewsfeedStandup"},{"name":"NewsfeedAvailableStandup"}]},{"kind":"INTERFACE","name":"HelpRequest","possibleTypes":[{"name":"TeamHelpRequest"}]},{"kind":"INTERFACE","name":"GithubActivityItem","possibleTypes":[{"name":"GithubActivityIssue"},{"name":"GithubActivityPullRequest"}]}]}}
\ No newline at end of file
+{"__schema":{"types":[{"kind":"INTERFACE","name":"ProjectBase","possibleTypes":[{"name":"CohortProject"},{"name":"Project"}]},{"kind":"INTERFACE","name":"SkillInterface","possibleTypes":[{"name":"Skill"},{"name":"AcquiredSkill"},{"name":"DesiredSkill"}]},{"kind":"INTERFACE","name":"UserBase","possibleTypes":[{"name":"User"},{"name":"CohortMember"},{"name":"ProjectMember"}]},{"kind":"INTERFACE","name":"HelpRequestBase","possibleTypes":[{"name":"HelpRequest"},{"name":"InactiveMember"},{"name":"ChangeProject"}]},{"kind":"INTERFACE","name":"NewsfeedItem","possibleTypes":[{"name":"GithubActivityIssue"},{"name":"GithubActivityPullRequest"},{"name":"NewsfeedVoyage"},{"name":"NewsfeedStandup"},{"name":"NewsfeedAvailableStandup"}]},{"kind":"INTERFACE","name":"GithubActivityItem","possibleTypes":[{"name":"GithubActivityIssue"},{"name":"GithubActivityPullRequest"}]}]}}
\ No newline at end of file