Skip to content

Commit 3dcaa38

Browse files
Merge branch 'pu/cw/dependendTasksFilter' into '2024.11'
feature(Tasks): add filter option to dependend tasks grids See merge request tine20/tine20!5365
2 parents d3c07a4 + 9fd7ab5 commit 3dcaa38

File tree

9 files changed

+137
-31
lines changed

9 files changed

+137
-31
lines changed

tine20/Projects/Model/Project.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class Projects_Model_Project extends Tinebase_Record_NewAbstract
7979
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
8080
self::FLD_TASKS => [
8181
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
82+
Tasks_Model_Task::FLD_ORGANIZER => [],
8283
Tasks_Model_Task::FLD_DEPENDENT_TASKS => [],
8384
Tasks_Model_Task::FLD_DEPENDENS_ON => [
8485
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [

tine20/Projects/js/ProjectEditDialog.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @copyright Copyright (c) 2011-2021 Metaways Infosystems GmbH (http://www.metaways.de)
77
*/
88
import dependentTasksPanel from "../../Tasks/js/DependentTasksPanel";
9+
import Stringable from 'ux/Stringable';
910

1011
Ext.ns('Tine.Projects');
1112

@@ -210,7 +211,15 @@ Tine.Projects.ProjectEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
210211
items: [
211212
new dependentTasksPanel({
212213
title: Tine.Tasks.Model.Task.getAppName(),
213-
editDialog: this
214+
editDialog: this,
215+
filter: [
216+
{ field: "tasksDue", operator: "equals", value: "currentContact" },
217+
{ field: "source:Projects_Model_Project", operator: "definedBy?condition=and&setOperator=oneOf", value: [
218+
{ field: ":id", operator: "equals", value: new Stringable('...', () => {
219+
return this.record.id;
220+
}) }
221+
]}
222+
]
214223
})
215224
]
216225
}]

tine20/Tasks/Model/Task.php

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
2828
public const FLD_DUE = 'due';
2929
public const FLD_ESTIMATED_DURATION = 'estimated_duration';
3030
public const FLD_STATUS = 'status';
31+
public const FLD_ORGANIZER = 'organizer';
32+
3133

3234
public const TASK_STATUS_NEEDS_ACTION = 'NEEDS-ACTION';
3335
public const TASK_STATUS_COMPLETED = 'COMPLETED';
@@ -79,8 +81,8 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
7981
self::COLUMNS => ['description'],
8082
self::FLAGS => [self::TYPE_FULLTEXT],
8183
],
82-
'organizer' => [
83-
self::COLUMNS => ['organizer'],
84+
self::FLD_ORGANIZER => [
85+
self::COLUMNS => [self::FLD_ORGANIZER],
8486
],
8587
'uid__id' => [
8688
self::COLUMNS => ['uid', 'id'],
@@ -104,7 +106,7 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
104106
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
105107
Tasks_Model_TaskDependency::FLD_DEPENDS_ON => [
106108
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
107-
'organizer' => [],
109+
self::FLD_ORGANIZER => [],
108110
'source' => [],
109111
],
110112
],
@@ -114,7 +116,7 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
114116
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
115117
Tasks_Model_TaskDependency::FLD_TASK_ID => [
116118
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
117-
'organizer' => [],
119+
self::FLD_ORGANIZER => [],
118120
],
119121
],
120122
],
@@ -174,7 +176,7 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
174176
'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => false),
175177
self::DEFAULT_VAL => 'NEEDS-ACTION',
176178
),
177-
'organizer' => array(
179+
self::FLD_ORGANIZER => array(
178180
'label' => 'Organizer / Responsible', //_('Organizer / Responsible')
179181
'type' => 'user',
180182
self::NULLABLE => true,
@@ -322,7 +324,7 @@ class Tasks_Model_Task extends Tinebase_Record_Abstract
322324
* @var array
323325
*/
324326
protected static $_resolveForeignIdFields = array(
325-
'Tinebase_Model_User' => array('created_by', 'last_modified_by', 'organizer'),
327+
'Tinebase_Model_User' => array('created_by', 'last_modified_by', self::FLD_ORGANIZER),
326328
'recursive' => array('attachments' => 'Tinebase_Model_Tree_Node'),
327329
);
328330

@@ -344,16 +346,16 @@ public function setFromArray(array &$_data)
344346
$_data['class'] = self::CLASS_PUBLIC;
345347
}
346348

347-
if (isset($_data['organizer']) && is_array($_data['organizer'])
349+
if (isset($_data[self::FLD_ORGANIZER]) && is_array($_data[self::FLD_ORGANIZER])
348350
) {
349-
if (isset($_data['organizer']['account_id'])) {
350-
$_data['organizer'] = $_data['organizer']['account_id'];
351-
} else if (isset($_data['organizer']['accountId'])) {
352-
$_data['organizer'] = $_data['organizer']['accountId'];
351+
if (isset($_data[self::FLD_ORGANIZER]['account_id'])) {
352+
$_data[self::FLD_ORGANIZER] = $_data[self::FLD_ORGANIZER]['account_id'];
353+
} else if (isset($_data[self::FLD_ORGANIZER]['accountId'])) {
354+
$_data[self::FLD_ORGANIZER] = $_data[self::FLD_ORGANIZER]['accountId'];
353355
} else {
354356
if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(
355357
__METHOD__ . '::' . __LINE__ . ' Account ID missing from organizer data: '
356-
. print_r($_data['organizer'], true));
358+
. print_r($_data[self::FLD_ORGANIZER], true));
357359
}
358360
}
359361

@@ -384,7 +386,7 @@ public function getNotificationMessage(): string
384386
$dueDateString = Tinebase_Translation::dateToStringInTzAndLocaleFormat($this->due, $timezone, $locale);
385387

386388
// resolve values
387-
Tinebase_User::getInstance()->resolveUsers($this, 'organizer', true);
389+
Tinebase_User::getInstance()->resolveUsers($this, self::FLD_ORGANIZER, true);
388390
$status = Tasks_Config::getInstance()->get(Tasks_Config::TASK_STATUS)->records->getById($this->status);
389391
$organizerName = ($this->organizer) ? $this->organizer->accountDisplayName : '';
390392

@@ -442,7 +444,7 @@ public function getNotificationMessage(): string
442444
*/
443445
public function resolveOrganizer()
444446
{
445-
Tinebase_User::getInstance()->resolveUsers($this, 'organizer', true);
447+
Tinebase_User::getInstance()->resolveUsers($this, self::FLD_ORGANIZER, true);
446448

447449
if (! empty($this->organizer) && $this->organizer instanceof Tinebase_Model_User) {
448450
$contacts = Addressbook_Controller_Contact::getInstance()->getMultiple($this->organizer->contact_id, TRUE);

tine20/Tasks/Model/TasksDueFilter.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,38 @@
1616
class Tasks_Model_TasksDueFilter extends Tinebase_Model_Filter_Abstract
1717
{
1818
protected $_operators = [self::OP_EQUALS];
19+
protected $_contact = null;
1920

2021
public function appendFilterSql($_select, $_backend)
2122
{
2223
if (is_array($this->_value)) {
2324
throw new Tinebase_Exception_SystemGeneric('bad value for tasks due filter');
2425
}
2526

26-
/** @var Addressbook_Model_Contact $contact */
27-
$contact = Addressbook_Controller_Contact::getInstance()->get(
27+
/** @var Addressbook_Model_Contact $this->_contact */
28+
$this->_contact = Addressbook_Controller_Contact::getInstance()->get(
2829
array_search($this->_value, [Addressbook_Model_Contact::CURRENTCONTACT, Tinebase_Model_User::CURRENTACCOUNT]) !== false ? Tinebase_Core::getUser()->contact_id : $this->_value);
2930

3031
$stati = Tasks_Config::getInstance()->{Tasks_Config::ATTENDEE_STATUS}->records->filter('is_open', 1)
3132
->getArrayOfIds();
3233
$filters = [
3334
['field' => Tasks_Model_Task::FLD_ATTENDEES, 'operator' => 'definedBy', 'value' => [
34-
['field' => Tasks_Model_Attendee::FLD_USER_ID, 'operator' => 'equals', 'value' => $contact->getId()],
35+
['field' => Tasks_Model_Attendee::FLD_USER_ID, 'operator' => 'equals', 'value' => $this->_contact->getId()],
3536
['field' => Tasks_Model_Attendee::FLD_STATUS, 'operator' => 'in', 'value' => $stati],
3637
]]
3738
];
38-
if ($contact->account_id) {
39+
if ($this->_contact->account_id) {
3940
$filters[] = [
4041
Tinebase_Model_Filter_FilterGroup::CONDITION => Tinebase_Model_Filter_FilterGroup::CONDITION_AND,
4142
Tinebase_Model_Filter_FilterGroup::FILTERS => [
42-
['field' => 'organizer', 'operator' => 'equals', 'value' => $contact->account_id],
43+
['field' => 'organizer', 'operator' => 'equals', 'value' => $this->_contact->account_id],
4344
['field' => Tasks_Model_Task::FLD_DUE, 'operator' => 'before', 'value' => Tinebase_DateTime::now()],
4445
],
4546
];
4647
$filters[] = [
4748
Tinebase_Model_Filter_FilterGroup::CONDITION => Tinebase_Model_Filter_FilterGroup::CONDITION_AND,
4849
Tinebase_Model_Filter_FilterGroup::FILTERS => [
49-
['field' => 'organizer', 'operator' => 'equals', 'value' => $contact->account_id],
50+
['field' => 'organizer', 'operator' => 'equals', 'value' => $this->_contact->account_id],
5051
['field' => Tasks_Model_Task::FLD_ATTENDEES, 'operator' => 'notDefinedBy', 'value' => [
5152
['field' => Tasks_Model_Attendee::FLD_STATUS, 'operator' => 'in', 'value' => $stati],
5253
]],
@@ -59,6 +60,7 @@ public function appendFilterSql($_select, $_backend)
5960

6061
Tinebase_Backend_Sql_Filter_FilterGroup::appendFilters($_select,
6162
Tinebase_Model_Filter_FilterGroup::getFilterForModel(Tasks_Model_Task::class, [
63+
['field' => 'status', 'operator' => 'notin', 'value' => $stati],
6264
['field' => Tasks_Model_Task::FLD_DEPENDENS_ON, 'operator' => 'notDefinedBy', 'value' => [
6365
['field' => Tasks_Model_TaskDependency::FLD_DEPENDS_ON, 'operator' => 'definedBy', 'value' => [
6466
['field' => 'status', 'operator' => 'notin', 'value' => $stati],
@@ -70,4 +72,21 @@ public function appendFilterSql($_select, $_backend)
7072
],
7173
]), $_backend);
7274
}
75+
76+
/**
77+
* returns array with the filter settings of this filter
78+
*
79+
* @param bool $_valueToJson resolve value for json api?
80+
* @return array
81+
*/
82+
public function toArray($_valueToJson = false)
83+
{
84+
$result = parent::toArray($_valueToJson);
85+
86+
if ($_valueToJson && $this->_contact instanceof Tinebase_Record_Abstract) {
87+
$result['value'] = $this->_contact->toArray();
88+
}
89+
90+
return $result;
91+
}
7392
}

tine20/Tasks/js/DependentTasksPanel.js

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
2323
this.editDialogConfig = this.editDialogConfig || {};
2424
this.editDialogConfig.mode = this.editDialogConfig.mode || 'load(remote):save(local)';
2525
this.editDialogConfig.dependendTaskPanel = this;
26+
this.viewConfig = _.assign(this.viewConfig || {},{
27+
getRowClass: this.getRowClass.createDelegate(this)
28+
});
29+
2630
this.columns = [
2731
{
2832
id: 'summary',
@@ -32,8 +36,7 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
3236
sortable: true,
3337
quickaddField: new Ext.form.TextField({
3438
emptyText: this.app.i18n._('Add a task...')
35-
}),
36-
sortable: true
39+
})
3740
}, {
3841
id: 'due',
3942
header: this.app.i18n._("Due Date"),
@@ -43,7 +46,6 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
4346
editor: new Ext.ux.form.ClearableDateField({
4447
//format : 'd.m.Y'
4548
}),
46-
sortable: true,
4749
quickaddField: new Ext.ux.form.ClearableDateField({
4850
//value: new Date(),
4951
//format : "d.m.Y"
@@ -64,8 +66,7 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
6466
quickaddField: new Tine.Tinebase.widgets.keyfield.ComboBox({
6567
app: 'Tasks',
6668
keyFieldName: 'taskPriority'
67-
}),
68-
sortable: true
69+
})
6970
}, {
7071
id: 'dependens_on',
7172
header: this.app.i18n._("Depends on"),
@@ -85,8 +86,7 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
8586
sortable: true,
8687
quickaddField: new Ext.ux.PercentCombo({
8788
autoExpand: true
88-
}),
89-
sortable: true
89+
})
9090
}, {
9191
id: 'status',
9292
header: this.app.i18n._("Status"),
@@ -123,5 +123,67 @@ export default Ext.extend(Tine.widgets.grid.QuickaddGridPanel, {
123123
];
124124

125125
this.supr().initComponent.call(this);
126+
127+
if (this.filter) {
128+
this.filterButton = new Ext.Toolbar.Button({
129+
enableToggle: true,
130+
pressed: true,
131+
stateful: true,
132+
stateId: 'my-dependent-tasks-filter',
133+
stateEvents: ['toggle'],
134+
iconCls: 'action_filter',
135+
text: this.app.i18n._('My open tasks'),
136+
listeners: {
137+
toggle: this.applyFilter,
138+
scope: this
139+
}
140+
});
141+
this.getBottomToolbar().add('->', this.filterButton);
142+
this.editDialog.on('load', this.applyFilter, this);
143+
}
144+
},
145+
146+
applyFilter: async function() {
147+
this.filteredData = null;
148+
149+
if (this.filterButton.pressed) {
150+
const {results: filteredData} = await Tine.Tasks.searchTasks(this.filter);
151+
this.filteredData = filteredData;
152+
153+
this.store.filterBy((r) => {
154+
return _.find(filteredData, {id: r.id});
155+
})
156+
} else {
157+
this.store.clearFilter();
158+
}
159+
},
160+
161+
/**
162+
* Return CSS class to apply to rows depending upon due status
163+
*
164+
* @param {Tine.Tasks.Model.Task} record
165+
* @param {Integer} index
166+
* @return {String}
167+
*/
168+
getRowClass: function(record, index) {
169+
let classNames = [];
170+
171+
const due = record.get('due');
172+
173+
if(record.get('status') == 'COMPLETED') {
174+
classNames.push('tasks-grid-completed');
175+
} else if (due) {
176+
var dueDay = due.format('Y-m-d');
177+
var today = new Date().format('Y-m-d');
178+
179+
if (dueDay == today) {
180+
classNames.push('tasks-grid-duetoday');
181+
} else if (dueDay < today) {
182+
classNames.push('tasks-grid-overdue');
183+
}
184+
185+
}
186+
187+
return _.join(_.uniq(classNames), ' ');
126188
}
127189
});

tine20/Tinebase/js/ux/Stringable.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Stringable extends String {
2+
constructor (s, toString) {
3+
super(s)
4+
this.toString = toString
5+
}
6+
}
7+
8+
export default Stringable

tine20/Tinebase/js/widgets/grid/QuickaddGridPanel.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,9 @@ Tine.widgets.grid.QuickaddGridPanel = Ext.extend(Ext.ux.grid.QuickaddGridPanel,
252252
* @return {Array}
253253
*/
254254
getFromStoreAsArray: function(deleteAutoIds) {
255-
var result = Tine.Tinebase.common.assertComparable([]);
256-
this.store.each(function(record) {
255+
const result = Tine.Tinebase.common.assertComparable([]);
256+
const data = this.store.snapshot || this.store;
257+
data.each(function(record) {
257258
var data = record.data;
258259
if (deleteAutoIds && String(data.id).match(/ext-gen/)) {
259260
delete data.id;

tine20/library/ExtJS/src/data/JsonReader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
226226
var s = this.meta, Record = this.recordType,
227227
f = Record.prototype.fields, fi = f.items, fl = f.length, v;
228228

229-
var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
229+
var root = this.getRoot(o) || [], c = root.length, totalRecords = c, success = true;
230230
if(s.totalProperty){
231231
v = parseInt(this.getTotal(o), 10);
232232
if(!isNaN(v)){

tine20/library/ExtJS/src/widgets/Button.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,10 @@ Ext.Button = Ext.extend(Ext.BoxComponent, {
549549
return this;
550550
},
551551

552+
getState: function() {
553+
return {pressed: this.pressed}
554+
},
555+
552556
/**
553557
* Focus the button
554558
*/

0 commit comments

Comments
 (0)