//////////////////////////////////////////////////////////////////////////////////////// //Document Ready //////////////////////////////////////////////////////////////////////////////////////// const PPT_ORIGINAL_WIDTH = 1280; const PPT_ORIGINAL_HEIGHT = 720; let selectedPdServiceId = null; let selectedPdServiceTitle = null; let selectedVersionIds = null; let selectedValue = null; let startDate = null; let endDate = null; let versionListData = []; let timeData = []; let scopeData = []; let resourceData = []; let currentDate = null; let username = null; function execDocReady() { var pluginGroups = [ [ "../reference/light-blue/lib/vendor/jquery.ui.widget.js", "../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Templates_js_tmpl.js", "../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Load-Image_js_load-image.js", "../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Canvas-to-Blob_js_canvas-to-blob.js", "../reference/light-blue/lib/jquery.iframe-transport.js", "../reference/light-blue/lib/jquery.fileupload.js", "../reference/light-blue/lib/jquery.fileupload-fp.js", "../reference/light-blue/lib/jquery.fileupload-ui.js" ], [ "../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" ], [ "../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.min.css", "../reference/light-blue/lib/bootstrap-datepicker.js", "../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.full.min.js", "../reference/lightblue4/docs/lib/widgster/widgster.js", //d3 변경 "../reference/jquery-plugins/d3-6.7.0/d3.min.js", "../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.min.js", // timerStyle "../reference/jquery-plugins/timerStyles.js" ], [ "../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", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/dataTables.buttons.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.html5.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.print.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/jszip.min.js", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.js", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.css", "./js/common/swiperHelper.js", "./css/customSwiper.css", "./js/reportWeekly/reportWeeklyApi.js", // echarts "../reference/jquery-plugins/echarts-5.5.0/dist/echarts.min.js", "./js/common/chart/eCharts/basicRadar.js", // PDF.js 3.x (ES Module 미사용 - Nginx MIME type 문제 해결) // viewer.html에서 pdf.js, viewer.js를 직접 로드하므로 여기서는 로드 불필요 "../reference/jquery-plugins/PPTXjs-1.21.1/PPTXjs-1.21.1/js/jszip.min.js", "../reference/jquery-plugins/PPTXjs-1.21.1/PPTXjs-1.21.1/js/pptxjs.min.js", "../reference/jquery-plugins/PPTXjs-1.21.1/PPTXjs-1.21.1/css/pptxjs.css" ] // 추가적인 플러그인 그룹들을 이곳에 추가하면 됩니다. ]; loadPluginGroupsParallelAndSequential(pluginGroups) .then(function () { console.log("모든 플러그인 로드 완료"); //사이드 메뉴 처리 $(".widget").widgster(); setSideMenu("sidebar_menu_report", "sidebar_menu_report_weekly"); //coming soon $("#count-down").TimeCircles({ circle_bg_color: "#f8f8f8", use_background: true, bg_width: 0.2, fg_width: 0.013, time: { Days: { color: "#f8f8f8" }, Hours: { color: "#f8f8f8" }, Minutes: { color: "#f8f8f8" }, Seconds: { color: "#f8f8f8" } } }); //제품(서비스) 셀렉트 박스 이니시에이터 makePdServiceSelectBox(); //버전 멀티 셀렉트 박스 이니시에이터 makeVersionMultiSelectBox(); //주차 선택 셀렉트 박스 이니시에이터 (제품/버전 선택 후 동적 생성) initWeekSelectBox(); //PPT 섹션 반응형 스케일링 초기화 initPptScaling(); // 버튼 클릭 이벤트 btnClickEvent(); //PDF Viewer 초기화 - 테스트용 PDF 로드 initPdfViewer(); let today = new Date(); let year = today.getFullYear().toString(); let month = (today.getMonth() + 1).toString().padStart(2, "0"); let day = today.getDate().toString().padStart(2, "0"); currentDate = year + month + day; if (username == null) { getUsername(); } //PPTX Viewer 초기화 - 테스트용 PPTX 로드 // initPptxViewer(); }) .catch(function () { console.error("플러그인 로드 중 오류 발생"); }); } /////////////////////// //제품 서비스 셀렉트 박스 ////////////////////// function makePdServiceSelectBox() { //제품 서비스 셀렉트 박스 이니시에이터 $(".chzn-select").each(function () { console.log("[ fullDataSheet :: makePdServiceSelectBox ] :: select2 data => ", $(this).data()); $(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, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// pdServiceListData = []; for (var k in data.response) { var obj = data.response[k]; pdServiceListData.push({ pdServiceId: obj.c_id, pdServiceName: obj.c_title }); var newOption = new Option(obj.c_title, obj.c_id, false, false); $("#selected_pdService").append(newOption).trigger("change"); } ////////////////////////////////////////////////////////// console.log("[fullDataSheet :: makePdServiceSelectBox] :: pdServiceListData => "); console.table(pdServiceListData); } } }); $("#selected_pdService").on("select2:open", function () { //슬림스크롤 makeSlimScroll(".select2-results__options"); }); // --- select2 ( 제품(서비스) 검색 및 선택 ) 이벤트 --- // $("#selected_pdService").on("select2:select", function (e) { selectedPdServiceId = $("#selected_pdService").val(); let pdServiceTitle = $("#select2-selected_pdService-container").attr("title"); selectedPdServiceTitle = pdServiceTitle; $("#pdServiceTitle").text(pdServiceTitle); // 제품( 서비스 ) 선택했으니까 자동으로 버전을 선택할 수 있게 유도 // 디폴트는 base version 을 선택하게 하고 ( select all ) //~> 이벤트 연계 함수 :: Version 표시 jsTree 빌드 bind_VersionData_By_PdService(); }); } // end makePdServiceSelectBox() //////////////////////////////////////// //버전 멀티 셀렉트 박스 //////////////////////////////////////// function makeVersionMultiSelectBox() { //버전 선택시 셀렉트 박스 이니시에이터 $("#multiple-version").multipleSelect({ filter: true, // selectBox 닫혔을 때 onClose: function () { var versionTag = $("#multiple-version").val(); console.log("[ fullDataSheet :: makeVersionMultiSelectBox ] :: versionTag"); console.log(versionTag); selectedVersionIds = versionTag.join(","); if (selectedPdServiceId === null || selectedPdServiceId == "") { jError("제품(서비스)가 선택되지 않았습니다."); } if (versionTag === null || versionTag == "") { jError("버전이 선택되지 않았습니다."); $("#multiple-version").siblings(".ms-parent").css("z-index", 1000); return; } let filteredVersionData = versionListData.filter((item) => versionTag.includes(item.c_id.toString())); // 시작일 종료일 세팅(datetimepicker) // setEdgeDateRange(filteredVersionData); // 주차 선택 셀렉트 박스 갱신 (선택된 버전 기준) makeWeekSelectBox(filteredVersionData); $("#multiple-version").siblings(".ms-parent").css("z-index", 1000); }, // selectBox 열렸을 때 onOpen: function () { $("#multiple-version").siblings(".ms-parent").css("z-index", 9999); } }); } function bind_VersionData_By_PdService() { $("#multiple-version option").remove(); $.ajax({ url: "/auth-user/api/arms/pdService/versions-with-date?c_id=" + $("#selected_pdService").val(), type: "GET", dataType: "json", progress: true, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// var pdServiceVersionIds = []; versionListData = []; for (var k in data.response) { var obj = data.response[k]; pdServiceVersionIds.push(obj.c_id); versionListData.push({ c_id: obj.c_id, c_title: obj.c_title, start_date: obj.c_pds_version_start_date, end_date: obj.c_pds_version_end_date }); var newOption = new Option(obj.c_title, obj.c_id, true, false); $("#multiple-version").append(newOption); } selectedVersionIds = pdServiceVersionIds.join(","); $("#multiple-version").multipleSelect("refresh").multipleSelect("checkAll"); // 시작일 종료일 세팅(datetimepicker) setEdgeDateRange(versionListData); // 주차 선택 셀렉트 박스 갱신 makeWeekSelectBox(versionListData); if (data.length > 0) { console.log("display 재설정."); } } } }); } //////////////////////////////////////// // 선택한 버전 - min,max 날짜 세팅 //////////////////////////////////////// function setEdgeDateRange(versionData) { if (!versionData || Object.keys(versionData).length === 0) { console.log("[ fullDataSheet :: setEdgeDateRange ] :: versionData 가 없습니다."); return false; } let minMaxDate = versionData.reduce( (acc, curr) => { const startDate = new Date(curr.start_date); const endDate = new Date(curr.end_date); if (!acc.min || startDate < acc.min) { acc.min = startDate; } if (!acc.max || endDate > acc.max) { acc.max = endDate; } return acc; }, { min: null, max: null } ); console.log( "[ fullDataSheet :: setEdgeDateRange ] :: " + "minMaxDate.min => " + minMaxDate.min + ", minMaxDate.max => " + minMaxDate.max ); const oneMonthAgo = new Date(minMaxDate.max); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); if (oneMonthAgo > minMaxDate.min) { oneMonthAgo.setTime(minMaxDate.min.getTime()); } // $('#date_timepicker_start').datetimepicker('setOptions', { // value: oneMonthAgo // }); // // $('#date_timepicker_end').datetimepicker('setOptions', { // value: minMaxDate.max // }); } // 주차 선택 셀렉트 박스 초기화 (select2만 적용) function initWeekSelectBox() { $("#select-week").select2({ placeholder: "Select a Week", templateResult: formatWeekOption }); // 주차 선택 이벤트 $("#select-week").on("select2:select", function (e) { var selectedOption = $(this).find("option:selected"); selectedValue = $(this).val(); // 예: "2026-W01" startDate = selectedOption.data("startDate"); endDate = selectedOption.data("endDate"); // 미래 주차 선택 방지 var today = new Date(); today.setHours(0, 0, 0, 0); if (startDate && new Date(startDate) > today) { $("#select-week").val("").trigger("change"); $("#yearAndWeek").text(""); jError("주간보고를 생성할 수 없는 주차입니다. 활성화된 주차를 선택해주세요."); return; } console.log("[ reportWeekly :: initWeekSelectBox ] :: 선택된 주차 => ", selectedValue); console.log("[ reportWeekly :: initWeekSelectBox ] :: 시작일 => ", startDate); console.log("[ reportWeekly :: initWeekSelectBox ] :: 종료일 => ", endDate); // 예: onWeekSelected(selectedValue, startDate, endDate); // TimeData 세팅 let allWeeklyData = ReportWeeklyApi.fetchAllWeeklyData( selectedPdServiceId, selectedPdServiceTitle, selectedVersionIds, selectedValue, startDate, endDate ); let week = $("#select2-select-week-container").attr("title"); $("#yearAndWeek").text(week); ReportWeeklyApi.fetchPDF( selectedPdServiceId, selectedPdServiceTitle, selectedVersionIds, selectedValue, startDate, endDate, "inline" ); if (allWeeklyData === null) { console.log("allWeeklyData is null."); } else { allWeeklyData.then(function (data) { timeData = data.time; scopeData = data.scope; resourceData = data.resource; console.log("[ reportWeekly :: initWeekSelectBox ] :: timeData => ", timeData); console.log("[ reportWeekly :: initWeekSelectBox ] :: scopeData => ", scopeData); console.log("[ reportWeekly :: initWeekSelectBox ] :: resourceData => ", resourceData); let goalDataArr = [resourceData.totalAssignee, scopeData.totalReqCount, timeData.totalDuration]; let currentDataArr = [ resourceData.activeAssignee, scopeData.resolvedReqCount + scopeData.closedReqCount, timeData.progressDuration ]; console.log("[ reportWeekly :: initWeekSelectBox ] :: goalDataArr => ", goalDataArr); console.log("[ reportWeekly :: initWeekSelectBox ] :: currentDataArr => ", currentDataArr); drawWeeklyReportRadar("chart_div", goalDataArr, currentDataArr); // Time 영역 let progressRate = timeData.progressRate + "%"; $("#progress-rate").text(progressRate); $("#req_progress_bar").text(progressRate).css("width", progressRate); let dateRange = timeData.earliestDate + " ~ " + timeData.latestDate + " 중 경과"; $("#overall-date-range").text(dateRange); let scheduleComplianceRate = scopeData.scheduleComplianceRate + "%"; let scheduleComplianceCount = scopeData.scheduleComplianceCount + " 개"; $("#compliance-rate").text(scheduleComplianceRate); $("#compliance-count").text("( " + scheduleComplianceCount + " )"); optimisticExpression(scopeData.scheduleComplianceRate, "compliance-rate"); optimisticExpression(scopeData.scheduleComplianceRate, "compliance-count"); let delayedReqRate = scopeData.delayedReqRate + "%"; let delayedReqCount = scopeData.delayedReqCount + " 개"; $("#delayed-req-count").text(delayedReqRate); $("#delayed-req-rate").text("( " + delayedReqCount + " )"); passimisticExpression(scopeData.delayedReqRate, "delayed-req-count"); passimisticExpression(scopeData.delayedReqRate, "delayed-req-rate"); //.Time 영역 // Scope 영역 let newlyAddedReqCount = scopeData.newlyAddedReqCount; let weeklyCompletedCount = scopeData.weeklyCompletedCount; // 금주 완료 $("#complete-count-selected-week").text(addPlusWhenInputIsNumber(weeklyCompletedCount) + " 개"); if (weeklyCompletedCount > 0) { $("#complete-count-selected-week").css("color", "#e5603b"); } // 신규추가 $("#newly-added-req-count").text(addPlusWhenInputIsNumber(newlyAddedReqCount) + " 개"); if (newlyAddedReqCount > 0) { $("#newly-added-req-count").css("color", "#56bc76"); } // 전체 let totalReqCount = scopeData.totalReqCount; $("#total-req-count").text(totalReqCount); $("#total-description").text("전체"); // 완료 (해결됨+닫힘) let completeCount = scopeData.resolvedReqCount + scopeData.closedReqCount; $("#complete-req-count").text(completeCount); let scopeCompleteRate = calculateRate(totalReqCount, completeCount, 1); $("#complete-req-description").text("완료 " + scopeCompleteRate); // 진행중 $("#in-progress-req-count").text(scopeData.progressReqCount); $("#in-progress-req-description").text("진행중 " + calculateRate(totalReqCount, scopeData.progressReqCount, 1)); // 열림 $("#open-req-count").text(scopeData.openReqCount); $("#open-req-description").text("열림 " + calculateRate(totalReqCount, scopeData.openReqCount, 1)); // 기타 $("#others-req-count").text(scopeData.others); $("#others-req-description").text("기타 " + calculateRate(totalReqCount, scopeData.others, 1)); // .Scope 영역 // Resource 영역 let totalAssignee = resourceData.totalAssignee; let activeAssignee = resourceData.activeAssignee; $("#total-assignee-count").text(totalAssignee); $("#active-assignee-count").text(activeAssignee); $("#avg-issue-per-active-assignee").text(resourceData.avgIssuePerAssigneeWithinWeek + " 개"); $("#avg-issue-per-overall-assignee").text(resourceData.avgIssuePerAssigneeOverallTime + " 개"); let top3AssigneeWithinWeek = resourceData.top3AssigneeWithinWeek; console.log("[ reportWeekly :: initWeekSelectBox ] :: top3AssigneeWithinWeek => ", top3AssigneeWithinWeek); renderTopContributors(top3AssigneeWithinWeek); // .Resource 영역 // 계획 - 현황 비교 $("#plan-end-date").text(timeData.totalDuration); $("#current-weekend").text(timeData.progressDuration); $("#time-badge").text(progressRate); $("#table-total-req-count").text(totalReqCount); $("#table-complete-req-count").text(completeCount); $("#scope-badge").text(scopeCompleteRate); $("#table-total-resource").text(totalAssignee); $("#table-active-resource").text(activeAssignee); let resourceActiveRate = calculateRate(totalAssignee, activeAssignee, 1); $("#resource-badge").text(resourceActiveRate); }); } }); } function addPlusWhenInputIsNumber(param) { if (param && param > 0) { return "+" + param; } else { return param; } } function calculateRate(total, current, fixed) { if (!total || !current) { console.log("[ reportWeekly :: calculateRate ] :: total, current => " + total + ", " + current); if (current === 0) { return "0%"; } return "정보 없음"; } let rate = (current / total) * 100; return rate.toFixed(fixed) + "%"; } // 100% 에 근접할 수록 좋음 function optimisticExpression(rate, targetId) { console.log("optimisticExpression rate, id => " + rate + ", " + targetId); let target = "#" + targetId; if (rate && rate >= 95) { $(target).css("color", "#56bc76"); } else if (rate && rate >= 75) { $(target).css("color", "#618fb0"); } else if (rate && rate >= 50) { $(target).css("color", "#e5603b"); } else if (rate && rate >= 25) { $(target).css("color", "#eac85e"); } else { $(target).css("color", "#db2a34"); // edit-red } } // 0% 에 근접할 수록 좋음 function passimisticExpression(rate, targetId) { let target = "#" + targetId; if (rate && rate < 5) { $(target).css("color", "#56bc76"); } else if (rate && rate <= 25) { $(target).css("color", "#618fb0"); } else if (rate && rate <= 50) { $(target).css("color", "#e5603b"); } else if (rate && rate <= 75) { $(target).css("color", "#eac85e"); } else { $(target).css("color", "#db2a34"); // edit-red } } // 주차 옵션 포맷팅 함수 (현재/미래 주차 스타일 적용) function formatWeekOption(option) { if (!option.id) { return option.text; } var $option = $(option.element); var endDate = $option.data("endDate"); var $result = $("").text(option.text); if (endDate) { var today = new Date(); today.setHours(0, 0, 0, 0); var weekEndDate = new Date(endDate); if (weekEndDate >= today) { $result.addClass("week-future"); } else { $result.addClass("week-past"); } } return $result; } function makeWeekSelectBox(versionData) { // 기존 옵션 제거 $("#select-week option").not(":first").remove(); $("#select-week").val("").trigger("change"); if (!versionData || versionData.length === 0) { console.log("[ reportWeekly :: makeWeekSelectBox ] :: versionData가 없습니다."); return; } // 가장 빠른 start_date와 가장 늦은 end_date 찾기 let minMaxDate = versionData.reduce( (acc, curr) => { const startDate = parseVersionDate(curr.start_date); const endDate = parseVersionDate(curr.end_date); if (!acc.min || startDate < acc.min) { acc.min = startDate; } if (!acc.max || endDate > acc.max) { acc.max = endDate; } return acc; }, { min: null, max: null } ); console.log( "[ reportWeekly :: makeWeekSelectBox ] :: minDate => " + minMaxDate.min + ", maxDate => " + minMaxDate.max ); // 주차 목록 생성 let weeks = generateWeekRanges(minMaxDate.min, minMaxDate.max); for (var i = 0; i < weeks.length; i++) { var week = weeks[i]; var newOption = new Option(week.label, week.value, false, false); $(newOption).data("startDate", week.startDate); $(newOption).data("endDate", week.endDate); $("#select-week").append(newOption); } $("#select-week").trigger("change"); console.log("[ reportWeekly :: makeWeekSelectBox ] :: 주차 목록 생성 완료, 총 " + weeks.length + "주"); } // 버전 날짜 문자열 파싱 (yyyy/MM/dd HH:mm 형식) function parseVersionDate(dateStr) { if (!dateStr) return null; // "2025/09/01 13:38" 형식 파싱 var parts = dateStr.split(" ")[0].split("/"); return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); } // ISO 주차 번호 계산 (월요일 시작 기준) function getISOWeekNumber(date) { var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); var dayNum = d.getUTCDay() || 7; // 일요일을 7로 변환 d.setUTCDate(d.getUTCDate() + 4 - dayNum); // 목요일로 이동 var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); var weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7); return { year: d.getUTCFullYear(), week: weekNo }; } // 해당 날짜가 속한 주의 월요일 구하기 function getMondayOfWeek(date) { var d = new Date(date); var day = d.getDay(); var diff = d.getDate() - day + (day === 0 ? -6 : 1); // 월요일로 조정 return new Date(d.setDate(diff)); } // 해당 날짜가 속한 주의 일요일 구하기 function getSundayOfWeek(date) { var monday = getMondayOfWeek(date); var sunday = new Date(monday); sunday.setDate(monday.getDate() + 6); return sunday; } // 날짜 포맷팅 (YY.MM.DD) function formatShortDate(date) { var yy = String(date.getFullYear()).slice(-2); var mm = String(date.getMonth() + 1).padStart(2, "0"); var dd = String(date.getDate()).padStart(2, "0"); return yy + "." + mm + "." + dd; } // 주차 범위 목록 생성 function generateWeekRanges(startDate, endDate) { var weeks = []; var currentMonday = getMondayOfWeek(startDate); while (currentMonday <= endDate) { var currentSunday = getSundayOfWeek(currentMonday); var isoWeek = getISOWeekNumber(currentMonday); // 라벨 생성: "26년 1주차(25.12.29~26.01.04)" var label = String(isoWeek.year).slice(-2) + "년 " + isoWeek.week + "주차 (" + formatShortDate(currentMonday) + "~" + formatShortDate(currentSunday) + ")"; weeks.push({ label: label, value: isoWeek.year + "-W" + String(isoWeek.week).padStart(2, "0"), startDate: new Date(currentMonday), endDate: new Date(currentSunday), year: isoWeek.year, week: isoWeek.week }); // 다음 주 월요일로 이동 currentMonday = new Date(currentMonday); currentMonday.setDate(currentMonday.getDate() + 7); } return weeks; } // 서수 접미사 반환 (1st, 2nd, 3rd) function getOrdinalSuffix(n) { var suffixes = ["st", "nd", "rd"]; if (n >= 1 && n <= 3) { return suffixes[n - 1]; } return "th"; } // Top3 기여자 렌더링 function renderTopContributors(top3AssigneeWithinWeek) { var $container = $("#top-contributor-div"); $container.empty(); if (!top3AssigneeWithinWeek || top3AssigneeWithinWeek.length === 0) { var $li = $( '
  • ' + '
    ' + '
    ' + " 작업 인원이 없습니다. " + "
    " + "
    " + "
  • " ); $container.append($li); return; } for (var i = 0; i < top3AssigneeWithinWeek.length; i++) { var assignee = top3AssigneeWithinWeek[i]; var assigneeInfo = assignee.uniqueAssigneeVO; var assigneeExpression = assigneeInfo.name + " (" + assignee.totalIssueCount + " 개)"; var ordinal = getOrdinalSuffix(i + 1); var contributorId = "contributor-" + (i + 1) + ordinal + "-name"; var $li = $( '
  • ' + '
    ' + '
    ' + '' + "
    " + "
    " + "
  • " ); console.log(assignee); $li.find("#" + contributorId).text(assigneeExpression); $container.append($li); } } //////////////////////////////////////// // PPT 섹션 반응형 스케일링 //////////////////////////////////////// function initPptScaling() { updatePptScale(); $(window).on("resize", debounce(updatePptScale, 100)); } function updatePptScale() { var $wrapper = $("#ppt-scale-wrapper"); var $content = $("#ppt-content"); if ($wrapper.length === 0 || $content.length === 0) { return; } var wrapperWidth = $wrapper.width(); var scale = wrapperWidth / PPT_ORIGINAL_WIDTH; // 최대 스케일은 1 (원본 크기 이상으로 확대하지 않음) scale = Math.min(scale, 1); $content.css("transform", "scale(" + scale + ")"); // wrapper 높이 조정 (스케일된 높이에 맞춤) var scaledHeight = PPT_ORIGINAL_HEIGHT * scale; $wrapper.css("height", scaledHeight + "px"); } // 디바운스 함수 (리사이즈 이벤트 최적화) function debounce(func, wait) { var timeout; return function () { var context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { func.apply(context, args); }, wait); }; } //////////////////////////////////////// // PDF Viewer 제어 //////////////////////////////////////// var PDF_VIEWER_BASE_URL = "../reference/jquery-plugins/pdfjs-3.11.174-dist/web/viewer.html"; // PDF 로드 함수 function loadPdfViewer(pdfUrl) { var viewerUrl = PDF_VIEWER_BASE_URL + "?file=" + encodeURIComponent(pdfUrl); $("#pdf-viewer-iframe").attr("src", viewerUrl); // 다운로드 버튼 업데이트 $("#btn-pdf-download").attr("href", pdfUrl).attr("download", pdfUrl.split("/").pop()); } // PDF Viewer 초기화 (테스트용 PDF 로드) function initPdfViewer() { var testPdfUrl = "/arms/mock/default_template_ARMS_pdf.pdf"; loadPdfViewer(testPdfUrl); } // 백엔드 API에서 PDF 로드 (향후 사용) -> POST 방식도 염두필요. // 예시: loadPdfFromApi(pdServiceId, versionId, weekValue) function loadPdfFromApi(pdServiceId, versionId, weekValue) { var apiUrl = "/auth-user/api/arms/report/weekly/pdf?pdServiceId=" + pdServiceId + "&versionId=" + versionId + "&week=" + weekValue; // API 호출하여 PDF URL 받아오기 $.ajax({ url: apiUrl, type: "GET", dataType: "json", success: function (response) { if (response && response.pdfUrl) { loadPdfViewer(response.pdfUrl); } else { console.error("[ reportWeekly :: loadPdfFromApi ] :: PDF URL을 받지 못했습니다."); } }, error: function (xhr, status, error) { console.error("[ reportWeekly :: loadPdfFromApi ] :: API 호출 실패 => ", error); } }); } function btnClickEvent() { $("#btn_report_download").click(function () { if (!selectedPdServiceId) { jError("Product(Service) 과 버전(Version), 주(Week)를 선택해주세요."); } if (!selectedVersionIds) { jError("버전(Version), 주(Week)를 선택해주세요."); } if (!selectedValue) { jError("주간보고를 위한 주(Week)을 선택해주세요."); } if (selectedValue && startDate && endDate) { ReportWeeklyApi.fetchExportPPT( selectedPdServiceId, selectedPdServiceTitle, selectedVersionIds, selectedValue, startDate, endDate ); } }); $("#btn_template_upload").click(function () { console.log("템플릿 업로드 버튼. 업로드 모달 추가 필요."); }); $("#btn_template_download").click(function () { var templateName = "WeeklyReport_template_" + currentDate + ".pptx"; var req = new XMLHttpRequest(); req.open("GET", "/auth-user/api/arms/export/templates/download/weekly-report?templateName=" + templateName, true); req.responseType = "arraybuffer"; req.setRequestHeader("Authorization", "Bearer " + $.cookie("madAccessToken")); req.onload = function () { if (req.status === 200) { var arrayBuffer = req.response; if (arrayBuffer && arrayBuffer.byteLength > 0) { var blob = new Blob([arrayBuffer], { type: "application/octet-stream" }); var link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); link.download = templateName; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(link.href); } else { jError("템플릿 다운로드에 실패했습니다. 파일이 비어있습니다."); } } else { jError("템플릿 다운로드에 실패했습니다. (HTTP " + req.status + ")"); } }; req.onerror = function () { jError("네트워크 에러로 템플릿 다운로드에 실패했습니다."); }; req.send(); }); $("#btn_pdf_show").click(function () { if (!selectedPdServiceId) { jError("Product(Service) 과 버전(Version), 주(Week)를 선택해주세요."); } if (!selectedVersionIds) { jError("버전(Version), 주(Week)를 선택해주세요."); } if (!selectedValue) { jError("주간보고를 위한 주(Week)을 선택해주세요."); } if (selectedValue && startDate && endDate) { ReportWeeklyApi.fetchPDF( selectedPdServiceId, selectedPdServiceTitle, selectedVersionIds, selectedValue, startDate, endDate, "inline" ); } }); $("#template_ppt_fileupload").fileupload({ autoUpload: true, url: "/auth-user/api/arms/report-template/upload", dropZone: $("#template_ppt_dropzone"), limitMultiFileUploads: 1, formData: function () { return [ { name: "templateName", value: "weekly-report" }, { name: "userId", value: username || "" } ]; }, fail: function () { jError( { ko: "업로드한 PPT의 정보를 다시 확인해 주세요. 데이터 반영 중 문제가발생했습니다.", en: "Please double-check the information in the PPT file you uploaded. There was an error reflecting the data.", jp: "Please double-check the information in the PPT file you uploaded. There was an error reflecting the data." }[getCookie("locale") || "ko"] ); }, done: function (e, data) { if (data.textStatus == "success") { jNotify( { ko: "템플릿 PPT 업로드 완료", en: "", jp: "" }[getCookie("locale") || "ko"] ); } else { jError( { ko: "템플릿 PPT 업로드 실패. 다시 확인해주세요.", en: "", jp: "" }[getCookie("locale") || "ko"] ); } // PDF 표시 다시 하려면 추가. }, stop: function () { $("#template-ppt-upload-modal").modal("hide"); } }); } ///////////////////////////////////////////// // 템플릿 업로드시 username 을 전달하기 위해 사용 ///////////////////////////////////////////// function getUsername() { $.ajax({ url: "/auth-user/user-info", type: "GET", global: false, statusCode: { 200: function (data) { console.log("getUserInfo : success =>", data.response.username); username = data.response.username; }, 401: function (json) { jError("클라이언트가 인증되지 않았거나, 유효한 인증 정보가 부족하여 요청이 거부되었습니다."); location.href = "/oauth2/authorization/middle-proxy"; return false; }, 403: function (json) { jError("서버가 해당 요청을 이해했지만, 권한이 없어 요청이 거부되었습니다."); return false; } } }); }