//////////////////////////////////////////////////////////////////////////////////////// //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 = $( '