이호영 이호영 2025-02-06
발송결과 종료
@d6bed63fec0236e78b6d21a9f1f81913f2872919
src/main/java/itn/let/mjo/addr/service/AddrService.java
--- src/main/java/itn/let/mjo/addr/service/AddrService.java
+++ src/main/java/itn/let/mjo/addr/service/AddrService.java
@@ -109,4 +109,8 @@
 	void deleteAddr_advc(AddrGroupVO addrGroupVO) throws Exception;
 
 	int getAddrCount(AddrGroupVO addrGroupVO) throws Exception;
+
+	StatusResponse insertByAddrGrpDataAndAddrDataAjax(AddrVO addrVO) throws Exception;
+
+	StatusResponse deleteAddrNoDataAjax(AddrVO addrVO) throws Exception;
 }
src/main/java/itn/let/mjo/addr/service/impl/AddrDAO.java
--- src/main/java/itn/let/mjo/addr/service/impl/AddrDAO.java
+++ src/main/java/itn/let/mjo/addr/service/impl/AddrDAO.java
@@ -292,5 +292,9 @@
 		return (Integer)select("AddrDAO.getAddrCount", addrVO);
 	}
 
+	public int deleteAddrPhoneNo(AddrVO addrVO) {
+		return update("AddrDAO.deleteAddrPhoneNo", addrVO);
+	}
+
 	
 }
src/main/java/itn/let/mjo/addr/service/impl/AddrServiceImpl.java
--- src/main/java/itn/let/mjo/addr/service/impl/AddrServiceImpl.java
+++ src/main/java/itn/let/mjo/addr/service/impl/AddrServiceImpl.java
@@ -34,6 +34,8 @@
 import itn.let.mjo.addr.service.AddrService;
 import itn.let.mjo.addr.service.AddrTransHistVO;
 import itn.let.mjo.addr.service.AddrVO;
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * 주소록 관리를 위한 서비스 구현 클래스
@@ -49,6 +51,7 @@
  *  2021.04.08  ITN          최초 생성
  *  </pre>
  */
+@Slf4j
 @Service("AddrService")
 public class AddrServiceImpl  extends EgovAbstractServiceImpl implements AddrService {
 
@@ -644,5 +647,57 @@
 		
 		return aa; 
 	}
+
+	@Override
+	public StatusResponse insertByAddrGrpDataAndAddrDataAjax(AddrVO addrVO) throws Exception {
+
+		String userId = addrVO.getMberId();
+		
+		AddrGroupVO addrGroupVO = new AddrGroupVO();
+		addrGroupVO.setAddrGrpNm(addrVO.getAddrGrpNm());
+		addrGroupVO.setMberId(userId);
+		addrGroupVO.setFrstRegisterId(userId);
+		
+		int usedCnt = addrGroupDAO.selectDuplAddrGroupCnt(addrGroupVO);
+		if(usedCnt > 0) {
+			return new StatusResponse(HttpStatus.BAD_REQUEST, "이미 등록되어있는 그룹이름입니다.", LocalDateTime.now());
+		}
+
+		String addrGrpId = addrGroupDAO.insertAddrGroup(addrGroupVO);
+		
+
+
+		List<AddrVO> addrDataInfo = new ArrayList<AddrVO>();
+
+		for(String phone : addrVO.getAddrPhones()) {
+			AddrVO addrTempVO = new AddrVO();
+			addrTempVO.setAddrPhoneNo(phone);
+			addrTempVO.setAddrGrpId(addrGrpId);
+			addrTempVO.setBookmark("N"); //북마크 : N
+			addrTempVO.setFrstRegisterId(userId);
+			addrTempVO.setMberId(userId);
+			addrDataInfo.add(addrTempVO);
+		}
+
+		int resultCnt = addrDAO.insertAddrList(addrDataInfo);
+		
+		
+
+		return new StatusResponse(HttpStatus.OK, "총" + resultCnt + "건의 주소록 등록이 완료되었습니다.", addrVO);
+		
+	}
+
+	@Override
+	public StatusResponse deleteAddrNoDataAjax(AddrVO addrVO) throws Exception {
+		
+//		AddrPhones
+		//아이디 저장
+		
+		//주소록 디비에서 연락처 정보를 delete 시킴
+		int resultCnt = addrDAO.deleteAddrPhoneNo(addrVO);
+		
+		
+		return new StatusResponse(HttpStatus.OK, "총 " + resultCnt + "건의 주소록을 삭제하였습니다.", addrVO);
+	}
     
 }
src/main/java/itn/let/mjo/addr/web/AddrController.java
--- src/main/java/itn/let/mjo/addr/web/AddrController.java
+++ src/main/java/itn/let/mjo/addr/web/AddrController.java
@@ -4,6 +4,7 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -31,10 +32,13 @@
 import org.apache.poi.xssf.usermodel.XSSFRow;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.ui.ModelMap;
 import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
@@ -52,12 +56,14 @@
 import itn.com.cmm.util.StringUtil;
 import itn.com.utl.fcc.service.EgovStringUtil;
 import itn.let.fax.addr.service.FaxAddrVO;
+import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.addr.service.AddrGroupService;
 import itn.let.mjo.addr.service.AddrGroupVO;
 import itn.let.mjo.addr.service.AddrService;
 import itn.let.mjo.addr.service.AddrTransHistVO;
 import itn.let.mjo.addr.service.AddrVO;
 import itn.let.mjo.msgdata.service.PhoneVO;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * 주소록 관한 controller 클래스를 정의한다.
@@ -75,6 +81,7 @@
  *
  * </pre>
  */
+@Slf4j
 @Controller
 public class AddrController {
 
@@ -2223,6 +2230,49 @@
 		return modelAndView;
 	} 	
 	
+
+	@RequestMapping(value = {"/web/mjon/addr/insertByAddrGrpDataAndAddrDataAjax.do"})
+	public ResponseEntity<StatusResponse> insertByAddrGrpDataAndAddrDataAjax(@RequestBody AddrVO addrVO) throws Exception {
+		
+		ModelAndView modelAndView = new ModelAndView();
+		modelAndView.setViewName("jsonView");
+		
+		//로그인 권한정보 불러오기
+		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
+		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+		
+		if(userId == null) {
+			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+		}
+		
+		log.info(" + addrVO.getAddrPhones() :: [{}]", addrVO.getAddrPhones().length);
+		log.info(" + addrVO.getAddrGrpNm() :: [{}]", addrVO.getAddrGrpNm());
+		addrVO.setMberId(userId);
+		
+		
+		return ResponseEntity.ok().body(addrService.insertByAddrGrpDataAndAddrDataAjax(addrVO));
+	}	
+	
+	@RequestMapping(value = {"/web/mjon/addr/deleteAddrNoDataAjax.do"})
+	public ResponseEntity<StatusResponse> deleteAddrNoDataAjax(@RequestBody AddrVO addrVO) throws Exception {
+		
+		ModelAndView modelAndView = new ModelAndView();
+		modelAndView.setViewName("jsonView");
+		
+		//로그인 권한정보 불러오기
+		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
+		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+		
+		if(userId == null) {
+			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+		}
+		
+		addrVO.setMberId(userId);
+		
+		return ResponseEntity.ok().body(addrService.deleteAddrNoDataAjax(addrVO));
+	}	
+	
+	
 	
 
 	public boolean getNameRepLenChk(String type, String value) {
src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
--- src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
+++ src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
@@ -114,8 +114,9 @@
 import itn.let.uss.umt.service.MberManageVO;
 import itn.let.uss.umt.service.UserManageVO;
 import itn.let.utl.sim.service.EgovClntInfo;
+import lombok.extern.slf4j.Slf4j;
 
-
+@Slf4j
 @Controller
 public class MjonMsgDataController {
 
@@ -816,6 +817,7 @@
 		
 		mjonMsgDataVO.setMsgSeqList(tempList);
 		
+		log.info("===================================================");
 		List<MjonMsgVO> resultList = mjonMsgDataService.selectReSendMsgDataList(mjonMsgDataVO);
 		
 		//문자발송 이미지 처리 - 사용하지 않아서 주석처리함.
src/main/java/itn/let/mjo/msgsent/service/MjonMsgDetailSentVO.java
--- src/main/java/itn/let/mjo/msgsent/service/MjonMsgDetailSentVO.java
+++ src/main/java/itn/let/mjo/msgsent/service/MjonMsgDetailSentVO.java
@@ -53,6 +53,7 @@
 	
 	private String statusCd; // 진행상태 코드
 	private String divideYN;
+	private String divideText;
 	private int diffMin;
 	private String totPrice;
 
src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentDAO.java
--- src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentDAO.java
+++ src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentDAO.java
@@ -186,5 +186,9 @@
 		
 		return (List<MjonMsgDetailSentVO>) list("MjonMsgSentDAO.findByMsgDetailListAjax", mjonMsgDetailSentVO);
 	}
+
+	public List<String> findByReqDateWhereMsgGroupId(String msgGroupId) {
+		return (List<String>) list("MjonMsgSentDAO.findByReqDateWhereMsgGroupId", msgGroupId);
+	}
 	
 }
src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentServiceImpl.java
--- src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentServiceImpl.java
+++ src/main/java/itn/let/mjo/msgsent/service/impl/MjonMsgSentServiceImpl.java
@@ -4,9 +4,12 @@
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -126,13 +129,21 @@
 
 		Map<String, Object> resultMap = new HashMap<String, Object>();
 		
-		System.out.println("mjonMsgSentVO.getSearchConditionSite() :: "+ mjonMsgSentVO.getSearchConditionSite());
 		// 목록
 		List<MjonMsgSentVO> resultList = mjonMsgSentDAO.selectAllMsgSentList_advc(mjonMsgSentVO);
 
+		// 내용이 없고 이미지만 있을 경우
+		resultList.stream().forEach(t->{
+			log.info("+ t.getMsgKind() : [{}]",t.getMsgType());
+			if("6".equals(t.getMsgType()) 
+					&& StringUtils.isEmpty(t.getSmsTxt())
+					&& !"0".equals(t.getFileCnt()) 
+					) {
+				t.setSmsTxt("이미지");
+			}
+		});
+
 		// groupID에 대한 결과건수(대기, 성공 실패) 분할건수를 가져옴
-		// TODO : 분할여부는 예약일 때만 가져오게 해야되는지 검토 필요
-		
 		resultList = makeDetailFunction(resultList);
 
 		/*
@@ -169,8 +180,8 @@
 		resultVO.setResultSValue(updatedVO.getResultSValue()); // 성공건수
 		resultVO.setResultWValue(updatedVO.getResultWValue()); // 대기건수
 		resultVO.setResultFValue(updatedVO.getResultFValue()); // 실패건수
-		resultVO.setTotPrice(updatedVO.getTotPrice());
-		resultVO.setDivideYN(updatedVO.getDivideYN());
+		resultVO.setTotPrice(updatedVO.getTotPrice()); // 총 발송 금액 (성공건수 * 개별금액)
+		resultVO.setDivideYN(updatedVO.getDivideYN()); // 분할 여부
 		
 
 		// 퍼센트 계산 실행
@@ -182,21 +193,21 @@
 		
 		// 진행상태 코드화
 		String statusCode = getStatusCode(MjonMsgSentVO.builder()
-				.reserveCYn(resultVO.getReserveCYn())
-				.reserveYn(resultVO.getReserveYn())
-				.msgGroupCnt(resultVO.getMsgGroupCnt())
-				.resultSValue(resultVO.getResultSValue())
-				.resultWValue(resultVO.getResultWValue())
-				.resultFValue(resultVO.getResultFValue())
-				.diffMin(resultVO.getDiffMin())
-				.build());
-		log.info(" ++ statusCode :: [{}]" ,statusCode);
+													.reserveCYn(resultVO.getReserveCYn())
+													.reserveYn(resultVO.getReserveYn())
+													.msgGroupCnt(resultVO.getMsgGroupCnt())
+													.resultSValue(resultVO.getResultSValue())
+													.resultWValue(resultVO.getResultWValue())
+													.resultFValue(resultVO.getResultFValue())
+													.diffMin(resultVO.getDiffMin())
+													.build());
+
 		resultVO.setStatusCd(statusCode);
 		
 		// 결과 출력
-		System.out.println("성공률: " + returnMap.get("successPct"));
-		System.out.println("대기율: " + returnMap.get("waitingPct"));
-		System.out.println("실패율: " + returnMap.get("failedPct"));
+//		log.info("성공률: [{}]", returnMap.get("successPct"));
+//		log.info("대기율: [{}]", returnMap.get("waitingPct"));
+//		log.info("실패율: [{}]", returnMap.get("failedPct"));
 		
 		
 		// 광고일떄 (광고)와 줄바꿈+무료거부 0808800858 삭제
@@ -205,11 +216,17 @@
 					.replaceAll("\\s*무료거부 0808800858", ""));
 		}
 		
+		// 그림문자일 경우 그림정보 가져오기
 		if(StringUtils.isNotEmpty(resultVO.getFileCnt()) && Integer.parseInt(resultVO.getFileCnt()) > 0)
 		{
-			
 			List<FileInfoVO> fileInfos = getFileInfo(resultVO);
 			resultVO.setFileInfos(fileInfos);
+		}
+		
+		// 분할문자인 경우
+		if("Y".equals(resultVO.getDivideYN())) {
+			String divideText = calculateBatchInfo(resultVO);
+			resultVO.setDivideText(divideText);
 		}
 		
 		
@@ -221,6 +238,37 @@
 	}
 
 
+	private String calculateBatchInfo(MjonMsgDetailSentVO resultVO) {
+		
+		String msgGroupId = resultVO.getMsgGroupId();
+		
+
+		List<String> requestTimes = mjonMsgSentDAO.findByReqDateWhereMsgGroupId(msgGroupId);
+		
+		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S");
+		Map<LocalDateTime, Integer> timeCountMap = new LinkedHashMap<>();
+
+		// REQ_DATE 그룹화 (같은 시간대 몇 건인지)
+		for (String timeStr : requestTimes) {
+			LocalDateTime time = LocalDateTime.parse(timeStr, formatter);
+			timeCountMap.put(time, timeCountMap.getOrDefault(time, 0) + 1);
+		}
+
+		//  가장 첫 번째 시간 & 간격 계산
+		List<LocalDateTime> sortedKeys = new ArrayList<>(timeCountMap.keySet());
+		if (sortedKeys.size() < 2) {
+			return "데이터 부족 (분석 불가)";
+		}
+
+		int batchSize = timeCountMap.get(sortedKeys.get(0)); // 한 번에 보낸 건수
+		int intervalMinutes = sortedKeys.get(1).getMinute() - sortedKeys.get(0).getMinute(); // 시간 간격 계산
+//		int batchCount = sortedKeys.size(); // 총 몇 번 보냈는지
+
+//		return String.format("%,d건씩 %d분 간격 (%d회 발송)", batchSize, intervalMinutes, batchCount);
+		return String.format("%,d건씩 %d분 간격", batchSize, intervalMinutes);
+		
+	}
+
 	private List<FileInfoVO> getFileInfo(MjonMsgDetailSentVO result) throws Exception {
 		
 		
src/main/resources/egovframework/sqlmap/let/mjo/addr/Addr_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/mjo/addr/Addr_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/mjo/addr/Addr_SQL_Mysql.xml
@@ -933,7 +933,7 @@
 	
 	</select>
 	
-	<insert id="AddrDAO.insertAddrList" parameterClass="java.util.List">
+	<update id="AddrDAO.insertAddrList" parameterClass="java.util.List">
 		/* AddrDAO.insertAddrList */
 		INSERT INTO MJ_ADDR
 		(
@@ -967,7 +967,18 @@
           )
 		</iterate>
 	
-	</insert>
+	</update>
+	
+	<update id="AddrDAO.deleteAddrPhoneNo" parameterClass="addrVO">
+		
+	DELETE FROM MJ_ADDR
+		
+	WHERE MBER_ID = #mberId#
+		<iterate prepend="AND ADDR_PHONE_NO IN" open="(" close=")" conjunction="," property="addrPhones">
+			#addrPhones[]#
+		</iterate>
+	
+	</update>
 	
 	<!-- 주소록 그룹명 중복확인 -->
 	<select id="AddrDAO.selectDuplAddrCnt" parameterClass="addrVO" resultClass="int">
src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
--- src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
@@ -4021,6 +4021,8 @@
 	
 	<select id="MjonMsgDataDAO.selectReSendMsgDataList" parameterClass="mjonMsgDataVO" resultClass="mjonMsgVO">
 		
+		/* MjonMsgDataDAO.selectReSendMsgDataList */
+		
 		SELECT MSG_ID       AS msgId,
 		       USER_ID      AS userId,
 		       USERDATA      AS msgSeq,
@@ -7936,6 +7938,7 @@
 	</select>
 
 	<select id="MjonMsgDataDAO.selectMjMsgListByResend" parameterClass="mjonMsgDataVO" resultClass="mjonMsgDataVO">
+		/* MjonMsgDataDAO.selectMjMsgListByResend */
 		SELECT 
 			CALL_TO AS callTo 
 		FROM MJ_MSG_DATA 
src/main/resources/egovframework/sqlmap/let/msg/MjonMsgSent_SQL_mysql.xml
--- src/main/resources/egovframework/sqlmap/let/msg/MjonMsgSent_SQL_mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/msg/MjonMsgSent_SQL_mysql.xml
@@ -410,6 +410,14 @@
 			
 	</select>
 	
+	<!-- REQ_DATE 조회-->
+	<select id="MjonMsgSentDAO.findByReqDateWhereMsgGroupId" parameterClass="String" resultClass="String">
+		/* MjonMsgSentDAO.findByReqDateWhereMsgGroupId*/
+		
+		SELECT REQ_DATE  FROM MJ_MSG_DATA WHERE MSG_GROUP_ID =#msgGroupId#
+			
+	</select>
+	
 	
 	<!-- 전체 발송결과 조회  (전송사별)-->
 	<select id="MjonMsgSentDAO.selectAllMsgSentList_advc" parameterClass="mjonMsgSentVO" resultClass="mjonMsgSentVO">
src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentDetailView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentDetailView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentDetailView.jsp
@@ -24,9 +24,14 @@
 
 <script type="text/javascript">
 
+var currentSearchKeyword = ""; // 검색어 저장
+var currentTabFilter = "전체"; // 현재 선택된 탭 (기본값: 전체)
+
 var $tbDtailList = null; //에러 팝업 영역
 $(document).ready(function(){
 
+	// 탭별 하위 버튼 활성화
+	fn_rowBtnSH('전체');
 	
 	//Tabulator AJAX Data Loading
 	$tbDtailList = new Tabulator("#detailPopup", {
@@ -67,44 +72,84 @@
 		$("#goList").submit();
 	});
 	
-
+	
+	
+	// 탭 버튼 클릭 이벤트
+	$(".tabType3 .tab button").on("click", function () {
+	
+		// 모든 탭의 active 클래스 제거
+		$(".tabType3 .tab").removeClass("active");
+	
+		// 클릭한 버튼의 부모 요소(li)에 active 클래스 추가
+		$(this).parent().addClass("active");
+	
+		// 기존 버튼들의 title 속성 초기화
+		$(".tabType3 .tab button").removeAttr("title");
+	
+		// 선택된 버튼의 title 속성을 "선택됨"으로 변경
+		$(this).attr("title", "선택됨");
+	
+		// 검색어 초기화
+		$("#searchInput").val("");
+	
+		// 필터 적용 (검색 필터 없이 탭 기준으로만 적용)
+		fn_applyFilters();
+	});
+	
 	// 검색 버튼 클릭 시 실행
 	$("#searchBtn").on("click", function () {
-		searchTable();
+	    fn_search();
 	});
 
-	// Enter 키 입력 시 실행
+	// 실시간 검색 및 Enter 키 이벤트 처리
 	$("#searchInput").on("keyup", function (event) {
+		let keyword = $(this).val().trim();
+	
+		if (keyword.length > 2) {
+			fn_applyFilters({ field: "phone", type: "like", value: keyword });
+		} else {
+			fn_applyFilters(null);
+		}
+	
+		// Enter 키 입력 시 검색 실행
 		if (event.key === "Enter") {
-			searchTable();
+			fn_search();
 		}
 	});
 	
 	
 	
-	// 탭 버튼 클릭 이벤트
-	$(".tabType3 .tab button").on("click", function () {
-		console.log('.tabType3 .tab button');
-		// 모든 탭의 active 클래스 제거
-		$(".tabType3 .tab").removeClass("active");
-
-		// 클릭한 버튼의 부모 요소(li)에 active 클래스 추가
-		$(this).parent().addClass("active");
-		
-		// 기존 버튼들의 title 속성 초기화
-		$(".tabType3 .tab button").removeAttr("title");
-
-		// 선택된 버튼의 title 속성을 "선택됨"으로 변경
-		$(this).attr("title", "선택됨");
-		
-
-		// 클릭한 버튼의 텍스트 가져오기
-		let resultText = $(this).text().trim();
-		fn_tabTable('result', resultText);
-		
+	$('.listClose').on("click", function (){
+		tooltipInit();
+	});
+	
+	$('.grpClose').on("click", function (){
+		$('#grpNm').val('')
 	});
 	
 });
+
+//검색 실행 함수
+function fn_search() {
+	let keyword = $("#searchInput").val().trim();
+	
+	if (keyword.length < 3) {
+		alert("검색어를 3자 이상 입력해주세요.");
+		return;
+	}
+	
+	fn_applyFilters({ field: "phone", type: "like", value: keyword });
+}
+
+/**
+ * @Discription : 튤팁 닫을 때 팝업 초기화
+ */
+function tooltipInit(){
+
+	$tbDtailList.clearFilter();
+	$("#searchInput").val('');
+	$("#initTab").click();
+}
 
 /** 
  * @Discription : 상세결과 팝업 내용 가져오는 로직
@@ -158,35 +203,40 @@
 	
 };
 
-/**
- * @Description: 검색 기능
+
+
+/** 
+ * @Discription : 필터 적용
  */
-function searchTable() {
-	let column = $("#searchColumn").val(); // 선택한 컬럼
-	let keyword = $("#searchInput").val(); // 검색어
-
-	// 검색어가 3자 이상일 때만 필터 적용
-// 	if (keyword.length >= 3) {
-	$tbDtailList.setFilter(column, "like", keyword);
-	fn_setPlaceholder("검색 결과가 없습니다.");
-// 	} else {
-// 		$tbDtailList.clearFilter(); // 검색어가 짧으면 필터 제거
-// 	}
-}
-
-/**
- * @Description: 탭 검색 기능
- */
-function fn_tabTable(column, keyword) {
-
-	if(keyword == '전체'){
-		$tbDtailList.clearFilter();
-	}else{
-		$tbDtailList.setFilter(column, "like", keyword);
+function fn_applyFilters(newFilter) {
+	// 현재 적용된 모든 필터 가져오기
+	let filters = [];
+	
+	// 현재 선택된 탭 값 가져오기
+	let selectedTab = $(".tabType3 .tab.active button").text().trim();
+	
+	// 탭 필터 적용 (탭이 "전체"가 아닐 경우)
+	if (selectedTab !== "전체") {
+	    filters.push({ field: "result", type: "like", value: selectedTab });
 	}
 	
+	// 검색어가 입력된 경우 검색 필터 추가
+	if (newFilter && newFilter.value) {
+	    filters.push(newFilter);
+	}
+	
+	// 필터 적용
+	$tbDtailList.setFilter(filters);
+	
+	// Placeholder 업데이트
 	fn_setPlaceholder("검색 결과가 없습니다.");
+	
+	// 툽팁 하위 버튼 삭제
+	fn_rowBtnSH(selectedTab);
 }
+
+
+
 
 /**
  * @Description: 타블레이서 설명 수정 
@@ -207,21 +257,38 @@
 /**
  * @Description: 필터링된 데이터만 다운로드
  */
-function fn_downloadFilteredExcel() {
-	let filteredData = $tbDtailList.getData("active"); // 필터링된 데이터 가져오기
-	
-	if (filteredData.length === 0) {
-		alert("다운로드할 데이터가 없습니다.");
-		return;
+ function fn_downloadFilteredExcel() {
+		// 현재 날짜 및 시간 가져오기 (YYYYMMDD_HHMMSS 형식)
+		let now = new Date();
+		let timestamp = now.getFullYear() +
+			("0" + (now.getMonth() + 1)).slice(-2) +
+			("0" + now.getDate()).slice(-2) + "_" +
+			("0" + now.getHours()).slice(-2) +
+			("0" + now.getMinutes()).slice(-2) +
+			("0" + now.getSeconds()).slice(-2);
+
+		// 파일명 생성
+		let fileName = "filtered_data_" + timestamp + ".xlsx";
+
+		// 필터링된 데이터 가져오기
+		let filteredData = getFilteredDataByTab();
+
+		if (filteredData.length === 0) {
+			alert("다운로드할 데이터가 없습니다.");
+			return;
+		}
+
+		console.log("엑셀 다운로드 - 필터링된 데이터:", filteredData);
+
+		// 필터링된 데이터만 다운로드 (Tabulator의 기존 데이터를 변경하지 않음)
+		let workbook = XLSX.utils.book_new();
+		let worksheet = XLSX.utils.json_to_sheet(filteredData);
+
+		XLSX.utils.book_append_sheet(workbook, worksheet, "Filtered Data");
+
+		// 엑셀 파일 다운로드
+		XLSX.writeFile(workbook, fileName);
 	}
-
-//     console.log("엑셀 다운로드 - 필터링된 데이터:", filteredData);
-
-	$tbDtailList.download("xlsx", "filtered_data.xlsx", {
-		sheetName: "Filtered Data",
-		data: filteredData // 필터링된 데이터만 다운로드
-	});
-}
 	
 /**
  * @ 예약 취소
@@ -285,75 +352,235 @@
 	}
 	
 }
-	
+
 /**
  * @문자 재전송
  */
-function fnMjMsgReSendAll(msgGroupId
-		, replaceCnt, electionCnt, advertisementCnt
-		) {
-	
-	
+function fnMjMsgReSendAll(msgGroupId) {
+	// 치환 여부 체크
+	var replaceYn = fn_getReplaceChk();
+	// 문자 종류 (일반:N, 광고:A, 선거:C, 관리자:S)
+	var msgKind = $('#msgKind').val();
 	
 	var form = document.reSendAllForm;
 	form.msgResendAllFlag.value = "Y";
 	form.msgResendAllGroupId.value = msgGroupId;
 
-	if (replaceCnt > 0) {
-		if (confirm("특정문구 일괄변환 문자(치환문자)의 경우 문자내용은 재전송할 수 없고 받는 사람 목록만 불러올 수 있습니다.\n받는사람 목록을 불러올까요?")) {
-			// 광고문자
-			form.msgResendAllReplaceYn.value = "Y";
-			if (electionCnt > 0) {
-				form.action="/web/mjon/msgcampain/selectMsgDataView.do";
-			}
-			else {
-				if (advertisementCnt > 0) {
-					// 광고문자
-					form.msgResendAllAdvertiseYn.value = "Y";
-					form.action="/web/mjon/msgdata/excel/selectMsgExcelDataView.do";
-				}
-				else
-				{
-					form.action="/web/mjon/msgdata/selectMsgDataView.do";
-				}
-			}
-			form.submit();					
-		}
+	// 치환문자 포함 여부에 따른 분기
+	var msg = "";
+	if (replaceYn) {
+		msg = "문자 내용에 포함된 특정 문구가 변환되지 않은 상태([*이름*],[*1*] 등)로 표기됩니다.\n문자내용, 받는 사람 목록 확인 후 발송해주세요";
+// 		form.msgResendAllReplaceYn.value = "Y";
+	} else {
+		var title = (msgKind == 'C') ? "선거문자발송" : "문자발송";
+		msg = title + " 화면으로 이동합니다.\n문자내용, 받는 사람 목록 확인 후 발송해주세요.";
 	}
-	else {
-		var title = "";
-		if (electionCnt > 0) {
-			title = "선거문자발송";
-		}
-		else {
-			title = "문자발송";
-		}
-		
-	    if (confirm(title + " 화면으로 이동합니다.\n문자내용, 받는 사람 목록 확인후 발송해주세요.")) {
-			if (electionCnt > 0) {
-				form.action="/web/mjon/msgcampain/selectMsgDataView.do";
-			}
-			else {
-				if (advertisementCnt > 0) {
-					// 광고문자
-					form.msgResendAllAdvertiseYn.value = "Y";
-					form.action="/web/mjon/msgdata/excel/selectMsgExcelDataView.do";
-				}
-				else
-				{
-					form.action="/web/mjon/msgdata/selectMsgDataView.do";
-				}
-			}
-			form.submit();		
-	    }
+	
+	if (!confirm(msg)) {
+		return;
+	}
+
+	// msgKind에 따른 action 설정
+	form.action = getMsgActionUrl(msgKind);
+	if (msgKind === 'A') {
+		form.msgResendAllAdvertiseYn.value = "Y"; // 광고문자 설정
+	}
+	
+	form.submit();
+}
+
+function fn_getReplaceChk(){
+	// 체크할 패턴 목록
+	var patterns = [/\[\*이름\*\]/, /\[\*1\*\]/, /\[\*2\*\]/, /\[\*3\*\]/, /\[\*4\*\]/];
+
+	// 대상 요소의 텍스트 가져오기
+	var text = $("#smsTxt").text();
+
+	// 패턴이 포함되어 있는지 확인
+	var found = patterns.some(function(pattern) {
+		return pattern.test(text);
+	});
+	
+	return found;
+}
+
+/**
+ * msgKind 값에 따른 action URL 반환
+ */
+function getMsgActionUrl(msgKind) {
+	switch (msgKind) {
+		case 'C':
+			return "/web/mjon/msgcampain/selectMsgDataView.do";
+		case 'A':
+			return "/web/mjon/msgdata/excel/selectMsgExcelDataView.do";
+		default:
+			return "/web/mjon/msgdata/selectMsgDataView.do";
+	}
+}
+
+function fn_rowBtnSH(tabText){
+	var $addReg = $('#addReg');
+	var $addRemove = $('#addRemove');
+	// addReg 주소록 등록
+	// addRemove 주소록 삭제
+	if(tabText == '전체'
+		|| tabText == '성공'){
+		$addReg.show();
+		$addRemove.hide();
+	}else if(tabText == '대기'){
+		$addReg.hide();
+		$addRemove.hide();
+	}else if(tabText == '실패' ){
+		$addReg.hide();
+		$addRemove.show();
 	}
 }
 
 
+function fnAddAddrNo(){
+	
+	
+	let url = "/web/mjon/addr/insertByAddrGrpDataAndAddrDataAjax.do";
+	
+
+    // 필터링된 데이터 가져오기 (탭 필터 적용)
+    let filteredData = getFilteredDataByTab();
+
+	console.log('filteredData : ', filteredData)
+	
+    // phone 필드 데이터만 추출
+    let addrPhones = filteredData.map(row => row.phone);
+	console.log('addrPhones : ', addrPhones)
+
+	if(addrPhones.length < 1){
+		alert('해당 탭에 데이터가 없습니다.');
+		return false;
+	}
+	
+	// 주소록 그룹명 가져오기
+	let addrGrpNm = $('#grpNm').val();
+	
+	// 데이터 객체 생성
+	let data = {
+		addrPhones: addrPhones,
+		addrGrpNm: addrGrpNm
+	};
+
+	if(!confirm("연락처 정보를 주소록에 등록 하시겠습니까?")){
+		return false;
+	}
+	
+
+	$.ajax({
+		type: "POST",
+		url: url,
+		data: JSON.stringify(data),
+		dataType: "json",
+		contentType: "application/json",
+		async: false,
+		processData: false,
+		success: function(data) {
+			
+			if(data.status == 'BAD_REQUEST'){
+				alert(data.message);
+				return false;
+			}
+			
+
+			// 성공 메세지
+			alert(data.message);
+			// 그룹등록 팝업 닫기
+			$('.grpClose').click();
+			
+		},
+		error: function(error) {
+			alert("오류가 발생하였습니다.")
+			console.error("에러 발생:", error);
+		}
+	});
+}
+
+function fnDelAddrNo(){
+	
+	
+	let url = "/web/mjon/addr/deleteAddrNoDataAjax.do";
+	
+
+    // 필터링된 데이터 가져오기 (탭 필터 적용)
+    let filteredData = getFilteredDataByTab();
+
+	console.log('filteredData : ', filteredData)
+	
+    // phone 필드 데이터만 추출
+    let addrPhones = filteredData.map(row => row.phone);
+	console.log('addrPhones : ', addrPhones)
+
+	if(addrPhones.length < 1){
+		alert('주소록에 살제할 연락처가 없습니다.');
+		return false;
+	}
+	
+	// 데이터 객체 생성
+	let data = {
+		addrPhones: addrPhones
+	};
+	
+	let selectedTab = $(".tabType3 .tab.active button").text().trim();
+	if(!confirm(selectedTab+" 명단의 번호를 주소록에서 삭제하시겠습니까?")){
+		return false;
+	}
+	
+	$.ajax({
+		type: "POST",
+		url: url,
+		data: JSON.stringify(data),
+		dataType: "json",
+		contentType: "application/json",
+		async: false,
+		processData: false,
+		success: function(data) {
+			
+			if(data.status == 'BAD_REQUEST'){
+				alert(data.message);
+				return false;
+			}
+			
+
+			// 성공 메세지
+			alert(data.message);
+			
+		},
+		error: function(error) {
+			alert("오류가 발생하였습니다.")
+			console.error("에러 발생:", error);
+		}
+	});
+}
+/**
+ * @description 현재 선택된 탭(`result` 필터) 기준으로 데이터를 필터링
+ * @returns {Array} 필터링된 데이터 리스트
+ */
+function getFilteredDataByTab() {
+	// 현재 적용된 모든 필터 가져오기
+	let filters = $tbDtailList.getFilters();
+	
+	// 현재 모든 데이터 가져오기 (전체 데이터에서 필터 적용)
+	let allData = $tbDtailList.getData();
+	
+	// 현재 적용된 필터에서 "result" 필터만 찾기
+	let tabFilter = filters.find(filter => filter.field === "result");
+	
+	// 탭 필터 적용하여 데이터 필터링 (수신번호 필터는 무시)
+	return allData.filter(row => tabFilter ? row.result.includes(tabFilter.value) : true);
+}
+
 </script>
 <div class="inner">
-	<input id="msgGroupId" name="msgGroupId" type="hidden" value="${result.msgGroupId}"/>
-		<!-- send top -->
+	<!-- js 참고용 hidden -->
+	<input id="msgGroupId" type="hidden" value="${result.msgGroupId}"/>
+	<input id="msgKind" type="hidden" value="${result.msgKind}"/> <!-- 문자종류(일반:N, 광고:A, 선거:C, 관리자:S) -->
+	
+	<!-- send top -->
 	<div class="send_top">
 		<!-- 결제관리 - 요금 사용내역 -->
 		<div class="rev_admin_cont serv_content current">
@@ -421,7 +648,8 @@
 														<div class="di_info">
 															<button class="di">분할</button>
 															<div class="di_hover_layer">
-																<strong>100,000건</strong>씩 <strong>35분</strong> 간격
+																<strong>${result.divideText }</strong>
+<!-- 																<strong>100,000건</strong>씩 <strong>35분</strong> 간격 -->
 															</div>
 														</div>
 													</c:if>
@@ -562,7 +790,7 @@
 										<c:if test="${result.msgKind eq 'A' }" >
 											<p class="ad_tit">(광고)</p>
 										</c:if>
-										<p class="none_txt"><c:out value="${result.smsTxt }" /></p>
+										<p class="none_txt" id="smsTxt"><c:out value="${result.smsTxt }" /></p>
 										<p class="realtime"></p>
 										<c:if test="${result.msgKind eq 'A' }" >
 											<p class="deny_receipt">무료 거부 080-0000-0000</p>
@@ -596,31 +824,28 @@
 		<div class="popup-com ad_layer rev_popup04" tabindex="0" data-tooltip-con="rev_popup04" data-focus="rev_popup04" data-focus-prev="rev_popup04-close" style="width:530px;">
 			<div class="popup_heading">
 				<p>발송대상 리스트</p>
-				<button type="button" class="tooltip-close" data-focus="rev_popup04-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+				<button type="button" class="tooltip-close listClose" data-focus="rev_popup04-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 			</div>
 			<div class="layer_in">
 
-				<div class="gorup_join_cont" style="margin:-15px 0 0 0;">
-					<div class="group_input">
-						<div class="input_left">발신번호</div>
-						<div class="input_right type1"><c:out value="${result.callFrom }" /></div>
-					</div>
-				</div>
+<!-- 				<div class="gorup_join_cont" style="margin:-15px 0 0 0;"> -->
+<!-- 					<div class="group_input"> -->
+<!-- 						<div class="input_left">발신번호</div> -->
+<%-- 						<div class="input_right type1"><c:out value="${result.callFrom }" /></div> --%>
+<!-- 					</div> -->
+<!-- 				</div> -->
 
-				<div class="popup_search_type1">								
+				<div class="popup_search_type2">							
 					<label for="" class="label">검색종류 선택</label>
-					<select id="searchColumn" class="selType1 select_btn">
-						<option value="phone">수신번호</option>
-						<option value="result">상세결과</option>
-					</select>
+					<div class="title">수신번호</div>
 					<label for="" class="label">검색어입력</label>
-					<input type="text" class="send_text" id="searchInput" name="" value="" placeholder="3자 이상 입력하세요." onfocus="this.placeholder=''" onblur="this.placeholder='3자 이상 입력하세요.'">
+					<input type="text" class="send_text" id="searchInput" placeholder="3자 이상 입력하세요." onfocus="this.placeholder=''" onblur="this.placeholder='3자 이상 입력하세요.'">
 					<button type="button" id="searchBtn" class="btnType btnType2" style="width:63px; margin:0;">검색</button>
 				</div>
 
 				<div class="list_tab_wrap2 type4">
 					<ul class="tabType3" id="tabType" name="tabType">
-						<li class="tab active"><button type="button" title="선택됨">전체</button></li>
+						<li class="tab active"><button type="button" id="initTab" title="선택됨">전체</button></li>
 						<li class="tab"><button type="button">대기</button></li>
 						<li class="tab"><button type="button">성공</button></li>
 						<li class="tab"><button type="button">실패</button></li>
@@ -634,18 +859,44 @@
 				<div class="table_btn clearfix">
 					<div class="table_btn_left">
 						<button type="button" onclick="fn_downloadFilteredExcel()" class="excel_btn btnType"><i class="downroad"></i>엑셀 다운로드</button>
-						<button type="button" data-tooltip="rev_popup02" class="btnType btnType14"><i class="add_img"></i>주소록 등록</button>
-						<button type="button" class="btnType btnType15"><i class="remove_img"></i>주소록에서 번호 삭제</button>
+						<button type="button" id="addReg" data-tooltip="rev_popup02" class="btnType btnType14"><i class="add_img"></i>주소록 등록</button>
+						<button type="button" onclick="fnDelAddrNo()" id="addRemove" class="btnType btnType15"><i class="remove_img"></i>주소록에서 번호 삭제</button>
 					</div>
 				</div>
 			</div>
 			<div class="popup_btn_wrap2" style="margin: -40px auto 30px auto;">
-				<button type="button" class="tooltip-close" data-focus="adr_popup01-close"  data-focus-next="popup02">닫기</button>                      
+				<button type="button" class="tooltip-close listClose" data-focus="adr_popup01-close"  data-focus-next="popup02">닫기</button>                      
 			</div>
 
 		</div>
 	</div>
 	<!-- //발송대상 리스트 안내 팝업 -->
+	
+	<div class="tooltip-wrap">
+		<div class="popup-com adr_layer rev_popup02" tabindex="0" data-tooltip-con="rev_popup02" data-focus="rev_popup02" data-focus-prev="rev_popup02-close" style="width: 500px;">
+			<div class="popup_heading">
+				<p>그룹등록</p>
+				<button type="button" class="tooltip-close grpClose" data-focus="rev_popup02-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+			</div>
+			<div class="layer_in">
+				<div class="gorup_join_cont">
+					<p class="adr_pop_title">선택된 발송내역 전화번호를 그룹으로 등록합니다.</p>
+					<div class="group_input" style="margin-top: 0;">
+						<div class="input_left">그룹이름</div>
+						<div class="input_right">
+							<label for="grpNm" class="label">새 그룹명 입력</label>
+							<input type="text" id="grpNm" name="grpNm" placeholder="새 그룹명 입력" onfocus="this.placeholder=''" onblur="this.placeholder='새 그룹명 입력'" class="inputLight">
+						</div>
+					</div>
+					<div class="popup_btn_wrap2">
+						<button type="button" onclick="javascript:fnAddAddrNo(); return false;">저장</button>
+						<button type="button" class="tooltip-close grpClose" data-focus="rev_popup02-close" data-focus-next="rev_popup02">취소</button>                      
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+	
 	
 	<!-- 이전 리스트 상태(검색조건, 페이징) 그대로 가기 위한 form -->
 	<form id="goList" name="goList" method="post" action="/web/mjon/msgsent/selectMsgSentView.do">
@@ -658,6 +909,7 @@
 		<input type="hidden" name="searchEndDate" value="<c:out value='${searchVO.searchEndDate }' />" />
 		<input type="hidden" name="searchCondition" value="<c:out value='${searchVO.searchCondition }' />" />
 		<input type="hidden" name="searchKeyword" value="<c:out value='${searchVO.searchKeyword }' />" />
+		<input type="hidden" name="pageUnit" value="<c:out value='${searchVO.pageUnit }' />" />
 	</form>
 	
 	<!-- 예약 취소 -->
src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgsent/MsgSentView.jsp
@@ -844,6 +844,7 @@
 				<form id="searchForm" name="searchForm" method="post">
 <!-- 					<input type="hidden" id="pageIndex" name="pageIndex" value="1"/> -->
 					<input type="hidden" id="pageIndex" name="pageIndex" value="<c:out value="${searchVO.pageIndex}" />" />
+					<input type="hidden" id="pageUnit" name="pageUnit" value="<c:out value="${searchVO.pageUnit}" />" />
 					<input type="hidden" id="msgGroupIdList" name="msgGroupIdList" value=""/>
 					<input type="hidden" id="msgGroupId" name="msgGroupId" value=""/>
 					<input type="hidden" name="searchSortCnd" value="<c:out value="${searchVO.searchSortCnd}" />" />
Add a comment
List