export default class workout {
constructor(toClone) {
- if(toClone === void(0)) {
- this.attributes = {...DEFAULT_ATTRIBUTES};
- this.datesDone = [];
- this.name = "New Workout";
- this.description = "";
- } else {
+ if((toClone !== void(0))&&(toClone instanceof workout)) {
this.attributes = {...toClone.attributes};
this.datesDone = [...toClone.datesDone];
this.name = toClone.name;
this.description = toClone.description;
+ } else {
+ this.attributes = {...DEFAULT_ATTRIBUTES};
+ this.datesDone = [];
+ this.name = "New Workout";
+ this.description = "";
}
}
- add(dates) { // add new workout to
+ add(dates) { // add new workout to datesDone
if(!(dates instanceof Array)) {
throw new TypeError('Workout::add(dates) expects parameter `dates` to be an array of dates');
}
...new Set([
...this.datesDone,
...dates.map((date) => {
- date = Date.parse(date);
- return new Date(date.getTime() - (date.getTimezoneOffset() * 60000 ))
+ return new Date(date)
.toISOString()
.split("T")[0];
})
])
];
- this.datesDone.sort();
+ this.datesDone.sort((a,b) => {
+ return a<b;
+ });
}
changeDescription(str) {
if(typeof str != "string") {
return this.datesDone.slice(0,1);
}
remove(date) {
- throw new Error("Not implemented");
+ for(let i=0;i<this.datesDone.length;i++) {
+ if(this.datesDone[i]==date) {
+ this.datesDone.splice(i,1);
+ }
+ }
}
setName(name) {
if(typeof name != "string") {
import {daysAgo} from '../days.ago/days.ago.js';
import header from '../header/header.js';
import {manage} from '../manage/manage.js';
-//import {recent} from '../recent/recent.js';
-//(props.view=="manage")?createElement(manage,null):createElement(recent,null)
+import {recent} from '../recent/recent.js';
export default class Main extends React.Component {
constructor(props) {
createElement("div",{className:style.viewContainer},
createElement(daysAgo,null),
createElement("input",{type:"button",onClick:this.switchView.bind(this,otherView),value:otherView}),
- createElement(manage,null)
+ (props.view=="manage")?createElement(manage,null):createElement(recent,null)
)
);
}
super(props);
this.changeDescription = this.changeDescription.bind(this);
this.changeName = this.changeName.bind(this);
+ this.completeWorkout = this.completeWorkout.bind(this);
this.toggleAttribute = this.toggleAttribute.bind(this);
}
changeDescription(event) {
changeName(event) {
this.props.name(event.target.value);
}
+ completeWorkout() {
+ this.props.complete();
+ }
toggleAttribute(attr) {
this.props.toggle(attr);
}
...DEFAULT_ATTRIBUTES_ORDER,
"times_done",
"last_done",
- "description"
+ "description",
+ "done"
].map((field) => {
let val;
- if(data[field] === void(0)) {
+ if(field=="done") {
+ return createElement("td",{key:"cell-"+field},
+ createElement("input",{type:"button",value:"Completed",onClick:this.completeWorkout})
+ );
+ } else if(data[field] === void(0)) {
return createElement("td",{key:"cell-attr-"+field,onClick:this.toggleAttribute.bind(this,field)},
(data.attributes[field])?"Yes":"No"
);
workouts,
changeDescription,
changeName,
+ completeWorkout,
toggleAttribute
} = this.props;
- console.log(workouts);
const headers = [
"workout name",
...DEFAULT_ATTRIBUTES_ORDER,
"times done",
"last done",
- "description"
+ "description",
+ ""
].map((x) => {
return createElement("th",{key:"head-"+x},x);
});
return createElement(manageRow,{
key:"row-"+i,
data:workouts[i],
+ complete:() => completeWorkout(i),
description:(val) => changeDescription(i,val),
name:(val) => changeName(i,val),
toggle:(attr) => toggleAttribute(i,attr)
import Manage from './manage.component.js';
import {constants} from '../../constants.js';
-const {CHANGE_ATTRIBUTE,CHANGE_WORKOUT_DESCRIPTION,CHANGE_WORKOUT_NAME,NEW_WORKOUT} = constants;
+const {ADD_WORKOUT,CHANGE_ATTRIBUTE,CHANGE_WORKOUT_DESCRIPTION,CHANGE_WORKOUT_NAME,NEW_WORKOUT} = constants;
const mapStateToProps = (state) => {
const {workouts} = state;
name:val
});
},
+ completeWorkout:(workout) => {
+ const now = new Date();
+ dispatch({
+ type:ADD_WORKOUT,
+ name:workout,
+ toAdd:[new Date(now.getTime() - (now.getTimezoneOffset() * 60000 ))
+ .toISOString()
+ .split("T")[0]
+ ]
+ });
+ },
newWorkout:() => {
dispatch({
type:NEW_WORKOUT
+import style from './recent.row.css';
+
+export default class recentRow extends React.Component {
+ constructor(props) {
+ super(props);
+ this.handleDateChange = this.handleDateChange.bind(this);
+ }
+ handleDateChange(event) {
+ const {date,dateChange} = this.props;
+ dateChange(date,event.target.value);
+ }
+ render() {
+ const {name,date} = this.props;
+ return createElement("tr",null,
+ createElement("td",null,name),
+ createElement("td",null,
+ createElement("input",{defaultValue:date,onBlur:this.handleDateChange})
+ )
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+import style from './recent.css';
+
+import recentRow from '../recent.row/recent.row.js';
+
+export default class Recent extends React.Component {
+ render() {
+ const {data,handleDateChange} = this.props;
+ const headers = ["workout name","date"].map((x) => {
+ return createElement("th",{key:"head-"+x},x);
+ });
+ const rows = data.map((i) => {
+ return createElement(recentRow,{
+ key:"row-"+i.name+"-"+i.date,
+ name:i.name,
+ date:i.date,
+ dateChange:(old,date) => handleDateChange(i.name,old,date)
+ });
+ });
+ return createElement("div",{className:style.container},
+ createElement("table",{className:style.table},
+ createElement("thead",null,
+ createElement("tr",null,
+ headers
+ )
+ ),
+ createElement("tbody",null,rows)
+ )
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+:local(.container) {
+
+}
+
+:local(.table) {
+
+}
\ No newline at end of file
+import {connect} from 'react-redux';
+
+import Recent from './recent.component.js';
+
+import {constants} from '../../constants.js';
+const {CHANGE_WORKOUT_DATE} = constants;
+
+const mapStateToProps = (state) => {
+ return {
+ data:state.view.data
+ };
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ handleDateChange:(workout,old,date) => {
+ dispatch({
+ type:CHANGE_WORKOUT_DATE,
+ workout,
+ old,
+ new:date
+ });
+ }
+ };
+}
+
+export const recent = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Recent);
\ No newline at end of file
ADD_WORKOUT:"ADD_WORKOUT",
CHANGE_ATTRIBUTE:"CHANGE_ATTRIBUTE",
CHANGE_VIEW:"CHANGE_VIEW",
+ CHANGE_WORKOUT_DATE:"CHANGE_WORKOUT_DATE",
CHANGE_WORKOUT_DESCRIPTION:"CHANGE_WORKOUT_DESCRIPTION",
CHANGE_WORKOUT_NAME:"CHANGE_WORKOUT_NAME",
DEFAULT_ATTRIBUTES:{
sortOrder:"desc"
};
+const generateDaysAgo = (workouts) => {
+ const daysAgo = {...defaultDaysAgo};
+ const now = new Date();
+ for(let i in workouts) {
+ const last = workouts[i].last_done[0];
+ if(last === void(0)) {
+ continue;
+ }
+ let lastDate = new Date(last);
+ lastDate = new Date(lastDate.getTime() + (now.getTimezoneOffset() * 60000));
+ for(let attr in workouts[i].attributes) {
+ if(workouts[i].attributes[attr]) {
+ const diff = Math.floor((now-lastDate)/(1000*60*60*24));
+ if((daysAgo[attr]<0)||(diff<daysAgo[attr])) {
+ daysAgo[attr] = diff;
+ }
+ }
+ }
+ }
+ return daysAgo;
+};
+
export default function view(state = defaultState,action) {
const {CHANGE_VIEW,SORT_VIEW} = constants;
if((action.workouts == void(0))||(typeof action.workouts != "object" )) {
return state;
}
- if(!Object.keys(action.workouts).every((x) => x instanceof workout)) {
+ if(!Object.keys(action.workouts).every((x) => action.workouts[x] instanceof workout)) {
return state;
}
- console.log('here');
switch(action.type) {
case CHANGE_VIEW:
if((action.view === void(0))||(action.view == state.view)) {
description:actions.workouts[i].description
});
}
+ newStateManageView.daysAgo = generateDaysAgo(action.workouts);
return newStateManageView;
case "recent":
const newStateRecentView = {
view:"recent"
};
newStateRecentView.data = [];
- for(let i=0;i<action.workouts.length;i++) {
- for(let j=0;j<action.workouts[i].datesDone;j++) {
+ for(let i in action.workouts) {
+ for(let j=0;j<action.workouts[i].datesDone.length;j++) {
newStateRecentView.data.push({
- name:actions.workouts[i].name,
- date:actions.workouts[i].datesDone[j]
+ name:action.workouts[i].name,
+ date:action.workouts[i].datesDone[j]
});
}
}
+ newStateRecentView.daysAgo = generateDaysAgo(action.workouts);
return newStateRecentView;
default:
return state;
import {constants} from '../constants.js';
export default function workouts(state = {},action) {
- const {ADD_WORKOUT,CHANGE_ATTRIBUTE,CHANGE_WORKOUT_DESCRIPTION,CHANGE_WORKOUT_NAME,DEFAULT_ATTRIBUTES,DELETE_WORKOUT,NEW_WORKOUT,REMOVE_WORKOUT} = constants;
+ const {ADD_WORKOUT,CHANGE_ATTRIBUTE,CHANGE_WORKOUT_DATE,CHANGE_WORKOUT_DESCRIPTION,CHANGE_WORKOUT_NAME,DEFAULT_ATTRIBUTES,DELETE_WORKOUT,NEW_WORKOUT,REMOVE_WORKOUT} = constants;
if(typeof action != "object") {
return state;
}
} catch(err) {
return state;
}
+ case CHANGE_WORKOUT_DATE:
+ if(action.workout === void(0)) {
+ return state;
+ }
+ if((action.old === void(0))||(action.new === void(0))) {
+ return state;
+ }
+ if(action.old === action.new) {
+ return state;
+ }
+ const newStateAfterWorkoutDateChange = {...state};
+ try {
+ newStateAfterWorkoutDateChange[action.workout].remove(action.old);
+ newStateAfterWorkoutDateChange[action.workout].add([action.new]);
+ return newStateAfterWorkoutDateChange;
+ } catch(err) {
+ console.error(err);
+ return state;
+ }
case CHANGE_WORKOUT_DESCRIPTION:
if(action.workout === void(0)) {
return state;
});
+ describe(CHANGE_WORKOUT_DESCRIPTION,() => {
+
+ it('should not change the state when an invalid action is given',() => {
+ assert.fail('Not implemented');
+ });
+
+ it('should not change the state when trying to change the description for a nonexistent workout',() => {
+ assert.fail('Not implemented');
+ });
+
+ it('should successfully change the description of an already existing workout',() => {
+ assert.fail('Not implemented');
+ });
+
+ });
+
describe(DELETE_WORKOUT,() => {
it('should not change the state when an invalid action is given',() => {