/** * Created by maprex on 2021-03-23 */ 'use strict'; define([ 'app', 'angular' ], function (app, angular) { app.controller('issueListTimelineController', ['$scope', '$rootScope', '$log', '$q', '$resourceProvider', '$controller', '$injector', 'Gantt', 'SweetAlert', '$filter', 'IssueType', 'Priority', 'Severity', 'IssueStatus', 'CustomField','IssueSearch', function ($scope, $rootScope, $log, $q, $resourceProvider, $controller, $injector, Gantt, SweetAlert, $filter, IssueType, Priority, Severity, IssueStatus, CustomField, IssueSearch) { $scope.fn = { initSearch : initSearch, // 검색 초기화 getPageList : getPageList, // 목록을 조회한다. makeSearchConditions : makeSearchConditions, // 검색조건을 가져옴 getIssueTypes : getIssueTypes, // 이슈 유형 목록을 가져온다. getPriorities : getPriorities, // 우선순위 목록을 가져온다. getSeverities : getSeverities, // 중요도 목록을 가져온다. getIssueStatuses : getIssueStatuses, // 이슈 상태 목록을 가져온다. getCustomFields : getCustomFields, // 사용자 정의 필드 목록을 가져온다. startExecute : startExecute }; $scope.vm = { search : { title : "", // 제목 description : "", // 내용 combinationIssueNumber : "", // 이슈 번호 projectType : "BTS_PROJECT", // 프로젝트 유형 registerDateRange : "", // 등록일 기간 검색 startDateRange : "", // 시작일 기간 검색 completeDateRange : "", // 완료일 기간 검색 severityIds : [], // 중요도 검색 priorityIds : [], // 우선순위 검색 issueStatusIds : [], // 이슈 상태 검색 issueTypeIds : [] // 이슈 유형 검색 }, arrProjects : [], // 프로젝트 배열 projectIssues : new Object(), // 프로젝트 전체 이슈 projectCompleteIssues : new Object(), // 프로젝트 완료 이슈 searchView : false, // 상세 검색 조건 표시 여부 detailView : false, // 상세 모드 변경 값 tableConfigs : [], // 테이블 셋팅 정보 responseData : { data : [] }, projectName : "", // 프로젝트 검색 userName : "", // 담당자 검색 registerName : "", // 등록자 검색 projects : [], // 프로젝트 issueStatuses : [], // 이슈 상태 issueTypes : [], // 이슈 유형 priorities : [], // 우선 순위 severities : [], // 중요도 users : [], // 담당자 registers : [], // 등록자 customFields : [], // 사용자 정의 필드 issueTableConfigs : [], // 이슈 테이블 설정 page : { selectedPage : 0, selectedPageRowCount : String(99999) }, relationIssues :[], // 연관 이슈 배열 useGantt : false, chart : null // 간트차트 }; angular.extend(this, $controller('autoCompleteController', {$scope : $scope, $injector : $injector})); $scope.init = function () { $scope.fn.startExecute(); } // 검색 필드 초기화 function initSearch() { $state.go($state.current, {}, {reload : true}); } // 이슈 검색 조건을 만든다. function makeSearchConditions() { var conditions = { title : $scope.vm.search.title, description : $scope.vm.search.description, projectType : $scope.vm.search.projectType, combinationIssueNumber : $scope.vm.search.combinationIssueNumber.trim(), beginRegisterDate : "", endRegisterDate : "", beginStartDate : "", endStartDate : "", beginCompleteDate : "", endCompleteDate : "", projectIds : (function () { var projectIds = []; angular.forEach($scope.vm.projects, function (project) { projectIds.push(project.id); }); return projectIds; })(), issueStatusIds : (function () { var issueStatusIds = []; angular.forEach($scope.vm.search.issueStatusIds, function (issueStatusId) { issueStatusIds.push(issueStatusId.fieldKey); }); return issueStatusIds; })(), issueTypeIds : (function () { var issueTypeIds = []; angular.forEach($scope.vm.search.issueTypeIds, function (issueTypeId) { issueTypeIds.push(issueTypeId.fieldKey); }); return issueTypeIds; })(), priorityIds : (function () { var priorityIds = []; angular.forEach($scope.vm.search.priorityIds, function (priorityId) { priorityIds.push(priorityId.fieldKey); }); return priorityIds; })(), severityIds : (function () { var severityIds = []; angular.forEach($scope.vm.search.severityIds, function (severityId) { severityIds.push(severityId.fieldKey); }); return severityIds; })(), userIds : (function () { var userIds = []; angular.forEach($scope.vm.users, function (user) { userIds.push(user.id); }); return userIds; })(), registerIds : (function () { var registerIds = []; angular.forEach($scope.vm.registers, function (register) { registerIds.push(register.id); }); return registerIds; })(), issueCustomFields : (function () { var issueCustomFields = []; angular.forEach($scope.vm.customFields, function (customField) { var useValues = []; if (angular.isArray(customField.useValues)) { angular.forEach(customField.useValues, function (useValue) { useValues.push(useValue.value); }); } else { useValues.push(customField.useValues); } // useValues 를 배열로 변환한다. var temp = angular.copy(customField); temp.useValues = useValues; issueCustomFields.push(temp); }); return issueCustomFields; })() }; // 등록일 if ($rootScope.isDefined($scope.vm.search.registerDateRange)) { var registerDateRange = $scope.vm.search.registerDateRange.split("~"); conditions.beginRegisterDate = registerDateRange[0].trim(); conditions.endRegisterDate = registerDateRange[1].trim(); } // 시작일 if ($rootScope.isDefined($scope.vm.search.startDateRange)) { var startDateRange = $scope.vm.search.startDateRange.split("~"); conditions.beginStartDate = startDateRange[0].trim(); conditions.endStartDate = startDateRange[1].trim(); } // 종료일 if ($rootScope.isDefined($scope.vm.search.completeDateRange)) { var completeDateRange = $scope.vm.search.completeDateRange.split("~"); conditions.beginCompleteDate = completeDateRange[0].trim(); conditions.endCompleteDate = completeDateRange[1].trim(); } return conditions; } // 이슈 상태 목록 function getIssueStatuses() { var deferred = $q.defer(); $scope.vm.issueStatuses = []; IssueStatus.find($resourceProvider.getContent({}, $resourceProvider.getPageContent(0, 1000))).then(function (result) { if (result.data.message.status === "success") { angular.forEach(result.data.data, function (issueType) { $scope.vm.issueStatuses.push({ fieldKey : issueType.id, fieldValue : issueType.name }); }); } else { SweetAlert.swal($filter("translate")("common.failedToIssueStatusListLookup"), result.data.message.message, "error"); // 이슈 상태 목록 조회 실패 } deferred.resolve(result.data.data); }); return deferred.promise; } // 이슈 유형 목록 function getIssueTypes() { var deferred = $q.defer(); $scope.vm.issueTypes = []; IssueType.find($resourceProvider.getContent({}, $resourceProvider.getPageContent(0, 1000))).then(function (result) { if (result.data.message.status === "success") { angular.forEach(result.data.data, function (issueType) { $scope.vm.issueTypes.push({ fieldKey : issueType.id, fieldValue : issueType.name }); }); } else { SweetAlert.swal($filter("translate")("issue.failedToIssueTypeListLookup"), result.data.message.message, "error"); // 이슈 유형 목록 조회 실패 } deferred.resolve(result.data.data); }); return deferred.promise; } // 우선순위 목록 function getPriorities() { var deferred = $q.defer(); $scope.vm.priorities = []; Priority.find($resourceProvider.getContent({}, $resourceProvider.getPageContent(0, 1000))).then(function (result) { if (result.data.message.status === "success") { angular.forEach(result.data.data, function (prioritiy) { $scope.vm.priorities.push({ fieldKey : prioritiy.id, fieldValue : prioritiy.name }); }); } else { SweetAlert.swal($filter("translate")("issue.failedToPriorityListLookup"), result.data.message.message, "error"); // 우선순위 목록 조회 실패 } deferred.resolve(result.data.data); }); return deferred.promise; } // 중요도 목록 function getSeverities() { var deferred = $q.defer(); $scope.vm.severities = []; Severity.find($resourceProvider.getContent({}, $resourceProvider.getPageContent(0, 1000))).then(function (result) { if (result.data.message.status === "success") { angular.forEach(result.data.data, function (severity) { $scope.vm.severities.push({ fieldKey : severity.id, fieldValue : severity.name }); }); } else { SweetAlert.swal($filter("translate")("issue.failedToCriticalListLookup"), result.data.message.message, "error"); // 중요도 목록 조회 실패 } deferred.resolve(result.data.data); }); return deferred.promise; } // 사용자 정의 필드 목록 function getCustomFields() { var deferred = $q.defer(); $scope.vm.customFields = []; CustomField.find($resourceProvider.getContent({}, $resourceProvider.getPageContent(0, 1000))).then(function (result) { if (result.data.message.status === "success") { angular.forEach(result.data.data, function (customField) { switch (customField.customFieldType) { case "INPUT" : case "NUMBER" : case "DATETIME" : case "IP_ADDRESS" : case "EMAIL" : case "SITE" : case "TEL" : customField.useValues = ""; break; case "MULTI_SELECT" : case "SINGLE_SELECT" : customField.useValues = []; break; } $scope.vm.customFields.push(customField); }); } else { SweetAlert.swal($filter("translate")("issue.failedToUserDefinedFieldListLookup"), result.data.message.message, "error"); // 사용자 정의 필드 목록 조회 실패 } deferred.resolve(result.data.data); }); return deferred.promise; } function drawGanttChart(useProject = false) { google.charts.load('current', {'packages':['gantt'], 'language': 'ko'}); google.charts.setOnLoadCallback(drawChart); } function toMilliseconds(minutes) { return minutes * 60 * 1000; } // 검색조건에서 해당 프로젝트 찾기 function findProjectSearch(projectId) { var projects = $scope.vm.projects; var find = false; for (let i = 0; i < projects.length; i++) { if (projects[i].id == projectId) { find = true; break; } } return find; } function getPageList(selectedPage) { // 현재 페이지 정보 var currentPage = 0; // $rootScope.spinner = true; // 현재 선택된 프로젝트를 검색 기본으로 추가 if ($rootScope.workProject != null && $rootScope.workProject.id > -1) { var find = findProjectSearch($rootScope.workProject.id); if (!find) { $scope.vm.projects.push($rootScope.workProject); } } var conditions = $scope.fn.makeSearchConditions(); Gantt.find($resourceProvider.getContent(conditions, $resourceProvider.getPageContent(currentPage, $scope.vm.page.selectedPageRowCount))).then(function (result) { if (result.data.message.status === "success") { $scope.vm.page.selectedPage = currentPage + 1; $scope.vm.responseData = result.data; drawGanttChart(); } else { //SweetAlert.error($filter("translate")("issue.failedIssueLookup"), result.data.message.message); // 이슈 조회 실패 } }); } // 이슈리스트에 해당 아이디가 존재하는지 여부 확인 function containsIssue(issueId) { var responseData = $scope.vm.responseData; if (responseData != null) { var data = responseData.data; for (var i=0; i < data.length; i++) { var el = data[i]; if (el.id == issueId) { return true; } } } return false } // 순환구조여부 확인 // 연관 일감이 양방향으로 적용되어 있을때 차트에서는 오류가 발생하므로 양방향일 경우 처리 안하기 위함 function isCycle(parentId, childId) { var data = $scope.vm.relationIssues; if (data != null && data.length > 0) { for (var i=0; i < data.length; i++) { if (data[i].chartParent == childId && data[i].chartChild == parentId) { return true; } } } return false; } var chart; var otherData; var options; function drawChart() { var responseData = $scope.vm.responseData; var page = responseData.page; otherData = new google.visualization.DataTable(); otherData.addColumn('string', 'Task ID'); otherData.addColumn('string', 'Task Name'); otherData.addColumn('string', 'Resource'); otherData.addColumn('date', 'Start'); otherData.addColumn('date', 'End'); otherData.addColumn('number', '기간'); otherData.addColumn('number', 'Percent Complete'); otherData.addColumn('string', 'Dependencies'); var data = responseData.data; var dataCount = data.length; var trackHeight = 22; var bottomHeight = 50; var chartHeight = dataCount * trackHeight + bottomHeight; var arrPalette = []; if (page.totalCount > 0) { $scope.vm.relationIssues = []; data.forEach(el => { var start = null; if (el.startDate != null) { start = new Date(el.startDate); start.setHours(0,0,0); } var end = null; if (el.completeDate != null) { end = new Date(el.completeDate); end.setHours(23, 59, 59); } var duration = 0; if (start != null && end != null) { duration = end.getTime() - start.getTime(); } var p = { "color": el.issueStatusColor, "dark": el.issueStatusColor, "light": el.issueStatusColor }; arrPalette.push(p); // 연관이슈 설정 var relationIssue = null; if ( el.issueRelationIssueVos != null && el.issueRelationIssueVos.length > 0) { var i = 0; relationIssue = ""; el.issueRelationIssueVos.forEach(rel => { if (i > 0) { relationIssue += ","; } if (containsIssue(rel.id) && !isCycle(el.id, rel.id)) { relationIssue += String(rel.id); var pair = { chartParent : el.id, chartChild : rel.id } $scope.vm.relationIssues.push(pair); i++; } }); } otherData.addRow([String(el.id), el.title, String(el.id), start, end, duration, 100, relationIssue]); }) $scope.vm.useGantt = true; } else { var p = { "color": "0066cc", "dark": "0066cc", "light": "0066cc" }; arrPalette.push(p); otherData.addRow(["none", "일감이 없습니다", "none", new Date(), null, toMilliseconds(0), 100, null]); $scope.vm.useGantt = false; } options = { gantt: { defaultStartDate : new Date(), barHeight : 15, barCornerRadius : 1, criticalPathEnabled: false, // Critical path arrows will be the same as other arrows. arrow: { angle: 100, width: 2, color: '#0066cc', radius: 0, spaceAfter : 0, }, labelStyle: { fontName: 'NanumSquare', fontSize: 12, color: '#d5c209' }, palette: arrPalette, trackHeight : trackHeight }, height: chartHeight, animation: {"startup": true} }; var container = document.getElementById('chart_div'); if (container == null) { return; } chart = new google.visualization.Gantt(container); hideChartTooltip(container, chart); google.visualization.events.addListener(chart, 'onmouseover', function (e) { }); google.visualization.events.addListener(chart, 'click', function() { }); google.visualization.events.addListener(chart, 'select', function() { var selection = chart.getSelection(); if (selection.length > 0) { var responseData = $scope.vm.responseData; var data = responseData.data; var issue = data[selection[0].row]; // 이슈 번호를 저장한 후 이슈 목록으로 이동한다. $rootScope.$broadcast("makeIssueSearch", issue); } }); window.addEventListener('resize', function() { chart.draw(otherData, options); }, false); //화면 크기에 따라 그래프 크기 변경 chart.draw(otherData, options); } // 이슈를 배열에 추가 function addIssue(projectId, issue) { if ($scope.vm.projectCompleteIssues[projectId] == null) $scope.vm.projectCompleteIssues[projectId] = []; if ($scope.vm.projectIssues[projectId] == null) $scope.vm.projectIssues[projectId] = []; if (issue.issueStatusType == "CLOSE") { $scope.vm.projectCompleteIssues[projectId].push(issue); } $scope.vm.projectIssues[projectId].push(issue); } // google chart 기본 tooltip 가리기 function hideChartTooltip(container, chart) { google.visualization.events.addOneTimeListener(chart, 'ready', function () { var observer = new MutationObserver(function (nodes) { Array.prototype.forEach.call(nodes, function(node) { if (node.addedNodes.length > 0) { Array.prototype.forEach.call(node.addedNodes, function(addedNode) { if ((addedNode.tagName === 'rect') && (addedNode.getAttribute('fill') === 'white')) { addedNode.setAttribute('fill', 'transparent'); addedNode.setAttribute('stroke', 'transparent'); Array.prototype.forEach.call(addedNode.parentNode.getElementsByTagName('text'), function(label) { label.setAttribute('fill', 'transparent'); }); } }); } }); }); observer.observe(container, { childList: true, subtree: true }); }); } function startExecute() { var promises = { getIssueTypes: $scope.fn.getIssueTypes(), getPriorities: $scope.fn.getPriorities(), getSeverities: $scope.fn.getSeverities(), getIssueStatuses: $scope.fn.getIssueStatuses(), getCustomFields: $scope.fn.getCustomFields() } $q.all(promises).then(function (results) { $log.debug("promises 결과 ", results); $scope.fn.getPageList(); }); } $rootScope.$on('changeChartTab', function(evt) { if (chart != null) { chart.clearChart(); $scope.fn.startExecute(); } }); }]); });