//////////////////////////////////////////////////////////////////////////////////////// //Document Ready //////////////////////////////////////////////////////////////////////////////////////// const PPT_ORIGINAL_WIDTH = 1280; const PPT_ORIGINAL_HEIGHT = 720; let selectedPdServiceId = null; // 제품(서비스) 아이디 let selectedPdServiceTitle = null; // 제품(서비스) 이름 let selectedPdServiceVersionIds = []; // 선택된 버전 아이디 let selectedAssigneeId; // 선택된 작업자 계정 ID let selectedAssigneeName; // 선택된 작업자 이름 let selectedReportType = "personal"; // 선택된 보고서 유형 ("weekly" | "personal") let selectedValue = null; // 선택된 주차 값 (예: "2026-W01") let startDate = null; // 선택된 주차 시작일 let endDate = null; // 선택된 주차 종료일 let versionListData = []; // 버전 목록 데이터 var previousBlobUrl = null; var previousAudioBlobUrl = null; var previousAudioBlob = null; let aiServiceStatus = null; // AI 서비스 상태 const PDF_VIEWER_BASE_URL = "../reference/jquery-plugins/pdfjs-3.11.174-dist/web/viewer.html"; let timeData = []; let scopeData = []; let resourceData = []; 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", ], [ "../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", // 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", "./js/reportWeekly/reportWeeklyApi.js" ] // 추가적인 플러그인 그룹들을 이곳에 추가하면 됩니다. ]; loadPluginGroupsParallelAndSequential(pluginGroups) .then(function () { console.log("모든 플러그인 로드 완료"); // DWR targetPage 등록 (현재 페이지 등록) Chat.setCurrentPage("report-performance"); //사이드 메뉴 처리 $(".widget").widgster(); setSideMenu("sidebar_menu_insight", "sidebar_menu_report", "sidebar_menu_report_performance"); //제품(서비스) 셀렉트 박스 이니시에이터 makePdServiceSelectBox(); //버전 멀티 셀렉트 박스 이니시에이터 makeVersionMultiSelectBox(); // 작업자 목록 멀티 셀렉트 박스 이니시에이터 makeAssigneeSelectBox(); //주차 선택 셀렉트 박스 이니시에이터 (제품/버전 선택 후 동적 생성) initWeekSelectBox(); // 보고서 유형 선택 토글 이벤트 initReportTypeToggle(); // AI 서비스 상태 확인 checkAiServiceStatus(); //PPT 섹션 반응형 스케일링 초기화 initPptScaling(); //PDF Viewer 초기화 - 테스트용 PDF 로드 initPdfViewer(); downloadPdfViewer(); }) .catch(function () { console.error("플러그인 로드 중 오류 발생"); }); } //////////////////////////////////////////////////////////////////////////////////////// //제품 서비스 셀렉트 박스 //////////////////////////////////////////////////////////////////////////////////////// function makePdServiceSelectBox() { //제품 서비스 셀렉트 박스 이니시에이터 $("#selected_pdService").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"); } ////////////////////////////////////////////////////////// jSuccess("제품(서비스) 조회가 완료 되었습니다."); } }, error: function (e) { jError("제품(서비스) 조회 중 에러가 발생했습니다."); } }); $("#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; // 제품( 서비스 ) 선택했으니까 자동으로 버전을 선택할 수 있게 유도 // 디폴트는 base version 을 선택하게 하고 ( select all ) //~> 이벤트 연계 함수 :: Version 표시 jsTree 빌드 bind_VersionData_By_PdService(); // 제품 변경 시 플레이어 초기화 resetAudioPlayer(); }); } // end makePdServiceSelectBox() //////////////////////////////////////////////////////////////////////////////////////// //버전 멀티 셀렉트 박스 //////////////////////////////////////////////////////////////////////////////////////// function makeVersionMultiSelectBox() { //버전 선택시 셀렉트 박스 이니시에이터 $("#multiple-version").multipleSelect({ filter: true, // selectBox 닫혔을 때 onClose: function () { var versionTag = $("#multiple-version").val(); console.log("[ fullDataSheet :: makeVersionMultiSelectBox ] :: versionTag"); selectedPdServiceVersionIds = versionTag; if (isEmpty(selectedPdServiceId)) { jError("제품(서비스)가 선택되지 않았습니다."); return; } if (isEmpty(versionTag)) { jError("버전이 선택되지 않았습니다."); // // assginee 선택을 초기화 $("#selected_assignee option").remove(); return; } // 실무자 선택 bind_Assignee_By_PdServiceVersions(); // 주차 선택 셀렉트 박스 갱신 (선택된 버전 기준) let filteredVersionData = versionListData.filter((item) => versionTag.includes(item.c_id.toString())); makeWeekSelectBox(filteredVersionData); // 버전 변경 시 플레이어 초기화 resetAudioPlayer(); } }); } 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", contentType: "application/json;charset=UTF-8", progress: true, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// versionListData = []; for (var k in data.response) { var obj = data.response[k]; selectedPdServiceVersionIds.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, true); $("#multiple-version").append(newOption); } ////////////////////////////////////////////////////////// $("#multiple-version").multipleSelect("refresh"); // 실무자 선택 bind_Assignee_By_PdServiceVersions(); // 주차 선택 셀렉트 박스 갱신 makeWeekSelectBox(versionListData); } } }); } //////////////////////////////////////////////////////////////////////////////////////// // 작업자 셀렉트 박스 //////////////////////////////////////////////////////////////////////////////////////// function makeAssigneeSelectBox() { console.log("makeAssigneeSelectBox"); //버전 선택시 셀렉트 박스 이니시에이터 $("#selected_assignee").multipleSelect({ filter: true, onClose: function () { selectedAssigneeId = $("#selected_assignee option:selected").val(); selectedAssigneeName = $("#selected_assignee option:selected").text(); console.log("selectedAssigneeId: " + selectedAssigneeId); console.log("selectedAssigneeName: " + selectedAssigneeName); // 모든 선택이 완료되면 자동으로 AI 뉴스 오디오 생성 // fetchNewsAudio(); } }); } function bind_Assignee_By_PdServiceVersions() { console.log("bind_Assignee_By_PdServiceVersions"); $("#selected_assignee option").remove(); $("#selected_assignee").append(new Option("Select Assignee", "", true, true)); $.ajax({ url: "/engine-search-api/assignees/rolling3m", type: "POST", dataType: "json", data: JSON.stringify({ pdServiceId: selectedPdServiceId, pdServiceVersions: selectedPdServiceVersionIds }), contentType: "application/json;charset=UTF-8", statusCode: { 200: function (data) { console.log(data); ////////////////////////////////////////////////////////// for (let obj of data) { let newOption = new Option(obj.displayName, obj.accountId, false, false); $("#selected_assignee").append(newOption); } $("#selected_assignee").multipleSelect("refresh"); ////////////////////////////////////////////////////////// jSuccess("실무자 목록 조회가 완료되었습니다. (" + data.length + "명)"); }, error: function (e) { jError("실무자 목록 조회 중 에러가 발생했습니다."); } } }); } //////////////////////////////////////// // 주차 선택 셀렉트 박스 //////////////////////////////////////// // 주차 선택 셀렉트 박스 초기화 (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"); jError("주간보고를 생성할 수 없는 주차입니다. 활성화된 주차를 선택해주세요."); return; } console.log("[ reportPerformance :: initWeekSelectBox ] :: 선택된 주차 => ", selectedValue); console.log("[ reportPerformance :: initWeekSelectBox ] :: 시작일 => ", startDate); console.log("[ reportPerformance :: initWeekSelectBox ] :: 종료일 => ", endDate); }); } // 주차 옵션 포맷팅 함수 (현재/미래 주차 스타일 적용) 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("[ reportPerformance :: 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( "[ reportPerformance :: 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("[ reportPerformance :: 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; } //////////////////////////////////////// // 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 제어 //////////////////////////////////////// // PDF 로드 함수 function loadPdfViewer(pdfUrl) { var viewerUrl = PDF_VIEWER_BASE_URL + "?file=" + encodeURIComponent(pdfUrl) + "#zoom=page-width"; $("#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); } // 보고서 표시 버튼 이벤트 function downloadPdfViewer() { $("#btn_report_download").click(function () { if (selectedReportType === "weekly") { // 주간업무 보고 if (isEmpty(selectedPdServiceId)) { jError("제품(서비스)가 선택되지 않았습니다."); return; } if (isEmpty(selectedPdServiceVersionIds)) { jError("버전이 선택되지 않았습니다."); return; } if (isEmpty(selectedValue)) { jError("주간보고를 위한 주(Week)를 선택해주세요."); return; } ReportWeeklyApi.fetchPDF( selectedPdServiceId, selectedPdServiceTitle, selectedPdServiceVersionIds.join(","), selectedValue, startDate, endDate, "inline" ); } else { // 개인업무 보고 fetchPerformanceWithAudio(selectedPdServiceId, selectedPdServiceTitle, selectedPdServiceVersionIds, selectedAssigneeId, selectedAssigneeName); } }); } var fetchPDF = function (pdServiceId, pdServiceName, pdServiceVersionLinks, assigneeId, assigneeName) { if (!isValidatedValues()) { return; } function buildRequestDTOPDF(pdServiceId, pdServiceName,pdServiceVersionLinks, assigneeId, assigneeName) { return { "pdServiceId": Number(pdServiceId), "pdServiceName": pdServiceName, "pdServiceVersions": pdServiceVersionLinks, "assignee": { "accountId": assigneeId, "name": assigneeName }, "display": "attachment" }; } let dto = buildRequestDTOPDF(pdServiceId, pdServiceName, pdServiceVersionLinks, assigneeId, assigneeName); // byte[] 다운로드를 위해 blob 타입으로 요청 return $.ajax({ url: `/auth-user/api/arms/report/personal/export/performance`, type: "POST", data: JSON.stringify(dto), contentType: "application/json;charset=UTF-8", xhrFields: { responseType: "blob" // binary 데이터 수신 }, success: function (blob, status, xhr) { // 이전 Blob URL 해제 (메모리 누수 방지) if (previousBlobUrl) { URL.revokeObjectURL(previousBlobUrl); } // Blob URL 생성 var blobUrl = URL.createObjectURL(blob); previousBlobUrl = blobUrl; // PDF.js viewer에 Blob URL 전달 var viewerBaseUrl = "../reference/jquery-plugins/pdfjs-3.11.174-dist/web/viewer.html"; var viewerUrl = viewerBaseUrl + "?file=" + encodeURIComponent(blobUrl) + "#zoom=page-width"; var contentDisposition = xhr.getResponseHeader("Content-Disposition"); var filename = "PersonalReport.pdf"; if (contentDisposition) { var filenameMatch = contentDisposition.match(/filename\*?=(?:UTF-8'')?["']?([^"';\n]+)["']?/i); if (filenameMatch && filenameMatch[1]) { filename = decodeURIComponent(filenameMatch[1]); } } // iframe src 업데이트 var $iframe = $("#pdf-viewer-iframe"); $iframe.attr("src", viewerUrl); // iframe 로드 완료 후 커스텀 스타일 및 파일명 적용 $iframe.off("load.pdfStyle").on("load.pdfStyle", function () { try { var iframeDoc = this.contentDocument || this.contentWindow.document; var customStyle = iframeDoc.createElement("style"); customStyle.textContent = ".dropdownToolbarButton > select, .toolbarField { font-size: 13px !important; }"; iframeDoc.head.appendChild(customStyle); // pdfjs 내장 다운로드 버튼 파일명 설정 var pdfViewerApp = this.contentWindow.PDFViewerApplication; if (pdfViewerApp) { pdfViewerApp._contentDispositionFilename = filename; } } catch (e) { console.warn("PDF viewer 스타일 적용 실패:", e); } }); // 다운로드 버튼 업데이트 $("#btn-pdf-download").attr("href", blobUrl).attr("download", filename); console.log("[ ReportPerformanceApi :: fetchPDF ] :: PDF 로드 완료 => ", filename); }, error: function (xhr, status, error) { console.error("[ ReportPerformanceApi :: fetchPDF ] :: 가져오기 실패 => ", error); jError("PDF 파일 가져오기에 실패했습니다."); } }); }; var fetchPerformanceWithAudio = function (pdServiceId, pdServiceName, pdServiceVersionLinks, assigneeId, assigneeName) { if (!isValidatedValues()) { return; } var dto = { "pdServiceId": Number(pdServiceId), "pdServiceName": pdServiceName, "pdServiceVersions": pdServiceVersionLinks, "assignee": { "accountId": assigneeId, "name": assigneeName }, "display": "attachment" }; // 오디오 로딩 표시 $("#audio-loading").show(); $("#audio-player-wrapper").hide(); $("#audio-status").hide(); $.ajax({ url: "/auth-user/api/arms/report/personal/export/performance-audio", type: "POST", data: JSON.stringify(dto), contentType: "application/json;charset=UTF-8", dataType: "json", success: function (data) { // --- PDF 처리 --- var byteCharacters = atob(data.fileBase64); var byteNumbers = new Array(byteCharacters.length); for (var i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); var pdfBlob = new Blob([byteArray], { type: data.contentType || "application/pdf" }); if (previousBlobUrl) { URL.revokeObjectURL(previousBlobUrl); } var blobUrl = URL.createObjectURL(pdfBlob); previousBlobUrl = blobUrl; var filename = data.fileName || "PersonalReport.pdf"; var viewerBaseUrl = "../reference/jquery-plugins/pdfjs-3.11.174-dist/web/viewer.html"; var viewerUrl = viewerBaseUrl + "?file=" + encodeURIComponent(blobUrl) + "#zoom=page-width"; var $iframe = $("#pdf-viewer-iframe"); $iframe.attr("src", viewerUrl); $iframe.off("load.pdfStyle").on("load.pdfStyle", function () { try { var iframeDoc = this.contentDocument || this.contentWindow.document; var customStyle = iframeDoc.createElement("style"); customStyle.textContent = ".dropdownToolbarButton > select, .toolbarField { font-size: 13px !important; }"; iframeDoc.head.appendChild(customStyle); var pdfViewerApp = this.contentWindow.PDFViewerApplication; if (pdfViewerApp) { pdfViewerApp._contentDispositionFilename = filename; } } catch (e) { console.warn("PDF viewer 스타일 적용 실패:", e); } }); $("#btn-pdf-download").attr("href", blobUrl).attr("download", filename); console.log("[ ReportPerformanceApi :: fetchPerformanceWithAudio ] :: PDF 로드 완료 => ", filename); // --- Audio 처리 --- if (data.audioBase64) { var audioBytes = atob(data.audioBase64); var audioArray = new Uint8Array(audioBytes.length); for (var j = 0; j < audioBytes.length; j++) { audioArray[j] = audioBytes.charCodeAt(j); } var audioBlob = new Blob([audioArray], { type: "audio/wav" }); if (previousAudioBlobUrl) { URL.revokeObjectURL(previousAudioBlobUrl); } previousAudioBlob = audioBlob; previousAudioBlobUrl = URL.createObjectURL(audioBlob); var audioPlayer = document.getElementById("news-audio-player"); audioPlayer.src = previousAudioBlobUrl; $("#audio-loading").hide(); $("#audio-player-wrapper").show(); $("#btn-audio-download") .attr("href", previousAudioBlobUrl) .attr("download", "ai-news-audio.wav"); console.log("[ ReportPerformanceApi :: fetchPerformanceWithAudio ] :: 오디오 로드 완료"); } else { $("#audio-loading").hide(); $("#audio-status").show().find("p").html( ' 오디오 데이터가 없습니다.' ); } }, error: function (xhr, status, error) { console.error("[ ReportPerformanceApi :: fetchPerformanceWithAudio ] :: 가져오기 실패 => ", error); jError("보고서 생성에 실패했습니다."); $("#audio-loading").hide(); $("#audio-status").show().find("p").html( ' 오디오 생성에 실패했습니다.' ); } }); }; function isValidatedValues() { if (isEmpty(selectedPdServiceId)) { jError("제품(서비스)가 선택되지 않았습니다."); return false; } if (isEmpty(selectedPdServiceVersionIds)) { jError("버전이 선택되지 않았습니다."); return false; } if (isEmpty(selectedAssigneeId)) { jNotify("실무자를 선택하지 않았습니다."); return false; } return true; } //////////////////////////////////////// // AI 서비스 상태 확인 //////////////////////////////////////// function checkAiServiceStatus() { $.ajax({ global: false, url: "/ai-api/status", type: "GET", dataType: "json", contentType: "application/json;charset=UTF-8", success: function (data) { aiServiceStatus = data; applyAiServiceStatus(); }, error: function (xhr, status, error) { aiServiceStatus = { ttsAvailable: false }; applyAiServiceStatus(); } }); } function applyAiServiceStatus() { if (!aiServiceStatus.ttsAvailable) { $("#audio-status").show().find("p").html( ' 현재 AI 오디오 서비스를 이용할 수 없습니다.' ); } } //////////////////////////////////////// // AI News Audio Player //////////////////////////////////////// function fetchNewsAudio() { if (aiServiceStatus && !aiServiceStatus.ttsAvailable) { return; } if (isEmpty(selectedPdServiceId) || isEmpty(selectedPdServiceVersionIds) || isEmpty(selectedAssigneeId)) { return; } $("#audio-loading").show(); $("#audio-player-wrapper").hide(); $("#audio-status").hide(); var dto = { "pdServiceId": Number(selectedPdServiceId), "pdServiceVersions": selectedPdServiceVersionIds, "assigneeId": selectedAssigneeId, "message": "KPI" }; $.ajax({ global: false, url: "/ai-api/performance/news-audio", type: "POST", data: JSON.stringify(dto), contentType: "application/json;charset=UTF-8", xhrFields: { responseType: "blob" }, success: function (blob) { if (previousAudioBlobUrl) { URL.revokeObjectURL(previousAudioBlobUrl); } previousAudioBlob = blob; previousAudioBlobUrl = URL.createObjectURL(blob); var audioPlayer = document.getElementById("news-audio-player"); audioPlayer.src = previousAudioBlobUrl; $("#audio-loading").hide(); $("#audio-player-wrapper").show(); $("#btn-audio-download") .attr("href", previousAudioBlobUrl) .attr("download", "ai-news-audio.wav"); console.log("[ ReportPerformance :: fetchNewsAudio ] :: 오디오 생성 완료"); }, error: function () { $("#audio-loading").hide(); $("#audio-player-wrapper").hide(); $("#audio-status").show().find("p").html( ' 오디오 생성에 실패했습니다.' ); } }); } function resetAudioPlayer() { var audioPlayer = document.getElementById("news-audio-player"); if (audioPlayer) { audioPlayer.pause(); audioPlayer.src = ""; } if (previousAudioBlobUrl) { URL.revokeObjectURL(previousAudioBlobUrl); previousAudioBlobUrl = null; } previousAudioBlob = null; $("#audio-loading").hide(); $("#audio-player-wrapper").hide(); $("#btn-audio-download").attr("href", "#").removeAttr("download"); if (aiServiceStatus && !aiServiceStatus.ttsAvailable) { $("#audio-status").show().find("p").html( ' AI 음성 서비스가 일시적으로 중단되어 텍스트 보고서만 제공됩니다' ); } else { $("#audio-status").show().find("p").html( ' 보고서 생성 시 AI 음성 요약이 함께 제공됩니다' ); } } //////////////////////////////////////////////////////////////////////////////////////// // 보고서 유형 선택 토글 //////////////////////////////////////////////////////////////////////////////////////// function initReportTypeToggle() { $("#report-btn-group label").on("click", function () { var $clicked = $(this); var isPersonal = $clicked.find("#personal-option").length > 0; var isWeekly = $clicked.find("#weekly-option").length > 0; if (isPersonal) { console.log("[ reportPerformance :: initReportTypeToggle ] :: 개인업무 보고 선택"); $("#assignee-section").removeClass("hidden"); $("#weekly-section").addClass("hidden"); selectedReportType = "personal"; } else if (isWeekly) { console.log("[ reportPerformance :: initReportTypeToggle ] :: 주간업무 보고 선택"); $("#weekly-section").removeClass("hidden"); $("#assignee-section").addClass("hidden"); selectedReportType = "weekly"; } // 유형 변경 시 플레이어 초기화 resetAudioPlayer(); }); }