task-manager 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +4 -0
  3. data/Rakefile +27 -0
  4. data/app/assets/images/task-manager/icons/add.png +0 -0
  5. data/app/assets/images/task-manager/icons/delete.png +0 -0
  6. data/app/assets/images/task-manager/icons/edit.png +0 -0
  7. data/app/assets/javascripts/task-manager/application.js +43 -0
  8. data/app/assets/javascripts/task-manager/extjs/app/controller/Plans.js +359 -0
  9. data/app/assets/javascripts/task-manager/extjs/app/controller/Tasks.js +56 -0
  10. data/app/assets/javascripts/task-manager/extjs/app/helper/application_helper.js +24 -0
  11. data/app/assets/javascripts/task-manager/extjs/app/model/Assignee.js +19 -0
  12. data/app/assets/javascripts/task-manager/extjs/app/model/Plan.js +195 -0
  13. data/app/assets/javascripts/task-manager/extjs/app/model/Task.js +23 -0
  14. data/app/assets/javascripts/task-manager/extjs/app/store/Assignees.js +53 -0
  15. data/app/assets/javascripts/task-manager/extjs/app/store/AssigneesTree.js +3 -0
  16. data/app/assets/javascripts/task-manager/extjs/app/store/Booleans.js +9 -0
  17. data/app/assets/javascripts/task-manager/extjs/app/store/Days.js +38 -0
  18. data/app/assets/javascripts/task-manager/extjs/app/store/Hours.js +31 -0
  19. data/app/assets/javascripts/task-manager/extjs/app/store/Minutes.js +67 -0
  20. data/app/assets/javascripts/task-manager/extjs/app/store/Months.js +19 -0
  21. data/app/assets/javascripts/task-manager/extjs/app/store/Plans.js +6 -0
  22. data/app/assets/javascripts/task-manager/extjs/app/store/QuarterlyMonths.js +10 -0
  23. data/app/assets/javascripts/task-manager/extjs/app/store/Statuses.js +11 -0
  24. data/app/assets/javascripts/task-manager/extjs/app/store/Tasks.js +6 -0
  25. data/app/assets/javascripts/task-manager/extjs/app/store/Types.js +12 -0
  26. data/app/assets/javascripts/task-manager/extjs/app/store/WeekDays.js +14 -0
  27. data/app/assets/javascripts/task-manager/extjs/app/view/plan/AssignablesWindow.js +23 -0
  28. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Edit.js +189 -0
  29. data/app/assets/javascripts/task-manager/extjs/app/view/plan/EditWindow.js +21 -0
  30. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Grid.js +92 -0
  31. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Index.js +24 -0
  32. data/app/assets/javascripts/task-manager/extjs/app/view/plan/New.js +190 -0
  33. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Search.js +132 -0
  34. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignables.js +22 -0
  35. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignablesGrid.js +22 -0
  36. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignablesTree.js +9 -0
  37. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Window.js +21 -0
  38. data/app/assets/javascripts/task-manager/extjs/app/view/task/Grid.js +62 -0
  39. data/app/assets/javascripts/task-manager/extjs/app/view/task/Index.js +21 -0
  40. data/app/assets/javascripts/task-manager/extjs/app/view/task/Search.js +148 -0
  41. data/app/assets/javascripts/task-manager/extjs/lib/ux/MultiSelectablePagingGrid.js +20 -0
  42. data/app/assets/javascripts/task-manager/extjs/lib/ux/RowExpander.js +230 -0
  43. data/app/assets/stylesheets/task-manager/application.css.scss +13 -0
  44. data/app/assets/stylesheets/task-manager/extjs.css.scss +43 -0
  45. data/app/controllers/task_manager/api/v1/plans_controller.rb +251 -0
  46. data/app/controllers/task_manager/api/v1/tasks_controller.rb +82 -0
  47. data/app/controllers/task_manager/application_controller.rb +4 -0
  48. data/app/helpers/task_manager/application_helper.rb +4 -0
  49. data/app/models/task_manager/assignable.rb +10 -0
  50. data/app/models/task_manager/callable.rb +10 -0
  51. data/app/models/task_manager/plan.rb +114 -0
  52. data/app/models/task_manager/task.rb +81 -0
  53. data/app/serializers/task_manager/plan_serializer.rb +8 -0
  54. data/app/serializers/task_manager/task_serializer.rb +8 -0
  55. data/app/views/layouts/task-manager/application.html.erb +14 -0
  56. data/config/routes.rb +8 -0
  57. data/db/migrate/20121102054723_setup_hstore.rb +9 -0
  58. data/db/migrate/20121102055137_create_task_manager_plans.rb +16 -0
  59. data/db/migrate/20121102142657_create_task_manager_assignables.rb +13 -0
  60. data/db/migrate/20121102150720_create_task_manager_callables.rb +13 -0
  61. data/db/migrate/20121105170602_create_task_manager_tasks.rb +18 -0
  62. data/lib/task-manager.rb +37 -0
  63. data/lib/task-manager/api_constraints.rb +12 -0
  64. data/lib/task-manager/deadline_calculator.rb +22 -0
  65. data/lib/task-manager/deadline_validator.rb +27 -0
  66. data/lib/task-manager/engine.rb +5 -0
  67. data/lib/task-manager/version.rb +3 -0
  68. data/lib/tasks/task-manager_tasks.rake +4 -0
  69. metadata +327 -0
@@ -0,0 +1,21 @@
1
+ Ext.define('TM.view.task.Index', {
2
+ extend: 'Ext.panel.Panel',
3
+ xtype: 'task_index',
4
+
5
+ requires: ['TM.view.task.Search'],
6
+ title: '任务管理',
7
+ closable: true,
8
+
9
+ // store: '',
10
+ layout: {
11
+ type: 'vbox',
12
+ align: 'stretch'
13
+ },
14
+
15
+ items: [{
16
+ xtype: 'task_search'
17
+ }, {
18
+ xtype: 'task_grid',
19
+ flex: 1
20
+ }]
21
+ })
@@ -0,0 +1,148 @@
1
+ Ext.define('TM.view.task.Search', {
2
+ extend: 'Ext.form.Panel',
3
+ xtype: 'task_search',
4
+
5
+ border: 0,
6
+ bodyPadding: '5 5 0',
7
+
8
+ items: [{
9
+ xtype: 'fieldset',
10
+ id: 'fieldset',
11
+ title: '查询',
12
+
13
+ layout: {
14
+ type: 'form',
15
+ border: 0,
16
+ margin: 2
17
+ },
18
+ items: [{
19
+ border: 0,
20
+ items: [{
21
+ layout: 'column',
22
+ border: 0,
23
+ defaults: {
24
+ xtype: 'textfield',
25
+ id: 'textfield',
26
+ labelAlign: 'right',
27
+ width: 300,
28
+ labelWidth: 130
29
+ },
30
+ items: [{
31
+ fieldLabel: '名称',
32
+ name: 'q[name_cont]'
33
+ }, {
34
+ fieldLabel: '类型',
35
+ editable: false,
36
+ xtype: 'combo',
37
+ id: 'types',
38
+ store: 'TM.store.Types',
39
+ valueField: 'value',
40
+ name: 'q[task_type_eq]'
41
+ }, {
42
+ fieldLabel: '状态',
43
+ xtype: 'combo',
44
+ editable: false,
45
+ valueField: 'value',
46
+ store: 'TM.store.Statuses',
47
+ name: 'q[status_eq]'
48
+ }]
49
+ }, {
50
+ layout: 'column',
51
+ border: 0,
52
+ defaults: {
53
+ border: 0,
54
+ labelAlign: 'right',
55
+ width: 300,
56
+ labelWidth: 130
57
+ },
58
+ items: [{
59
+ layout: 'column',
60
+ border: 0,
61
+ defaults: {
62
+ xtype: 'datefield',
63
+ id: 'datefield',
64
+ labelAlign: 'right',
65
+ width: 300,
66
+ labelWidth: 130
67
+ },
68
+ items: [{
69
+ fieldLabel: '完成时间 从',
70
+ format: 'Y-m-d',
71
+ editable: false,
72
+ name: 'q[finished_at_gteq]',
73
+ id: 'last_task'
74
+ }, {
75
+ fieldLabel: '截至时间 从',
76
+ format: 'Y-m-d',
77
+ xtype: 'datefield',
78
+ editable: false,
79
+ name: 'q[deadline_gteq]'
80
+ }]
81
+ }, {
82
+ layout: 'column',
83
+ border: 0,
84
+ defaults: {
85
+ xtype: 'datefield',
86
+ labelAlign: 'right',
87
+ width: 300,
88
+ labelWidth: 130
89
+ },
90
+ items: [{
91
+ fieldLabel: '至',
92
+ format: 'Y-m-d',
93
+ editable: false,
94
+ name: 'q[finished_at_lteq]'
95
+ }, {
96
+ fieldLabel: '至',
97
+ format: 'Y-m-d',
98
+ xtype: 'datefield',
99
+ editable: false,
100
+ name: 'q[deadline_lteq]'
101
+ }]
102
+ }, {
103
+ layout: 'hbox',
104
+ margin: '20 0 0 60',
105
+ items: [{
106
+ xtype: 'button',
107
+ formBind: true,
108
+ width: 60,
109
+ text: '查询',
110
+ action: 'query'
111
+ }, {
112
+ xtype: 'button',
113
+ margin: '0 0 0 20',
114
+ width: 60,
115
+ text: '重置',
116
+ action: 'reset'
117
+ }]
118
+ }]
119
+ }],
120
+ }]
121
+ }],
122
+
123
+ // buttons: [{
124
+ // formBind: true,
125
+ // width: 60,
126
+ // text: '查询',
127
+ // action: 'query'
128
+ // }, {
129
+ // margin: '0 0 0 20',
130
+ // width: 60,
131
+ // text: '重置',
132
+ // action: 'reset'
133
+ // }],
134
+
135
+ hasQueryParams: function() {
136
+ var hasParams = false;
137
+ Ext.Object.each(this.getValues(), function(key, value) {
138
+ if(value) {
139
+ hasParams = true;
140
+
141
+ // Break each
142
+ return false;
143
+ }
144
+ });
145
+
146
+ return hasParams;
147
+ }
148
+ });
@@ -0,0 +1,20 @@
1
+ Ext.define('Ext.ux.MultiSelectablePagingGrid', {
2
+ extend: 'Ext.grid.Panel',
3
+ selType: 'checkboxmodel',
4
+ selModel: {
5
+ mode: 'MULTI',
6
+ allowDeselect: true,
7
+ checkOnly: true
8
+ },
9
+
10
+ initComponent: function() {
11
+ this.dockedItems = [{
12
+ xtype: 'pagingtoolbar',
13
+ store: this.store,
14
+ dock: 'bottom',
15
+ displayInfo: true
16
+ }];
17
+
18
+ this.callParent(arguments);
19
+ }
20
+ });
@@ -0,0 +1,230 @@
1
+ // feature idea to enable Ajax loading and then the content
2
+ // cache would actually make sense. Should we dictate that they use
3
+ // data or support raw html as well?
4
+
5
+ /**
6
+ * @class Ext.ux.RowExpander
7
+ * @extends Ext.AbstractPlugin
8
+ * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
9
+ * a second row body which expands/contracts. The expand/contract behavior is configurable to react
10
+ * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
11
+ *
12
+ * @ptype rowexpander
13
+ */
14
+ Ext.define('Ext.ux.RowExpander', {
15
+ extend: 'Ext.AbstractPlugin',
16
+
17
+ requires: [
18
+ 'Ext.grid.feature.RowBody',
19
+ 'Ext.grid.feature.RowWrap'
20
+ ],
21
+
22
+ alias: 'plugin.rowexpander',
23
+
24
+ rowBodyTpl: null,
25
+
26
+ /**
27
+ * @cfg {Boolean} expandOnEnter
28
+ * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
29
+ * key is pressed (defaults to <tt>true</tt>).
30
+ */
31
+ expandOnEnter: true,
32
+
33
+ /**
34
+ * @cfg {Boolean} expandOnDblClick
35
+ * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
36
+ * (defaults to <tt>true</tt>).
37
+ */
38
+ expandOnDblClick: true,
39
+
40
+ /**
41
+ * @cfg {Boolean} selectRowOnExpand
42
+ * <tt>true</tt> to select a row when clicking on the expander icon
43
+ * (defaults to <tt>false</tt>).
44
+ */
45
+ selectRowOnExpand: false,
46
+
47
+ rowBodyTrSelector: '.x-grid-rowbody-tr',
48
+ rowBodyHiddenCls: 'x-grid-row-body-hidden',
49
+ rowCollapsedCls: 'x-grid-row-collapsed',
50
+
51
+
52
+
53
+ renderer: function(value, metadata, record, rowIdx, colIdx) {
54
+ if (colIdx === 0) {
55
+ metadata.tdCls = 'x-grid-td-expander';
56
+ }
57
+ return '<div class="x-grid-row-expander">&#160;</div>';
58
+ },
59
+
60
+ /**
61
+ * @event expandbody
62
+ * <b<Fired through the grid's View</b>
63
+ * @param {HTMLElement} rowNode The &lt;tr> element which owns the expanded row.
64
+ * @param {Ext.data.Model} record The record providing the data.
65
+ * @param {HTMLElement} expandRow The &lt;tr> element containing the expanded data.
66
+ */
67
+ /**
68
+ * @event collapsebody
69
+ * <b<Fired through the grid's View.</b>
70
+ * @param {HTMLElement} rowNode The &lt;tr> element which owns the expanded row.
71
+ * @param {Ext.data.Model} record The record providing the data.
72
+ * @param {HTMLElement} expandRow The &lt;tr> element containing the expanded data.
73
+ */
74
+
75
+ constructor: function() {
76
+ this.callParent(arguments);
77
+ var grid = this.getCmp();
78
+ this.recordsExpanded = {};
79
+ // <debug>
80
+ if (!this.rowBodyTpl) {
81
+ Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined.");
82
+ }
83
+ // </debug>
84
+ // TODO: if XTemplate/Template receives a template as an arg, should
85
+ // just return it back!
86
+ var rowBodyTpl = Ext.create('Ext.XTemplate', this.rowBodyTpl),
87
+ features = [{
88
+ ftype: 'rowbody',
89
+ columnId: this.getHeaderId(),
90
+ recordsExpanded: this.recordsExpanded,
91
+ rowBodyHiddenCls: this.rowBodyHiddenCls,
92
+ rowCollapsedCls: this.rowCollapsedCls,
93
+ getAdditionalData: this.getRowBodyFeatureData,
94
+ getRowBodyContents: function(data) {
95
+ return rowBodyTpl.applyTemplate(data);
96
+ }
97
+ },{
98
+ ftype: 'rowwrap'
99
+ }];
100
+
101
+ if (grid.features) {
102
+ grid.features = features.concat(grid.features);
103
+ } else {
104
+ grid.features = features;
105
+ }
106
+
107
+ // NOTE: features have to be added before init (before Table.initComponent)
108
+ },
109
+
110
+ init: function(grid) {
111
+ this.callParent(arguments);
112
+
113
+ // Columns have to be added in init (after columns has been used to create the
114
+ // headerCt). Otherwise, shared column configs get corrupted, e.g., if put in the
115
+ // prototype.
116
+ grid.headerCt.insert(0, this.getHeaderConfig());
117
+ grid.on('render', this.bindView, this, {single: true});
118
+ },
119
+
120
+ getHeaderId: function() {
121
+ if (!this.headerId) {
122
+ this.headerId = Ext.id();
123
+ }
124
+ return this.headerId;
125
+ },
126
+
127
+ getRowBodyFeatureData: function(data, idx, record, orig) {
128
+ var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments),
129
+ id = this.columnId;
130
+ o.rowBodyColspan = o.rowBodyColspan - 1;
131
+ o.rowBody = this.getRowBodyContents(data);
132
+ o.rowCls = this.recordsExpanded[record.internalId] ? '' : this.rowCollapsedCls;
133
+ o.rowBodyCls = this.recordsExpanded[record.internalId] ? '' : this.rowBodyHiddenCls;
134
+ o[id + '-tdAttr'] = ' valign="top" rowspan="2" ';
135
+ if (orig[id+'-tdAttr']) {
136
+ o[id+'-tdAttr'] += orig[id+'-tdAttr'];
137
+ }
138
+ return o;
139
+ },
140
+
141
+ bindView: function() {
142
+ var view = this.getCmp().getView(),
143
+ viewEl;
144
+
145
+ if (!view.rendered) {
146
+ view.on('render', this.bindView, this, {single: true});
147
+ } else {
148
+ viewEl = view.getEl();
149
+ if (this.expandOnEnter) {
150
+ this.keyNav = Ext.create('Ext.KeyNav', viewEl, {
151
+ 'enter' : this.onEnter,
152
+ scope: this
153
+ });
154
+ }
155
+ if (this.expandOnDblClick) {
156
+ view.on('itemdblclick', this.onDblClick, this);
157
+ }
158
+ this.view = view;
159
+ }
160
+ },
161
+
162
+ onEnter: function(e) {
163
+ var view = this.view,
164
+ ds = view.store,
165
+ sm = view.getSelectionModel(),
166
+ sels = sm.getSelection(),
167
+ ln = sels.length,
168
+ i = 0,
169
+ rowIdx;
170
+
171
+ for (; i < ln; i++) {
172
+ rowIdx = ds.indexOf(sels[i]);
173
+ this.toggleRow(rowIdx);
174
+ }
175
+ },
176
+
177
+ toggleRow: function(rowIdx) {
178
+ var rowNode = this.view.getNode(rowIdx),
179
+ row = Ext.get(rowNode),
180
+ nextBd = Ext.get(row).down(this.rowBodyTrSelector),
181
+ record = this.view.getRecord(rowNode),
182
+ grid = this.getCmp();
183
+
184
+ if (row.hasCls(this.rowCollapsedCls)) {
185
+ row.removeCls(this.rowCollapsedCls);
186
+ nextBd.removeCls(this.rowBodyHiddenCls);
187
+ this.recordsExpanded[record.internalId] = true;
188
+ this.view.fireEvent('expandbody', rowNode, record, nextBd.dom);
189
+ } else {
190
+ row.addCls(this.rowCollapsedCls);
191
+ nextBd.addCls(this.rowBodyHiddenCls);
192
+ this.recordsExpanded[record.internalId] = false;
193
+ this.view.fireEvent('collapsebody', rowNode, record, nextBd.dom);
194
+ }
195
+ },
196
+
197
+ onDblClick: function(view, cell, rowIdx, cellIndex, e) {
198
+
199
+ this.toggleRow(rowIdx);
200
+ },
201
+
202
+ getHeaderConfig: function() {
203
+ var me = this,
204
+ toggleRow = Ext.Function.bind(me.toggleRow, me),
205
+ selectRowOnExpand = me.selectRowOnExpand;
206
+
207
+ return {
208
+ id: this.getHeaderId(),
209
+ width: 24,
210
+ sortable: false,
211
+ resizable: false,
212
+ draggable: false,
213
+ hideable: false,
214
+ menuDisabled: true,
215
+ cls: Ext.baseCSSPrefix + 'grid-header-special',
216
+ renderer: function(value, metadata) {
217
+ metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
218
+
219
+ return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander">&#160;</div>';
220
+ },
221
+ processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
222
+ if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) {
223
+ var row = e.getTarget('.x-grid-row');
224
+ toggleRow(row);
225
+ return selectRowOnExpand;
226
+ }
227
+ }
228
+ };
229
+ }
230
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require ./extjs
13
+ */
@@ -0,0 +1,43 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ */
13
+ .btn-add-icon {
14
+ background: url(image_path('task-manager/icons/add.png')) no-repeat;
15
+ }
16
+ .btn-edit-icon {
17
+ background: url(image_path('task-manager/icons/edit.png')) no-repeat;
18
+ }
19
+ .btn-delete-icon {
20
+ background: url(image_path('task-manager/icons/delete.png')) no-repeat;
21
+ }
22
+ table.data {
23
+ border: 1px solid #aaa;
24
+ width: 100%;
25
+ margin: 5px;
26
+ margin-left: 60px;
27
+ tr {
28
+ td.title {
29
+ font-weight: bold;
30
+ font-size: 14px;
31
+ border-right: 1px solid #aaa;
32
+ text-align: center;
33
+ width: 100px;
34
+ }
35
+ td.name {
36
+ text-align: right;
37
+ width: 70px;
38
+ }
39
+ td {
40
+ padding: 3px;
41
+ }
42
+ }
43
+ }