/** * 20건 수동문자 * * 주요 기능: * 1. 발송 가능 상태(발송전)인 대상자만 선택 가능 * 2. 엔터키 롱프레스 연속 선택 (현재 페이지에서만) * 3. 선택해제 시 완전 초기화 */ var isSpacePressed = false; var isSendLocked = false; $(document).ready(function(){ // 필요 시 초기화 코드 }); /* ====================== 엔터키 연속 선택 ====================== */ $(document).on("keydown", function (e) { var isSpaceChecked = $("input:checkbox[name='sendBySpace']").is(":checked"); // Spacebar로 발송 (기존 유지) if (e.keyCode === 32 && isSpaceChecked && !$(".put_text").is(':focus')) { e.preventDefault(); if (isSpacePressed || isSendLocked) return; isSpacePressed = true; // isSendLocked = true; var checkedCnt = twSmsTable.getSelectedData().length; if (checkedCnt > 0) { setTimeout(function () { $(".btn_send").click(); }, 500); } else { isSendLocked = false; alert("발송할 대상을 선택해 주세요."); } return; } // ==================== 엔터키 연속 선택 ==================== if (e.keyCode === 13 && !$(".put_text").is(':focus')) { if ($(e.target).is("input, textarea, select, [contenteditable='true']")) { return; } e.preventDefault(); if (isEnterPressed || isSendLocked) return; isEnterPressed = true; window.enterSelectCurrentPage = twSmsTable.getPage() || 1; // 처음 시작할 때 1개 선택 selectRowsFastInCurrentPage(1); // 연속 선택 시작 enterLongPressTimer = setInterval(function () { var selected = selectRowsFastInCurrentPage(3); // 현재 페이지에 더 이상 선택할 행이 없으면 → 선택 완료로 판단하고 발송 if (!selected) { clearInterval(enterLongPressTimer); enterLongPressTimer = null; window.enterSelectCurrentPage = null; if (isSendLocked) return; // isSendLocked = true; triggerSend(); return; } // 페이지가 바뀌었으면 강제 종료 if (twSmsTable.getPage() !== window.enterSelectCurrentPage) { clearInterval(enterLongPressTimer); enterLongPressTimer = null; isEnterPressed = false; window.enterSelectCurrentPage = null; } }, 120); } }); $(document).on("keyup", function (e) { if (e.keyCode === 13) { isEnterPressed = false; // isSendLocked = false; if (enterLongPressTimer) { clearInterval(enterLongPressTimer); enterLongPressTimer = null; } window.enterSelectCurrentPage = null; } if (e.keyCode === 32) { isSpacePressed = false; // isSendLocked = false; } }); // ====================== 발송 트리거 함수 ====================== function triggerSend() { var checkedCnt = twSmsTable.getSelectedData().length; if (checkedCnt > 0) { // UI 업데이트를 위한 최소한의 딜레이 (0.2초 추천) setTimeout(function () { $(".btn_send").click(); }, 200); } else { alert("발송할 대상을 선택해 주세요."); } } /* ====================== 선택 함수 ====================== */ /** * 현재 페이지에서 빠르게 선택 */ function selectRowsFastInCurrentPage(count) { var rows = getCurrentPageSelectableRows(); if (rows.length === 0) return false; var selectedCount = 0; var table = twSmsTable; table.blockRedraw(); try { for (var i = 0; i < rows.length; i++) { var row = rows[i]; if (!row.isSelected() && isSelectableStatus(row.getData().status)) { row.select(); selectedCount++; if (selectedCount >= count) break; } } } finally { table.restoreRedraw(); } // 선택 후 현재 페이지에 더 이상 선택 가능한 행이 없으면 false 반환 return getCurrentPageSelectableRows().length > 0; } /** * 현재 페이지에 보이는 선택 가능한 행만 반환 (이미 선택된 행은 제외) */ function getCurrentPageSelectableRows() { var table = twSmsTable; if (!table) return []; var visibleRows = table.getRows("visible"); var selectableRows = []; for (var i = 0; i < visibleRows.length; i++) { var row = visibleRows[i]; if (isSelectableStatus(row.getData().status) && !row.isSelected()) { selectableRows.push(row); } } return selectableRows; } /** * 발송전 상태 체크 */ function isSelectableStatus(status) { var text = ""; if(status === null || status === undefined){ text = ""; }else{ text = String(status).replace(/<[^>]*>/g, "").replace(/\s/g, ""); } // 선택 가능: 미발송 상태만 허용 if(text === "" || text === "N" || text === "발송전"){ return true; } // 선택 불가: 발송 성공/제외 if(text === "Y" || text === "발송성공"){ return false; } if(text === "F" || text === "발송제외"){ return false; } return false; } /* ====================== 선택해제 ====================== */ function clearAllSelection(){ if(!window.twSmsTable) return; var table = window.twSmsTable; table.blockRedraw(); try { table.deselectRow(); // 전체 해제 table.getRows().forEach(function(row){ row.deselect(); }); } finally { table.restoreRedraw(); } // 엔터키 선택 상태 초기화 if (enterLongPressTimer) { clearInterval(enterLongPressTimer); enterLongPressTimer = null; } window.enterSelectCurrentPage = null; } /* ====================== 페이지네이션 ====================== */ $(document).on("click", "#sms-pagination .page-btn", function(){ var page = $(this).data("page"); if($(this).prop("disabled")) return; // 엔터키 선택 중이었으면 상태 초기화 if (window.enterSelectCurrentPage) { window.enterSelectCurrentPage = null; } var table = window.twSmsTable || table; // window.twSmsTable이 우선 if(page === "first"){ table.setPage(1); return; } if(page === "prev"){ table.previousPage(); return; } if(page === "next"){ table.nextPage(); return; } if(page === "last"){ table.setPage(table.getPageMax()); return; } table.setPage(Number(page)); }); /* ====================== 기타 기존 함수 ====================== */ function twUpdateTotCnt(){ var totRows = twSmsTable.getRows().length; var twTotalRowCnt = Number(totRows) || 0; $('#totCallToCnt').text(twTotalRowCnt); if(window.twSmsTable){ window.twSmsTable.redraw(true); } } function updateTwSmSTable(addrData){ const existingData = twSmsTable.getData(); const phoneToItemMap = new Map(existingData.map(item => [item.phone, item])); const newlyAdded = []; // 1. 데이터 병합 및 중복 제거 루프 수행 for (const item of addrData) { const phone = item.phone; if (phone && !phoneToItemMap.has(phone)) { newlyAdded.push(item); phoneToItemMap.set(phone, item); } } // 2. 루프 종료 후 최종 Map의 크기(전체 데이터 건수) 체크 const MAX_LIMIT = 300000; console.log('phoneToItemMap.size : ', phoneToItemMap.size); if (phoneToItemMap.size > MAX_LIMIT) { alert(`최대 허용 건수(${MAX_LIMIT.toLocaleString()}건)를 초과했습니다. 현재: ${phoneToItemMap.size.toLocaleString()}건`); console.error("Data limit exceeded after merge."); return; // 테이블 업데이트를 하지 않고 종료 } console.log('phoneToItemMap.size!!!!!!!! : ', phoneToItemMap.size); twSmsTable.setData(Array.from(phoneToItemMap.values())); twUpdateTotCnt(); var tmpJobId = $('#tmpJobId').val(); console.log('tmpJobId : ', tmpJobId); console.log('newlyAdded.length : ', newlyAdded.length ); if(newlyAdded.length < 1){ alert("중복 제거 후 추가할 데이터가 없습니다."); return false; } if(tmpJobId){ fn_updateCampainTmpJob(tmpJobId, newlyAdded); }else{ fn_insertCampainTmpJob(newlyAdded); } } function fn_udpateCampainJob(p_tmpJobId, p_jobStatus){ var sendData = { "tmpJobId" : p_tmpJobId, "jobStatus" : p_jobStatus } console.log('sendData :: ',sendData); $.ajax({ type: 'POST', url: '/web/mjon/msgcampain/updateCampainJobStatus.do', contentType: 'application/json', data: JSON.stringify(sendData), dataType: 'json', success : function(data) { console.log(data); if(data.status == 'OK') { // 성공 후 처리 로직 } else { // 실패 처리 로직 } }, error : function(jqXHR, textStatus, errorThrown) { console.error("AJAX Error:", textStatus, errorThrown); console.error("Response:", jqXHR.responseText); } }); } function fn_updateCampainTmpJob(tmpJobId, newlyAdded){ console.log('newlyAdded : ', newlyAdded); var sendData = { "totalCount" : newlyAdded.length ,"tmpJobId" : tmpJobId } $.ajax({ type: 'POST', url: "/web/mjon/msgcampain/updateMjCampainTmpJob.do", contentType: 'application/json', data: JSON.stringify(sendData), dataType: 'json', beforeSend: function() { $('.loading_layer').addClass('active'); }, complete: function() { }, success: function(data) { console.log(data.status); if(data.status == 'OK') { console.log(' data :: '+data.status) console.log(' data :: '+data.status) console.log(' data :: '+data.status) console.log(' data :: '+data.status) console.log(' data :: '+data.status) console.log(' data :: '+data.status) fn_insertCampainTmp(newlyAdded) // 성공 후 처리 로직 } else { alert(data.message); return false; // 실패 처리 로직 } }, error: function(jqXHR, textStatus, errorThrown) { console.error("AJAX Error:", textStatus, errorThrown); } }); } /** * @discription 수신자 목록 저장 * @param newlyAdded */ function fn_insertCampainTmpJob(newlyAdded){ if (newlyAdded.length < 1) return; var sendData = { "totalCount" : newlyAdded.length } $.ajax({ type: 'POST', url: "/web/mjon/msgcampain/insertMjCampainTmpJob.do", contentType: 'application/json', data: JSON.stringify(sendData), dataType: 'json', beforeSend: function() { $('.loading_layer').addClass('active'); }, complete: function() { }, success: function(data) { console.log(data.msg); $('#tmpJobId').val(data.data); fn_insertCampainTmp(newlyAdded) }, error: function(jqXHR, textStatus, errorThrown) { console.error("AJAX Error:", textStatus, errorThrown); } }); } /** * @discription 수신자 목록 저장 * @param newlyAdded */ function fn_insertCampainTmp(newlyAdded){ if (newlyAdded.length < 1) return; var currentTmpJobId = $('#tmpJobId').val(); var sendList = newlyAdded.map(function(row) { return { callNm: row.name || "", callTo: row.phone || "", tmpJobId: currentTmpJobId }; }); $.ajax({ type: 'POST', url: "/web/mjon/msgcampain/insertMyAddrTWCallToAjax_advc.do", contentType: 'application/json', data: JSON.stringify(sendList), dataType: 'json', beforeSend: function() { $('.loading_layer').addClass('active'); }, complete: function() { $('.loading_layer').removeClass('active'); refreshSendCounts(); // fn_udpateCampainJob(data.data, 'C'); }, success: function(data) { console.log(data.msg); // fn_udpateCampainJob(currentTmpJobId, 'C'); }, error: function(jqXHR, textStatus, errorThrown) { console.error("AJAX Error:", textStatus, errorThrown); } }); } // 초기화 버튼 function fnCallToListReset(){ var totalCount = twSmsTable.getData().length; if(totalCount < 1){ alert("받는사람 목록이 없습니다."); return false; } if(!confirm("기존 받는사람이 모두 초기화되며, 복구할 수 없습니다. 진행하시겠습니까?")){ return false; } var sendData = { "tmpJobId" : $('#tmpJobId').val() , "jobStatus" : $('#jobStatus').val() } $.ajax({ type: "POST", url: "/web/mjon/msgcampain/deleteCandidateTWCallToData.do", contentType: 'application/json', data: JSON.stringify(sendData), dataType: 'json', success: function(data) { // if(data.status == 'OK') }, error: function (e) { console.log("ERROR : ", e); }, beforeSend: function() { $('.loading_layer').addClass('active'); }, complete: function() { location.reload() } }); } // 발송 event var imgFilePath = []; // 최종 선택한 이미지 경로 저장 변수 var imgFileId = []; // 최종 선택한 파일 아이디 저장 변수 var templateYn = []; // 템플릿 사용 여부 저장 변수 //이벤트가 아닌 일반 개별 단가 셋팅해주기 function getEventEachPrice(evnMsgType){ var eventEachPrice = 0; var evnImgCnt = $("#imgCnt").val(); //일반 단가로 변경해주기 if(evnMsgType == '4'){ eventEachPrice = $("#sPrice").val(); console.log("eventEachPrice Short::: "+eventEachPrice); }else if(evnMsgType == '6'){ if(evnImgCnt == 0){//장문 가격 eventEachPrice = $("#mPrice").val(); console.log("eventEachPrice Long ::: "+eventEachPrice); }else if(evnImgCnt == 1){ eventEachPrice = $("#pPrice").val(); console.log("eventEachPrice p1 ::: "+eventEachPrice); }else if(evnImgCnt == 2){ eventEachPrice = $("#p2Price").val(); console.log("eventEachPrice p2 ::: "+eventEachPrice); }else if(evnImgCnt == 3){ eventEachPrice = $("#p3Price").val(); console.log("eventEachPrice p3 ::: "+eventEachPrice); } } return eventEachPrice; } //문자발송 처리 함수 function fn_sendMsgData(e){ console.log("isTrusted :: ", e?.isTrusted); if (isSendLocked) { console.warn("발송 처리 중입니다. 중복 요청을 무시합니다."); return false; } const isUserClick = e && e.isTrusted === true; /* if(e && e.isTrusted){ console.log("마우스 클릭 (사용자 직접)"); }else{ console.log("스크립트/키 이벤트로 실행됨"); }*/ // return false; // 타블레이터 호출 var $selectedData = twSmsTable.getSelectedData(); // 데이터 가져오기 // console.log('$selectedData : ', $selectedData); if(!checkConf()){ //문자온 conf-check alert("현재 문자 발송하기 기능 점검 중입니다.\n\n1분 후 다시 시도해주세요."); return false; } var form = document.msgForm; var totPriceOnly = stringReplaceAll(form.totPrice.value, ",", ""); var userMoneyOnly = stringReplaceAll(form.myPrice.value, ",", ""); // test 주석 if(parseFloat(userMoneyOnly) < parseFloat(totPriceOnly)){ alert("문자 발송에 필요한 회원님의 보유 잔액이 부족 합니다."); return false; } // console.log(' 11111 : '); // 폼 유효성 검사 if (!validateForm(form)) return false; // console.log(' 2222 : '); // var blineCodeYn = $("#blineCode").val(); // if(blineCodeYn != 'N'){ // if(!blineSendCheck(blineCodeYn)){ //문자온 conf-check // alert("현재 문자 발송하기 기능 점검 중입니다.\n\n잠시 후 다시 시도해주세요."); // return false; // } // } if($selectedData.length > 300000){ alert("30만건까지 발송이 가능합니다.\n받는사람 목록을 확인해주세요."); return false; } // 👇 여기만 조건 추가 if(isUserClick){ if(!confirm("문자를 전송하시겠습니까?")){ return false; } } var msgType = conTypeCheck($('#smsTxtArea').val()); //입력 문자 길이 체크 console.log(msgType) console.log(msgType) console.log(msgType) console.log(msgType) console.log(msgType) //금액 소수점 제거 하여 적용 20220623 소수점 버림 처리 if(msgType == "P"){//그림문자 //그림 이미지 갯수에 따른 금액 계산 if(imgCnt > 2){//그림 3장일경우 price = $('#p3Price').val(); }else if(imgCnt > 1){//그림 2장일경우 price = $('#p2Price').val(); }else{//그림 1장일 경우 price = $('#pPrice').val(); } }else if(msgType == "M"){//장문 문자 price = $('#mPrice').val(); }else{//단문문자 price = $('#sPrice').val(); } // var msgCnt = userMoney / price; var totPrice = price * $selectedData.length; // form.totPrice.value = totPrice.replace(",",""); //이벤트 진행중 회원의 경우 이벤트 잔여금액 비교 처리 필요. //이벤트 캐시가 부족한 경우 이벤트 캐시로 발송 가능한 만큼만 이벤트 금액으로 발송 처리해줌. //이 후 나머지 발송 건수는 이벤트 캐시가 아닌 일반 단가로 발송 처리를 진행 함. var eventStatus = form.eventStatus.value; if(eventStatus == 'Y'){ //이벤트 진행 대상자이면 문자종류에 이벤트로 구분 form.eventYn.value = "Y"; //이벤트문자 종류 설정 } var eventRemainCash = parseFloat(form.eventRemainCash.value); console.log('eventStatus : ', eventStatus); console.log('parseFloat(eventRemainCash) : ', parseFloat(eventRemainCash)); console.log('parseFloat(totPriceOnly) : ', parseFloat(totPrice)); console.log('parseFloat(eventRemainCash) < parseFloat(totPriceOnly) : ', parseFloat(eventRemainCash) < parseFloat(totPriceOnly)); if(eventStatus == 'Y' && parseFloat(eventRemainCash) < parseFloat(totPrice)) { // 이벤트 금액 부족 상태 요청하신 모두 발송할 수 없습니다 console.log('eventRemainCash : ', eventRemainCash); if(!confirm("고객님의 이벤트 캐시 잔액(" + eventRemainCash + "원) 부족으로 요청하신 문자("+numberWithCommas($selectedData.length)+"건)를 모두 발송할 수 없습니다. 이벤트 캐시 초과 건에 대해서는 일반 단가를 적용하여 발송하시겠습니까?")){ return false; } // 이벤트 캐시 초과 건에 대해서는 일반 단가를 적용하여 발송하시겠습니까 var evnMsgType = $("#msgType").val(); // 문자전송 타입 var norEachPrice = getNorEachPrice(evnMsgType); // 일반전송 금액 var eventEachPrice = getEventEachPrice(evnMsgType); // 이벤트 전송 금액 var eventCount = eventRemainCash/eventEachPrice; // 이벤트금액으로 전송가능한 수량 console.log('eventCount : ', parseInt(eventCount)); var norCount = numberWithCommas($selectedData.length)-parseInt(eventCount); // 일반금액으로 전송수량 console.log('norCount : ', parseInt(norCount)); var norAccount = norCount * norEachPrice; // 일반금액으로 변경한 발송금액 var totAccount = userMoneyOnly - norAccount // console.log('norAccount : ', norAccount); console.log('userMoneyOnly : ', userMoneyOnly); console.log('totAccount : ', totAccount); if(totAccount < 0){ alert("고객님의 이벤트 캐시 잔액(" + parseInt(totAccount)*-1 + "원) 부족으로 요청하신 문자("+numberWithCommas($selectedData.length)+"건)를 모두 발송할 수 없습니다."); return false; } } isSendLocked = true; // console.log(' !!! imgFilePath : ', imgFilePath); // img 처리 영역 imgFilePath = []; $('.thumb_wrap').find('.thumb_img').each(function(idx, el) { if($("#delLi"+idx).hasClass("Template")){ templateYn.push("Y"); } else { templateYn.push("N"); } if($(el).hasClass("edit_y")) { //이미지 변환 const imgBase64 = $(el).attr("src"); const decodImg = atob(imgBase64.split(',')[1]); // base64~ 부분을 제외하고 데이터 디코딩 let array = []; for (let i = 0; i < decodImg.length; i++) { array.push(decodImg.charCodeAt(i)); } const file = new Blob([new Uint8Array(array)], {type: 'image/jpeg'}); const fileName = 'canvas_img_' + new Date().getMilliseconds() + '.jpg'; let formData = new FormData(); formData.append('file', file, fileName); $.ajax({ type: 'post', url: '/web/mjon/msgdata/insertEditImageAjax.do', cache: false, data: formData, dataType:'json', processData: false, contentType: false, async: false, success: function (returnData, status) { if(status == 'success'){ console.log('returnData : ',returnData) // imgFilePath 에 배열로 담아주기(이미지 순서대로) //console.log(imgFilePath); imgFilePath.push(returnData.filefullPath); imgFileId.push(returnData.atchFileId); } else if(status== 'fail'){ alert(returnData.message); } }, error: function (e) { alert("이미지 업로드에 실패하였습니다."); console.log("ERROR : ", e); } }); } else { if(imgFilePath.length != imgFileId.length){ //에디터 안거친 템플릿 이미지면 바로 src 때림 imgFilePath.push($(el).attr("src")); } } }); form.templateYnAdvc.value = templateYn; form.imgFilePathAdvc.value = imgFilePath; form.imgFileIdAdvc.value = imgFileId; form.callFrom.value = removeDash($('#callFromList').val()); // 발신번호 대쉬(-) 문자 없애주기 //광고 종류 설정 //발송 Ajax 호출해주기 sendMsgAjax_advc(); } // 기업회원 및 선거후보자 체크 function deptCandidateChk(){ var candidateNm = $.trim($("#candidateNmChk").val()); var dept = $.trim($("#deptChk").val()); // 기업회원이면 if(dept == 'c'){ $('[data-tooltip="alert_layer_popup02"]').trigger('click'); // 기업회원 팝업 }else if(!candidateNm){ // 값이 없으면 $('[data-tooltip="alert_layer_popup"]').trigger('click'); // 후보자 등록 팝업 } } function sendMsgAjax_advc(){ // 타블레이터 호출 var $selectedData = twSmsTable.getSelectedData(); // 데이터 가져오기 // 폼 데이터를 배열로 직렬화 var form = $('#msgForm'); var formDataArray = form.serializeArray(); // 배열을 객체로 변환 var formData = {}; $.each(formDataArray, function(index, field) { formData[field.name] = field.value; }); // 빈 값 제거 removeEmptyValues(formData); // 선택된 데이터 추가 formData["mjonMsgSendVOList"] = $selectedData; // JSON 데이터 확인 console.log("최종 formData:", JSON.stringify(formData)); // var url = "/web/mjon/msgdata/sendMsgDataAjax_advc.do"; // var url = "/web/mjon/msgcampain/sendMsgDataAjax.do"; var url = "/web/mjon/msgcampain/sendMsgDataAjax_advc.do"; // 시작 시간 // 프로그래스파 시간을 위한 계산 var estimtedTime = calculateEstimatedTime($selectedData.length); $.ajax({ type: "POST", url: url, data: JSON.stringify(formData), contentType: 'application/json', dataType: 'json', success: function (data) { console.log('발송 결과 data : ', data); console.log('발송 결과 data : ', data); console.log(' ::data.status:: '); if (data.status === "OK") { const resultSts = Number(data.object.resultSts || 0); const resultBlockSts = Number(data.object.resultBlockSts || 0); const resultBlockList = data.object.resultBlockList || []; // 선택된 데이터는 선택 해제 전에 미리 복사 const $selectedData = twSmsTable.getSelectedData(); // 1. 테이블 상태 정확히 업데이트 updateTableAfterSend(resultSts, resultBlockSts, resultBlockList); // 2. DB 실제 SEND_FLAG 업데이트 updateDbSendResult(resultBlockList, $selectedData); // 자동 페이지 이동 기능 const isAutoPageMove = $("#autoPageMove").is(":checked"); if (isAutoPageMove) { // console.log("currentPage :: ", twSmsTable.getPage()); // console.log("pageMax :: ", twSmsTable.getPageMax()); const currentPage = Number(twSmsTable.getPage() || 1); const pageMax = Number(twSmsTable.getPageMax() || 1); // 현재 페이지에서 아직 선택 가능한 행(= 체크박스 살아있는 발송전 상태) 조회 const remainSelectableRows = getCurrentPageSelectableRows(); console.log("remainSelectableRows.length :: ", remainSelectableRows.length); // 현재 페이지에 더 이상 선택 가능한 행이 없을 때만 다음 페이지 이동 if (remainSelectableRows.length === 0 && currentPage < pageMax) { setTimeout(function() { twSmsTable.setPage(currentPage + 1); }, 300); } } } else if (data.status === "BAD_REQUEST") { alert(data.message || "잘못된 요청입니다."); } else if (data.status === "UNAUTHORIZED") { alert(data.message); goMsgUrlMove(); } else if("FORBIDDEN" == data.status){ // 정지 회원 처리 deptCandidateChk(); return false; } else { alert("발송 처리 중 오류가 발생했습니다."); } // 갱신된 금액 이벤트 잔액 불러오기 getBeforeCashData(); }, beforeSend : function(xmlHttpRequest) { // 프로그래스 바 실행 progressStart(estimtedTime); }, complete : function(xhr, textStatus) { // 프로그래스 바 종료 progressComplete(); // ✅ 여기서 잠금을 해제합니다. 서버 응답이 완전히 끝난 후 다음 발송 허용. isSendLocked = false; }, error: function (e) { alert("문자 발송에 실패하였습니다."); console.log("ERROR : ", e); } }); } // 발송 후 DB SEND_FLAG 업데이트 (20건 기준 단순화) function updateDbSendResult(resultBlockList, $selectedData) { console.log(' :: updateDbSendResult :: ') // Tabulator에서 현재 선택된 행 데이터 가져오기 if ($selectedData.length === 0) return; const blockSet = new Set(resultBlockList || []); const failPhones = []; const successPhones = []; // 선택된 데이터 순회하면서 성공/실패 분류 $selectedData.forEach(item => { const phone = String(item.phone || "").replace(/[^0-9]/g, ''); if (blockSet.has(phone)) { failPhones.push(phone); } else { successPhones.push(phone); } }); $.ajax({ type: "POST", url: "/web/mjon/msgcampain/updateSendFlagAfterSend.do", contentType: 'application/json', data: JSON.stringify({ successPhones: successPhones, failPhones: failPhones }), dataType: 'json', success: function(res) { console.log("DB SEND_FLAG 업데이트 완료", res); }, error: function(e) { console.error("DB 업데이트 실패", e); } }); } //폼 유효성 검사 함수 function validateForm(form) { // 타블레이터 호출 var $selectedData = twSmsTable.getSelectedData(); // 데이터 가져오기 // console.log('$selectedData : ', $selectedData); if($selectedData.length < 1){ alert("받는사람 내역을 입력해주세요."); return false; } if(form.callFromList.value == ""){ alert("발신번호를 입력해 주세요."); return false; } if (form.subjectChkYn.value === 'N') { form.mmsSubject.value = ""; } else if (getSpacialStringChk(form.mmsSubject.value)) { alert("문자 제목에는 치환문자(엑셀 내 *이름*, *1*, *2*, *3*, *4* 등)를 사용하실 수 없습니다."); return false; } //문자제목에 이모지가 있는지 체크 var titleStatusYn = $("input[name='subjectChkYn']:checked").val(); if(titleStatusYn == 'Y') { if(!emojiCheck(form.mmsSubject.value)) return false; } // 문자내용에 이모지가 있는지 체크 var strCont = form.smsTxtArea.value; if (!emojiCheck(strCont)) return false; var rtnStr = strChinJpnCheck(strCont); if(rtnStr.length > 0){ alert("입력하신 문구 중 \" " + rtnStr + " \" 는 일부 휴대폰에서 표기되지 않을 수 있습니다."); } if (imgFilePath.length === 0 && !form.smsTxtArea.value) { alert("문자내용을 입력해 주세요."); return false; } return true; } //빈 값이나 null 값을 제거하는 함수 function removeEmptyValues(obj) { Object.keys(obj).forEach(function(key) { if (obj[key] === null || obj[key] === '') { delete obj[key]; } }); } // 선택된 데이터의 길이에 따라 예상 시간 계산 함수 function calculateEstimatedTime(selectedCount) { //기준값 // const processTimePerBatch = 130; // 130초 // 30만건 기준 10분으로 기준을 잡아서 // 시간계산함 const processTimePerBatch = 600; const batchSize = 300000; // 1건당 처리 시간 const timePerRecord = processTimePerBatch / batchSize; // 예상 시간 계산 const estimatedTimeInSeconds = selectedCount * timePerRecord; return estimatedTimeInSeconds.toFixed(2); } /** * 발송 성공 후 테이블 상태 정확히 업데이트 * resultBlockList를 이용해 실제 성공/실패 구분 */ function updateTableAfterSend(resultSts, resultBlockSts, resultBlockList) { const table = window.twSmsTable; if (!table) return; // 빠른 조회를 위한 Set (전화번호 기준) const blockSet = new Set(resultBlockList || []); table.blockRedraw(); // 성능 최적화 (한 번에 redraw) try { const selectedRows = table.getSelectedRows(); // 선택된 행만 처리 selectedRows.forEach(row => { const data = row.getData(); const phone = String(data.phone || "").replace(/[^0-9]/g, ''); // 대시 제거 console.log("phone :: "+ phone); // 실제 발송 결과 판단 if (blockSet.has(phone)) { data.status = "발송제외"; // 또는 "F" (formatter가 처리한다면 F도 OK) } else { data.status = "발송완료"; // 또는 "Y" } row.update(data); // 행 업데이트 row.deselect(); // 선택 해제 }); } finally { table.restoreRedraw(); // redraw 복구 } // 좌측 카운트 갱신 refreshSendCounts(); console.log(`✅ 테이블 업데이트 완료: 성공 ${resultSts}건, 제외 ${resultBlockSts}건`); } function refreshSendCounts() { var table = window.twSmsTable; if (!table) return; var allRows = table.getData(); // 전체 데이터 (모든 페이지 포함) var totalCnt = allRows.length; var completeCnt = 0; // 발송완료 (Y) var excludeCnt = 0; // 발송제외 (F) var remainCnt = 0; // 잔여 (발송전) var hasSent = false; allRows.forEach(function(row) { var status = String(row.status || "").trim(); if (status === "Y" || status === "발송완료") { completeCnt++; hasSent = true; } else if (status === "F" || status === "발송제외") { excludeCnt++; hasSent = true; } else { remainCnt++; } }); // 화면에 표시 $("#totCallToCnt").text(numberWithCommas(totalCnt)); $("#sendCompleteCnt").text(numberWithCommas(completeCnt)); $("#excludeSendCnt").text(numberWithCommas(excludeCnt)); $("#remainCnt").text(numberWithCommas(remainCnt)); // 테이블 redraw (필요 시) table.redraw(true); if(hasSent){ $(".callToBtn").prop("disabled", true); } /*else{ $(".callToBtn").prop("disabled", false); }*/ } function moveToFirstSelectablePage(){ if(!window.twSmsTable){ return; } var table = window.twSmsTable; var rows = table.getRows("active"); var pageSize = table.getPageSize() || 20; if(!rows || !rows.length){ table.setPage(1); return; } for(var i = 0; i < rows.length; i++){ var rowData = rows[i].getData(); if(isSelectableStatus(rowData.status)){ var targetPage = Math.floor(i / pageSize) + 1; if((table.getPage() || 1) !== targetPage){ table.setPage(targetPage); } return; } } table.setPage(1); }