Skip to content

Commit

Permalink
WIP task status
Browse files Browse the repository at this point in the history
Resolves #5
  • Loading branch information
onejgordon committed Mar 12, 2017
1 parent 99866be commit 6061e0f
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 75 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Expand Up @@ -4,6 +4,10 @@
"node": true,
"browser": true
},
"parser": "babel-eslint",
"rules": {
"strict": 0
},
"parserOptions": {
ecmaVersion: 6,
ecmaFeatures: {
Expand Down
3 changes: 2 additions & 1 deletion api.py
Expand Up @@ -85,9 +85,10 @@ def update(self, d):
id = self.request.get_range('id')
params = tools.gets(self,
strings=['title'],
booleans=['archived'],
booleans=['archived', 'wip'],
integers=['status']
)
logging.debug(params)
if id:
task = Task.get_by_id(int(id), parent=self.user.key)
else:
Expand Down
7 changes: 7 additions & 0 deletions models.py
Expand Up @@ -252,6 +252,7 @@ class Task(UserAccessible):
dt_done = ndb.DateTimeProperty()
title = ndb.TextProperty()
status = ndb.IntegerProperty(default=TASK.NOT_DONE)
wip = ndb.BooleanProperty(default=False)
archived = ndb.BooleanProperty(default=False)

def json(self):
Expand All @@ -262,6 +263,7 @@ def json(self):
'ts_done': tools.unixtime(self.dt_done),
'status': self.status,
'archived': self.archived,
'wip': self.wip,
'title': self.title,
'done': self.is_done()
}
Expand Down Expand Up @@ -299,9 +301,14 @@ def Update(self, **params):
self.status = params.get('status')
if change and self.is_done():
self.dt_done = datetime.now()
self.wip = False
message = random.choice(TASK_DONE_REPLIES)
if 'archived' in params:
self.archived = params.get('archived')
if self.archived:
self.wip = False
if 'wip' in params:
self.wip = params.get('wip')
return message

def is_done(self):
Expand Down
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -5,7 +5,7 @@
"main": "app.js",
"repository": {
"type": "git",
"url": ""
"url": "github.com/onejgordon/flow-dashboard"
},
"scripts": {
"test": "jest"
Expand Down Expand Up @@ -53,10 +53,13 @@
"toastr": "^2.1.2"
},
"devDependencies": {
"babel-eslint": "^6.1.2",
"babelify": "^6.1.3",
"browserify": "^13.3.0",
"browserify-resolutions": "^1.0.6",
"browserify-shim": "^3.8.10",
"eslint": "^3.17.1",
"eslint-plugin-react": "^6.10.0",
"gulp": "^3.8.10",
"gulp-hash-src": "^0.1.6",
"gulp-html-replace": "^1.4.3",
Expand Down
30 changes: 16 additions & 14 deletions src/js/components/Dashboard.js
@@ -1,7 +1,4 @@
var React = require('react');
var AppConstants = require('constants/AppConstants');
var util = require('utils/util');
var api = require('utils/api');
var GoalViewer = require('components/GoalViewer');
var ProjectViewer = require('components/ProjectViewer');
var HabitWidget = require('components/HabitWidget');
Expand All @@ -10,10 +7,9 @@ var MiniJournalWidget = require('components/MiniJournalWidget');
var TaskWidget = require('components/TaskWidget');
var FlashCard = require('components/FlashCard');
import {findItemById} from 'utils/store-utils';
import {clone, merge, get} from 'lodash';
import {RaisedButton, Dialog, IconButton,
TextField, FlatButton, FontIcon,
IconMenu, MenuItem, Paper} from 'material-ui';
import {get} from 'lodash';
import {Dialog, IconButton, FontIcon,
IconMenu, MenuItem} from 'material-ui';

export default class Dashboard extends React.Component {
static defaultProps = {}
Expand All @@ -24,8 +20,12 @@ export default class Dashboard extends React.Component {
};
}

componentDidMount() {
document.onkeydown = this.handle_key_down.bind(this);
componentWillMount() {
document.addEventListener("keydown", this.handle_key_down.bind(this));
}

componentWillUnmount() {
document.removeEventListener("keydown", this.handle_key_down.bind(this));
}

handle_key_down(e) {
Expand All @@ -34,12 +34,14 @@ export default class Dashboard extends React.Component {
let in_input = tag == 'input' || tag == 'textarea';
if (in_input) return true;
if (keyCode == 84) { // t
this.refs.taskwidget.show_new_box();
document.getElementById('TaskWidget').scrollIntoView();
return false;
this.refs.taskwidget.show_new_box();
document.getElementById('TaskWidget').scrollIntoView();
e.preventDefault();
return false;
} else if (keyCode == 72) { // h
document.getElementById('HabitWidget').scrollIntoView();
return false;
document.getElementById('HabitWidget').scrollIntoView();
e.preventDefault();
return false;
}
}

Expand Down
73 changes: 28 additions & 45 deletions src/js/components/TaskWidget.js
@@ -1,19 +1,22 @@
var React = require('react');
import { FontIcon, IconButton, ListItem, List,
Checkbox, RaisedButton, TextField, Paper,
import { FontIcon, IconButton, List,
RaisedButton, TextField, Paper,
FlatButton, IconMenu, MenuItem } from 'material-ui';
var util = require('utils/util');
var UserStore = require('stores/UserStore');
var api = require('utils/api');
var TaskLI = require('components/list_items/TaskLI');
import {findIndexById} from 'utils/store-utils';
import {cyanA400} from 'material-ui/styles/colors';
var ProgressLine = require('components/common/ProgressLine');
var toastr = require('toastr');
import {changeHandler} from 'utils/component-utils';


@changeHandler
export default class TaskWidget extends React.Component {
static propTypes = {
show_task_progressbar: React.PropTypes.bool
}

static defaultProps = {
show_task_progressbar: true
}
Expand Down Expand Up @@ -62,22 +65,6 @@ export default class TaskWidget extends React.Component {
});
}

archive(task) {
// Toggle done on server
let params = {
id: task.id,
archived: 1
}
api.post("/api/task", params, (res) => {
if (res.task) {
let {tasks} = this.state;
let idx = findIndexById(tasks, res.task.id, 'id');
if (idx > -1) tasks[idx] = res.task;
this.setState({tasks});
}
});
}

add_task() {
let {form} = this.state;
api.post("/api/task", {title: form.new_task}, (res) => {
Expand Down Expand Up @@ -105,30 +92,25 @@ export default class TaskWidget extends React.Component {
return {tasks_done, tasks_total}
}

render_task(t) {
let icon = this.ICONS[t.status-1];
let click = null;
let archive = null;
let done = t.status == this.DONE;
let archived = t.archived;
if (t.status == this.NOT_DONE) click = this.update_status.bind(this, t, this.DONE);
if (done) click = this.update_status.bind(this, t, this.NOT_DONE);
if (!archived) archive = <IconButton onClick={this.archive.bind(this, t)} tooltip="Archive" iconClassName="material-icons">archive</IconButton>
let st = { fill: this.TASK_COLOR };
let check = <Checkbox iconStyle={st} onCheck={click} checked={done} disabled={archived} />
let hours_until = util.hours_until(t.ts_due);
let _icon = <i className="glyphicon glyphicon-time" />;
if (hours_until < 0) _icon = <i className="glyphicon glyphicon-alert" style={{color: "#FC4750"}} />;
else if (hours_until <= 3) _icon = <i className="glyphicon glyphicon-hourglass" style={{color: "orange"}} />;
let secondary = <span>{ _icon }&nbsp;{util.from_now(t.ts_due)}</span>
return (
<ListItem key={t.id}
primaryText={ t.title }
secondaryText={secondary}
leftCheckbox={check}
style={{fontWeight: 'normal'}}
rightIconButton={archive} />
);
task_update(task, params) {
// Toggle done on server
params.id = task.id;
api.post("/api/task", params, (res) => {
if (res.task) {
let {tasks} = this.state;
let idx = findIndexById(tasks, res.task.id, 'id');
if (idx > -1) tasks[idx] = res.task;
this.setState({tasks});
}
});
}

archive(task) {
this.task_update(task, {archived: 1});
}

set_task_wip(task, is_wip) {
this.task_update(task, {wip: is_wip ? 1 : 0});
}

render() {
Expand All @@ -152,8 +134,9 @@ export default class TaskWidget extends React.Component {
<ProgressLine value={current_mins} total={total_mins} />
{ tasks.length > 0 ?
<List>
{ tasks.map((t) => {
{ tasks.sort((a, b) => { return b.wip - a.wip;}).map((t) => {
return <TaskLI key={t.id} task={t}
onUpdateWIP={this.set_task_wip.bind(this)}
onUpdateStatus={this.update_status.bind(this)}
onArchive={this.archive.bind(this)} />;
}) }
Expand Down
49 changes: 35 additions & 14 deletions src/js/components/list_items/TaskLI.js
@@ -1,56 +1,77 @@
var React = require('react');
import {ListItem, FontIcon, IconButton, Checkbox} from 'material-ui';
import {ListItem, FontIcon, IconButton,
IconMenu, MenuItem, Checkbox} from 'material-ui';
var util = require('utils/util');
var DateTime = require('components/common/DateTime');
var api = require('utils/api');

export default class TaskLI extends React.Component {
static propTypes = {
onUpdateStatus: React.PropTypes.func,
onArchive: React.PropTypes.func,
onUpdateWIP: React.PropTypes.func
}

static defaultProps = {
task: null,
onUpdateStatus: null,
onArchive: null
onArchive: null,
onUpdateWIP: null
}

constructor(props) {
super(props);
this.state = {
};
this.ICONS = ['check', 'check_circle', 'archive'];
this.NOT_DONE = 1;
this.DONE = 2;
this.TASK_COLOR = "#DF00FF";
}

set_wip(is_wip) {
this.props.onUpdateWIP(this.props.task, is_wip);
}

render() {
let t = this.props.task;
let icon = this.ICONS[t.status-1];
let click = null;
let archive = null;
let menu = [];
let done = t.status == this.DONE;
let archived = t.archived;
if (!archived) menu.push({icon: 'archive', click: this.props.onArchive.bind(this, t), label: 'Archive'});
if (!done) {
if (t.wip) menu.push({icon: 'stop', click: this.set_wip.bind(this, false), label: 'Clear WIP'});
else menu.push({icon: 'play_for_work', click: this.set_wip.bind(this, true), label: 'On It (Set as WIP)'});
}
if (t.status == this.NOT_DONE) click = this.props.onUpdateStatus.bind(this, t, this.DONE);
if (done) click = this.props.onUpdateStatus.bind(this, t, this.NOT_DONE);
if (!archived) archive = <IconButton onClick={this.props.onArchive.bind(this, t)} tooltip="Archive" iconClassName="material-icons">archive</IconButton>
let st = { fill: this.TASK_COLOR };
let check = <Checkbox iconStyle={st} onCheck={click} checked={done} disabled={archived} />
let hours_until = util.hours_until(t.ts_due);
let _icon, secondary;
if (!done) {
if (done) secondary = "Done";
else if (archived) secondary = "Archived";
else {
_icon = <i className="glyphicon glyphicon-time" />;
if (hours_until < 0) _icon = <i className="glyphicon glyphicon-alert" style={{color: "#FC4750"}} />;
else if (hours_until <= 3) _icon = <i className="glyphicon glyphicon-hourglass" style={{color: "orange"}} />;
secondary = <span>{ _icon }&nbsp;{util.from_now(t.ts_due)}</span>
} else {
secondary = "Done";
}

let rightIcon = (
<IconMenu iconButtonElement={<IconButton iconClassName="material-icons">more_vert</IconButton>}>
{ menu.map((mi, i) => {
return <MenuItem key={i} leftIcon={<FontIcon className="material-icons">{mi.icon}</FontIcon>} onClick={mi.click}>{mi.label}</MenuItem>
}) }
</IconMenu>
);
let primaryText;
if (t.wip) primaryText = <div className="wip">[ WIP ] {t.title}</div>;
else primaryText = t.title;
return (
<ListItem key={t.id}
primaryText={ t.title }
primaryText={ primaryText }
secondaryText={secondary}
leftCheckbox={check}
style={{fontWeight: 'normal'}}
rightIconButton={archive} />
rightIconButton={rightIcon} />
);
}
}
6 changes: 6 additions & 0 deletions static/flow.css
Expand Up @@ -115,6 +115,12 @@ body {
color: white;
}

.TaskWidget div.wip {
font-size: 1.3em;
font-weight: 100;
color: #F9D23D;
}

.dateProgressBar {
display: block;
height: 2px;
Expand Down

0 comments on commit 6061e0f

Please sign in to comment.