Преглед изворни кода

Ticket #5425 - Tasks: Functionality improvements. Stage 2.

AntonLV пре 5 месеци
родитељ
комит
aec4f9275f

+ 16 - 0
modules/boonex/russian/data/langs/bx_tasks/ru.xml

@@ -163,6 +163,18 @@
     <string name="_bx_tasks_category_form_entry_input_value_err"><![CDATA[Значение обязательно. Пожалуйста, заполните это поле]]></string>
     <string name="_bx_tasks_lmi_cpt_categories"><![CDATA[Категории]]></string>
 
+    <string name="_bx_tasks_form_f_input_author"><![CDATA[Автор]]></string>
+    <string name="_bx_tasks_form_f_input_initial_members"><![CDATA[Назначено на]]></string>
+    <string name="_bx_tasks_form_f_input_type"><![CDATA[Тип]]></string>
+    <string name="_bx_tasks_form_f_input_priority"><![CDATA[Приоритет]]></string>
+    <string name="_bx_tasks_form_f_input_estimate"><![CDATA[Временные оценки]]></string>
+    <string name="_bx_tasks_form_f_input_state"><![CDATA[Состояние]]></string>
+    <string name="_bx_tasks_form_f_input_save"><![CDATA[Сохраните для дальнейшего использования]]></string>
+    <string name="_bx_tasks_form_f_input_title"><![CDATA[Заголовок]]></string>
+    <string name="_bx_tasks_form_f_input_apply"><![CDATA[Применить]]></string>
+    <string name="_bx_tasks_form_f_input_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_tasks_popup_f_title_add"><![CDATA[Добавить фильтр]]></string>
+    
     <!-- ACL -->
     <string name="_bx_tasks_acl_action_create_entry"><![CDATA[Создать Задание]]></string>
     <string name="_bx_tasks_acl_action_delete_entry"><![CDATA[Удалить Задание]]></string>
@@ -539,6 +551,10 @@
     <string name="_bx_tasks_txt_total"><![CDATA[Потрачено времени: {0}]]></string>
     <string name="_bx_tasks_txt_estimate"><![CDATA[Выделено времени: {0}]]></string>
     <string name="_bx_tasks_txt_set"><![CDATA[set...]]></string>
+    <string name="_bx_tasks_txt_flt_system"><![CDATA[Системные]]></string>
+    <string name="_bx_tasks_txt_flt_my_permanent"><![CDATA[Мои сохраненные]]></string>
+    <string name="_bx_tasks_txt_flt_my_temporary"><![CDATA[Мои временные]]></string>
+    <string name="_bx_tasks_txt_flt_user_current"><![CDATA[Текущий пользователь]]></string>
 
     <string name="_bx_tasks_txt_timer_start"><![CDATA[Старт]]></string>
     <string name="_bx_tasks_txt_timer_stop"><![CDATA[Стоп]]></string>

+ 2 - 0
modules/boonex/tasks/classes/BxTasksConfig.php

@@ -191,6 +191,7 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_PRE_LIST_TYPES' => 'bx_tasks_types',
             'OBJECT_PRE_LIST_STICKERS' => 'bx_tasks_stickers',
             'OBJECT_PRE_LIST_PRIORITIES' => 'bx_tasks_priorities',
+            'OBJECT_PRE_LIST_ESTIMATES' => 'bx_tasks_estimates',
             'OBJECT_PRE_LIST_STATES' => 'bx_tasks_states',
             
             'BADGES_AVALIABLE' => true,
@@ -291,6 +292,7 @@ class BxTasksConfig extends BxBaseModTextConfig
             'tasks' => $sPrefix . '-tasks',
             'time_popup' => $sPrefix . '-time-popup',
             'total_popup' => $sPrefix . '-total-popup',
+            'filter_popup' => $sPrefix . '-filter-popup',
             'timer' => $sPrefix . '-timer-',
             'timer_actions' => $sPrefix . '-timer-actions',
             'pre_values_popup' => $sPrefix . '-pv-popup-',

+ 31 - 1
modules/boonex/tasks/classes/BxTasksDb.php

@@ -243,7 +243,7 @@ class BxTasksDb extends BxBaseModTextDb
         $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
         $sSelectClause = '`tf`.*';
         $sJoinClause = $sWhereClause = '';
-        $sOrderClause = '`tf`.`order` ASC';
+        $sOrderClause = '`tf`.`added` ASC, `tf`.`permanent` ASC';
 
         if(!empty($aParams))
             switch($aParams['sample']) {
@@ -269,6 +269,25 @@ class BxTasksDb extends BxBaseModTextDb
 
                 case 'active':
                     $sWhereClause .= " AND `tf`.`active` <> '0'";
+
+                    if(($iContextId = $aParams['context_id'] ?? false) !== false) {
+                        $aMethod['params'][1]['context_id'] = $iContextId;
+                        
+                        $sWhereClause .= " AND `tf`.`context_id` = :context_id";
+                    }
+
+                    if(($iAuthor = $aParams['author'] ?? false) !== false) {
+                        $aMethod['params'][1]['author'] = $iAuthor;
+
+                        $sWhereClause .= " AND `tf`.`author` = :author";
+                    }
+
+                    if(($iPermanent = $aParams['permanent'] ?? false) !== false) {
+                        $aMethod['params'][1]['permanent'] = $iPermanent;
+
+                        $sWhereClause .= " AND `tf`.`permanent` = :permanent";
+                    }
+
                     break;
             }
 
@@ -295,6 +314,17 @@ class BxTasksDb extends BxBaseModTextDb
         return $aFilter;
     }
 
+    public function cleanFilters($iContextId, $iAuthor, $iLifetime = 3600) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->query("DELETE FROM `" . $CNF['TABLE_FILTERS'] . "` WHERE `context_id` = :context_id AND `author` = :author AND UNIX_TIMESTAMP() - `added` > :lifetime AND `permanent` = '0'", [
+            'context_id' => $iContextId,
+            'author' => $iAuthor,
+            'lifetime' => $iLifetime
+        ]);
+    }
+
     public function getTimeTracks($aParams = []) 
     {
         $CNF = &$this->_oConfig->CNF;

+ 286 - 17
modules/boonex/tasks/classes/BxTasksModule.php

@@ -63,19 +63,7 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
 
     public function actionApplyFilter($iContextId, $iFilterId)
     {
-        $CNF = &$this->_oConfig->CNF;
-        $sCookieKey = $CNF['COOKIE_SETTING_KEY'];
-
-        $aFilters = [];
-        if(isset($_COOKIE[$sCookieKey]))
-            $aFilters = json_decode($_COOKIE[$sCookieKey], true);
-
-        if(($iFilterId = (int)$iFilterId) != 0)
-            $aFilters[$iContextId] = $iFilterId;
-        else
-            unset($aFilters[$iContextId]);
-
-        bx_setcookie($sCookieKey, json_encode($aFilters), time() + 60 * 60 * 24 * 365);
+        $this->applyFilter($iContextId, $iFilterId);
 
         return echoJson([
             'content' => $this->_oTemplate->getEntries($iContextId, ['filter' => $iFilterId]),
@@ -83,6 +71,76 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         ]);
     }
 
+    public function actionCreateFilter($iContextId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sJsObject = $this->_oConfig->getJsObject('tasks');
+        
+        $iAuthorId = bx_get_logged_profile_id();
+
+        $oForm = $this->_getFilterForm($iContextId);
+        if(!$oForm)
+            return echoJson([]);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = [
+                'context_id' => $iContextId,
+                'author' => $iAuthorId, 
+                'added' => time()
+            ];
+
+            $sTitle = '';
+            $aValues = [];
+            foreach($oForm->aInputs as $sName => $aInput) {
+                if(in_array($sName, ['save', 'title', 'controls']))
+                    continue;
+
+                if(($mixedValue = $oForm->getCleanValue($sName))) {
+                    $sFltTitle = '';
+                    if(($aFltValues = $aInput['values'] ?? false) && is_array($aFltValues))
+                        $sFltTitle = ' (' . (is_array($mixedValue) ? implode(', ', array_intersect_key($aFltValues, array_flip($mixedValue))) : $aFltValues[$mixedValue]) . ')';
+
+                    $sTitle .= $aInput['caption'] . $sFltTitle . ' + ';
+                    $aValues[] = [
+                        'f' => $sName,
+                        'v' => $mixedValue
+                    ];
+                }
+            }
+
+            if($aValues && is_array($aValues) && ($aCnds = $this->_getFilterConditions($iContextId, $aValues)))
+                $aValsToAdd['conditions'] = json_encode($aCnds);
+
+            if($oForm->getCleanValue('save') == 'on') {
+                $aValsToAdd = array_merge($aValsToAdd, [
+                    'title' => $oForm->getCleanValue('title') ?: $sFltTitle,
+                    'permanent' => 1
+                ]);
+            }
+            else
+                $aValsToAdd['title'] = trim($sTitle, " +");
+
+            $aRes = [];
+            if(($iId = (int)$oForm->insert($aValsToAdd)) != 0) {
+                $this->applyFilter($iContextId, $iId);
+
+                $aRes = ['reload' => 1];
+            }
+            else
+                $aRes = ['msg' => _t('_bx_tasks_txt_err_cannot_perform_action')];
+
+            return echoJson($aRes);
+        }
+
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oConfig->getHtmlIds('filter_popup'), _t('_bx_tasks_popup_f_title_add'), $this->_oTemplate->parseHtmlByName('popup_form.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true)
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
     /*
      * Moved from popup to page
      * 
@@ -146,11 +204,11 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
             $sPopupTitle = _t('_bx_tasks_form_list_entry_display_edit');
         }
 
-        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'process_task_list_form/' . $iContextId . '/' . $iId . '/';
-        if (!$oForm)
+        if(!$oForm)
             return '';
-		
-        $oForm->initChecker($aContentInfo, array());
+
+	$oForm->setAction(BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'process_task_list_form/' . $iContextId . '/' . $iId . '/');
+        $oForm->initChecker($aContentInfo, []);
         if($oForm->isSubmittedAndValid()) {
             if ($iId == 0){
                 $aValsToAdd['context_id'] = $iContextId;
@@ -1358,6 +1416,22 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         return $sResult;
     }
 
+    public function applyFilter($iContextId, $iFilterId)
+    {
+        $sCookieKey = $this->_oConfig->CNF['COOKIE_SETTING_KEY'];
+
+        $aFilters = [];
+        if(isset($_COOKIE[$sCookieKey]))
+            $aFilters = json_decode($_COOKIE[$sCookieKey], true);
+
+        if(($iFilterId = (int)$iFilterId) != 0)
+            $aFilters[$iContextId] = $iFilterId;
+        else
+            unset($aFilters[$iContextId]);
+
+        bx_setcookie($sCookieKey, json_encode($aFilters), time() + 60 * 60 * 24 * 365);
+    }
+
     /**
      * Internal methods
      */
@@ -1395,6 +1469,201 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         if($iCompleted != (int)$this->_oConfig->isCompleted($aContentInfo[$CNF['FIELD_STATE']]))
             $this->complete($iContentId, $iCompleted);
     }
+
+    protected function _getFilterConditions($iContextId = 0, $aItems = [])
+    {
+        $iJoins = 0;
+        $aResult = [
+            'join' => [],
+            'where' => []
+        ];
+
+        if(count($aItems) > 1) {
+            $aResult['where'] = ['grp' => true, 'o' => 'AND', 'cnds' => []];
+
+            foreach($aItems as $aItem)
+                if(($mixedCnd = $this->_getFilterCondition($iContextId, $aItem)) !== false) {
+                    if(isset($mixedCnd['join'])) {
+                        $iJoins++;
+                        $aResult['join'][] = $mixedCnd['join'];
+                    }
+
+                    if(isset($mixedCnd['where']))
+                        $aResult['where']['cnds'][] = $mixedCnd['where'];
+                }
+        }
+        else {
+            $mixedCnd = $this->_getFilterCondition($iContextId, reset($aItems));
+
+            if(isset($mixedCnd['join'])) {
+                $iJoins++;
+                $aResult['join'][] = $mixedCnd['join'];
+            }
+
+            if(isset($mixedCnd['where']))
+                $aResult['where'] = $mixedCnd['where'];
+        }
+
+        if($iJoins == 1)
+            $aResult['join'] = reset($aResult['join']);
+        else if($iJoins > 1)
+            $aResult['join'] = ['grp' => true, 'cnds' => $aResult['join']];
+
+        return $aResult;
+    }
+
+    protected function _getFilterCondition($iContextId = 0, $aItem = [])
+    {
+        $sMethod = '_getFilterCondition' . bx_gen_method_name($aItem['f']);
+        if(method_exists($this, $sMethod))
+            return $this->$sMethod($iContextId, $aItem);
+
+        $mixedV = $mixedO = false;
+        if(!($bArray = is_array($aItem['v'])) || count($aItem['v']) == 1) {
+            $mixedV = $bArray ? reset($aItem['v']) : $aItem['v'];
+            $mixedO = '=';
+        }
+        else {
+            $mixedV = $aItem['v'];
+            $mixedO = 'IN';
+        }
+
+        if(!$mixedV || !$mixedO)
+            return false;
+
+        return [
+            'where' => ['cnd' => true, 't' => 'te', 'f' => $aItem['f'], 'v' => $mixedV, 'o' => $mixedO]
+        ];
+    }
+    
+    protected function _getFilterConditionInitialMembers ($iContextId = 0, $aItem = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return [
+            'join' => ['cnd' => true, 'j' => 'LEFT', 'tj' => $CNF['TABLE_ASSIGNMENTS'], 'taj' => 'ta', 'fj' => 'content', 'tam' => 'te', 'fm' => 'id'],
+            'where' => ['cnd' => true, 't' => 'ta', 'f' => 'initiator', 'v' => $aItem['v'], 'o' => '=']
+        ];
+    }
+
+    protected function _getFilterForm($iContextId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sJsObject = $this->_oConfig->getJsObject('tasks');
+        
+        $aFieldToList = [
+            $CNF['FIELD_TYPE'] => $CNF['OBJECT_PRE_LIST_TYPES'],
+            $CNF['FIELD_PRIORITY'] => $CNF['OBJECT_PRE_LIST_PRIORITIES'],
+            $CNF['FIELD_ESTIMATE'] => $CNF['OBJECT_PRE_LIST_ESTIMATES'],
+            $CNF['FIELD_STATE'] => $CNF['OBJECT_PRE_LIST_STATES']
+        ];
+
+        $aForm = [
+            'form_attrs' => [
+                'id' => 'bx-tasks-filter-add',
+                'action' => BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'create_filter/' . $iContextId,
+                'method' => BX_DOL_FORM_METHOD_DEFAULT
+            ],
+            'params' => [
+                'db' => [
+                    'table' => $CNF['TABLE_FILTERS'],
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_apply'
+                ],
+            ],
+            'inputs' => [
+                $CNF['FIELD_AUTHOR'] => [
+                    'type' => 'radio_set',
+                ],
+                $CNF['FIELD_INITIAL_MEMBERS'] => [
+                    'type' => 'radio_set',
+                ],
+                $CNF['FIELD_TYPE'] => [
+                    'type' => 'checkbox_set',
+                ],
+                $CNF['FIELD_PRIORITY'] => [
+                    'type' => 'checkbox_set',
+                ],
+                $CNF['FIELD_ESTIMATE'] => [
+                    'type' => 'checkbox_set',
+                ],
+                $CNF['FIELD_STATE'] => [
+                    'type' => 'checkbox_set',
+                ],
+                'save' => [
+                    'type' => 'switcher',
+                    'name' => 'save',
+                    'caption' => _t('_bx_tasks_form_f_input_save'),
+                    'value' => 'on',
+                    'attrs' => [
+                        'onchange' => $sJsObject . '.onChangeSave(this)'
+                    ]
+                ],
+                'title' => [
+                    'type' => 'text',
+                    'name' => 'title',
+                    'caption' => _t('_bx_tasks_form_f_input_title'),
+                    'value' => '',
+                    'required' => '0',
+                    'tr_attrs' => [
+                        'class' => 'bx-tasks-ffi-hidden'
+                    ]
+                ],
+                'controls' => [
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    [
+                        'type' => 'submit',
+                        'name' => 'do_apply',
+                        'value' => _t('_bx_tasks_form_f_input_apply'),
+                    ], [
+                        'type' => 'reset',
+                        'name' => 'do_cancel',
+                        'value' => _t('_bx_tasks_form_f_input_cancel'),
+                        'attrs' => [
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                            'class' => 'bx-def-margin-sec-left',
+                        ],
+                    ]
+                ]
+            ]
+        ];
+
+        foreach($aForm['inputs'] as $sName => $aInput) {
+            if(isset($aInput['name']))
+                continue;
+
+            $aForm['inputs'][$sName] = array_merge($aForm['inputs'][$sName], [
+                'name' => $sName,
+                'caption' => _t('_bx_tasks_form_f_input_' . $sName),
+                'value' => '',
+                'values' => []
+            ]);
+
+            if(($sList = $aFieldToList[$sName] ?? false) && ($aList = BxDolForm::getDataItems($sList))) {
+                if(isset($aList['']))
+                    unset($aList['']);
+
+                $aForm['inputs'][$sName]['values'] = $aList;
+            }
+        }
+
+        foreach([$CNF['FIELD_AUTHOR'], $CNF['FIELD_INITIAL_MEMBERS']] as $sK)
+            if(isset($aForm['inputs'][$sK])) {
+                $aForm['inputs'][$sK]['values'][$this->_iProfileId] = _t('_bx_tasks_txt_flt_user_current');
+
+                if($iContextId && ($oContext = BxDolProfile::getInstance($iContextId)) !== false) {
+                    $aMembers = bx_srv($oContext->getModule(), 'fans', [$oContext->getContentId(), true]);
+                    foreach($aMembers as $iMember)
+                        if(($iMember != $this->_iProfileId) && ($oMember = BxDolProfile::getInstance($iMember)) !== false && !($oMember instanceof BxDolProfileUndefined))
+                            $aForm['inputs'][$sK]['values'][$iMember] = $oMember->getDisplayName();
+                }
+            }
+
+        return new BxTemplFormView($aForm);
+    }
 }
 
 /** @} */

+ 50 - 18
modules/boonex/tasks/classes/BxTasksTemplate.php

@@ -239,35 +239,25 @@ class BxTasksTemplate extends BxBaseModTextTemplate
             'actions' => $oActions->getCode()
         ]);
     }
-
+   
     public function getEntriesList($iContextId = 0, $aParams = [])
     {
         $CNF = &$this->_oConfig->CNF;
         $sJsObject = $this->_oConfig->getJsObject('tasks');
 
+        $iLoggedId = bx_get_logged_profile_id();
         $oModule = $this->getModule();
 
         $bContext = !empty($iContextId);
         $bAllowAdd = $bContext && $oModule->isAllowAdd($iContextId);
 
-        $aTmplVarsFilterItems = [];
-
         $aFilters = [];
-        if(($sK = $CNF['COOKIE_SETTING_KEY']) && isset($_COOKIE[$sK]))
-            $aFilters = json_decode($_COOKIE[$sK], true);
+        if(($sFilters = $_COOKIE[$CNF['COOKIE_SETTING_KEY']] ?? false))
+            $aFilters = json_decode($sFilters, true);
         $iFilterSelected = $aParams['filter'] ?? ($aFilters[$iContextId] ?? 0);
 
-        $aFilterItems = array_merge([['id' => 0, 'title' => '_bx_tasks_filter_title_select']], $this->_oDb->getFilters(['sample' => 'active']));
-        foreach($aFilterItems as $aFilterItem)
-            $aTmplVarsFilterItems[] = [
-                'filter_id' => $aFilterItem['id'],
-                'filter_title' => _t($aFilterItem['title']),
-                'bx_if:show_filter_selected' => [
-                    'condition' => $aFilterItem['id'] == $iFilterSelected,
-                    'content' => [true]
-                ]
-            ];
-
+        $sFilters = $this->_getEntriesFilters($iContextId, array_merge($aParams, ['filter_selected' => $iFilterSelected]));
+        
         $this->addCssJs();
         $this->addJs([
             'jquery-ui/jquery-ui.min.js',
@@ -279,11 +269,11 @@ class BxTasksTemplate extends BxBaseModTextTemplate
             'object' => $sJsObject,
             'context_id' => $iContextId,
             'bx_if:show_filters' => [
-                'condition' => !empty($aTmplVarsFilterItems),
+                'condition' => !empty($sFilters),
                 'content' => [
                     'object' => $sJsObject,
                     'context_id' => $iContextId,
-                    'bx_repeat:filter_items' => $aTmplVarsFilterItems
+                    'filters' => $sFilters
                 ]
             ],
             'bx_if:allow_add_list' => [
@@ -495,6 +485,48 @@ class BxTasksTemplate extends BxBaseModTextTemplate
             'bx_repeat:stickers' => $aTmplVarsStickers
         ]);
     }
+
+    protected function _getEntriesFilters($iContextId = 0, $aParams = [])
+    {
+        $sJsObject = $this->_oConfig->getJsObject('tasks');
+        $iLoggedId = bx_get_logged_profile_id();
+
+        $this->_oDb->cleanFilters($iContextId, $iLoggedId);
+
+        $aInput = [
+            'type' => 'select', 
+            'name' => 'filters',
+            'value' => $aParams['filter_selected'] ?? 0,
+            'values' => [
+                ['key' => 0, 'value' => _t('_bx_tasks_filter_title_select')]
+            ],  
+            'attrs' => [
+                'onchange' => $sJsObject . '.applyFilter(this, ' . $iContextId . ')'
+            ]
+        ];
+
+        $aTypes = [
+            '_bx_tasks_txt_flt_system' => ['sample' => 'active', 'context_id' => 0, 'author' => 0],
+            '_bx_tasks_txt_flt_my_permanent' => ['sample' => 'active', 'context_id' => $iContextId, 'author' => $iLoggedId, 'permanent' => 1],
+            '_bx_tasks_txt_flt_my_temporary' => ['sample' => 'active', 'context_id' => $iContextId, 'author' => $iLoggedId, 'permanent' => 0]
+        ];
+
+        foreach($aTypes as $sTitle => $aType) {
+            $aFilterItems = $this->_oDb->getFilters($aType);
+            if($aFilterItems && is_array($aFilterItems)) {
+                $aInput['values'][] = ['type' => 'group_header', 'value' => _t($sTitle)];
+                foreach($aFilterItems as $aFilterItem)
+                    $aInput['values'][] = [
+                        'key' => $aFilterItem['id'], 
+                        'value' => _t($aFilterItem['title'])
+                    ];
+                $aInput['values'][] = ['type' => 'group_end'];
+            }
+        }
+
+        $oForm = new BxTemplFormView([]);
+        return $oForm->genInputSelect($aInput);
+    }
 }
 
 /** @} */

+ 16 - 0
modules/boonex/tasks/install/langs/en.xml

@@ -163,6 +163,18 @@
     <string name="_bx_tasks_category_form_entry_input_value_err"><![CDATA[Value is essential. Please, fill in this field]]></string>
     <string name="_bx_tasks_lmi_cpt_categories"><![CDATA[Categories]]></string>
 
+    <string name="_bx_tasks_form_f_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_tasks_form_f_input_initial_members"><![CDATA[Assigned to]]></string>
+    <string name="_bx_tasks_form_f_input_type"><![CDATA[Type]]></string>
+    <string name="_bx_tasks_form_f_input_priority"><![CDATA[Priority]]></string>
+    <string name="_bx_tasks_form_f_input_estimate"><![CDATA[Estimate]]></string>
+    <string name="_bx_tasks_form_f_input_state"><![CDATA[State]]></string>
+    <string name="_bx_tasks_form_f_input_save"><![CDATA[Save for future usage]]></string>
+    <string name="_bx_tasks_form_f_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_tasks_form_f_input_apply"><![CDATA[Apply]]></string>
+    <string name="_bx_tasks_form_f_input_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_tasks_popup_f_title_add"><![CDATA[Add Filter]]></string>
+    
     <!-- ACL -->
     <string name="_bx_tasks_acl_action_create_entry"><![CDATA[Create Task]]></string>
     <string name="_bx_tasks_acl_action_delete_entry"><![CDATA[Delete Task]]></string>
@@ -543,6 +555,10 @@
     <string name="_bx_tasks_txt_total"><![CDATA[Total: {0}]]></string>
     <string name="_bx_tasks_txt_estimate"><![CDATA[Estimate: {0}]]></string>
     <string name="_bx_tasks_txt_set"><![CDATA[set...]]></string>
+    <string name="_bx_tasks_txt_flt_system"><![CDATA[System]]></string>
+    <string name="_bx_tasks_txt_flt_my_permanent"><![CDATA[My saved]]></string>
+    <string name="_bx_tasks_txt_flt_my_temporary"><![CDATA[My temporary]]></string>
+    <string name="_bx_tasks_txt_flt_user_current"><![CDATA[Current user]]></string>
 
     <string name="_bx_tasks_txt_timer_start"><![CDATA[Start]]></string>
     <string name="_bx_tasks_txt_timer_stop"><![CDATA[Stop]]></string>

+ 8 - 7
modules/boonex/tasks/install/sql/install.sql

@@ -102,22 +102,23 @@ CREATE TABLE IF NOT EXISTS `bx_tasks_pre_values` (
 -- TABLE: filters
 CREATE TABLE IF NOT EXISTS `bx_tasks_filters` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
+  `context_id` int(11) NOT NULL DEFAULT '0',
   `author` int(11) NOT NULL DEFAULT '0',
   `added` int(11) NOT NULL DEFAULT '0',
   `title` varchar(255) NOT NULL,
   `conditions` text NOT NULL,
+  `permanent` TINYINT NOT NULL DEFAULT '0',
   `active` TINYINT NOT NULL DEFAULT '1',
-  `order` int(11) NOT NULL DEFAULT '0',
   PRIMARY KEY (`id`)
 );
 
-INSERT INTO `bx_tasks_filters` (`author`, `added`, `title`, `conditions`, `active`, `order`) VALUES
+INSERT INTO `bx_tasks_filters` (`author`, `added`, `title`, `conditions`, `permanent`, `active`) VALUES
 ('0', '0', '_bx_tasks_filter_title_by_author', '{"where":{"cnd":true,"t":"te","f":"author","v":"{logged_pid}","o":"="}}', 1, 1), 
-('0', '0', '_bx_tasks_filter_title_by_author_uncompleted', '{"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"te","f":"author","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"0","o":"="}]}}', 1, 2),
-('0', '0', '_bx_tasks_filter_title_by_author_completed', '{"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"te","f":"author","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"1","o":"="}]}}', 1, 3),
-('0', '0', '_bx_tasks_filter_title_by_assignee', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="}}', 1, 4),
-('0', '0', '_bx_tasks_filter_title_by_assignee_uncompleted', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"0","o":"="}]}}', 1, 5),
-('0', '0', '_bx_tasks_filter_title_by_assignee_completed', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"1","o":"="}]}}', 1, 6);
+('0', '0', '_bx_tasks_filter_title_by_author_uncompleted', '{"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"te","f":"author","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"0","o":"="}]}}', 1, 1),
+('0', '0', '_bx_tasks_filter_title_by_author_completed', '{"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"te","f":"author","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"1","o":"="}]}}', 1, 1),
+('0', '0', '_bx_tasks_filter_title_by_assignee', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="}}', 1, 1),
+('0', '0', '_bx_tasks_filter_title_by_assignee_uncompleted', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"0","o":"="}]}}', 1, 1),
+('0', '0', '_bx_tasks_filter_title_by_assignee_completed', '{"join":{"cnd":true,"j":"LEFT","tj":"bx_tasks_assignments","taj":"ta","fj":"content","tam":"te","fm":"id"},"where":{"grp":true,"o":"AND","cnds":[{"cnd":true,"t":"ta","f":"initiator","v":"{logged_pid}","o":"="},{"cnd":true,"t":"te","f":"completed","v":"1","o":"="}]}}', 1, 1);
 
 -- TABLE: storages & transcoders
 CREATE TABLE IF NOT EXISTS `bx_tasks_covers` (

+ 26 - 0
modules/boonex/tasks/js/tasks.js

@@ -160,6 +160,32 @@ BxTasksView.prototype.onApplyFilter = function (oData) {
     }
 };
 
+BxTasksView.prototype.createFilter = function (oSource, iContextId) {
+    var $this = this;
+    var oDate = new Date();
+
+    this.loadingInBlock(oSource, true);
+
+    $.get(
+        this._oOptions.sActionUrl + 'create_filter/' + iContextId, 
+        {
+            _t: oDate.getTime()
+        },
+        function(oData) {
+            $this.loadingInBlock(oSource, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+
+    return false;
+};
+
+BxTasksView.prototype.onChangeSave = function (oElement) {
+    $(oElement).parents('form').find('#bx-form-element-title').toggleClass('bx-tasks-ffi-hidden', !$(oElement).prop('checked'));
+};
+
 BxTasksView.prototype.loadingInBlock = function(e, bShow) {
     var oParent = $(e).length ? $(e).parents('.bx-db-container:first') : $('body'); 
     bx_loading(oParent, bShow);

+ 6 - 5
modules/boonex/tasks/template/browse_tasks.html

@@ -1,10 +1,11 @@
 <div class="flex justify-between">
     <bx_if:show_filters>
-        <select class="bx-btn bx-btn-small bx-def-margin-sec-right" onchange="__object__.applyFilter(this, __context_id__)">
-            <bx_repeat:filter_items>
-                <option value="__filter_id__" <bx_if:show_filter_selected>selected="selected"</bx_if:show_filter_selected>>__filter_title__</option>
-            </bx_repeat:filter_items>
-        </select>
+        <div class="bx-tasks-tasklist-filters flex flex-nowrap">
+            __filters__
+            <a class="bx-btn bx-btn-small bx-def-margin-sec-right" href="javascript:void(0);" onclick="__object__.createFilter(this, __context_id__)">
+                <bx_image_auto:plus />
+            </a>
+        </div>
     </bx_if:show_filters>
     <span>
         <bx_if:allow_add_list>

+ 20 - 0
modules/boonex/tasks/template/css/main.css

@@ -94,6 +94,26 @@
     width: 800px;
 }
 
+/*-- Tasks List: Filters List --*/
+.bx-tasks-tasklist-filters select.bx-form-input-select {
+    position: relative;
+    display: inline-flex;
+    max-width: 10rem;
+    margin-right: 0.5rem;
+    padding-top: 0.25rem;
+    padding-bottom: 0.25rem;
+    line-height: 1.0rem;
+    font-size: 0.75rem;
+}
+
+/*-- Tasks List: Filter Form --*/
+.bx-media-desktop #bx-tasks-filter-popup .bx-tasks-form-cnt {
+    width: auto;
+}
+.bx-tasks-ffi-hidden {
+    display: none;
+}
+
 /*--- View Task ---*/
 .bx-tasks-stickers .bx-def-label {
     color: #ffffff;