var weeklyReqStatusTable = null; var reqStatusTable = null; var originData = null; // 원본 데이터 저장용 ////////////////// //Document Ready ////////////////// var selectedPdServiceId; // 제품(서비스) 아이디 var selectedVersionId; // 선택된 버전 아이디 //////////////////////////////////////////////////////////////////////////////////////// //Document Ready //////////////////////////////////////////////////////////////////////////////////////// function execDocReady() { var pluginGroups = [ [ "../reference/jquery-plugins/select2-4.0.2/dist/css/select2_lightblue4.css", "../reference/jquery-plugins/lou-multi-select-0.9.12/css/multiselect-lightblue4.css", "../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select-bluelight.css", "../reference/jquery-plugins/select2-4.0.2/dist/js/select2.min.js", "../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.quicksearch.js", "../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.multi-select.js", "../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select.min.js", ], [ // Table "js/common/table_new.js", // Data Table "../reference/jquery-plugins/dataTables-1.10.16/media/css/jquery.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/css/responsive.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/css/select.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/media/js/jquery.dataTables.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/js/dataTables.responsive.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/js/dataTables.select.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/RowGroup/js/dataTables.rowsGroup.min.js" ], [ // Slim Scroll "../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.min.js", // Widget "../reference/lightblue4/docs/lib/widgster/widgster.js", // comming soon "../reference/jquery-plugins/timerStyles.js", // echarts "../reference/jquery-plugins/echarts-5.5.0/dist/echarts.min.js", ], ]; loadPluginGroupsParallelAndSequential(pluginGroups) .then(function () { console.log("모든 플러그인 로드 완료"); $(".widget").widgster(); //coming soon $("#count-down").TimeCircles( { circle_bg_color: "#f8f8f8", use_background: true, bg_width: .2, fg_width: 0.013, time: { Days: { color: "#f8f8f8" }, Hours: { color: "#f8f8f8" }, Minutes: { color: "#f8f8f8" }, Seconds: { color: "#f8f8f8" } } } ); //좌측 메뉴 setSideMenu( "sidebar_menu_insight", "sidebar_menu_report", "sidebar_menu_report_req_status" ); $(".top-menu-div-progress").matchHeight({ target: $(".top-menu-div") }); $(".top-menu-status-graph").matchHeight({ target: $(".top-menu-div") }); //제품(서비스) 셀렉트 박스 이니시에이터 makePdServiceSelectBox(); //버전 멀티 셀렉트 박스 이니시에이터 makeVersionMultiSelectBox(); }) .catch(function (e) { console.error("플러그인 로드 중 오류 발생"); console.error(e); }); } /////////////////////// //제품 서비스 셀렉트 박스 ////////////////////// function makePdServiceSelectBox() { //제품 서비스 셀렉트 박스 이니시에이터 $(".chzn-select").each(function () { $(this).select2($(this).data()); }); //제품 서비스 셀렉트 박스 데이터 바인딩 $.ajax({ url: "/auth-user/api/arms/pdServicePure/getPdServiceMonitor.do", type: "GET", contentType: "application/json;charset=UTF-8", dataType: "json", progress: true, async: false, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// for (var k in data.response) { var obj = data.response[k]; var newOption = new Option(obj.c_title, obj.c_id, false, false); $("#selected_pdService").append(newOption).trigger("change"); } ////////////////////////////////////////////////////////// } } }); $("#selected_pdService").on("select2:open", function () { //슬림스크롤 makeSlimScroll(".select2-results__options"); }); // --- select2 ( 제품(서비스) 검색 및 선택 ) 이벤트 --- // $("#selected_pdService").on("select2:select", function (e) { selectedPdServiceId = $("#selected_pdService").val(); // 제품( 서비스 ) 선택했으니까 자동으로 버전을 선택할 수 있게 유도 // 디폴트는 base version 을 선택하게 하고 ( select all ) //~> 이벤트 연계 함수 :: Version 표시 jsTree 빌드 bind_VersionData_By_PdService(); }); } // end makePdServiceSelectBox() //////////////////////////////////////////////////////////// //버전 멀티 셀렉트 박스 (버전 옵션 선택 시) //////////////////////////////////////////////////////////// function makeVersionMultiSelectBox() { //버전 선택 셀렉트 박스 이니시에이터 $(".multiple-select").multipleSelect({ filter: true, bubbles: true, cancelable: true, onClose: function () { console.log("onOpen event fire!\n"); const versionTag = $(".multiple-select").val(); if (versionTag === null || versionTag == "") { jError("버전이 선택되지 않았습니다."); $(".ms-parent").css("z-index", 1000); return; } selectedPdServiceId = $("#selected_pdService").val(); selectedVersionId = versionTag.join(','); pdServiceProgress($("#selected_pdService").val(), selectedVersionId); reportReqStatusTableLoad(selectedPdServiceId, selectedVersionId); reportWeeklyReqStatusTableLoad(selectedPdServiceId, selectedVersionId); $(".ms-parent").css("z-index", 1000); }, onOpen: function() { console.log("open event"); $(".ms-parent").css("z-index", 9999); } }); } //////////////////////////////////////////////////////////// // 제품서비스 - 버전 데이터 바인딩 (제품 선택 시) //////////////////////////////////////////////////////////// function bind_VersionData_By_PdService() { $(".multiple-select option").remove(); $.ajax({ url: "/auth-user/api/arms/pdService/getVersionList?c_id=" + $("#selected_pdService").val(), type: "GET", dataType: "json", progress: true, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// var pdServiceVersionIds = []; for (var k in data.response) { var obj = data.response[k]; pdServiceVersionIds.push(obj.c_id); var newOption = new Option(obj.c_title, obj.c_id, true, false); $(".multiple-select").append(newOption); } selectedVersionId = pdServiceVersionIds.join(","); pdServiceProgress($("#selected_pdService").val(), selectedVersionId); reportReqStatusTableLoad(selectedPdServiceId, selectedVersionId); reportWeeklyReqStatusTableLoad(selectedPdServiceId, selectedVersionId); if (data.length > 0) { console.log("display 재설정."); } $(".multiple-select").multipleSelect("refresh"); ////////////////////////////////////////////////////////// } } }); } function pdServiceProgress(selectId, selecteVersionId) { let endPointUrl = "/T_ARMS_REQADD_" + selectId + "/calculateProgress.do"; if(selecteVersionId) { let versionInfo ="?c_req_pdservice_versionset_link=" + selecteVersionId; endPointUrl += versionInfo; } $.ajax({ url: "/auth-user/api/arms/reqAddStatePure" + endPointUrl, type: "GET", dataType: "json", progress: true, statusCode: { 200: function (result) { $("#total_work").val(result.totalWork); $("#planed_work").val(result.planWork); $("#performance_capability").val(result.performanceCapability.toFixed(0)); $("#plan_progress_rate").val(result.planProgressRate.toFixed(2)); $("#performance_progress_rate").val(result.performanceProgressRate.toFixed(2)); $("#project_progress").val(result.projectProgress.toFixed(2)); if (result.projectProgress < 0) { $("#project_progress").css("color" ,"#DB2A34"); } else { $("#project_progress").css("color", "#a4c6ff"); } } } }); } function reportWeeklyReqStatusTableLoad(pdServiceId, pdServiceVersionLinks) { let urlBuilder = new UrlBuilder().setBaseUrl( `/auth-user/api/arms/reqAddStatePure/T_ARMS_REQADD_${pdServiceId}/weeklyReportReqStatus` ); if (pdServiceVersionLinks !== undefined) { urlBuilder.addQueryParam("c_req_pdservice_versionset_link", pdServiceVersionLinks); } const url = urlBuilder.build(); $.ajax({ url: url, success: function (data) { console.log(data); weeklyChartLoad(data); } }); } function reportReqStatusTableLoad(pdServiceId, pdServiceVersionLinks) { let urlBuilder = new UrlBuilder().setBaseUrl( `/auth-user/api/arms/reqAddStatePure/T_ARMS_REQADD_${pdServiceId}/reportReqStatus` ); if (pdServiceVersionLinks !== undefined) { urlBuilder.addQueryParam("c_req_pdservice_versionset_link", pdServiceVersionLinks); } const url = urlBuilder.build(); var columnList = buildReqStatusColumnList(); if ($.fn.DataTable.isDataTable('#req_status_table')) { $('#req_status_table').DataTable().destroy(); $('#req_status_table').empty(); reqStatusTable = null; } reqStatusTable = $("#req_status_table").table({ columns: columnList, ajax: { url: url, dataSrc: function (json) { originData = json[0] || {}; return json.reduce(function (acc, cur) { var tempData = {}; Object.keys(cur).forEach(function (key) { var value = cur[key]; if (typeof value === "object") { Object.keys(value).forEach(function (inner) { tempData[key + "-" + inner] = value[inner]; }); } else { tempData[key] = value; } }); return acc.concat([tempData]); }, []); } }, columnDefs: [ { targets: "_all", createdCell: function (td) { $(td).css({ maxWidth: "450px", overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }); } } ] }); } function buildReqStatusColumnList() { const columns = [ { name: "title", title: "구분" }, { name: "versions", title: "버전 목록" }, { name: "status", title: "상태" }, { name: "priority", title: "우선순위" }, { name: "difficulty", title: "난이도" }, { name: "start_date", title: "시작일", customRender: parseDateCell }, { name: "end_date", title: "종료일", customRender: parseDateCell }, ]; return columns.map(col => ({ name: col.name, title: col.title, data: col.name, visible: true, render: col.customRender || defaultRender })); } // 데이터 테이블 구성 이후 꼭 구현해야 할 메소드 : 열 클릭시 이벤트 function dataTableClick(tempDataTable, selectedData) { console.log(selectedData); selectedIssue = selectedData; } // 데이터 테이블 데이터 렌더링 이후 콜백 함수. function dataTableCallBack(settings, json) { console.log("check"); if (settings.nTable.id !== "workforce_status_table") { return; } } function dataTableDrawCallback(tableInfo) { console.log("dataTableDrawCallback :: scrollPos => ", scrollPos); } var statusCell = function (data) { if (["", undefined, null].includes(data)) return "N/A"; switch (true) { case data >= 100: return ( " " + data ); case data >= 75: return ( " " + data ); case data >= 50: return ( " " + data ); default: return ( " " + data ); } }; var parseDateCell = function (data) { var date = data; if (isEmpty(data) || data === "false") { return "
N/A
"; } else { let displayText = dateFormatSlash(data); let color = "#f8f8f8"; // 기본 텍스트 색상 return "
" + displayText + "
"; } }; const defaultRender = (data) => data || "N/A"; function weeklyChartLoad(data) { const chartData = data.map(d => ({ week: d.key, plan: d.plan.resource, actual: d.actual.resource, planProgress: d.plan.progress, actualProgress: d.actual.progress })); const validActualRates = chartData .map(d => d.actualProgress / 100) .filter(rate => rate > 0 && !isNaN(rate)); const avgRate = validActualRates.length > 0 ? validActualRates.reduce((sum, r) => sum + r, 0) / validActualRates.length : 0; const { year: currentYear, week: currentWeek } = getCurrentWeek(); let currentWeekIndex = chartData.findIndex(d => d.week.includes(`${currentYear}년`) && d.week.includes(`${currentWeek}주차`) ); if (currentWeekIndex === -1) currentWeekIndex = chartData.length - 1; const planData = chartData.map(d => d.plan); const actualData = chartData.map((d, i) => i < currentWeekIndex ? d.actual : null); const forecastData = chartData.map((d, i) => i >= currentWeekIndex ? Math.round(d.plan * avgRate) : null ); const planRate = chartData.map(d => d.planProgress); const cumulativePlanRate = planRate.reduce((acc, curr, idx) => { if (idx === 0) { acc.push(curr); } else { acc.push(acc[idx - 1] + curr); } return acc; }, []); const actualRate = chartData.map((d, i) => i < currentWeekIndex ? d.actualProgress : null ); const forecastRate = chartData.map((d, i) => i >= currentWeekIndex ? Math.round(avgRate * 100) : null ); var dom = document.getElementById("multi-chart-container"); var myChart = echarts.init(dom, null, { renderer: "canvas", useDirtyRect: false }); var option; option = { tooltip: { trigger: 'axis', axisPointer: { type: "shadow" } }, legend: { data: ['계획 작업량', '실적 작업량', '예상 실적', '계획 달성률', '실적 달성률', '예상 실적률'], textStyle: { color: "white" }, tooltip: { show: true } }, xAxis: { type: 'category', data: chartData.map(d => d.week), axisLabel: { textStyle: { color: "white" } }, }, yAxis: [ { type: 'value', name: '작업량', nameTextStyle: { color: 'white' // <- 여기 추가 }, axisLabel: { textStyle: { color: "white" } }, splitLine: { show: true, lineStyle: { color: "rgba(255,255,255,0.2)", width: 1, type: "dashed" } } }, { type: 'value', name: '달성률 (%)', max: 120, nameTextStyle: { color: 'white' // <- 여기 추가 }, axisLabel: { textStyle: { color: "white" } }, splitLine: { show: true, lineStyle: { color: "rgba(255,255,255,0.2)", width: 1, type: "dashed" } } } ], series: [ { name: '계획 작업량', type: 'bar', data: planData }, { name: '실적 작업량', type: 'bar', data: actualData }, { name: '예상 실적', type: 'bar', itemStyle: { color: '#aaa' }, data: forecastData }, { name: '계획 달성률', type: 'line', yAxisIndex: 1, data: cumulativePlanRate }, { name: '실적 달성률', type: 'line', yAxisIndex: 1, data: actualRate }, { name: '예상 실적률', type: 'line', yAxisIndex: 1, lineStyle: { type: 'dashed' }, data: forecastRate } ], backgroundColor: "rgba(255,255,255,0)", }; if (option && typeof option === "object") { myChart.setOption(option, true); } window.addEventListener("resize", function () { myChart.resize(); }); } function getCurrentWeek() { const today = new Date(); const startOfYear = new Date(today.getFullYear(), 0, 1); const pastDaysOfYear = (today - startOfYear) / 86400000; const jan1Day = startOfYear.getDay() || 7; const weekNumber = Math.ceil((pastDaysOfYear + jan1Day - 1) / 7); return { year: today.getFullYear(), week: weekNumber }; }