diff --git a/module/api/lang/en.php b/module/api/lang/en.php
index 93d566cd92..212041a21b 100644
--- a/module/api/lang/en.php
+++ b/module/api/lang/en.php
@@ -14,53 +14,53 @@ $lang->api->common = 'API';
$lang->api->getModel = 'Super Model API';
$lang->api->sql = 'SQL Query API';
-$lang->api->index = 'Api Doc Home';
-$lang->api->editLib = 'Edit Api Doc';
-$lang->api->releases = 'Releases';
+$lang->api->index = 'Home';
+$lang->api->editLib = 'Edit';
+$lang->api->releases = 'Release';
$lang->api->deleteRelease = 'Delete Release';
-$lang->api->deleteLib = 'Delete Api Doc';
+$lang->api->deleteLib = 'Delete API Doc';
$lang->api->createRelease = 'Publish';
-$lang->api->createLib = 'Create Api Library';
-$lang->api->createApi = 'Create Api';
+$lang->api->createLib = 'Create API Library';
+$lang->api->createApi = 'Create API Document';
$lang->api->createAB = 'Create';
$lang->api->edit = 'Edit';
$lang->api->delete = 'Delete';
-$lang->api->position = 'Positions';
+$lang->api->position = 'Position';
$lang->api->startLine = "%s,%s";
$lang->api->desc = 'Description';
$lang->api->debug = 'Debug';
$lang->api->submit = 'Submit';
-$lang->api->url = 'Url';
+$lang->api->url = 'URL';
$lang->api->result = 'Result';
$lang->api->status = 'Status';
$lang->api->data = 'Content';
$lang->api->noParam = 'Get debugging does not require input parameters,';
-$lang->api->noModule = 'There is no directory under the interface library. Please maintain the directory first';
+$lang->api->noModule = 'No directory in the API library. Please add the directory first';
$lang->api->post = 'Please refer to the page form for post debugging';
-$lang->api->noUniqueName = 'Api library name already exists。';
-$lang->api->noUniqueVersion = 'Version already exists。';
+$lang->api->noUniqueName = 'The API library name exists.';
+$lang->api->noUniqueVersion = 'The version exists.';
$lang->api->version = 'Version';
$lang->api->createStruct = 'Create Data Structure';
$lang->api->editStruct = 'Edit Data Structure';
$lang->api->deleteStruct = 'Delete Data Structure';
-$lang->api->create = 'Ceate Doc';
-$lang->api->title = 'Interface Name';
-$lang->api->pageTitle = 'Api Library';
+$lang->api->create = 'Create API';
+$lang->api->title = 'Name';
+$lang->api->pageTitle = 'API Library';
$lang->api->module = 'Directory';
-$lang->api->apiDoc = 'Interface';
+$lang->api->apiDoc = 'API';
$lang->api->manageType = 'Manage Directory';
$lang->api->managePublish = 'Manage Version';
$lang->api->doing = 'Doing';
$lang->api->done = 'Done';
-$lang->api->basicInfo = 'Essential Information';
-$lang->api->apiDesc = 'Interface Description';
-$lang->api->confirmDelete = "Are you sure to delete this interface?";
-$lang->api->confirmDeleteLib = "Are you sure to delete this interface library?";
+$lang->api->basicInfo = 'Basic Information';
+$lang->api->apiDesc = 'Description';
+$lang->api->confirmDelete = "Do you want to delete this API?";
+$lang->api->confirmDeleteLib = "Do you want to delete this interface library?";
$lang->api->filterStruct = "use struct";
$lang->api->defaultVersion = "Current Version";
/* Common access control lang. */
-$lang->api->whiteList = 'White list';
+$lang->api->whiteList = 'Whitelist';
$lang->api->aclList['open'] = 'Open';
$lang->api->aclList['private'] = 'Private';
$lang->api->aclList['custom'] = 'Custom';
@@ -68,9 +68,9 @@ $lang->api->group = 'Group';
$lang->api->user = 'User';
$lang->api->noticeAcl = array(
- 'open' => 'Users who can access the api library which the library belongs can access it.',
- 'custom' => 'Users in the whiltelist can access it.',
- 'private' => 'Only the one who created it can access it.',
+ 'open' => 'Users who can access the API library can access it.',
+ 'custom' => 'Users on the whiltelist can access it.',
+ 'private' => 'Only the one who creates it can access it.',
);
/* fields of struct */
@@ -81,7 +81,7 @@ $lang->struct->field = 'Field';
$lang->struct->paramsType = 'Type';
$lang->struct->required = 'Require';
$lang->struct->desc = 'Description';
-$lang->struct->descPlaceholder = 'Parameter description';
+$lang->struct->descPlaceholder = 'Parameter Description';
$lang->struct->action = 'Action';
$lang->struct->addSubField = 'Add Subfield';
@@ -94,36 +94,36 @@ $lang->struct->typeOptions = array(
/* fields of form */
$lang->api->struct = 'Data Structure';
-$lang->api->structName = 'Structure Name';
+$lang->api->structName = 'Name';
$lang->api->structType = 'Type';
$lang->api->structAttr = 'Attribute';
-$lang->api->structAddedBy = 'Creator';
-$lang->api->structAddedDate = 'Created Time';
-$lang->api->name = 'Interface Library Name';
-$lang->api->baseUrl = 'Base Url';
-$lang->api->baseUrlDesc = 'Site or path. for example, api.zentao.com or /v1';
+$lang->api->structAddedBy = 'CreatedBy';
+$lang->api->structAddedDate = 'Created';
+$lang->api->name = 'API Library Name';
+$lang->api->baseUrl = 'Base URL';
+$lang->api->baseUrlDesc = 'Site or path, e.g., api.zentao.com or /v1.';
$lang->api->desc = 'Description';
$lang->api->control = 'Access Control';
-$lang->api->noLib = 'There is no interface library at present。';
-$lang->api->noApi = 'There is no interface for the time being。';
-$lang->api->noStruct = 'There is no structure for the time being。';
-$lang->api->lib = 'Interface Library';
-$lang->api->apiList = 'Interface List';
-$lang->api->formTitle = 'Interface Name';
+$lang->api->noLib = 'No API library yet.';
+$lang->api->noApi = 'No API yet.';
+$lang->api->noStruct = 'No API yet.';
+$lang->api->lib = 'API Library';
+$lang->api->apiList = 'API List';
+$lang->api->formTitle = 'API Name';
$lang->api->path = 'Request Path';
-$lang->api->protocol = 'Request Protocol';
-$lang->api->method = 'Request Method';
-$lang->api->requestType = 'Request Type';
-$lang->api->status = 'Development Status';
-$lang->api->owner = 'Person In Charge';
-$lang->api->paramsExample = 'Response Example';
+$lang->api->protocol = 'Protocol';
+$lang->api->method = 'Method';
+$lang->api->requestType = 'Type';
+$lang->api->status = 'Status';
+$lang->api->owner = 'Owner';
+$lang->api->paramsExample = 'Request Example';
$lang->api->header = 'Request Header';
-$lang->api->query = 'Request Parameters';
+$lang->api->query = 'Parameter';
$lang->api->params = 'Request Body';
$lang->api->response = 'Response';
$lang->api->responseExample = 'Response Example';
$lang->api->res = new stdClass();
-$lang->api->res->name = '名称';
+$lang->api->res->name = 'Name';
$lang->api->res->desc = 'Description';
$lang->api->res->type = 'Type';
$lang->api->req = new stdClass();
@@ -195,14 +195,14 @@ $lang->api->allParamsTypeOptions = array_merge($lang->api->paramsTypeOptions, $l
$lang->api->requiredOptions = array(0 => 'No', 1 => 'Yes');
$lang->doclib = new stdclass();
-$lang->doclib->name = 'Interface Library Name';
+$lang->doclib->name = 'API Library Name';
$lang->apistruct = new stdClass();
-$lang->apistruct->name = 'Structure Name';
+$lang->apistruct->name = 'Name';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = 'Version';
$lang->api->error = new stdclass();
-$lang->api->error->onlySelect = 'SQL interface only allow SELECT query.';
-$lang->api->error->disabled = 'For security reasons, this feature is disabled. You can go to the config directory and modify the configuration item %s to open this function.';
+$lang->api->error->onlySelect = 'SQL API only allows SELECT query.';
+$lang->api->error->disabled = 'For security reasons, this feature is disabled. Go to the config directory and modify the configuration item %s to enable it.';
diff --git a/module/common/lang/common.php b/module/common/lang/common.php
index 57a3c16b1c..ee993c8a48 100644
--- a/module/common/lang/common.php
+++ b/module/common/lang/common.php
@@ -9,6 +9,7 @@ $lang->project = new stdclass();
$lang->scrum = new stdclass();
$lang->waterfall = new stdclass();
$lang->execution = new stdclass();
+$lang->kanban = new stdclass();
$lang->story = new stdclass();
$lang->release = new stdclass();
$lang->branch = new stdclass();
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index 089c0c4521..99a8a185da 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -47,6 +47,7 @@ $lang->designedByAIUX = "reset = 'Reset';
$lang->cancel = 'Cancel';
$lang->refresh = 'Refresh';
+$lang->create = 'Create';
$lang->edit = 'Edit';
$lang->delete = 'Delete';
$lang->close = 'Close';
@@ -177,38 +178,38 @@ $lang->score->shortCommon = 'Score';
$lang->testreport->shortCommon = 'Report';
$lang->qa->shortCommon = 'QA';
-$lang->dashboard = 'Dashboard';
-$lang->contribute = 'Contribute';
-$lang->dynamic = 'Dynamic';
-$lang->contact = 'Contacts';
-$lang->whitelist = 'Whitelist';
-$lang->roadmap = 'Roadmap';
-$lang->track = 'Track';
-$lang->settings = 'Settings';
-$lang->overview = 'Overview';
-$lang->module = 'Module';
-$lang->priv = 'Privilege';
-$lang->design = 'Design';
-$lang->other = 'Other';
-$lang->estimation = 'Estimation';
-$lang->issue = 'Issue';
-$lang->risk = 'Risk';
-$lang->measure = 'Report';
-$lang->treeView = 'Tree View';
-$lang->groupView = 'Group View';
-$lang->kanban = 'Kanban';
-$lang->burn = 'Burndown';
-$lang->view = 'View';
-$lang->intro = 'Introduction';
-$lang->indexPage = 'Index';
-$lang->model = 'Model';
-$lang->redev = 'Develop';
-$lang->browser = 'Browser';
-$lang->db = 'Database';
-$lang->editor = 'Editor';
-$lang->timezone = 'Timezone';
-$lang->security = 'Security';
-$lang->calendar = 'Calendar';
+$lang->dashboard = 'Dashboard';
+$lang->contribute = 'Contribute';
+$lang->dynamic = 'Dynamic';
+$lang->contact = 'Contacts';
+$lang->whitelist = 'Whitelist';
+$lang->roadmap = 'Roadmap';
+$lang->track = 'Track';
+$lang->settings = 'Settings';
+$lang->overview = 'Overview';
+$lang->module = 'Module';
+$lang->priv = 'Privilege';
+$lang->design = 'Design';
+$lang->other = 'Other';
+$lang->estimation = 'Estimation';
+$lang->issue = 'Issue';
+$lang->risk = 'Risk';
+$lang->measure = 'Report';
+$lang->treeView = 'Tree View';
+$lang->groupView = 'Group View';
+$lang->executionKanban = 'Kanban';
+$lang->burn = 'Burndown';
+$lang->view = 'View';
+$lang->intro = 'Introduction';
+$lang->indexPage = 'Index';
+$lang->model = 'Model';
+$lang->redev = 'Develop';
+$lang->browser = 'Browser';
+$lang->db = 'Database';
+$lang->editor = 'Editor';
+$lang->timezone = 'Timezone';
+$lang->security = 'Security';
+$lang->calendar = 'Calendar';
$lang->my->work = 'Work';
@@ -227,8 +228,8 @@ $lang->doc->api = 'API';
$lang->doc->execution = $lang->execution->common;
$lang->doc->custom = 'Custom';
$lang->doc->wiki = 'WIKI';
-$lang->doc->apiDoc = 'Doc';
-$lang->doc->apiStruct = 'Struct';
+$lang->doc->apiDoc = 'API Docuemnt';
+$lang->doc->apiStruct = 'Data Structure';
$lang->product->list = $lang->productCommon . ' List';
$lang->product->kanban = $lang->productCommon . ' Kanban';
diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php
index 61fbac1fa3..e53a8fd484 100644
--- a/module/common/lang/menu.php
+++ b/module/common/lang/menu.php
@@ -246,7 +246,7 @@ if($config->systemMode == 'new') $lang->execution->homeMenu->executionkanban = a
$lang->execution->menu = new stdclass();
$lang->execution->menu->task = array('link' => "{$lang->task->common}|execution|task|executionID=%s", 'subModule' => 'task,tree', 'alias' => 'importtask,importbug');
-$lang->execution->menu->kanban = array('link' => "$lang->kanban|execution|kanban|executionID=%s");
+$lang->execution->menu->kanban = array('link' => "$lang->executionKanban|execution|kanban|executionID=%s");
$lang->execution->menu->burn = array('link' => "$lang->burn|execution|burn|executionID=%s");
$lang->execution->menu->view = array('link' => "$lang->view|execution|grouptask|executionID=%s", 'alias' => 'grouptask,tree,taskeffort,gantt,calendar,relation,maintainrelation');
$lang->execution->menu->story = array('link' => "$lang->SRCommon|execution|story|executionID=%s", 'subModule' => 'story', 'alias' => 'batchcreate,linkstory,storykanban');
@@ -521,6 +521,7 @@ $lang->navGroup->build = 'project';
$lang->navGroup->measrecord = 'project';
$lang->navGroup->milestone = 'project';
+$lang->navGroup->kanban = 'execution';
$lang->navGroup->execution = 'execution';
$lang->navGroup->task = 'execution';
$lang->navGroup->build = 'execution';
diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php
index 59b4a7c9c2..077ff2857b 100644
--- a/module/common/lang/zh-cn.php
+++ b/module/common/lang/zh-cn.php
@@ -47,6 +47,7 @@ $lang->designedByAIUX = "reset = '重填';
$lang->cancel = '取消';
$lang->refresh = '刷新';
+$lang->create = '新建';
$lang->edit = '编辑';
$lang->delete = '删除';
$lang->close = '关闭';
@@ -177,38 +178,38 @@ $lang->score->shortCommon = '积分';
$lang->testreport->shortCommon = '报告';
$lang->qa->shortCommon = 'QA';
-$lang->dashboard = '仪表盘';
-$lang->contribute = '贡献';
-$lang->dynamic = '动态';
-$lang->contact = '联系人';
-$lang->whitelist = '白名单';
-$lang->roadmap = '路线图';
-$lang->track = '矩阵';
-$lang->settings = '设置';
-$lang->overview = '概况';
-$lang->module = '模块';
-$lang->priv = '权限';
-$lang->design = '设计';
-$lang->other = '其他';
-$lang->estimation = '估算';
-$lang->issue = '问题';
-$lang->risk = '风险';
-$lang->measure = '度量';
-$lang->treeView = '树状图';
-$lang->groupView = '分组视图';
-$lang->kanban = '看板';
-$lang->burn = '燃尽图';
-$lang->view = '视图';
-$lang->intro = '介绍';
-$lang->indexPage = '首页';
-$lang->model = '模型';
-$lang->redev = '二次开发';
-$lang->browser = '浏览器';
-$lang->db = '数据库';
-$lang->editor = '编辑器';
-$lang->timezone = '时区';
-$lang->security = '安全';
-$lang->calendar = '日程';
+$lang->dashboard = '仪表盘';
+$lang->contribute = '贡献';
+$lang->dynamic = '动态';
+$lang->contact = '联系人';
+$lang->whitelist = '白名单';
+$lang->roadmap = '路线图';
+$lang->track = '矩阵';
+$lang->settings = '设置';
+$lang->overview = '概况';
+$lang->module = '模块';
+$lang->priv = '权限';
+$lang->design = '设计';
+$lang->other = '其他';
+$lang->estimation = '估算';
+$lang->issue = '问题';
+$lang->risk = '风险';
+$lang->measure = '度量';
+$lang->treeView = '树状图';
+$lang->groupView = '分组视图';
+$lang->executionKanban = '看板';
+$lang->burn = '燃尽图';
+$lang->view = '视图';
+$lang->intro = '介绍';
+$lang->indexPage = '首页';
+$lang->model = '模型';
+$lang->redev = '二次开发';
+$lang->browser = '浏览器';
+$lang->db = '数据库';
+$lang->editor = '编辑器';
+$lang->timezone = '时区';
+$lang->security = '安全';
+$lang->calendar = '日程';
$lang->my->work = '待处理';
diff --git a/module/common/lang/zh-tw.php b/module/common/lang/zh-tw.php
index c155a317a0..ce6620311a 100644
--- a/module/common/lang/zh-tw.php
+++ b/module/common/lang/zh-tw.php
@@ -177,37 +177,37 @@ $lang->score->shortCommon = '積分';
$lang->testreport->shortCommon = '報告';
$lang->qa->shortCommon = 'QA';
-$lang->dashboard = '儀表盤';
-$lang->contribute = '貢獻';
-$lang->dynamic = '動態';
-$lang->contact = '聯繫人';
-$lang->whitelist = '白名單';
-$lang->roadmap = '路線圖';
-$lang->track = '矩陣';
-$lang->settings = '設置';
-$lang->overview = '概況';
-$lang->module = '模組';
-$lang->priv = '權限';
-$lang->design = '設計';
-$lang->other = '其他';
-$lang->estimation = '估算';
-$lang->issue = '問題';
-$lang->risk = '風險';
-$lang->measure = '度量';
-$lang->treeView = '樹狀圖';
-$lang->groupView = '分組視圖';
-$lang->kanban = '看板';
-$lang->burn = '燃盡圖';
-$lang->view = '視圖';
-$lang->intro = '介紹';
-$lang->indexPage = '首頁';
-$lang->model = '模型';
-$lang->redev = '二次開發';
-$lang->browser = '瀏覽器';
-$lang->db = '資料庫';
-$lang->editor = '編輯器';
-$lang->timezone = '時區';
-$lang->security = '安全';
+$lang->dashboard = '儀表盤';
+$lang->contribute = '貢獻';
+$lang->dynamic = '動態';
+$lang->contact = '聯繫人';
+$lang->whitelist = '白名單';
+$lang->roadmap = '路線圖';
+$lang->track = '矩陣';
+$lang->settings = '設置';
+$lang->overview = '概況';
+$lang->module = '模組';
+$lang->priv = '權限';
+$lang->design = '設計';
+$lang->other = '其他';
+$lang->estimation = '估算';
+$lang->issue = '問題';
+$lang->risk = '風險';
+$lang->measure = '度量';
+$lang->treeView = '樹狀圖';
+$lang->groupView = '分組視圖';
+$lang->executionKanban = '看板';
+$lang->burn = '燃盡圖';
+$lang->view = '視圖';
+$lang->intro = '介紹';
+$lang->indexPage = '首頁';
+$lang->model = '模型';
+$lang->redev = '二次開發';
+$lang->browser = '瀏覽器';
+$lang->db = '資料庫';
+$lang->editor = '編輯器';
+$lang->timezone = '時區';
+$lang->security = '安全';
$lang->calendar = '日程';
$lang->my->work = '待處理';
diff --git a/module/common/view/kanban.html.php b/module/common/view/kanban.html.php
index 5b93969df8..65c13b8c6a 100644
--- a/module/common/view/kanban.html.php
+++ b/module/common/view/kanban.html.php
@@ -43,7 +43,7 @@
#kanbanList .kanban-header-col[data-type="doingProject"] + .kanban-header-col[data-type="doingExecution"] {padding: 38px 10px 0;}
#kanbanList .kanban-header-col[data-type="doingProject"]:after {content: attr(data-span-text); display: block; position: absolute; z-index: 10; left: 0; right: -100%; right: calc(-100% - 2px); top: 0; line-height: 36px; text-align: center; font-weight: bold; border-bottom: 2px solid #fff; background-color: #ededed;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-lane-items {height: 100%; display: flex; flex-direction: column; justify-content: center;}
-#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item {background-color: transparent; border: none; padding: 0; text-align: center;}
+#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item {background-color: transparent; border: none; padding: 0; text-align: center; box-shadow: none!important;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item:hover {box-shadow: none;}
#kanbanList .kanban-col[data-type="unclosedProduct"] .kanban-item > .title {white-space: normal;}
#kanbanList .kanban-col[data-type="normalRelease"] .kanban-item > .title {display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; height: 38px;}
diff --git a/module/datatable/lang/en.php b/module/datatable/lang/en.php
index a2253f5f34..0bc8414501 100644
--- a/module/datatable/lang/en.php
+++ b/module/datatable/lang/en.php
@@ -8,11 +8,11 @@ $lang->datatable->reset = 'Reset';
$lang->datatable->custom = 'Customize Columns';
$lang->datatable->customTip = 'Check columns to display and drag columns to order.';
-$lang->datatable->switchToTable = 'Switch to Common Table';
-$lang->datatable->switchToDatatable = 'Switch to Advanced Table';
+$lang->datatable->switchToTable = 'Common Table';
+$lang->datatable->switchToDatatable = 'Advanced Table';
$lang->datatable->required = 'Required';
$lang->datatable->confirmReset = 'Do you want to restore the default settings?';
-$lang->datatable->setGlobal = 'Set as Global';
+$lang->datatable->setGlobal = 'Set Global';
$lang->datatable->resetGlobal = 'Global Reset';
$lang->datatable->branch = 'Branch';
diff --git a/module/doc/lang/en.php b/module/doc/lang/en.php
index b282541b4b..f57d7a6036 100644
--- a/module/doc/lang/en.php
+++ b/module/doc/lang/en.php
@@ -29,12 +29,12 @@ $lang->doc->files = 'Files';
$lang->doc->addedBy = 'Author';
$lang->doc->addedDate = 'Added';
$lang->doc->editedBy = 'UpdatedBy';
-$lang->doc->editedDate = 'UpdatedDate';
+$lang->doc->editedDate = 'Updated';
$lang->doc->version = 'Version';
-$lang->doc->basicInfo = 'Basic Info';
+$lang->doc->basicInfo = 'Basic Information';
$lang->doc->deleted = 'Deleted';
$lang->doc->fileObject = 'Dependent Item';
-$lang->doc->whiteList = 'White List';
+$lang->doc->whiteList = 'Whitelist';
$lang->doc->contentType = 'Format';
$lang->doc->separator = "";
$lang->doc->fileTitle = 'File Name';
@@ -51,7 +51,7 @@ $lang->doc->item = ' Items';
$lang->doc->num = 'Documents';
$lang->doc->searchResult = 'Search Result';
$lang->doc->mailto = 'Mailto';
-$lang->doc->noModule = 'No document in this lib, please create it';
+$lang->doc->noModule = 'No document in this library. Create one.';
$lang->doc->noChapter = 'No chapters or articles in this book. Please add chapters and articles.';
$lang->doc->views = 'Views';
$lang->doc->draft = 'Draft';
@@ -73,7 +73,7 @@ $lang->doc->todayEdited = 'Updated Today';
$lang->doc->pastEdited = 'Total Updated';
$lang->doc->myDoc = 'My Documents';
$lang->doc->myCollection = 'My Favorites';
-$lang->doc->tableContents = 'Catalog';
+$lang->doc->tableContents = 'Directory';
/* Methods list */
$lang->doc->index = 'Document Home';
@@ -91,12 +91,12 @@ $lang->doc->manageType = 'Manage Category';
$lang->doc->editType = 'Edit';
$lang->doc->deleteType = 'Delete';
$lang->doc->addType = 'Add';
-$lang->doc->childType = 'Catalog';
-$lang->doc->catalogName = 'Catalog Name';
+$lang->doc->childType = 'Directory';
+$lang->doc->catalogName = 'Name';
$lang->doc->collect = 'Add Favorite';
$lang->doc->cancelCollection = 'Remove Favorite';
$lang->doc->deleteFile = 'Delete File';
-$lang->doc->menuTitle = 'Menu';
+$lang->doc->menuTitle = 'Direcotory';
$lang->doc->collectAction = 'Add Favorite';
diff --git a/module/execution/control.php b/module/execution/control.php
index 8447df3dc3..5d487439f6 100644
--- a/module/execution/control.php
+++ b/module/execution/control.php
@@ -1825,7 +1825,7 @@ class execution extends control
* Kanban.
*
* @param int $executionID
- * @param string $type
+ * @param string $browseType story|bug|task|all
* @param string $orderBy
* @param string $groupBy
* @access public
@@ -1841,11 +1841,11 @@ class execution extends control
/* Compatibility IE8. */
if(strpos($this->server->http_user_agent, 'MSIE 8.0') !== false) header("X-UA-Compatible: IE=EmulateIE7");
- $kanbanGroup = $this->loadModel('kanban')->getExecutionKanban($executionID, $type, $groupBy);
+ $kanbanGroup = $this->loadModel('kanban')->getExecutionKanban($executionID, $browseType);
if(empty($kanbanGroup))
{
- $this->kanban->createLanes($executionID, $type, $groupBy);
- $kanbanGroup = $this->kanban->getExecutionKanban($executionID);
+ $this->kanban->createLanes($executionID);
+ $kanbanGroup = $this->kanban->getExecutionKanban($executionID, $browseType);
}
$this->execution->setMenu($executionID);
diff --git a/module/execution/css/kanban.css b/module/execution/css/kanban.css
index 133c32a0a1..a04ce818c5 100644
--- a/module/execution/css/kanban.css
+++ b/module/execution/css/kanban.css
@@ -1,14 +1,30 @@
#main > .container {max-width: 1960px!important; padding: 0 10px}
.kanban + .kanban {margin-top: 15px}
+.kanban-item {position: relative;}
.kanban-item > .title { display: block;white-space: nowrap; overflow: hidden; text-overflow: ellipsis}
.kanban-item > .infos {position: relative; margin-top: 5px}
-.kanban-item > .infos > .info + .info {margin-left: 10px}
+.kanban-item > .infos > .info + .info {margin-left: 8px}
.kanban-item > .infos > .info-id,
+.kanban-item > .infos > .info-deadline,
.kanban-item > .infos > .info-estimate {font-size: 12px; position: relative; top: 2px}
.kanban-item > .infos > .label-pri {min-width: 16px; line-height: 14px; height: 16px; padding: 0}
.kanban-item > .infos > .label-severity {transform: scale(.75)}
.kanban-item > .infos > .avatar {position: absolute; right: 0; top: 0}
+.kanban-item > .actions {position: absolute; top: 4px; right: 4px; opacity: 0;}
+.kanban-item:hover > .actions {opacity: 1;}
+.kanban-item > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
+.kanban-item > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
+
+.kanban-header-col > .actions {padding-top: 22px}
+.kanban-header-parent-col .kanban-header-col > .actions {padding-top: 6px; padding-right: 2px;}
+.kanban-header-col > .actions > a {display: block; float: left; width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 4px; opacity: .7;}
+.kanban-header-col > .actions > a:hover {background-color: rgba(0,0,0,.075); opacity: 1;}
+
+.kanban-lane-name > .actions {position: absolute; top: 0; left: 0; right: 0; opacity: 0;}
+.kanban-lane-name:hover > .actions {opacity: 1;}
+.kanban-lane-name > .actions > a {display: block; width: 20px; height: 20px; line-height: 20px; text-align: center; opacity: .7; color: #fff;}
+.kanban-lane-name > .actions > a:hover {background-color: rgba(0,0,0,.2); opacity: 1;}
.kanban-affixed .kanban-header-col > .title > .text,
.kanban-affixed .kanban-header-col > .title > .count {color: #fff!important;}
diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js
index 1b1358a3c0..d4b47848ac 100644
--- a/module/execution/js/kanban.js
+++ b/module/execution/js/kanban.js
@@ -1,371 +1,45 @@
-/**
- * Get story demo kanban data
- * 获取“软件需求”看板演示数据
- * @returns {Object} Kanban data
- * @todo 该方法作为演示,应该在最终版本中移除
- */
-function getStoryKanbanDemoData()
+function changeView(view)
{
- /* Define kanban columns 定义看板列 */
- var columns =
- [
- /* 看板列定义数据结构:
- id: 列 ID(确保在页面上唯一),
- type: 类型,
- name: 显示的名称,
- color: 列名称颜色,
- parentType: 所属的父级列类型
- asParent: 是否作为父级列
- cardType: 看板卡片类型,例如:'story'(需求)
- limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
- {id: 'story-blacklog', type: 'blacklog', name: 'Blacklog', color: '', limit: 0},
- {id: 'story-ready', type: 'ready', name: '准备好', color: '', limit: 4},
- {id: 'story-dev', type: 'dev', name: '开发', color: '', limit: 4, asParent: true},
- {id: 'story-dev-doing', type: 'dev-doing', name: '进行中', color: '#126eed', limit: 2, parentType: 'dev'},
- {id: 'story-dev-done', type: 'dev-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'dev'},
- {id: 'story-test', type: 'test', name: '测试', color: '', limit: 4, asParent: true},
- {id: 'story-test-doing', type: 'test-doing', name: '进行中', color: '#126eed', limit: 2, parentType: 'test'},
- {id: 'story-test-done', type: 'test-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'test'},
- {id: 'story-accepted', type: 'accepted', name: '已验收', color: '', limit: 0},
- {id: 'story-published', type: 'published', name: '已发布', color: '', limit: 0},
- ];
-
- /* Define cards in blacklog column 定义 Blacklog 列上的卡片 */
- var blacklogColCards =
- [
- /* 需求数据结构:
- id: ID,通常为需求 ID,
- title: 名称,
- order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
- pri: 优先级,
- estimate: 预估时间,
- assignedTo: 指派给,
- deadline: 截止日期 */
- {id: 10010, title: '第一条 Blacklog', order: 1, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10011, title: '文档模块的实现', order: 2, pri: 2, estimate: 0, assignedTo: 'sunhao'},
- {id: 10012, title: '实现软件需求看板视图中不同分组条件的看板展示方式', order: 3, pri: 3, estimate: 10, assignedTo: 'admin'},
- {id: 10013, title: 'Blacklog 3', order: 4, pri: 0, estimate: 0, assignedTo: 'sunhao'},
- {id: 10014, title: '实现泳道的更多操作菜单', order: 5, pri: 0, estimate: 0, assignedTo: 'sunhao'},
- ];
- /* Define cards in ready column 定义 准备好 列上的卡片 */
- var readyColCards =
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10020, title: '在看板列中实现创建任务功能', order: 10, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10021, title: '实现泳道的上下移动', order: 11, pri: 2, estimate: 0, assignedTo: 'sunhao'},
- {id: 10024, title: '实现泳道的更多操作菜单', order: 15, pri: 0, estimate: 0, assignedTo: 'sunhao'},
- ];
- var devDoingColCards = /* Define cards in dev/doing column 定义 开发/进行中 列上的卡片 */
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10030, title: '实现看板的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10031, title: '实现卡片拖动效果功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
- var devDoneColCards = /* Define cards in dev/done column 定义 开发/完成 列上的卡片 */
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10040, title: '实现看板列卡片排序功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
- ];
- var testDoingColCards = /* Define cards in test/doing column 定义 测试/进行中 列上的卡片 */
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10050, title: '实现看板列的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10051, title: '实现Bug卡片的更多操作功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
- var testDoneColCards = /* Define cards in test/done column 定义 测试/完成 列上的卡片 */
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10060, title: '实现任务卡片的更多操作功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
- ];
- var acceptedColCards =
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10070, title: '实现执行看板的新建按钮组功能', order: 10, pri: 3, estimate: 12, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10071, title: '在看板列中实现提交Bug功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- {id: 10072, title: '在看板列中实现需求的添加和关联功能', order: 10, pri: 3, estimate: 10, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10073, title: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
- var publishedColCards =
- [
- /* 需求数据结构参见 blacklogColCards 定义 */
- {id: 10080, title: '实现任务看板视图中泳道分组下拉菜单功能', order: 10, pri: 3, estimate: 2, assignedTo: 'admin'},
- ];
-
- /* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
- var cards =
- {
- /* 看板列类型: 卡片列表 */
- blacklog: blacklogColCards,
- ready: readyColCards,
- 'dev-doing': devDoingColCards,
- 'dev-done': devDoneColCards,
- 'test-doing': testDoingColCards,
- 'test-done': testDoneColCards,
- accepted: acceptedColCards,
- published: publishedColCards,
- };
-
- /* Define kanban lanes 定义看板泳道 */
- var lanes =
- [
- /* 泳道数据结构:
- id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
- name: 名称,
- cards: 定义每列上的卡片
- color: 泳道名称背景色
- defaultCardType: 看板泳道上的卡片默认类型,例如:'story'(需求) */
- {id: 'story', name: '软件需求', cards: cards, color: '#3dc6fc', defaultCardType: 'story'},
- ]
-
- /* Return kanban data 返回看板数据 */
- /* 看板数据结构:
- id: ID,确保页面上唯一,可以为数字或字符串,
- columns: 定义看板上的所有列,
- lanes: 定义看板上的所有泳道
- defaultCardType: 看板上的卡片默认类型,例如:'story'(需求) */
- return kanbanGroup.story;
- return {id: 'story', columns: columns, lanes: lanes, defaultCardType: 'story'};
+ var link = createLink('execution', 'kanban', "executionID=" + executionID + '&type=' + view);
+ location.href = link;
}
-/**
- * Get bug demo kanban data
- * 获取“Bug”看板演示数据
- * @returns {Object} Kanban data
- * @todo 该方法作为演示,应该在最终版本中移除
- */
-function getBugKanbanDemoData()
-{
- return kanbanGroup.bug;
- /* Define kanban columns 定义看板列 */
- var columns =
- [
- /* 看板列定义数据结构:
- id: 列 ID(确保在页面上唯一),
- type: 类型,
- name: 显示的名称,
- color: 列名称颜色,
- parentType: 所属的父级列类型
- asParent: 是否作为父级列
- cardType: 看板卡片类型,例如:'story'(需求)
- limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
- {id: 'bug-wait', type: 'wait', name: '待确认', color: '', limit: 0},
- {id: 'bug-confirmed', type: 'confirmed', name: '已确认', color: '', limit: 4},
- {id: 'bug-resolving', type: 'resolving', name: '解决中', color: '', limit: 4, asParent: true},
- {id: 'bug-resolving-doing', type: 'resolving-doing',name: '进行中', color: '#126eed', limit: 2, parentType: 'resolving'},
- {id: 'bug-resolving-done', type: 'resolving-done', name: '完成', color: '#2fab8e', limit: 2, parentType: 'resolving'},
- {id: 'bug-test', type: 'test', name: '测试', color: '', limit: 4, asParent: true},
- {id: 'bug-test-doing', type: 'test-doing', name: '测试中', color: '#126eed', limit: 2, parentType: 'test'},
- {id: 'bug-test-done', type: 'test-done', name: '测试完毕', color: '#2fab8e', limit: 2, parentType: 'test'},
- {id: 'bug-closed', type: 'closed', name: '已关闭', color: '', limit: 0},
- ];
-
- /* Define cards in wait column 定义 待确认 列上的卡片 */
- var waitColCards =
- [
- /* bug 数据结构:
- id: ID,通常为 Bug ID,
- title: 名称,
- order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
- pri: 优先级,
- severity: 紧急程度,
- assignedTo: 指派给 */
- {id: 10010, title: '在看板列中实现创建任务功能', order: 10, pri: 1, severity: 2, assignedTo: 'admin'},
- {id: 10011, title: '实现泳道的上下移动', order: 11, pri: 2, severity: 1, assignedTo: 'sunhao'},
- {id: 10014, title: '实现泳道的更多操作菜单', order: 15, pri: 0, severity: 1, assignedTo: 'sunhao'},
- ];
- /* Define cards in confirmed column 定义 已确认 列上的卡片 */
- var confirmedColCards =
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10020, title: '在看板列中实现创建任务功能', order: 10, pri: 1, severity: 2, assignedTo: 'admin'},
- {id: 10021, title: '实现泳道的上下移动', order: 11, pri: 2, severity: 1, assignedTo: 'sunhao'},
- {id: 10024, title: '实现泳道的更多操作菜单', order: 15, pri: 0, severity: 1, assignedTo: 'sunhao'},
- ];
- var resolvingDoingColCards = /* Define cards in resolving/doing column 定义 开发/进行中 列上的卡片 */
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10030, title: '实现看板的更多操作菜单', order: 10, pri: 3, severity: 1, assignedTo: 'admin'},
- {id: 10031, title: '实现卡片拖动效果功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
- ];
- var resolvingDoneColCards = /* Define cards in resolving/done column 定义 开发/完成 列上的卡片 */
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10040, title: '实现看板列卡片排序功能', order: 10, pri: 1, severity: 4, assignedTo: 'admin'},
- ];
- var testDoingColCards = /* Define cards in test/doing column 定义 测试/进行中 列上的卡片 */
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10050, title: '实现看板列的更多操作菜单', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
- {id: 10051, title: '实现Bug卡片的更多操作功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
- ];
- var testDoneColCards = /* Define cards in test/done column 定义 测试/完成 列上的卡片 */
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10060, title: '实现任务卡片的更多操作功能', order: 10, pri: 1, severity: 4, assignedTo: 'admin'},
- ];
- var closedColCards =
- [
- /* Bug 数据结构参见 waitColCards 定义 */
- {id: 10070, title: '实现执行看板的新建按钮组功能', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
- {id: 10071, title: '在看板列中实现提交Bug功能', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
- {id: 10072, title: '在看板列中实现需求的添加和关联功能', order: 10, pri: 3, severity: 3, assignedTo: 'admin'},
- {id: 10073, title: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, severity: 2, assignedTo: 'sunhao'},
- ];
-
- /* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
- var cards =
- {
- /* 看板列类型: 卡片列表 */
- wait: waitColCards,
- confirmed: confirmedColCards,
- 'resolving-doing': resolvingDoingColCards,
- 'resolving-done': resolvingDoneColCards,
- 'test-doing': testDoingColCards,
- 'test-done': testDoneColCards,
- closed: closedColCards,
- };
-
- /* Define kanban lanes 定义看板泳道 */
- var lanes =
- [
- /* 泳道数据结构:
- id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
- name: 名称,
- cards: 定义每列上的卡片
- color: 泳道名称背景色
- defaultCardType: 看板泳道上的卡片默认类型,例如:'bug'(Bug) */
- {id: 'bug', name: 'Bug', cards: cards, color: '#9c30b0', defaultCardType: 'bug'},
- ]
-
- /* Return kanban data 返回看板数据 */
- /* 看板数据结构:
- id: ID,确保页面上唯一,可以为数字或字符串,
- columns: 定义看板上的所有列,
- lanes: 定义看板上的所有泳道
- defaultCardType: 看板上的卡片默认类型,例如:'bug'(Bug) */
- return {id: 'bug', columns: columns, lanes: lanes, defaultCardType: 'bug'};
-}
-
-/**
- * Get task demo kanban data
- * 获取“任务”看板演示数据
- * @returns {Object} Kanban data
- * @todo 该方法作为演示,应该在最终版本中移除
- */
-function getTaskKanbanDemoData()
-{
- return kanbanGroup.task;
- /* Define kanban columns 定义看板列 */
- var columns =
- [
- /* 看板列定义数据结构:
- id: 列 ID(确保在页面上唯一),
- type: 类型,
- name: 显示的名称,
- color: 列名称颜色,
- parentType: 所属的父级列类型
- asParent: 是否作为父级列
- cardType: 看板卡片类型,例如:'task'(任务)
- limit: 最大数目,该列上允许展示的卡片最大数目,如果为 0 表示无限制 */
- {id: 'task-wait', type: 'wait', name: '未开始', color: '', limit: 0},
- {id: 'task-dev', type: 'dev', name: '开发', color: '', limit: 4, asParent: true},
- {id: 'task-dev-doing', type: 'dev-doing', name: '研发中', color: '#126eed', limit: 2, parentType: 'dev'},
- {id: 'task-dev-done', type: 'dev-done', name: '研发完成', color: '#2fab8e', limit: 2, parentType: 'dev'},
- {id: 'task-pause', type: 'pause', name: '已暂停', color: '', limit: 0},
- {id: 'task-cancel', type: 'cancel', name: '已取消', color: '', limit: 0},
- {id: 'task-closed', type: 'closed', name: '已关闭', color: '', limit: 0},
- ];
-
- /* Define cards in wait column 定义 未开始 列上的卡片 */
- var waitColCards =
- [
- /* 任务数据结构:
- id: ID,通常为任务 ID,
- name: 名称,
- order: 显示顺序,如果指定为 0,则按 ID 倒序排序,
- pri: 优先级,
- estimate: 预估时间,
- assignedTo: 指派给,
- deadline: 截止日期 */
- {id: 10020, name: '在看板列中实现创建任务功能', order: 10, pri: 1, estimate: 2, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10021, name: '实现泳道的上下移动', order: 11, pri: 2, estimate: 0, assignedTo: 'sunhao', deadline: '2021-10-22'},
- {id: 10024, name: '实现泳道的更多操作菜单', order: 15, pri: 0, estimate: 0, assignedTo: 'sunhao'},
- ];
- var devDoingColCards = /* Define cards in dev/doing column 定义 开发/进行中 列上的卡片 */
- [
- /* 任务数据结构参见 waitColCards 定义 */
- {id: 10030, name: '实现看板的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10031, name: '实现卡片拖动效果功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
- var devDoneColCards = /* Define cards in dev/done column 定义 开发/完成 列上的卡片 */
- [
- /* 任务数据结构参见 waitColCards 定义 */
- {id: 10040, name: '实现看板列卡片排序功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
- ];
- var pauseColCards = /* Define cards in pause column 定义 已暂停 列上的卡片 */
- [
- /* 任务数据结构参见 waitColCards 定义 */
- {id: 10050, name: '实现看板列的更多操作菜单', order: 10, pri: 3, estimate: 0, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10051, name: '实现Bug卡片的更多操作功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
- var cancelColCards = /* Define cards in cancel column 定义 已取消 列上的卡片 */
- [
- /* 任务数据结构参见 waitColCards 定义 */
- {id: 10060, name: '实现任务卡片的更多操作功能', order: 10, pri: 1, estimate: 4, assignedTo: 'admin', deadline: '2021-10-30'},
- ];
- var closedColCards =
- [
- /* 任务数据结构参见 waitColCards 定义 */
- {id: 10070, name: '实现执行看板的新建按钮组功能', order: 10, pri: 3, estimate: 12, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10071, name: '在看板列中实现提交Bug功能', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- {id: 10072, name: '在看板列中实现任务的添加和关联功能', order: 10, pri: 3, estimate: 10, assignedTo: 'admin', deadline: '2021-10-30'},
- {id: 10073, name: '实现任务看板视图中不同分组条件的看板展示方式', order: 11, pri: 2, estimate: 2, assignedTo: 'sunhao'},
- ];
-
- /* Define kanban cards in lane 定义看板泳道内每一列的卡片,属性名为看板列类型,属性值为卡片列表 */
- var cards =
- {
- /* 看板列类型: 卡片列表 */
- wait: waitColCards,
- 'dev-doing': devDoingColCards,
- 'dev-done': devDoneColCards,
- pause: pauseColCards,
- cancel: cancelColCards,
- closed: closedColCards,
- };
-
- /* Define kanban lanes 定义看板泳道 */
- var lanes =
- [
- /* 泳道数据结构:
- id: 泳道 ID,确保页面上唯一,可以为数字或字符串,
- name: 名称,
- cards: 定义每列上的卡片
- color: 泳道名称背景色
- defaultCardType: 看板泳道上的卡片默认类型,例如:'task'(任务) */
- {id: 'task', name: '任务', cards: cards, color: '#126eed', defaultCardType: 'task'},
- ]
-
- /* Return kanban data 返回看板数据 */
- /* 看板数据结构:
- id: ID,确保页面上唯一,可以为数字或字符串,
- columns: 定义看板上的所有列,
- lanes: 定义看板上的所有泳道
- defaultCardType: 看板上的卡片默认类型,例如:'task'(任务) */
- return {id: 'task', columns: columns, lanes: lanes, defaultCardType: 'task'};
-}
-
-
/**
* Render user avatar
* @param {String|{account: string, avatar: string}} user User account or user object
* @returns {string}
*/
-function renderUserAvatar(user)
+function renderUserAvatar(user, objectType, objectID)
{
if(typeof user === 'string') user = {account: user};
if(!user.avatar && window.userList && window.userList[user.account]) user = window.userList[user.account];
- return $('').avatar({user: user});
+ if(objectType == 'task')
+ {
+ var link = createLink('task', 'assignto', 'executionID=' + executionID + '&id=' + objectID, '', true);
+ }
+ else
+ {
+ var link = createLink(objectType, 'assignto', 'id=' + objectID, '', true);
+ }
+
+ return $('').avatar({user: user});
+}
+
+/**
+ * Render deadline
+ * @param {String|Date} deadline Deadline
+ * @returns {JQuery}
+ */
+function renderDeadline(deadline)
+{
+ var date = $.zui.createDate(deadline);
+ var now = new Date();
+ now.setHours(0);
+ now.setMinutes(0);
+ now.setSeconds(0);
+ now.setMilliseconds(0);
+ var isEarlyThanToday = date.getTime() < now.getTime();
+ return $('').text(deadline).addClass(isEarlyThanToday ? 'text-red' : 'text-muted');
}
/**
@@ -397,14 +71,25 @@ function renderStoryItem(item, $item, col)
'' + item.pri + '',
item.estimate ? '' + item.estimate + 'h' : '',
].join(''));
- if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
+ if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'story', item.id));
+
+ var $actions = $item.find('.actions');
+ if(!$actions.length)
+ {
+ $actions = $([
+ '
'
+ ].join('')).appendTo($item);
+ }
$item.attr('data-type', 'story').addClass('kanban-item-story');
return $item;
}
-
/**
* Render bug item 提供方法渲染看板中的 Bug 卡片
* @param {Object} item Bug item object
@@ -434,7 +119,20 @@ function renderBugItem(item, $item, col)
'',
'' + item.pri + '',
].join(''));
- if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
+ if(item.deadline) $infos.append(renderDeadline(item.deadline));
+ if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'bug', item.id));
+
+ var $actions = $item.find('.actions');
+ if(!$actions.length)
+ {
+ $actions = $([
+ ''
+ ].join('')).appendTo($item);
+ }
$item.attr('data-type', 'bug').addClass('kanban-item-bug');
@@ -470,7 +168,20 @@ function renderTaskItem(item, $item, col)
'' + item.pri + '',
item.estimate ? '' + item.estimate + 'h' : '',
].join(''));
- if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo));
+ if(item.deadline) $infos.append(renderDeadline(item.deadline));
+ if(item.assignedTo) $infos.append(renderUserAvatar(item.assignedTo, 'task', item.id));
+
+ var $actions = $item.find('.actions');
+ if(!$actions.length)
+ {
+ $actions = $([
+ ''
+ ].join('')).appendTo($item);
+ }
$item.attr('data-type', 'task').addClass('kanban-item-task');
@@ -478,7 +189,6 @@ function renderTaskItem(item, $item, col)
}
/* Add column renderer/ 添加特定列类型或列条目类型渲染方法 */
-/* Add column renderer/ 添加特定列类型或列卡片类型渲染方法 */
addColumnRenderer('story', renderStoryItem);
addColumnRenderer('bug', renderBugItem);
addColumnRenderer('task', renderTaskItem);
@@ -492,10 +202,62 @@ addColumnRenderer('task', renderTaskItem);
*/
function renderColumnCount($count, count, col)
{
- var text = count + '/' + (!col.limit ? '' : '');
+ var text = count + '/' + (col.limit < 0 ? '' : col.limit);
$count.html(text + '');
}
+/**
+ * Render header column 渲染看板列头部
+ * @param {JQuery} $col Header column element
+ * @param {Object} col Header column object
+ * @param {JQuery} $header Header element
+ * @param {Object} kanban Kanban object
+ */
+function renderHeaderCol($col, col, $header, kanban)
+{
+ if(col.asParent) $col = $col.children('.kanban-header-col');
+ if(!$col.children('.actions').length)
+ {
+ var $actions = $('');
+ if(col.type === 'backlog' || col.type === 'wait' || col.type == 'unconfirmed')
+ {
+ $actions.append([
+ '',
+ '',
+ ''
+ ].join(''));
+ }
+ $actions.append([
+ '',
+ '',
+ ''
+ ].join(''));
+ $actions.appendTo($col);
+ }
+}
+
+/**
+ * Render lane name 渲染看板泳道名称
+ * @param {JQuery} $name Name element
+ * @param {Object} lane Lane object
+ * @param {JQuery} $kanban $kanban element
+ * @param {Object} columns Kanban columns
+ * @param {Object} kanban Kanban object
+ */
+function renderLaneName($name, lane, $kanban, columns, kanban)
+{
+ if(!$name.children('.actions').length)
+ {
+ $([
+ ''
+ ].join('')).appendTo($name);
+ }
+}
+
/**
* Updata kanban data
* 更新看板上的数据
@@ -657,6 +419,148 @@ function handleFinishDrop(event)
$('#kanbans').find('.can-drop-here').removeClass('can-drop-here');
}
+/**
+ * Handle sort cards in column 处理对列卡片进行排序
+ */
+function handleSortColCards()
+{
+ /* TODO: handle sort cards from column contextmenu */
+}
+
+/**
+ * Create column menu 创建列操作菜单
+ * @returns {Object[]}
+ */
+function createColumnMenu(options)
+{
+ var $col = options.$trigger.closest('.kanban-col');
+ var col = $col.data('col');
+ var kanbanID = options.kanban;
+
+ var items = [];
+ if(priv.hasEditName) items.push({label: executionLang.editName, url: $.createLink('kanban', 'setColumn', 'col=' + col.columnID + '&executionID=' + executionID), className: 'iframe'})
+ if(priv.hasSetWIP) items.push({label: executionLang.setWIP, url: $.createLink('kanban', 'setWIP', 'col=' + col.columnID + '&executionID=' + executionID), className: 'iframe'})
+ items.push({label: executionLang.sortColumn, items: ['按ID倒序', '按ID顺序'], className: 'iframe', onClick: handleSortColCards})
+ return items;
+}
+
+/**
+ * Create column create button menu 创建列添加按钮操作菜单
+ * @returns {Object[]}
+ */
+function createColumnCreateMenu(options)
+{
+ var $col = options.$trigger.closest('.kanban-col');
+ var col = $col.data('col');
+ var items = [];
+
+ if(col.laneType == 'story')
+ {
+ if(priv.canCreateStory) items.push({label: storyLang.create, url: $.createLink('story', 'create', 'productID=' + productID)});
+ if(priv.canBatchCreateStory) items.push({label: storyLang.batchCreate, url: $.createLink('story', 'batchcreate', 'productID=' + productID)});
+ if(priv.canLinkStory) items.push({label: executionLang.linkStory, url: $.createLink('execution', 'linkStory', 'executionID=' + executionID)});
+ if(priv.canLinkStoryByPlane) items.push({label: executionLang.linkStoryByPlan, url: '#linkStoryByPlan', 'attrs' : {'data-toggle': 'modal'}});
+ }
+ else if(col.laneType == 'bug')
+ {
+ if(priv.canCreateBug) items.push({label: bugLang.create, url: $.createLink('bug', 'create', 'productID=0&moduleID=0&extra=executionID=' + executionID)});
+ if(priv.canBatchCreateBug) items.push({label: bugLang.batchCreate, url: $.createLink('bug', 'batchcreate', 'productID=' + productID + '&moduleID=0&executionID=' + executionID)});
+ }
+ else
+ {
+ if(priv.canCreateTask) items.push({label: taskLang.create, url: $.createLink('task', 'create', 'executionID=' + executionID)});
+ if(priv.canBatchCreateTask) items.push({label: taskLang.batchCreate, url: $.createLink('task', 'batchcreate', 'executionID=' + executionID)});
+ }
+ return items;
+}
+
+/**
+ * Create lane menu 创建泳道操作菜单
+ * @returns {Object[]}
+ */
+function createLaneMenu(options)
+{
+ var $lane = options.$trigger.closest('.kanban-lane');
+ var $kanban = $lane.closest('.kanban');
+ var lane = $lane.data('lane');
+ var kanbanID = options.kanban;
+ var items =
+ [
+ {label: '泳道设置', icon: 'edit', url: $.createLink('kanban', 'laneedit', 'lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe'},
+ {label: '泳道上移', icon: 'arrow-up', url: $.createLink('kanban', 'lanemove', 'direction=up&lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe', disabled: !$kanban.prev('.kanban').length},
+ {label: '泳道下移', icon: 'arrow-down', url: $.createLink('kanban', 'lanemove', 'direction=down&lane=' + lane.id + '&kanban=' + kanbanID), className: 'iframe', disabled: !$kanban.next('.kanban').length},
+ ];
+ var bounds = options.$trigger[0].getBoundingClientRect();
+ items.$options = {x: bounds.right, y: bounds.top};
+ return items;
+}
+
+/**
+ * Create story menu 创建需求卡片操作菜单
+ * @returns {Object[]}
+ */
+function createStoryMenu(options)
+{
+ var $card = options.$trigger.closest('.kanban-item');
+ var story = $card.data('item');
+ var items =
+ [
+ {label: '编辑需求', icon: 'edit', url: $.createLink('story', 'edit', 'storyID=' + story.id), className: 'iframe'},
+ {label: '变更需求', icon: 'change', url: $.createLink('story', 'change', 'storyID=' + story.id), className: 'iframe'},
+ {label: '移除需求', icon: 'unlink', url: $.createLink('story', 'unlink', 'storyID=' + story.id), className: 'iframe'},
+ {label: '分解任务', icon: 'plus', url: $.createLink('task', 'create', 'storyID=' + story.id), className: 'iframe'},
+ {label: '批量分解', icon: 'pluses', url: $.createLink('task', 'batchCreate', 'storyID=' + story.id), className: 'iframe'},
+ ];
+ return items;
+}
+
+/**
+ * Create bug menu 创建 Bug 卡片操作菜单
+ * @returns {Object[]}
+ */
+function createBugMenu(options)
+{
+ var $card = options.$trigger.closest('.kanban-item');
+ var bug = $card.data('item');
+ var items =
+ [
+ {label: '编辑Bug', icon: 'edit', url: $.createLink('bug', 'edit', 'bugID=' + bug.id), className: 'iframe'},
+ {label: '确认Bug', icon: 'ok', url: $.createLink('bug', 'confirm', 'bugID=' + bug.id), className: 'iframe'},
+ {label: '复制Bug', icon: 'copy', url: $.createLink('bug', 'copy', 'bugID=' + bug.id), className: 'iframe'},
+ {label: '转软件需求', icon: 'lightbulb', url: $.createLink('story', 'create', 'bugID=' + bug.id), className: 'iframe'},
+ ];
+ return items;
+}
+
+ /**
+ * Create task menu 创建任务卡片操作菜单
+ * @returns {Object[]}
+ */
+function createTaskMenu(options)
+{
+ var $card = options.$trigger.closest('.kanban-item');
+ var task = $card.data('item');
+ var items =
+ [
+ {label: '编辑任务', icon: 'edit', url: $.createLink('task', 'edit', 'taskID=' + task.id), className: 'iframe'},
+ {label: '拆分子任务', icon: 'plus', url: $.createLink('task', 'create', 'taskID=' + task.id), className: 'iframe'},
+ {label: '复制任务', icon: 'copy', url: $.createLink('task', 'copy', 'taskID=' + task.id), className: 'iframe'},
+ {label: '取消任务', icon: 'cancel', url: $.createLink('task', 'cancel', 'taskID=' + task.id), className: 'iframe'},
+ ];
+ return items;
+}
+
+/* Define menu creators */
+window.menuCreators =
+{
+ column: createColumnMenu,
+ columnCreate: createColumnCreateMenu,
+ lane: createLaneMenu,
+ story: createStoryMenu,
+ bug: createBugMenu,
+ task: createTaskMenu,
+};
+
/* Overload kanban default options */
$.extend($.fn.kanban.Constructor.DEFAULTS,
{
@@ -683,26 +587,66 @@ $(function()
showCount: true,
showZeroCount: true,
fluidBoardWidth: true,
- countRender: renderColumnCount,
droppable:
{
target: findDropColumns,
finish: handleFinishDrop,
mouseButton: 'left'
- }
+ },
+ onRenderHeaderCol: renderHeaderCol,
+ onRenderLaneName: renderLaneName,
+ onRenderCount: renderColumnCount
};
/* Hide group menu. */
hideGroupMenu();
/* Create story kanban 创建需求看板 */
- createKanban('story', getStoryKanbanDemoData(), commonOptions);
+ if(browseType == 'all' || browseType == 'story') createKanban('story', kanbanGroup.story, commonOptions);
/* Create bug kanban 创建 Bug 看板 */
- createKanban('bug', getBugKanbanDemoData(), commonOptions);
+ if(browseType == 'all' || browseType == 'bug') createKanban('bug', kanbanGroup.bug, commonOptions);
/* Create task kanban 创建 任务 看板 */
- createKanban('task', getTaskKanbanDemoData(), commonOptions);
+ if(browseType == 'all' || browseType == 'task') createKanban('task', kanbanGroup.task, commonOptions);
+
+ /* Init iframe modals */
+ $(document).on('click', '.iframe', function(event)
+ {
+ $(this).modalTrigger({show: true, width: '500px'});
+ event.preventDefault();
+ });
+
+ /* Init contextmenu */
+ $('#kanbans').on('click', '[data-contextmenu]', function(event)
+ {
+ var $trigger = $(this);
+ var menuType = $trigger.data('contextmenu');
+
+ var menuCreator = window.menuCreators[menuType];
+ if(!menuCreator) return;
+
+ var options = $.extend({event, $trigger: $trigger}, $trigger.data());
+ var items = menuCreator(options);
+ if(!items || !items.length) return;
+
+ $.zui.ContextMenu.show(items, items.$options || {event: event});
+ });
+
+ /* Hide contextmenu when page scroll */
+ $(window).on('scroll', function()
+ {
+ $.zui.ContextMenu.hide();
+ });
+
+ $('#toStoryButton').on('click', function()
+ {
+ var planID = $('#plan').val();
+ if(planID)
+ {
+ parent.location.href = createLink('execution', 'importPlanStories', 'executionID=' + executionID + '&planID=' + planID);
+ }
+ })
});
/**
diff --git a/module/execution/lang/de.php b/module/execution/lang/de.php
index 8e7837fbd6..4d0a8a8d43 100644
--- a/module/execution/lang/de.php
+++ b/module/execution/lang/de.php
@@ -376,6 +376,11 @@ $lang->execution->kanbanHideCols = 'Geschlossene und abgebrochene Spalten in K
$lang->execution->kanbanShowOption = 'Aufklappen';
$lang->execution->kanbanColsColor = 'Spaltenfarben';
+$lang->execution->kanbanViewList['all'] = 'All';
+$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
+$lang->execution->kanbanViewList['bug'] = 'Bug';
+$lang->execution->kanbanViewList['task'] = 'Task';
+
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Möchten Sie die Einstellungen des Kanbans zurücksetzen?';
$lang->kanbanSetting->optionList['0'] = 'Verstecken';
diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php
index 916ae5454e..6f03cec38c 100644
--- a/module/execution/lang/en.php
+++ b/module/execution/lang/en.php
@@ -101,6 +101,9 @@ $lang->execution->copyNoExecution = 'There are no ' . $lang->executionCommon . '
$lang->execution->noTeam = 'No team members at the moment';
$lang->execution->or = ' or ';
$lang->execution->selectProject = 'Please select project';
+$lang->execution->editName = 'Edit Name';
+$lang->execution->setWIP = 'WIP Settings';
+$lang->execution->sortColumn = 'Kanban Card Sorting';
/* Fields of zt_team. */
$lang->execution->root = 'Root';
@@ -385,6 +388,11 @@ $lang->execution->kanbanHideCols = 'Closed & Cancelled Columns';
$lang->execution->kanbanShowOption = 'Unfold';
$lang->execution->kanbanColsColor = 'Customize Column Color';
+$lang->execution->kanbanViewList['all'] = 'All';
+$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
+$lang->execution->kanbanViewList['bug'] = 'Bug';
+$lang->execution->kanbanViewList['task'] = 'Task';
+
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Do you want to reset Kanban?';
$lang->kanbanSetting->optionList['0'] = 'Hide';
diff --git a/module/execution/lang/fr.php b/module/execution/lang/fr.php
index bb698b546b..5d769daf4f 100644
--- a/module/execution/lang/fr.php
+++ b/module/execution/lang/fr.php
@@ -376,6 +376,11 @@ $lang->execution->kanbanHideCols = 'Colonnes masquées';
$lang->execution->kanbanShowOption = 'Déplier';
$lang->execution->kanbanColsColor = 'Personnalisation Couleurs';
+$lang->execution->kanbanViewList['all'] = 'All';
+$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
+$lang->execution->kanbanViewList['bug'] = 'Bug';
+$lang->execution->kanbanViewList['task'] = 'Task';
+
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Voulez-vous réinitialiser le tableau Kanban ?';
$lang->kanbanSetting->optionList['0'] = 'Masquer';
diff --git a/module/execution/lang/vi.php b/module/execution/lang/vi.php
index c6bfcd7051..865839cfd3 100644
--- a/module/execution/lang/vi.php
+++ b/module/execution/lang/vi.php
@@ -377,6 +377,11 @@ $lang->execution->kanbanHideCols = 'Cột Đã đóng & đã hủy';
$lang->execution->kanbanShowOption = 'Mở ra';
$lang->execution->kanbanColsColor = 'Tùy biến màu cột';
+$lang->execution->kanbanViewList['all'] = 'All';
+$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}";
+$lang->execution->kanbanViewList['bug'] = 'Bug';
+$lang->execution->kanbanViewList['task'] = 'Task';
+
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = 'Bạn có muốn thiết lập lại Kanban?';
$lang->kanbanSetting->optionList['0'] = 'Ẩn';
diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php
index 4e26f70a5b..2bc0d374a5 100644
--- a/module/execution/lang/zh-cn.php
+++ b/module/execution/lang/zh-cn.php
@@ -101,6 +101,9 @@ $lang->execution->copyNoExecution = '没有可用的' . $lang->executionCommon .
$lang->execution->noTeam = '暂时没有团队成员';
$lang->execution->or = '或';
$lang->execution->selectProject = '请选择项目';
+$lang->execution->editName = '编辑名称';
+$lang->execution->setWIP = '在制品数量设置(WIP)';
+$lang->execution->sortColumn = '看板列卡片排序';
/* Fields of zt_team. */
$lang->execution->root = '源ID';
@@ -386,6 +389,11 @@ $lang->execution->kanbanHideCols = '看板隐藏已关闭、已取消列';
$lang->execution->kanbanShowOption = '显示折叠信息';
$lang->execution->kanbanColsColor = '看板列自定义颜色';
+$lang->execution->kanbanViewList['all'] = '综合看板';
+$lang->execution->kanbanViewList['story'] = "{$lang->SRCommon}看板";
+$lang->execution->kanbanViewList['bug'] = 'Bug看板';
+$lang->execution->kanbanViewList['task'] = '任务看板';
+
$lang->kanbanSetting = new stdclass();
$lang->kanbanSetting->noticeReset = '是否恢复看板默认设置?';
$lang->kanbanSetting->optionList['0'] = '隐藏';
diff --git a/module/execution/view/kanban.html.php b/module/execution/view/kanban.html.php
index a6afc6ff77..0476c0fb9c 100644
--- a/module/execution/view/kanban.html.php
+++ b/module/execution/view/kanban.html.php
@@ -81,6 +81,8 @@
+
+
@@ -104,12 +106,37 @@
- execution->linkStory, "id='toTaskButton'", 'btn btn-primary');?>
+ execution->linkStory, "id='toStoryButton'", 'btn btn-primary');?>
+
+
+ common::hasPriv('kanban', 'setColumn'),
+ 'hasSetWIP' => common::hasPriv('kanban', 'setWIP'),
+ 'canCreateTask' => $canCreateTask,
+ 'canBatchCreateTask' => $canBatchCreateTask,
+ 'canCreateBug' => $canCreateBug,
+ 'canBatchCreateBug' => $canBatchCreateBug,
+ 'canCreateStory' => $canCreateStory,
+ 'canBatchCreateStory' => $canBatchCreateStory,
+ 'canLinkStory' => $canLinkStory,
+ 'canLinkStoryByPlane' => $canLinkStoryByPlane,
+ )
+);
+?>
+execution);?>
+story);?>
+task);?>
+bug);?>
+execution->editName);?>
+execution->setWIP);?>
+execution->sortColumn);?>
diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php
index 28f1996d28..7bea902b36 100644
--- a/module/group/lang/resource.php
+++ b/module/group/lang/resource.php
@@ -25,6 +25,7 @@ $lang->moduleOrder[40] = 'release';
$lang->moduleOrder[45] = 'project';
$lang->moduleOrder[50] = 'projectstory';
$lang->moduleOrder[55] = 'execution';
+$lang->moduleOrder[56] = 'kanban';
$lang->moduleOrder[60] = 'task';
$lang->moduleOrder[65] = 'build';
@@ -547,6 +548,16 @@ $lang->release->methodOrder[60] = 'unlinkBug';
$lang->release->methodOrder[65] = 'batchUnlinkBug';
$lang->release->methodOrder[70] = 'changeStatus';
+/* Kanban */
+$lang->resource->kanban = new stdclass();
+$lang->resource->kanban->setLane = 'setLane';
+$lang->resource->kanban->setColumn = 'setColumn';
+$lang->resource->kanban->setWIP = 'setWIP';
+
+$lang->kanban->methodOrder[5] = 'setLane';
+$lang->kanban->methodOrder[10] = 'setColumn';
+$lang->kanban->methodOrder[15] = 'setWIP';
+
/* Execution. */
$lang->resource->execution = new stdclass();
$lang->resource->execution->view = 'view';
diff --git a/module/kanban/control.php b/module/kanban/control.php
index 41a885c264..a50062cd40 100644
--- a/module/kanban/control.php
+++ b/module/kanban/control.php
@@ -80,17 +80,21 @@ class kanban extends control
* @access public
* @return void
*/
- public function setLaneColumn($columnID)
+ public function setColumn($columnID)
{
$column = $this->kanban->getColumnById($columnID);
-
+
if($_POST)
{
- $params = fixer::input('post')->get();
- $this->kanban->updateLaneColumn($columnID, $params);
+ /* Check lane column name is unique. */
+ $exist = $this->kanban->getColumnByName($this->post->name, $column->lane);
+ if($exist and $exist->id != $columnID)
+ {
+ return $this->sendError($this->lang->kanban->noColumnUniqueName);
+ }
+
+ $changes = $this->kanban->updateLaneColumn($columnID, $column);
if(dao::isError()) return $this->sendError(dao::getError());
-
- $changes = common::createChanges($column, $params);
if($changes)
{
$actionID = $this->loadModel('action')->create('kanbancolumn', $columnID, 'Edited');
@@ -101,8 +105,51 @@ class kanban extends control
}
$this->view->column = $column;
- $this->view->title = $column->name . $this->lang->colon . $this->lang->kanban->setLaneColumn;
+ $this->view->title = $column->name . $this->lang->colon . $this->lang->kanban->setColumn;
$this->display();
}
+ /**
+ * AJAX: Update the cards sorting of the lane column.
+ *
+ * @param string $laneType story|bug|task
+ * @param int $columnID
+ * @param string $orderBy id_desc|id_asc|pri_desc|pri_asc|lastEditedDate_desc|lastEditedDate_asc|deadline_desc|deadline_asc|assignedTo_asc
+ * @access public
+ * @return void
+ */
+ public function ajaxCardsSort($laneType, $columnID, $orderBy = 'id_desc')
+ {
+ $oldCards = array();
+ $column = $this->dao->select('parent,cards')->from(TABLE_KANBANCOLUMN)->where('id')->eq($columnID)->fetch();
+
+ /* Get the cards of the kanban column. */
+ if($column->parent == -1)
+ {
+ $childColumns = $this->dao->select('id,cards')->from(TABLE_KANBANCOLUMN)->where('parent')->eq($columnID)->fetchAll();
+ foreach($childColumns as $childColumn)
+ {
+ $oldCards[$childColumn->id] = $childColumn->cards;
+ }
+ }
+ else
+ {
+ $oldCards[$columnID] = $column->cards;
+ }
+
+ /* Update Kanban column card order. */
+ $table = $this->config->objectTables[$laneType];
+ foreach($oldCards as $colID => $cards)
+ {
+ if(empty($cards)) continue;
+ $objects = $this->dao->select('id')->from($table)
+ ->where('id')->in($cards)
+ ->orderBy($orderBy)
+ ->fetchPairs('id');
+
+ $objectIdList = ',' . implode(',', $objects) . ',';
+ $this->dao->update(TABLE_KANBANCOLUMN)->set('cards')->eq($objectIdList)->where('id')->eq($colID)->exec();
+ }
+ echo true;
+ }
}
diff --git a/module/kanban/css/setlanecolumn.css b/module/kanban/css/setcolumn.css
similarity index 99%
rename from module/kanban/css/setlanecolumn.css
rename to module/kanban/css/setcolumn.css
index 1ae9a01187..38087ed055 100644
--- a/module/kanban/css/setlanecolumn.css
+++ b/module/kanban/css/setcolumn.css
@@ -2,3 +2,4 @@ td>ul {padding-left: 0;}
li {display: block; float: left; padding: 5px; width: 28px; height: 28px;}
li>a { position: relative; display: block; width: 100%; height: 100%; padding: 0; margin: 0; font-family: ZentaoIcon; font-size: 14px; font-style: normal; font-weight: 400; font-variant: normal; line-height: 1; text-align: center; text-transform: none; border: 1px solid transparent; border-radius: 50%; speak: none; -webkit-font-smoothing: antialiased; }
li>a.active:before {font-size: 14px; content: "\e5ca"}
+
diff --git a/module/kanban/js/setlanecolumn.js b/module/kanban/js/setcolumn.js
similarity index 100%
rename from module/kanban/js/setlanecolumn.js
rename to module/kanban/js/setcolumn.js
diff --git a/module/kanban/lang/en.php b/module/kanban/lang/en.php
index 52f6561a5e..b8e49f81fb 100644
--- a/module/kanban/lang/en.php
+++ b/module/kanban/lang/en.php
@@ -1,16 +1,24 @@
kanban = new stdClass();
-$lang->kanban->WIP = 'WIP';
-$lang->kanban->setWIP = 'WIP Settings';
-$lang->kanban->WIPStatus = 'WIP Status';
-$lang->kanban->WIPStage = 'WIP Stage';
-$lang->kanban->WIPType = 'WIP Type';
-$lang->kanban->WIPCount = 'WIP Count';
-$lang->kanban->noLimit = 'No Limit ∞';
-$lang->kanban->setLane = 'Lane Settings';
-$lang->kanban->laneName = 'Lane Name';
-$lang->kanban->laneColor = 'Lane Color';
+$lang->kanban->WIP = 'WIP';
+$lang->kanban->setWIP = 'WIP Settings';
+$lang->kanban->WIPStatus = 'WIP Status';
+$lang->kanban->WIPStage = 'WIP Stage';
+$lang->kanban->WIPType = 'WIP Type';
+$lang->kanban->WIPCount = 'WIP Count';
+$lang->kanban->noLimit = 'No Limit ∞';
+$lang->kanban->setLane = 'Lane Settings';
+$lang->kanban->laneName = 'Lane Name';
+$lang->kanban->laneColor = 'Lane Color';
+$lang->kanban->setLaneColumn = 'Column Settings';
+$lang->kanban->columnName = 'Column Name';
+$lang->kanban->columnColor = 'Column Color';
+$lang->kanban->noColumnUniqueName = 'The Kanban column name already exists.';
+
+$lang->kanban->error = new stdclass();
+$lang->kanban->error->parentLimitNote = 'The WIPs in the parent column cannot be < the sum of the WIPs in the child column.';
+$lang->kanban->error->childLimitNote = 'The sum of products in the child column cannot be > the number of products in the parent column.';
$this->lang->kanban->laneTypeList = array();
$this->lang->kanban->laneTypeList['story'] = $lang->SRCommon;
diff --git a/module/kanban/model.php b/module/kanban/model.php
index aa24a5849a..0e03bdefe5 100644
--- a/module/kanban/model.php
+++ b/module/kanban/model.php
@@ -32,86 +32,94 @@ class kanbanModel extends model
->fetchAll('id');
if(empty($lanes)) return array();
+
+ foreach($lanes as $lane) $this->updateCards($lane);
+
$columns = $this->dao->select('*')->from(TABLE_KANBANCOLUMN)
->where('deleted')->eq(0)
->andWhere('lane')->in(array_keys($lanes))
->fetchGroup('lane', 'id');
+ /* Get parent column type pairs. */
$parentTypes = $this->dao->select('id, type')->from(TABLE_KANBANCOLUMN)
->where('deleted')->eq(0)
->andWhere('lane')->in(array_keys($lanes))
->andWhere('parent')->eq(-1)
->fetchPairs('id', 'type');
- if($objectType == 'story' or $objectType == 'all') $stories = $this->loadModel('story')->getExecutionStories($executionID);
- if($objectType == 'bug' or $objectType == 'all') $bugs = $this->loadModel('bug')->getExecutionBugs($executionID);
- if($objectType == 'task' or $objectType == 'all') $tasks = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
+ /* Get group objects. */
+ if($browseType == 'all' or $browseType == 'story') $objectGroup['story'] = $this->loadModel('story')->getExecutionStories($executionID);
+ if($browseType == 'all' or $browseType == 'bug') $objectGroup['bug'] = $this->loadModel('bug')->getExecutionBugs($executionID);
+ if($browseType == 'all' or $browseType == 'task') $objectGroup['task'] = $this->loadModel('execution')->getKanbanTasks($executionID, "id");
- /* Init vars. */
- $kanban = array();
+ /* Build kanban group data. */
+ $kanbanGroup = array();
foreach($lanes as $laneID => $lane)
{
$laneData = array();
$columnData = array();
- $cards = array();
+ $laneType = $lane->type;
- $laneData['id'] = $lane->type;
+ $laneData['id'] = $laneType;
$laneData['name'] = $lane->name;
$laneData['color'] = $lane->color;
- $laneData['defaultCardType'] = $lane->type;
$laneData['order'] = $lane->order;
+ $laneData['defaultCardType'] = $laneType;
- foreach($columns[$laneID] as $colID => $col)
+ foreach($columns[$laneID] as $columnID => $column)
{
- $columnData[$col->id]['id'] = $lane->type . '-' . $col->type;
- $columnData[$col->id]['type'] = $col->type;
- $columnData[$col->id]['name'] = $col->name;
- $columnData[$col->id]['color'] = $col->color;
- $columnData[$col->id]['limit'] = $col->limit;
- $columnData[$col->id]['asParent'] = $col->parent == -1 ? true : false;
+ $columnData[$column->id]['id'] = $laneType . '-' . $column->type;
+ $columnData[$column->id]['columnID'] = $columnID;
+ $columnData[$column->id]['type'] = $column->type;
+ $columnData[$column->id]['name'] = $column->name;
+ $columnData[$column->id]['color'] = $column->color;
+ $columnData[$column->id]['limit'] = $column->limit;
+ $columnData[$column->id]['laneType'] = $laneType;
+ $columnData[$column->id]['asParent'] = $column->parent == -1 ? true : false;
- if($col->parent > 0)
+ if($column->parent > 0)
{
- $columnData[$col->id]['parentType'] = zget($parentTypes, $col->parent, '');
+ $columnData[$column->id]['parentType'] = zget($parentTypes, $column->parent, '');
}
- $cardIdList = array_filter(explode(',', $col->cards));
$cardOrder = 1;
- $item = array();
+ $cardIdList = array_filter(explode(',', $column->cards));
foreach($cardIdList as $cardID)
{
- if($lane->type == 'story') $object = zget($stories, $cardID, array());
- if($lane->type == 'task') $object = zget($tasks, $cardID, array());
- if($lane->type == 'bug') $object = zget($bugs, $cardID, array());
+ $cardData = array();
+ $objects = zget($objectGroup, $laneType, array());
+ $object = zget($objects, $cardID, array());
- $item['id'] = $object->id;
- $item['order'] = $cardOrder;
- $item['pri'] = $object->pri ? $object->pri : '';
- $item['estimate'] = $lane->type == 'bug' ? '' : $object->estimate;
- $item['assignedTo'] = $object->assignedTo;
- $item['deadline'] = $lane->type == 'task' ? $object->deadline : '';
+ $cardData['id'] = $object->id;
+ $cardData['order'] = $cardOrder;
+ $cardData['pri'] = $object->pri ? $object->pri : '';
+ $cardData['estimate'] = $laneType == 'bug' ? '' : $object->estimate;
+ $cardData['assignedTo'] = $object->assignedTo;
+ $cardData['deadline'] = $laneType == 'task' ? $object->deadline : '';
+ $cardData['severity'] = $laneType == 'bug' ? $object->severity : '';
- if($lane->type == 'task')
+ if($laneType == 'task')
{
- $item['name'] = $object->name;
+ $cardData['name'] = $object->name;
}
else
{
- $item['title'] = $object->title;
+ $cardData['title'] = $object->title;
}
- $laneData['cards'][$col->type][] = $item;
+ $laneData['cards'][$column->type][] = $cardData;
$cardOrder ++;
}
+ if(!isset($laneData['cards'][$column->type])) $laneData['cards'][$column->type] = array();
}
- $kanban[$lane->type]['id'] = $lane->type;
- $kanban[$lane->type]['columns'] = array_values($columnData);
- $kanban[$lane->type]['lanes'][] = $laneData;
- $kanban[$lane->type]['defaultCardType'] = $lane->type;
+ $kanbanGroup[$laneType]['id'] = $laneType;
+ $kanbanGroup[$laneType]['columns'] = array_values($columnData);
+ $kanbanGroup[$laneType]['lanes'][] = $laneData;
+ $kanbanGroup[$laneType]['defaultCardType'] = $laneType;
}
- return $kanban;
+ return $kanbanGroup;
}
/**
@@ -330,93 +338,97 @@ class kanbanModel extends model
/**
* Update column cards.
*
- * @param int $executionID
- * @param string $objectType story|bug|task
- * @param object $columns
+ * @param object $lane
* @access public
* @return void
*/
- public function updateCards($executionID, $objectType, $columns)
+ public function updateCards($lane)
{
- $objects = array();
- if($objectType == 'story') $objects = $this->loadModel('story')->getExecutionStories($executionID);
- if($objectType == 'bug') $objects = $this->loadModel('bug')->getExecutionBugs($executionID);
- if($objectType == 'task') $objects = $this->loadModel('execution')->getKanbanTasks($executionID);
- if($objectType == 'story')
+ $laneType = $lane->type;
+ $executionID = $lane->execution;
+ $cardPairs = $this->dao->select('*')->from(TABLE_KANBANCOLUMN)
+ ->where('deleted')->eq(0)
+ ->andWhere('lane')->eq($lane->id)
+ ->fetchPairs('type' ,'cards');
+
+ if($laneType == 'story')
{
- $data = new stdClass();
- foreach($columns as $colID => $col)
+ $stories = $this->loadModel('story')->getExecutionStories($executionID);
+ foreach($stories as $storyID => $story)
{
- foreach($objects as $storyID => $story)
+ foreach($this->config->kanban->storyColumnStageList as $colType => $stage)
{
- if($col->type == 'backlog' and $story->status == 'active' and $story->stage == 'projected') $data->cards .= $storyID . ',';
-
- if($colType == 'closed' and $story->status == 'closed' and $story->stage == 'closed') $data->cards .= $storyID . ',';
-
- if($story->stage == $colType and strpos(',backlog,closed,', $colType) === false and $story->status == 'active') $data->cards .= $storyID . ',';
- }
- if(!empty($data->cards)) $data->cards = ',' . $data->cards;
- }
-
- $this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
- if($colType == 'develop') $devColumnID = $this->dao->lastInsertId();
- if($colType == 'test') $testColumnID = $this->dao->lastInsertId();
- }
- elseif($type == 'bug')
- {
- foreach($this->lang->kanban->bugColumn as $colType => $name)
- {
- $data = new stdClass();
- $data->lane = $laneID;
- $data->name = $name;
- $data->type = $colType;
- $data->cards = '';
- if(strpos(',fixing,fixed,', $colType) !== false) $data->parent = $resolvingColumnID;
- if(strpos(',testing,tested,', $colType) !== false) $data->parent = $testColumnID;
- if(strpos(',resolving,fixing,test,testing,tested,', $colType) === false)
- {
- foreach($objects as $bugID => $bug)
+ if(strpos(',ready,develop,test,', $colType) !== false) continue;
+ if($colType == 'backlog' and $story->stage == $stage and strpos($cardPairs['ready'], ",$storyID,") === false and strpos($cardPairs['backlog'], ",$storyID,") === false)
{
- if($colType == 'unconfirmed' and $bug->status == 'active' and $bug->confirmed == 0) $data->cards .= $bugID . ',';
-
- if($colType == 'confirmed' and $bug->status == 'active' and $bug->confirmed == 1) $data->cards .= $bugID . ',';
-
- if($colType == 'fixed' and $bug->status == 'resolved') $data->cards .= $bugID . ',';
-
- if($colType == 'closed' and $bug->status == 'closed') $data->cards .= $bugID . ',';
+ $cardPairs['backlog'] .= empty($cardPairs['backlog']) ? ',' . $storyID . ',' : $storyID . ',';
+ }
+ elseif($story->stage == $stage and strpos($cardPairs[$colType], ",$storyID,") === false)
+ {
+ $cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $storyID . ',' : $storyID . ',';
+ }
+ elseif($story->stage != $stage and strpos($cardPairs[$colType], ",$storyID,") !== false)
+ {
+ $cardPairs[$colType] = str_replace(",$storyID,", ',', $cardPairs[$colType]);
}
- $this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
- if($colType == 'resolving') $resolvingColumnID = $this->dao->lastInsertId();
- if($colType == 'test') $testColumnID = $this->dao->lastInsertId();
}
}
}
- elseif($type == 'task')
+ elseif($laneType == 'bug')
{
- foreach($this->lang->kanban->taskColumn as $colType => $name)
+ $bugs = $this->loadModel('bug')->getExecutionBugs($executionID);
+ foreach($bugs as $bugID => $bug)
{
- $data = new stdClass();
- $data->lane = $laneID;
- $data->name = $name;
- $data->type = $colType;
- $data->cards = '';
- if(strpos(',developing,developed,', $colType) !== false) $data->parent = $devColumnID;
- if(strpos(',develop,', $colType) === false)
+ foreach($this->config->kanban->bugColumnStatusList as $colType => $status)
{
- foreach($objects as $taskID => $task)
+ if(strpos(',resolving,fixing,test,testing,tested,', $colType) !== false) continue;
+ if($colType == 'unconfirmed' and $bug->status == $status and $bug->confirmed == 0 and strpos($cardPairs['unconfirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
{
- if($colType == 'developing' and $task->status == 'doing') $data->cards .= $taskID . ',';
-
- if($colType == 'developed' and $task->status == 'done') $data->cards .= $taskID . ',';
-
- if(strpos(',wait,pause,canceled,closed,', $colType) !== false and $task->status == $colType) $data->cards .= $taskID . ',';
+ $cardPairs['unconfirmed'] .= empty($cardPairs['unconfirmed']) ? ',' . $bugID . ',' : $bugID . ',';
+ }
+ elseif($colType == 'confirmed' and $bug->status == $status and $bug->confirmed == 1 and strpos($cardPairs['confirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
+ {
+ $cardPairs['confirmed'] .= empty($cardPairs['confirmed']) ? ',' . $bugID . ',' : $bugID . ',';
+ }
+ elseif($colType == 'fixed' and $bug->status == $status and strpos($cardPairs['fixed'], ",$bugID,") === false and strpos($cardPairs['testing'], ",$bugID,") === false and strpos($cardPairs['tested'], ",$bugID,") === false)
+ {
+ $cardPairs['confirmed'] .= empty($cardPairs['confirmed']) ? ',' . $bugID . ',' : $bugID . ',';
+ }
+ elseif($bug->status == $status and strpos($cardPairs[$colType], ",$bugID,") === false)
+ {
+ $cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $bugID . ',' : $bugID . ',';
+ }
+ elseif($bug->status != $status and strpos($cardPairs[$colType], ",$bugID,") !== false)
+ {
+ $cardPairs[$colType] = str_replace(",$bugID,", ',', $cardPairs[$colType]);
}
-
- $this->dao->insert(TABLE_KANBANCOLUMN)->data($data)->exec();
- if($colType == 'develop') $devColumnID = $this->dao->lastInsertId();
}
}
}
+ elseif($laneType == 'task')
+ {
+ $tasks = $this->loadModel('execution')->getKanbanTasks($executionID);
+ foreach($tasks as $taskID => $task)
+ {
+ foreach($this->config->kanban->taskColumnStatusList as $colType => $status)
+ {
+ if($colType == 'develop') continue;
+ if($task->status == $status and strpos($cardPairs[$colType], ",$taskID,") === false)
+ {
+ $cardPairs[$colType] .= empty($cardPairs[$colType]) ? ',' . $taskID . ',' : $taskID . ',';
+ }
+ elseif($task->status != $status and strpos($cardPairs[$colType], ",$taskID,") !== false)
+ {
+ $cardPairs[$colType] = str_replace(",$taskID,", ',', $cardPairs[$colType]);
+ }
+ }
+ }
+ }
+
+ foreach($cardPairs as $colType => $cards)
+ {
+ $this->dao->update(TABLE_KANBANCOLUMN)->set('cards')->eq($cards)->where('lane')->eq($lane->id)->andWhere('type')->eq($colType)->exec();
+ }
}
/**
@@ -439,6 +451,24 @@ class kanbanModel extends model
return $column;
}
+ /**
+ * Get Column by column name.
+ *
+ * @param string $name
+ * @param int $laneID
+ * @access public
+ * @return object
+ */
+ public function getColumnByName($name, $laneID)
+ {
+ return $this->dao->select('*')
+ ->from(TABLE_KANBANCOLUMN)
+ ->where('name')->eq($name)
+ ->andWhere('lane')->eq($laneID)
+ ->fetch();
+ }
+
+
/**
* Get lane by id.
*
@@ -460,15 +490,57 @@ class kanbanModel extends model
*/
public function setWIP($columnID)
{
- $column = $this->getColumnById($columnID);
- $data = fixer::input('post')
+ $oldColumn = $this->getColumnById($columnID);
+ $column = fixer::input('post')
->cleanInt('limit')
->remove('WIPCount,noLimit')
->get();
- $this->dao->update(TABLE_KANBANCOLUMN)->data($data)
+ /* Check column limit. */
+ $sumChildLimit = 0;
+ if($oldColumn->parent == -1 and $column->limit != -1)
+ {
+ $childColumns = $this->dao->select('id,`limit`')->from(TABLE_KANBANCOLUMN)->where('parent')->eq($columnID)->fetchAll();
+ foreach($childColumns as $childColumn)
+ {
+ if($childColumn->limit == -1)
+ {
+ dao::$errors['limit'] = $this->lang->kanban->error->parentLimitNote;
+ return false;
+ }
+
+ $sumChildLimit += $childColumn->limit;
+ }
+
+ if($sumChildLimit > $column->limit)
+ {
+ dao::$errors['limit'] = $this->lang->kanban->error->parentLimitNote;
+ return false;
+ }
+ }
+ elseif($oldColumn->parent > 0)
+ {
+ $parentColumn = $this->getColumnByID($oldColumn->parent);
+ if($parentColumn->limit != -1)
+ {
+ $siblingLimit = $this->dao->select('`limit`')->from(TABLE_KANBANCOLUMN)
+ ->where('`parent`')->eq($oldColumn->parent)
+ ->andWhere('id')->ne($columnID)
+ ->fetch('limit');
+
+ $sumChildLimit = $siblingLimit + $column->limit;
+
+ if($column->limit == -1 or $siblingLimit == -1 or $sumChildLimit > $parentColumn->limit)
+ {
+ dao::$errors['limit'] = $this->lang->kanban->error->childLimitNote;
+ return false;
+ }
+ }
+ }
+
+ $this->dao->update(TABLE_KANBANCOLUMN)->data($column)
->autoCheck()
- ->checkIF($data->limit != -1, 'limit', 'gt', 0)
+ ->checkIF($column->limit != -1, 'limit', 'gt', 0)
->batchcheck($this->config->kanban->setwip->requiredFields, 'notempty')
->where('id')->eq($columnID)
->exec();
@@ -499,16 +571,24 @@ class kanbanModel extends model
/**
* Update lane column.
*
- * @param int $columnID
+ * @param int $columnID
+ * @param object $column
* @access public
* @return array
*/
- public function updateLaneColumn($columnID, $data)
+ public function updateLaneColumn($columnID, $column)
{
+ $data = fixer::input('post')->get();
+
$this->dao->update(TABLE_KANBANCOLUMN)->data($data)
->autoCheck()
->batchcheck($this->config->kanban->setlaneColumn->requiredFields, 'notempty')
->where('id')->eq($columnID)
->exec();
+
+ if(dao::isError()) return;
+
+ $changes = common::createChanges($column, $data);
+ return $changes;
}
}
diff --git a/module/kanban/view/setlanecolumn.html.php b/module/kanban/view/setcolumn.html.php
similarity index 90%
rename from module/kanban/view/setlanecolumn.html.php
rename to module/kanban/view/setcolumn.html.php
index 31e5abfc83..002ca7fef5 100644
--- a/module/kanban/view/setlanecolumn.html.php
+++ b/module/kanban/view/setcolumn.html.php
@@ -1,12 +1,12 @@
+ * @author Thanatos
* @package kanban
- * @version $Id: setlane.html.php 935 2021-10-26 16:24:24Z liyuchun@easycorp.ltd $
+ * @version $Id: setcolumn.html.php 935 2021-10-26 16:24:24Z xiawenlong@easycorp.ltd $
* @link https://www.zentao.net
*/
?>
diff --git a/module/program/lang/en.php b/module/program/lang/en.php
index 21d538aa13..34135a8a11 100644
--- a/module/program/lang/en.php
+++ b/module/program/lang/en.php
@@ -76,7 +76,7 @@ $lang->program->stakeholderTypeList['inside'] = 'Inside';
$lang->program->stakeholderTypeList['outside'] = 'Outside';
$lang->program->noProgram = 'No program.';
-$lang->program->showClosed = 'Closed programs.';
+$lang->program->showClosed = 'Closed';
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
$lang->program->confirmBatchUnlink = "Do you want to batch unlink these stakeholders?";
$lang->program->beginLetterParent = "Parent begin date: %s, begin date should be > = parent begin date.";
diff --git a/module/search/model.php b/module/search/model.php
index 5fb8555fad..32e5939de1 100644
--- a/module/search/model.php
+++ b/module/search/model.php
@@ -132,11 +132,27 @@ class searchModel extends model
$condition = '';
if($operator == "include")
{
- $condition = ' LIKE ' . $this->dbh->quote("%$value%");
+ if($this->post->$fieldName == 'module')
+ {
+ $allModules = $this->loadModel('tree')->getAllChildId($value);
+ if($allModules) $condition = helper::dbIN($allModules);
+ }
+ else
+ {
+ $condition = ' LIKE ' . $this->dbh->quote("%$value%");
+ }
}
elseif($operator == "notinclude")
{
- $condition = ' NOT LIKE ' . $this->dbh->quote("%$value%");
+ if($this->post->$fieldName == 'module')
+ {
+ $allModules = $this->loadModel('tree')->getAllChildId($value);
+ if($allModules) $condition = " NOT " . helper::dbIN($allModules);
+ }
+ else
+ {
+ $condition = ' NOT LIKE ' . $this->dbh->quote("%$value%");
+ }
}
elseif($operator == 'belong')
{
diff --git a/module/story/control.php b/module/story/control.php
index 2723bd9b80..1d1f494e1e 100644
--- a/module/story/control.php
+++ b/module/story/control.php
@@ -136,8 +136,15 @@ class story extends control
if($objectID != 0)
{
- if($objectID != $this->session->project) $this->loadModel('action')->create('story', $storyID, 'linked2execution', '', $objectID);
- if($this->config->systemMode == 'new') $this->loadModel('action')->create('story', $storyID, 'linked2project', '', $this->session->project);
+ $object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch();
+ if($object->type != 'project')
+ {
+ $this->loadModel('action')->create('story', $storyID, 'linked2execution', '', $objectID);
+ }
+ else
+ {
+ $this->loadModel('action')->create('story', $storyID, 'linked2project', '', $objectID);
+ }
}
if($todoID > 0)
diff --git a/module/upgrade/control.php b/module/upgrade/control.php
index 61ba70c6e0..8c5c7011cc 100644
--- a/module/upgrade/control.php
+++ b/module/upgrade/control.php
@@ -184,7 +184,7 @@ class upgrade extends control
$title = $this->lang->upgrade->toPMS15Guide;
if(isset($this->config->maxVersion))
{
- $this->lang->upgrade->to15Desc = str_replace('15', $this->lang->maxName, $this->lang->upgrade->to15Desc);
+ $this->lang->upgrade->to15Desc = str_replace('15.0', $this->lang->maxName, $this->lang->upgrade->to15Desc);
$title = $this->lang->upgrade->toMAXGuide;
}
elseif(isset($this->config->bizVersion))
diff --git a/www/js/zui/kanban/min.css b/www/js/zui/kanban/min.css
index 4af7426fa8..8d8c1de9b9 100644
--- a/www/js/zui/kanban/min.css
+++ b/www/js/zui/kanban/min.css
@@ -1,6 +1,6 @@
/*!
- * ZUI: ZUI Kanban View - v1.9.2 - 2021-10-27
+ * ZUI: ZUI Kanban View - v1.9.2 - 2021-10-29
* http://openzui.com
* GitHub: https://github.com/easysoft/zui.git
* Copyright (c) 2021 cnezsoft.com; Licensed MIT
- */.kanban{min-height:300px}.kanban-header,.kanban-lane,.kanban-sub-lane{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;flex-direction:row;background-color:#f1f3f5;border-bottom:2px solid #fff;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.kanban-header{padding-left:20px;background-color:rgba(0,0,0,.07)}.kanban-no-lane-name .kanban-header{padding-left:0}.kanban-col{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.not-use-flex .kanban-col{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.no-flex .kanban-col{float:left}.kanban-col+.kanban-col{border-left:2px solid #fff}.kanban-header-col{position:relative;height:32px}.kanban-header-col>.title{display:block;height:32px;margin:0 32px;line-height:32px;text-align:center}.kanban-header-col>.title>.text{display:inline-block;max-width:100px;margin:0 5px;overflow:hidden;text-overflow:'';white-space:nowrap}.kanban-header-col>.title>.count,.kanban-header-col>.title>.icon{position:relative;top:-12px}.kanban-header-col>.title>.count{color:#8b91a2}.kanban-header-col[data-id=ADD]>.title{margin:6px;opacity:.7}.kanban-header-col[data-id=ADD]>.actions{display:none}.kanban-header-col>.actions{position:absolute;top:0;right:0}.kanban-header-col>.actions>.btn{min-width:32px}.kanban-header-col>.actions>.btn>.icon{opacity:.5}.kanban-header-has-parent>.kanban-header-col{height:64px;padding:16px 0}.kanban-header-parent-col{padding:0!important}.kanban-header-parent-col>.kanban-header-col{height:32px;padding:0}.kanban-header-sub-cols{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-top:-1px;margin-left:-2px;flex-direction:row;border-top:2px solid #fff;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row}.kanban-header-sub-cols>.kanban-col{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.no-flex .kanban-header-sub-cols{display:table;table-layout:fixed}.no-flex .kanban-header-sub-cols>.kanban-col{width:50%}.kanban-lane{min-height:100px}.kanban-lane+.kanban-lane{margin-top:3px}.no-flex .kanban-lane{position:relative;padding-left:20px}.kanban-lane-name{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:20px;overflow:hidden;color:#fff;text-align:center;background-color:#3dc6fd;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.kanban-lane-name>.text{position:absolute;top:5px;right:1px;bottom:5px;display:block;overflow:hidden;line-height:20px;text-align:center;white-space:normal;-webkit-writing-mode:tb-rl;-ms-writing-mode:tb-rl;writing-mode:tb-rl;-webkit-writing-mode:vertical-rl;writing-mode:vertical-rl}.no-flex .kanban-lane-name{position:absolute;top:0;bottom:0;left:0}.kanban-header+.kanban-lane>.kanban-lane-name{margin-top:-33px}.kanban-header.kanban-header-has-parent+.kanban-lane>.kanban-lane-name{margin-top:-65px}.kanban-sub-lanes{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;flex-direction:column;-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-pack:stretch;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column}.kanban-sub-lanes.no-sub-lane{background-color:#f1f3f5}.kanban-sub-lane{-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto}.kanban-lane-col.drop-to{outline:3px solid rgba(255,190,57,.25);outline-offset:-3px}.kanban-lane-col.drop-to .kanban-lane-actions>.btn{background-color:rgba(255,190,57,.25);border:1px dotted #ff9800}.kanban-lane-col.drop-to .kanban-lane-actions>.btn>span{opacity:0}.kanban-lane-col[data-type=EMPTY]{background-color:#fff}.kanban-lane-items{max-height:300px;padding:0 10px 10px;overflow:auto}.kanban-size-inited .kanban-lane-items{max-height:100%!important}.kanban-lane-actions{padding:10px 15px}.kanban-item{padding:10px;margin-top:10px;background:#fff;border:1px solid #fff;border-radius:4px;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);-webkit-transition:-webkit-box-shadow .2s,-webkit-transform .2s;-o-transition:box-shadow .2s,-o-transform .2s;transition:-webkit-box-shadow .2s,-webkit-transform .2s;transition:box-shadow .2s,transform .2s;transition:box-shadow .2s,transform .2s,-webkit-box-shadow .2s,-webkit-transform .2s,-o-transform .2s}.kanban-item.drag-from{background-color:#aaa;opacity:.2}.kanban-item:hover{border-color:rgba(0,0,0,.1);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.09);box-shadow:0 4px 10px 0 rgba(0,0,0,.09)}.kanban-item.drag-shadow{border-color:rgba(0,0,0,.2);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.05),0 4px 20px 0 rgba(0,0,0,.3);box-shadow:0 4px 10px 0 rgba(0,0,0,.05),0 4px 20px 0 rgba(0,0,0,.3);-webkit-transition:-webkit-box-shadow .2s,-webkit-transform .4s!important;-o-transition:box-shadow .2s,-o-transform .4s!important;transition:-webkit-box-shadow .2s,-webkit-transform .4s!important;transition:box-shadow .2s,transform .4s!important;transition:box-shadow .2s,transform .4s,-webkit-box-shadow .2s,-webkit-transform .4s,-o-transform .4s!important}.kanban-item.drag-shadow.in{-webkit-transform:scale(1.1) rotate(5deg);-ms-transform:scale(1.1) rotate(5deg);-o-transform:scale(1.1) rotate(5deg);transform:scale(1.1) rotate(5deg)}.kanban-dragging{cursor:move}
\ No newline at end of file
+ */.kanban{min-height:300px}.kanban-header,.kanban-lane,.kanban-sub-lane{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;flex-direction:row;background-color:#f1f3f5;border-bottom:2px solid #fff;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.kanban-header{padding-left:20px;background-color:rgba(0,0,0,.07)}.kanban-no-lane-name .kanban-header{padding-left:0}.kanban-col{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.not-use-flex .kanban-col{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.no-flex .kanban-col{float:left}.kanban-col+.kanban-col{border-left:2px solid #fff}.kanban-header-col{position:relative;height:32px}.kanban-header-col>.title{display:block;height:32px;margin:0 32px;line-height:32px;text-align:center}.kanban-header-col>.title>.text{display:inline-block;max-width:100px;margin:0 5px;overflow:hidden;text-overflow:'';white-space:nowrap}.kanban-header-col>.title>.count,.kanban-header-col>.title>.icon{position:relative;top:-12px}.kanban-header-col>.title>.count{color:#8b91a2}.kanban-header-col[data-id=ADD]>.title{margin:6px;opacity:.7}.kanban-header-col[data-id=ADD]>.actions{display:none}.kanban-header-col>.actions{position:absolute;top:0;right:0}.kanban-header-col>.actions>.btn{min-width:32px}.kanban-header-col>.actions>.btn>.icon{opacity:.5}.kanban-header-has-parent>.kanban-header-col{height:64px;padding:16px 0}.kanban-header-parent-col{padding:0!important}.kanban-header-parent-col>.kanban-header-col{height:32px;padding:0}.kanban-header-sub-cols{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-top:-1px;margin-left:-2px;flex-direction:row;border-top:2px solid #fff;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row}.kanban-header-sub-cols>.kanban-col{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.no-flex .kanban-header-sub-cols{display:table;table-layout:fixed}.no-flex .kanban-header-sub-cols>.kanban-col{width:50%}.kanban-lane{min-height:100px}.kanban-lane+.kanban-lane{margin-top:3px}.no-flex .kanban-lane{position:relative;padding-left:20px}.kanban-lane-name{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:20px;overflow:hidden;color:#fff;text-align:center;background-color:#3dc6fd;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.kanban-lane-name>.text{position:absolute;top:5px;right:1px;bottom:5px;display:block;overflow:hidden;line-height:20px;text-align:center;white-space:normal;-webkit-writing-mode:tb-rl;-ms-writing-mode:tb-rl;writing-mode:tb-rl;-webkit-writing-mode:vertical-rl;writing-mode:vertical-rl}.no-flex .kanban-lane-name{position:absolute;top:0;bottom:0;left:0}.kanban-sub-lanes{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;flex-direction:column;-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-pack:stretch;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column}.kanban-sub-lanes.no-sub-lane{background-color:#f1f3f5}.kanban-sub-lane{-webkit-box-flex:1;-webkit-flex:auto;-ms-flex:auto;flex:auto}.kanban-lane-col.drop-to{outline:3px solid rgba(255,190,57,.25);outline-offset:-3px}.kanban-lane-col.drop-to .kanban-lane-actions>.btn{background-color:rgba(255,190,57,.25);border:1px dotted #ff9800}.kanban-lane-col.drop-to .kanban-lane-actions>.btn>span{opacity:0}.kanban-lane-col[data-type=EMPTY]{background-color:#fff}.kanban-lane-items{max-height:300px;padding:0 10px 10px;overflow:auto}.kanban-size-inited .kanban-lane-items{max-height:100%!important}.kanban-lane-actions{padding:10px 15px}.kanban-item{padding:8px 10px;margin-top:10px;background:#fff;border:1px solid #fff;border-radius:4px;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);-webkit-transition:-webkit-box-shadow .2s,-webkit-transform .2s;-o-transition:box-shadow .2s,-o-transform .2s;transition:-webkit-box-shadow .2s,-webkit-transform .2s;transition:box-shadow .2s,transform .2s;transition:box-shadow .2s,transform .2s,-webkit-box-shadow .2s,-webkit-transform .2s,-o-transform .2s}.kanban-item.drag-from{background-color:#aaa;opacity:.2}.kanban-item:hover{border-color:rgba(0,0,0,.1);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.09);box-shadow:0 4px 10px 0 rgba(0,0,0,.09)}.kanban-item.drag-shadow{border-color:rgba(0,0,0,.2);-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.05),0 4px 20px 0 rgba(0,0,0,.3);box-shadow:0 4px 10px 0 rgba(0,0,0,.05),0 4px 20px 0 rgba(0,0,0,.3);-webkit-transition:-webkit-box-shadow .2s,-webkit-transform .4s!important;-o-transition:box-shadow .2s,-o-transform .4s!important;transition:-webkit-box-shadow .2s,-webkit-transform .4s!important;transition:box-shadow .2s,transform .4s!important;transition:box-shadow .2s,transform .4s,-webkit-box-shadow .2s,-webkit-transform .4s,-o-transform .4s!important}.kanban-item.drag-shadow.in{-webkit-transform:scale(1.1) rotate(5deg);-ms-transform:scale(1.1) rotate(5deg);-o-transform:scale(1.1) rotate(5deg);transform:scale(1.1) rotate(5deg)}.kanban-dragging{cursor:move}
\ No newline at end of file
diff --git a/www/js/zui/kanban/min.js b/www/js/zui/kanban/min.js
index 6ee058f39f..9b6c02a187 100644
--- a/www/js/zui/kanban/min.js
+++ b/www/js/zui/kanban/min.js
@@ -1,7 +1,7 @@
/*!
- * ZUI: ZUI Kanban View - v1.9.2 - 2021-10-27
+ * ZUI: ZUI Kanban View - v1.9.2 - 2021-10-29
* http://openzui.com
* GitHub: https://github.com/easysoft/zui.git
* Copyright (c) 2021 cnezsoft.com; Licensed MIT
*/
-!function(a){"use strict";var n="zui.kanban",e="object"==typeof CSS&&CSS.supports("display","flex"),t=function(o,i){var d=this;d.name=n,d.$=a(o).addClass("kanban"),d.options=i=a.extend({},t.DEFAULTS,this.$.data(),i),d.data=i.data||[],d.render(d.data);var s=function(n){if(i.onAction){var e=a(this);i.onAction(e.data("action"),e,n,d)}};if(d.$.on("click",".action",s).on("dblclick",".action-dbc",s),"auto"===i.droppable&&(i.droppable=!i.readonly),i.droppable){var r=0,l={selector:".kanban-item",target:'.kanban-lane-col:not([data-type="EMPTY"])',drop:function(a){"function"==typeof i.droppable?i.droppable(a):i.onAction&&i.onAction("dropItem",a.element,a,d)},start:function(n){d.$.addClass("kanban-dragging"),r&&clearTimeout(r),r=setTimeout(function(){a(n.shadowElement).addClass("in"),r=0},50)},always:function(){d.$.removeClass("kanban-dragging"),r&&(clearTimeout(r),r=0)}};"object"==typeof i.droppable&&a.extend(l,i.droppable),d.$.droppable(l)}e&&i.useFlex||a(window).on("resize",function(){d.adjustSize()}),i.useFlex&&e||d.$.addClass("not-use-flex"),i.onCreate&&i.onCreate(d)};t.prototype.render=function(a){var n=this;a&&(n.data=a),n.data&&!Array.isArray(n.data)&&(n.data=[n.data]);var t=n.data||[];n.options.beforeRender&&n.options.beforeRender(n,t),n.$.toggleClass("kanban-readonly",!!n.options.readonly).toggleClass("kanban-no-lane-name",!!n.options.noLaneName),n.$.children(".kanban-board").addClass("kanban-expired");for(var o=0;o-1){var o=this.data[t];n=a.extend(o,n),this.data[t]=n}else this.data.push(n)}n.id||(n.id=a.zui.uuid());var i=this,d=i.$,s=d.children('.kanban-board[data-id="'+n.id+'"]');s.length?s.removeClass("kanban-expired"):(s=a('').appendTo(d),e||s.addClass("no-flex")),i.renderKanbanHeader(n,s),s.children(".kanban-lane").addClass("kanban-expired");for(var r=n.lanes||[],l=0;l .kanban-lane-items .kanban-item').length,h=s.find('.kanban-header-col[data-id="'+c+'"] > .title > .count');i.options.countRender?i.options.countRender(h,p,n.columns[l],i):h.text(p||(i.options.showZeroCount?0:""))}i.adjustKanbanSize(n,s),i.options.onRenderKanban&&i.options.onRenderKanban(s,n,i)},t.prototype.renderKanbanHeader=function(n,t){var o=this;t=t||o.$.children('.kanban-board[data-id="'+n.id+'"]');var i=t.children(".kanban-header");i.length||(i=a('').prependTo(t),e||i.addClass("clearfix")),i.children(".kanban-col").addClass("kanban-expired");for(var d=n.columns,s={},r=!1,l=0;l