diff --git a/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java b/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java index 2f99a2b..3f8bf89 100644 --- a/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java +++ b/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java @@ -12,6 +12,7 @@ import org.springframework.web.servlet.ModelAndView; import com.madeu.constants.Constants; import com.madeu.crm.kiosk.dto.ConsentFormDTO; +import com.madeu.crm.kiosk.dto.UserDTO; import com.madeu.crm.kiosk.service.KioskService; import com.madeu.init.ManagerDraftAction; import com.madeu.service.web.webloghistory.WebLogHistoryService; @@ -205,61 +206,32 @@ public class KioskController extends ManagerDraftAction { * 고객 등록 */ @PostMapping("/kiosk/putUser.do") - public HashMap putUser(HttpServletRequest request, HttpServletResponse response) { + public HashMap putUser(@RequestBody UserDTO dto, HttpServletRequest request) { log.debug("KioskController putUser START"); - - HashMap paramMap = HttpUtil.getParameterMap(request); - HashMap map = new HashMap(); - + + HashMap resultMap = new HashMap<>(); StringBuffer errorMsg = new StringBuffer(); + try { - paramMap.put("loginMemberId", "customer"); - paramMap.put("regId", "customer"); - paramMap.put("modId", "customer"); - map = kioskService.putUser(paramMap); + // DTO에 직접 필요한 공통 정보 세팅 + dto.setRegId("customer"); + dto.setModId("customer"); + dto.setMuUserId("U"); + + // DTO를 직접 서비스로 전달 (Service 인터페이스도 UserDTO를 받도록 수정 필요) + resultMap = kioskService.putUser(dto); + } catch (Exception e) { - e.printStackTrace(); - map.put("msgCode", Constants.FAIL); - map.put("msgDesc", "서버 오류가 발생했습니다."); + log.error("Error inserting user", e); + resultMap.put("msgCode", Constants.FAIL); + resultMap.put("msgDesc", "서버 오류가 발생했습니다."); errorMsg.append(e.getMessage()); } finally { - if (Constants.OK != map.get("msgCode")) { - map.put("msgCode", Constants.FAIL); - map.put("success", false); - if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) { - map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다."); - } - } - - // 로그 기록 - try { - HashMap visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request); - HashMap insertMap = new HashMap(); - - insertMap.put("url", "/kiosk/putUser.do"); - insertMap.put("func", "putUser"); - insertMap.put("funcName", "고객 등록"); - insertMap.put("service", "kioskService"); - insertMap.put("serviceName", "고객 등록"); - insertMap.put("requestValue", String.valueOf(paramMap)); - insertMap.put("responseValue", String.valueOf(map)); - insertMap.put("tId", map.get("tId")); - if (("").equals(String.valueOf(errorMsg)) || null == (String.valueOf(errorMsg)) - || 0 == String.valueOf(errorMsg).length()) { - insertMap.put("resultCode", "SUCCESS"); - } else { - insertMap.put("resultCode", "ERROR"); - } - insertMap.put("resultMsg", String.valueOf(errorMsg)); - insertMap.put("muMemberId", "customer"); - - webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap); - } catch (Exception e) { - e.printStackTrace(); - } + // 결과 처리 및 로그 기록 (기존과 동일) + processFinalResult(resultMap, dto, errorMsg, request); } - log.debug("KioskController putUser END"); - return map; + + return resultMap; } /** @@ -437,4 +409,55 @@ public class KioskController extends ManagerDraftAction { log.debug("KioskController getReserveUserOptionList END"); return map; } + /** + * 결과 처리 및 공통 응답 포맷 정리, 로그 기록 준비 메서드 + */ + private void processFinalResult(HashMap resultMap, UserDTO dto, StringBuffer errorMsg, HttpServletRequest request) { + + // 1. 결과 코드에 따른 성공 여부 및 기본 메시지 설정 + if (!Constants.OK.equals(resultMap.get("msgCode"))) { + resultMap.put("msgCode", Constants.FAIL); + resultMap.put("success", false); + if (null == resultMap.get("msgDesc") || "".equals(resultMap.get("msgDesc"))) { + resultMap.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다."); + } + } else { + resultMap.put("success", true); + if (null == resultMap.get("msgDesc") || "".equals(resultMap.get("msgDesc"))) { + resultMap.put("msgDesc", "성공적으로 등록되었습니다."); + } + } + + // 2. 웹 로그 기록 (비동기 또는 서비스 호출) + try { + HashMap visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request); + HashMap insertMap = new HashMap(); + + insertMap.put("url", "/kiosk/putUser.do"); + insertMap.put("func", "putUser"); + insertMap.put("funcName", "고객 등록"); + insertMap.put("service", "kioskService"); + insertMap.put("serviceName", "고객 등록"); + + // DTO의 toString()을 사용하여 요청 파라미터 기록 + insertMap.put("requestValue", dto != null ? dto.toString() : "NULL"); + insertMap.put("responseValue", String.valueOf(resultMap)); + insertMap.put("tId", resultMap.get("tId")); + + if (errorMsg.length() == 0) { + insertMap.put("resultCode", "SUCCESS"); + } else { + insertMap.put("resultCode", "ERROR"); + } + insertMap.put("resultMsg", errorMsg.toString()); + insertMap.put("muMemberId", "customer"); + + // 로그 서비스 호출 + webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap); + + } catch (Exception e) { + log.error("Log History Insertion Error: ", e); + // 로그 기록 실패가 비즈니스 로직에 영향을 주지 않도록 내부에서 예외 처리 + } + } } \ No newline at end of file diff --git a/src/main/java/com/madeu/crm/kiosk/dto/UserDTO.java b/src/main/java/com/madeu/crm/kiosk/dto/UserDTO.java new file mode 100644 index 0000000..f7cb4ce --- /dev/null +++ b/src/main/java/com/madeu/crm/kiosk/dto/UserDTO.java @@ -0,0 +1,48 @@ +package com.madeu.crm.kiosk.dto; +import java.util.Map; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class UserDTO { + // MyBatis mapping 필드 + private String muUserId; // 유저 ID 생성용 접두어 + private String userName; // 고객명 + private String nationality; // 국적 아이템 코드 + private String nationalityCode; // 국적 카테고리 코드 (C202404110001) + private String birthday; // 생년월일 (YYYYMMDD) + private String gender; // 성별 (M/F) + private String userRrn1; // 주민번호 앞자리 + private String userRrn2; // 주민번호 뒷자리 + private String userPno; // 여권번호 + private String userArc1; // 외국인등록번호 앞자리 (필요시) + private String userArc2; // 외국인등록번호 뒷자리 (필요시) + private String userType; // 고객타입 아이템 코드 + private String userTypeCode; // 고객타입 카테고리 코드 + private String phoneNumber; // 연락처 + private String phoneNumber2; // 보조 연락처 + private String muGroupId; // 그룹 ID + private String channel; // 방문경로 아이템 코드 + private String channelCode; // 방문경로 카테고리 코드 (C202404110003) + private String introUserId; // 추천인 고객 ID (텍스트 입력값) + private String introMemberId; // 추천직원 ID + private String etc; // 특이사항 + private String memo; // 메모 + private String smsYn; // SMS 수신여부 (Y/N) + private String refusePhotoYn; // 사진촬영동의여부 (Y/N) + private String remark; // 비고 + private String regId; // 등록자 ID + private String modId; // 수정자 ID + private String address; + private String addressDtl; + private String email; + private String introName; + private Map userNumber; + + // 서명 데이터 (Base64) + private String signatureData; +} \ No newline at end of file diff --git a/src/main/java/com/madeu/crm/kiosk/map/KioskMAP.java b/src/main/java/com/madeu/crm/kiosk/map/KioskMAP.java index e13002b..fb0072d 100644 --- a/src/main/java/com/madeu/crm/kiosk/map/KioskMAP.java +++ b/src/main/java/com/madeu/crm/kiosk/map/KioskMAP.java @@ -2,8 +2,11 @@ package com.madeu.crm.kiosk.map; import java.util.List; import java.util.Map; + import org.apache.ibatis.annotations.Mapper; + import com.madeu.crm.kiosk.dto.ConsentFormDTO; +import com.madeu.crm.kiosk.dto.UserDTO; @Mapper public interface KioskMAP { @@ -42,4 +45,6 @@ public interface KioskMAP { // 예약 고객 옵션 리스트 List> getReserveUserOptionList(Map paramMap); + + int insertUser(UserDTO dto); } \ No newline at end of file diff --git a/src/main/java/com/madeu/crm/kiosk/service/KioskService.java b/src/main/java/com/madeu/crm/kiosk/service/KioskService.java index faeb174..0ecf10d 100644 --- a/src/main/java/com/madeu/crm/kiosk/service/KioskService.java +++ b/src/main/java/com/madeu/crm/kiosk/service/KioskService.java @@ -1,8 +1,8 @@ package com.madeu.crm.kiosk.service; -import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -12,7 +12,9 @@ import org.springframework.stereotype.Service; import com.madeu.constants.Constants; import com.madeu.crm.kiosk.dto.ConsentFormDTO; +import com.madeu.crm.kiosk.dto.UserDTO; import com.madeu.crm.kiosk.map.KioskMAP; +import com.madeu.dao.web.webuser.WebUserSqlMapDAO; import com.madeu.util.ValidationCheckUtil; import lombok.extern.slf4j.Slf4j; @@ -23,7 +25,9 @@ public class KioskService { @Autowired private KioskMAP kioskMAP; // 인터페이스 기반 Mapper 주입 - + + @Autowired + private WebUserSqlMapDAO webUserSqlMapDAO; /** * 진료 리스트 조회 (option) */ @@ -95,33 +99,68 @@ public class KioskService { /** * 고객 등록 */ - public HashMap putUser(HashMap paramMap) throws Exception { + public HashMap putUser(UserDTO dto) throws Exception { HashMap map = new HashMap(); try { boolean check = true; String tId = String.valueOf(System.currentTimeMillis()); - // 유효성 검사 로직 - if(!ValidationCheckUtil.emptyCheck(String.valueOf(paramMap.get("nationality")))) { + // 1. DTO 필드를 이용한 유효성 검사 로직 + if(!ValidationCheckUtil.emptyCheck(dto.getNationality())) { check = false; map.put("msgCode", Constants.FAIL); map.put("msgDesc","국적 정보가 없습니다."); - } else if(!ValidationCheckUtil.emptyCheck(String.valueOf(paramMap.get("userName")))) { + } else if(!ValidationCheckUtil.emptyCheck(dto.getUserName())) { check = false; map.put("msgCode", Constants.FAIL); map.put("msgDesc","고객 성함 정보가 없습니다."); - } else if(!ValidationCheckUtil.nickNameCheck(String.valueOf(paramMap.get("userName")))) { + } else if(!ValidationCheckUtil.nickNameCheck(dto.getUserName())) { check = false; map.put("msgCode", Constants.FAIL); map.put("msgDesc","유효하지 않은 이름 형식입니다."); - } else if(!ValidationCheckUtil.phoneNumberCheck(String.valueOf(paramMap.get("phoneNumber")))) { + } else if(!ValidationCheckUtil.phoneNumberCheck(dto.getPhoneNumber())) { check = false; map.put("msgCode", Constants.FAIL); map.put("msgDesc","유효하지 않은 전화번호 형식입니다."); } if(check) { - String muUserId = ("U").concat(String.valueOf(System.currentTimeMillis())); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - paramMap.put("muUserId", muUserId); - paramMap.put("tId", tId); - paramMap.put("tDate", sdf.format(Calendar.getInstance().getTime())); + String muUserId = ("U").concat(String.valueOf(System.currentTimeMillis())); + // 2. DTO에 필요한 추가 정보 세팅 (Setter 활용) + // muUserId는 매퍼의 selectKey에서 생성되지만, 접두어 등이 필요하다면 세팅 + dto.setMuUserId(muUserId); - kioskMAP.putUser(paramMap); + // tId 등 로그용 데이터가 DTO에 필드로 정의되어 있다면 세팅, + // 없다면 결과 map에만 담아서 리턴 + map.put("tId", tId); + // 3. MyBatis 매퍼 호출 (DTO 객체 자체를 전달) + kioskMAP.insertUser(dto); + //A: 개인정보수집및이용안내 + //B: 메이드유시술동의서 + //C: 이용약관동의서 + //G: 사진촬영거부동의서 + String[] types = {"A","B","C","G"}; + HashMap consentFormMap = new HashMap<>(); + for (String type : types) { + String muUserAgreementId = ("UA").concat(String.valueOf(System.currentTimeMillis())); + + consentFormMap.put("muUserAgreementId", muUserAgreementId); + + + consentFormMap.put("muUserId", dto.getUserNumber().get("id")); + consentFormMap.put("userName", dto.getUserName()); + consentFormMap.put("type", type); + consentFormMap.put("renewalCycle", 12); + + LocalDateTime oneYearLater = LocalDateTime.now().plusYears(1); + + // MariaDB DATETIME 표준 형식 (YYYY-MM-DD HH:mm:ss) + String expirationDate = oneYearLater.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + consentFormMap.put("expirationDate", expirationDate); + consentFormMap.put("cdnUrl", "-"); + consentFormMap.put("filePath", "-"); + consentFormMap.put("fileName", "-"); + consentFormMap.put("originalFileName", "-"); + consentFormMap.put("loginMemberId", "kiosk"); + + webUserSqlMapDAO.insertUserAgreement(consentFormMap); + } + + map.put("msgCode", Constants.OK); map.put("msgDesc", "등록되었습니다."); } diff --git a/src/main/resources/mappers/crm/kiosk/KioskMAP.xml b/src/main/resources/mappers/crm/kiosk/KioskMAP.xml index 681cd83..53637df 100644 --- a/src/main/resources/mappers/crm/kiosk/KioskMAP.xml +++ b/src/main/resources/mappers/crm/kiosk/KioskMAP.xml @@ -148,7 +148,67 @@ - + + /** KioskMAP.insertUser **/ + + SELECT USER.id AS id + ,USER.NUMBER AS number + ,MCIN.MU_CATEGORY_ITEM_ID AS nationality + ,MCIT.MU_CATEGORY_ITEM_ID AS userType + ,MCIC.MU_CATEGORY_ITEM_ID AS channel + FROM (SELECT CONCAT('U', DATE_FORMAT(NOW(), '%Y%m%d'), '-', LPAD(IFNULL(MAX(SUBSTRING(USER_NUMBER, 11)) + 1, 1), 3, '0')) AS NUMBER + ,CONCAT(#{muUserId}, LPAD((SELECT NEXTVAL(MU_USER_SEQ)), 11, 0)) AS id + FROM MU_USER AS MU + WHERE USER_NUMBER LIKE CONCAT('U', DATE_FORMAT(NOW(), '%Y%m%d'), '%') + ) AS USER + LEFT JOIN MU_CATEGORY_ITEM AS MCIN + ON MCIN.CATEGORY_CODE = #{nationalityCode} + AND MCIN.CATEGORY_ITEM_CODE = #{nationality} + LEFT JOIN MU_CATEGORY_ITEM AS MCIT + ON MCIT.CATEGORY_CODE = #{userTypeCode} + AND MCIT.CATEGORY_ITEM_CODE = #{userType} + LEFT JOIN MU_CATEGORY_ITEM AS MCIC + ON MCIC.CATEGORY_CODE = #{channelCode} + AND MCIC.CATEGORY_ITEM_CODE = #{channel} + + + INSERT INTO MU_USER ( + MU_USER_ID, USER_NUMBER, USER_NAME, NATIONALITY, BIRTHDAY, GENDER, + USER_RRN1, USER_RRN2, USER_PNO, USER_TYPE, PHONE_NUMBER, + CHANNEL, INTRO_USER_ID, ETC, SMS_YN, REFUSE_PHOTO_YN, + WRITE_DATE, WRITE_TIME, CUD_FLAG, USE_YN, REG_ID, REG_DATE, MOD_ID, MOD_DATE, + ADDRESS, ADDRESS_DETAILS, EMAIL, INTRO_NAME + ) VALUES ( + #{userNumber.id} + ,#{userNumber.number} + ,#{userName} + ,#{userNumber.nationality} + ,#{birthday} + ,#{gender} + ,#{userRrn1} + ,#{userRrn2} + ,#{userPno} + ,#{userNumber.userType} + ,#{phoneNumber} + ,#{userNumber.channel} + ,#{introUserId} + ,#{etc} + ,#{smsYn} + ,#{refusePhotoYn} + ,NOW() + ,NOW() + ,'C' + ,'Y' + ,#{regId} + ,NOW() + ,#{modId} + ,NOW() + ,#{address} + ,#{addressDtl} + ,#{email} + ,#{introName} + ) + /** KioskSql.insertUser **/ diff --git a/src/main/resources/static/css/kiosk/new-patient.css b/src/main/resources/static/css/kiosk/new-patient.css index a722bad..06ea9a4 100644 --- a/src/main/resources/static/css/kiosk/new-patient.css +++ b/src/main/resources/static/css/kiosk/new-patient.css @@ -753,4 +753,24 @@ button { border-radius: 8px; font-weight: 600; cursor: pointer; +} + +.section-title { + display: flex; + align-items: center; + justify-content:间-between; /* 제목은 왼쪽, 메시지는 오른쪽 혹은 적절한 간격 */ +} + +.validation-msg { + font-size: 0.8rem; + color: #ff4d4f; /* 경고 빨간색 */ + font-weight: normal; + margin-left: 10px; + display: inline-block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateX(-5px); } + to { opacity: 1; transform: translateX(0); } } \ No newline at end of file diff --git a/src/main/resources/static/js/kiosk/new-patient.js b/src/main/resources/static/js/kiosk/new-patient.js index 009fc37..1eb4a8c 100644 --- a/src/main/resources/static/js/kiosk/new-patient.js +++ b/src/main/resources/static/js/kiosk/new-patient.js @@ -6,7 +6,7 @@ this.choicesInstances = new Map(); this.data = { nationality: '', - nationalityCode: '', // 선택된 국적 코드 저장용 + nationalityCode: '', visitPath: '' }; this.isInitialized = false; @@ -25,6 +25,7 @@ this.bindEvents(); this.isInitialized = true; + $('input').attr('autocomplete', 'off'); } validateDependencies() { return !!($ && window.Choices); } @@ -47,16 +48,15 @@ if (nationalityInstance) { const element = nationalityInstance.passedElement.element; element.addEventListener('change', (event) => { - this.data.nationalityCode = event.detail.value; // 국적 코드 업데이트 + this.data.nationalityCode = event.detail.value; this.updateUIByNationality(event.detail.value); }); } - $('#modalAddress, #btnSearchAddress').on('click', () => { - this.openAddrSearch(); - }); + $('#address, #btnSearchAddress').on('click', () => { + this.openAddrSearch(); + }); - // 체크박스 직접 클릭 방지 및 안내 문구 출력 const consentIds = ['#agreePrivacy', '#agreeProcedure', '#agreeTerms', '#refusePhoto']; $(consentIds.join(', ')).on('click', (e) => { if (!this.allowSystemCheck) { @@ -64,8 +64,33 @@ alert('동의서 보기 버튼을 클릭하여 내용을 확인하신 후 [동의함] 버튼을 눌러주세요.'); } }); - - + $('#btnFillTestData').on('click', () => { + // 1. 기본 텍스트 정보 + $('input[name="modalUserName"]').val('홍길동'); + $('input[name="modalUserRrn1"]').val('950101'); + $('input[name="modalUserRrn2"]').val('1234567').trigger('input'); // input 트리거로 생년월일/성별 로직 실행 + $('input[name="modalPhoneNumber"]').val('01012345678'); + $('input[name="address"]').val('서울특별시 강남구 테헤란로 123'); + $('input[name="addressDtl"]').val('메이드유 빌딩 5층'); + $('#email').val('test@madeu.com'); + $('input[name="introName"]').val('관리자'); + $('textarea[name="modalEtc"]').val('특이사항 없음 (테스트 데이터)'); + + // 2. 방문경로 (Choices.js 인스턴스 제어) + const channelInstance = this.choicesInstances.get('channel'); + if (channelInstance) { + const firstVal = channelInstance.config.choices[1]?.value; // 첫 번째 옵션 선택 + if (firstVal) channelInstance.setChoiceByValue(firstVal); + } + + // 3. 동의서 항목 강제 체크 + // 시스템 체크 허용 플래그를 일시적으로 켜서 click 방지 로직을 우회합니다. + this.allowSystemCheck = true; + $('#agreePrivacy, #agreeProcedure, #agreeTerms, #refusePhoto').prop('checked', true); + this.allowSystemCheck = false; + + //alert('테스트 데이터가 입력되었습니다.'); + }); } updateUIByNationality(selectedCode) { @@ -122,7 +147,7 @@ instance.setChoices(choices, 'value', 'label', true); if (choicesKey === 'nationality') { instance.setChoiceByValue(this.KOREA_CODE); - this.data.nationalityCode = this.KOREA_CODE; // 초기 국적 설정 + this.data.nationalityCode = this.KOREA_CODE; this.updateUIByNationality(this.KOREA_CODE); } } @@ -130,388 +155,256 @@ }); } - openAddrSearch() { - // 1. 다이얼로그를 담을 컨테이너 생성 (없으면 생성) - let $addrDialog = $('#addrSearchDialog'); - if ($addrDialog.length === 0) { - $addrDialog = $('').appendTo('body'); - } + openAddrSearch() { + let $addrDialog = $('#addrSearchDialog'); + if ($addrDialog.length === 0) { + $addrDialog = $('').appendTo('body'); + } - // 2. jQuery UI Dialog 초기화 - $addrDialog.dialog({ - modal: true, - title: '주소 검색', - width: 500, - height: 500, - resizable: false, - draggable: true, - open: function() { - const guideElement = document.getElementById('addrSearchDialog'); - // 3. daum.Postcode를 해당 div 내(embed)에 실행 - new daum.Postcode({ - oncomplete: (data) => { - let addr = data.userSelectedType === 'R' ? data.roadAddress : data.jibunAddress; - document.getElementById("modalAddress").value = addr; - document.getElementById("modalAddressDetail").focus(); - - // 선택 완료 후 다이얼로그 닫기 - $addrDialog.dialog('close'); - }, - width: '100%', - height: '100%' - }).embed(guideElement); - }, - close: function() { - $(this).dialog('destroy').empty(); - } - }); + $addrDialog.dialog({ + modal: true, + title: '주소 검색', + width: 500, + height: 500, + resizable: false, + draggable: true, + open: function() { + const guideElement = document.getElementById('addrSearchDialog'); + new daum.Postcode({ + oncomplete: (data) => { + let addr = data.userSelectedType === 'R' ? data.roadAddress : data.jibunAddress; + document.getElementById("address").value = addr; + document.getElementById("addressDtl").focus(); + $addrDialog.dialog('close'); + }, + width: '100%', + height: '100%' + }).embed(guideElement); + }, + close: function() { + $(this).dialog('destroy').empty(); + } + }); + } + + // NewPatientPage 클래스 내부에 추가 + savePatientData(signatureData) { + // 서버 UserDTO 구조와 매핑될 데이터 수집 + const param = { + userName: $('input[name="modalUserName"]').val(), + nationality: this.data.nationalityCode, // Choices로 선택된 코드값 + nationalityCode: 'C202404110001', // 국적 카테고리 고정값 + birthday: $('#modalBirthday').val(), + gender: $('input[name="modalGender"]:checked').val(), + userRrn1: $('input[name="modalUserRrn1"]').val(), + userRrn2: $('input[name="modalUserRrn2"]').val(), + userPno: $('input[name="modalUserPno"]').val(), + phoneNumber: $('input[name="modalPhoneNumber"]').val(), + channel: $('#selectChannel').val(), + channelCode: 'C202404110003', // 방문경로 카테고리 고정값 + introUserId: $('input[name="modalRecommendId"]').val(), // 추천인 텍스트 + etc: $('textarea[name="modalEtc"]').val(), + smsYn: $('input[name="modalSmsYn"]:checked').val(), + refusePhotoYn: $('#refusePhoto').is(':checked') ? 'Y' : 'N', + signatureData: signatureData, // 서명 Base64 데이터 + address: $('#address').val(), + addressDtl: $('#addressDtl').val(), + email: $('#email').val(), + introName: $('#introName').val() + }; + + // 로딩바가 있다면 여기서 표시 (예: $.blockUI();) + console.log(param); + $.ajax({ + url: '/kiosk/putUser.do', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify(param), + success: (res) => { + // Controller의 resultMap.put("success", true)와 대응 + if (res.success) { + alert(res.msgDesc || '고객 등록이 완료되었습니다.'); + location.href = '/kiosk'; // 완료 후 메인으로 이동 + } else { + alert(res.msgDesc || '저장에 실패했습니다.'); + } + }, + error: (xhr) => { + alert('서버와 통신 중 오류가 발생했습니다.'); + console.error(xhr); } + }); + } + } + + /** + * [Utility] 서명 다이얼로그 관리 클래스 + */ + class SignatureDialogManager { + constructor(modalId = 'agreementInsertModal') { + this.modalId = modalId; + this.callback = null; + this.signaturePad = null; + } + + init() { + $(`#${this.modalId}`).remove(); + $('body').append(this.getHtmlTemplate()); + + const $modal = $(`#${this.modalId}`); + $modal.dialog({ + autoOpen: false, + modal: true, + width: 500, + resizable: false, + draggable: true, + dialogClass: 'signature-ui-dialog', + open: () => { + this.initSignaturePad(); + $(".ui-dialog-titlebar").hide(); + } + }); + + this.setEvent(); + } + + initSignaturePad() { + const canvas = document.querySelector(`#${this.modalId} canvas`); + if (!canvas) return; + + try { + this.signaturePad = new SignaturePad(canvas, { + backgroundColor: 'rgb(255, 255, 255)', + penColor: 'rgb(0, 0, 0)' + }); + this.resizeCanvas(canvas); + } catch (e) { + console.error("SignaturePad 로드 실패."); + } + } + + resizeCanvas(canvas) { + const ratio = Math.max(window.devicePixelRatio || 1, 1); + canvas.width = canvas.offsetWidth * ratio; + canvas.height = canvas.offsetHeight * ratio; + canvas.getContext("2d").scale(ratio, ratio); + if (this.signaturePad) this.signaturePad.clear(); + } + + setEvent() { + const $modal = $(`#${this.modalId}`); + $modal.find('.btnCancle').on('click', () => $modal.dialog('close')); + $modal.find('.btnReset').on('click', () => { + if (this.signaturePad) this.signaturePad.clear(); + }); + $modal.find('.btnSave').on('click', () => { + if (this.signaturePad && this.signaturePad.isEmpty()) { + alert("서명을 진행해 주세요."); + return; + } + const dataUrl = this.signaturePad.toDataURL(); + if (this.callback) this.callback(dataUrl); + $modal.dialog('close'); + }); + } + + open(callback) { + this.init(); + this.callback = callback; + $(`#${this.modalId}`).dialog('open'); + } + + getHtmlTemplate() { + return ` + `; + } } - - /** - * [Modal] 고객 검색 팝업 클래스 - * 작성일 : 2024. 04. 02. - * 작성자 : NTsoft (Refactored to Class) - */ - /** - * [Modal] 고객 검색 팝업 클래스 (jQuery UI Dialog 버전) - */ - class UserIntroSelectModal { - constructor() { - this.callback = null; - this.dataList = null; - this.modalId = 'userIntroSelectModal'; - } - - init() { - $(`#${this.modalId}`).remove(); - $('body').append(this.getHtmlTemplate()); - - $(`#${this.modalId}`).dialog({ - autoOpen: false, - modal: true, - width: 450, // 3개 항목이므로 너비를 좀 더 슬림하게 조정 - resizable: false, - draggable: true, - dialogClass: 'user-select-dialog', // CSS 커스텀 클래스 연결 - open: function() { - $(".ui-dialog-titlebar").hide(); // 타이틀바 숨김 - $('.ui-widget-overlay').css({ 'opacity': 0.5, 'background': '#000' }); - } - }); - - this.setEvent(); - } - - setEvent() { - const $modal = $(`#${this.modalId}`); - $modal.find('.btnCancle').on("click", () => $modal.dialog("close")); - - $modal.find('#searchIntroUserBtn').on('click', () => this.searchIntroUserList()); - $modal.find('#introUserSearchKeyword').on('keypress', (e) => { - if (e.which === 13) this.searchIntroUserList(); - }); - } - - searchIntroUserList() { - const searchKeyword = document.querySelector("#introUserSearchKeyword").value; - if (searchKeyword.length < 2) { - alert("검색어를 2자 이상 입력해주세요."); - return; - } - - const formData = new FormData(); - formData.append("menuClass", window.menuClass || ""); - formData.append("userSearchKeywordParam", searchKeyword); - // 정렬 파라미터 추가 - formData.append("userSort", "BIRTHDAY DESC"); - - $.ajax({ - url: encodeURI('/webuser/selectListUserOption.do'), - data: formData, - dataType: "json", - processData: false, - contentType: false, - type: 'POST', - success: (data) => { - if (data.msgCode === '0') this.renderTable(data.rows); - } - }); - } - - renderTable(rows) { - const tbody = document.querySelector(`#${this.modalId} tbody`); - tbody.innerHTML = ""; - this.dataList = rows; - - if (!rows || rows.length === 0) { - tbody.innerHTML = '조회 결과가 없습니다.'; - return; - } - - rows.forEach((user, index) => { - const genderClass = (user.gender === '남' || user.gender === 'M') ? 'gender-M' : 'gender-F'; - const tr = document.createElement('tr'); - tr.innerHTML = ` - ${user.userName} - ${this.convertDateFormat(user.birthday)} - ${user.gender} - `; - tr.onclick = () => { - if (this.callback) this.callback(user); - $(`#${this.modalId}`).dialog("close"); - }; - tbody.appendChild(tr); - }); - } - - popup(callback) { - this.init(); - this.callback = callback; - $(`#${this.modalId}`).dialog("open"); - } - - convertDateFormat(input) { - if (!input) return '-'; - const dateMatch = input.match(/\d{4}-\d{2}-\d{2}/); - return dateMatch ? dateMatch[0].replace(/-/g, '.') : input; - } - - getHtmlTemplate() { - return ` - `; - } - } - - /** - * [Utility] 서명 다이얼로그 관리 클래스 (jQuery UI 버전) - */ - class SignatureDialogManager { - constructor(modalId = 'agreementInsertModal') { - this.modalId = modalId; - this.callback = null; - this.signaturePad = null; - } - - /** - * 다이얼로그 초기화 및 DOM 생성 - */ - init() { - $(`#${this.modalId}`).remove(); - $('body').append(this.getHtmlTemplate()); - - const $modal = $(`#${this.modalId}`); - - // jQuery UI Dialog 설정 - $modal.dialog({ - autoOpen: false, - modal: true, - width: 500, - resizable: false, - draggable: true, - dialogClass: 'signature-ui-dialog', - open: () => { - this.initSignaturePad(); - // 기본 타이틀바 숨기기 (커스텀 헤더 사용 시) - $(".ui-dialog-titlebar").hide(); - } - }); - - this.setEvent(); - } - - /** - * 서명 패드 라이브러리 초기화 - */ - initSignaturePad() { - const canvas = document.querySelector(`#${this.modalId} canvas`); - if (!canvas) return; - - try { - this.signaturePad = new SignaturePad(canvas, { - backgroundColor: 'rgb(255, 255, 255)', - penColor: 'rgb(0, 0, 0)' - }); - this.resizeCanvas(canvas); - } catch (e) { - console.error("SignaturePad 로드 실패. 라이브러리를 확인하세요."); - } - } - - /** - * 캔버스 크기 최적화 - */ - resizeCanvas(canvas) { - const ratio = Math.max(window.devicePixelRatio || 1, 1); - canvas.width = canvas.offsetWidth * ratio; - canvas.height = canvas.offsetHeight * ratio; - canvas.getContext("2d").scale(ratio, ratio); - if (this.signaturePad) this.signaturePad.clear(); - } - - /** - * 이벤트 바인딩 - */ - setEvent() { - const $modal = $(`#${this.modalId}`); - - // 취소/닫기 - $modal.find('.btnCancle').on('click', () => $modal.dialog('close')); - - // 지우기 (초기화) - $modal.find('.btnReset').on('click', () => { - if (this.signaturePad) this.signaturePad.clear(); - }); - - // 등록 완료 (저장) - $modal.find('.btnSave').on('click', () => { - if (this.signaturePad && this.signaturePad.isEmpty()) { - alert("서명을 진행해 주세요."); - return; - } - - const dataUrl = this.signaturePad.toDataURL(); - if (this.callback) this.callback(dataUrl); - $modal.dialog('close'); - }); - } - - /** - * 다이얼로그 오픈 - * @param {Function} callback 서명 완료 후 실행될 함수 - */ - open(callback) { - this.init(); - this.callback = callback; - $(`#${this.modalId}`).dialog('open'); - } - - /** - * HTML 템플릿 - */ - getHtmlTemplate() { - return ` - `; - } - } - - $(document).ready(() => { - - // 클래스 인스턴스 생성 (기존 소스와 호환성을 위해 변수명 유지 가능) - const userIntroSelectModal = new UserIntroSelectModal(); - - const signatureDialog = new SignatureDialogManager(); - $('.registration_bth').on('click', (e) => { - e.preventDefault(); - - - // 2. 서명 모달 오픈 (jQuery UI 기반으로 커스텀한 경우) - if (typeof signatureDialog !== 'undefined') { - signatureDialog.open((signatureData) => { - // 서명 완료 시 실행될 콜백: 여기서 실제 서버 전송(Ajax) 함수 호출 - this.savePatientData(signatureData); - }); - } - }); - const pageApp = new NewPatientPage(); + const signatureDialog = new SignatureDialogManager(); + const pageApp = new NewPatientPage(); window.newPatientPage = pageApp; pageApp.init(); - $('.consent-link').on('click', function(e) { - e.preventDefault(); - openConsentDialog($(this).data('type')); - }); - $('.searchIntroUser').on('click', function() { - // 모달 팝업 호출 - userIntroSelectModal.popup(function(data) { - // [Callback] 고객을 선택했을 때 실행될 로직 - console.log("선택된 추천인 정보:", data); - - // 1. input 필드에 선택된 사용자의 이름 표시 - $('input[name="modalRecommendId"]').val(data.userName); - - // 2. (옵션) 실제 DB 저장을 위한 ID값 등을 hidden 필드에 저장할 경우 - // if ($('#recommendUserNo').length > 0) { - // $('#recommendUserNo').val(data.muUserId); - // } + $('.btnSave').on('click', function(e) { + e.preventDefault(); + if (validateForm()) { + //alert("검증 완료! 서명 단계로 이동합니다."); + if (typeof signatureDialog !== 'undefined') { + signatureDialog.open((signatureData) => { + // 실제 저장 로직 호출 예시: pageApp.savePatientData(signatureData); + console.log("서명 완료", signatureData); + pageApp.savePatientData(signatureData); + }); + } + } + }); - }, { type: "recommend" }); // 필요 시 요청 파라미터 전달 - }); - function openConsentDialog(type) { - if ($('#dynamicConsentDialog').length) $('#dynamicConsentDialog').dialog('destroy').remove(); + $('.consent-link').on('click', function(e) { + e.preventDefault(); + openConsentDialog($(this).data('type')); + }); - const $dialog = $('
'); + function openConsentDialog(type) { + if ($('#dynamicConsentDialog').length) $('#dynamicConsentDialog').dialog('destroy').remove(); + + const $dialog = $('
'); let isAgreed = false; - $dialog.dialog({ - modal: true, width: 800, height: 700, - title: getDialogTitle(type), - resizable: false, draggable: true, - buttons: [ - { - text: "동의함", class: "registration_bth", - click: function() { + $dialog.dialog({ + modal: true, width: 800, height: 700, + title: getDialogTitle(type), + resizable: false, draggable: true, + buttons: [ + { + text: "동의함", class: "registration_bth", + click: function() { isAgreed = true; pageApp.allowSystemCheck = true; - $('#' + type).prop('checked', true); + $('#' + type).prop('checked', true); pageApp.allowSystemCheck = false; - $(this).dialog('close'); - } - }, - { text: "닫기", class: "cancel_btn", click: function() { $(this).dialog('close'); } } - ], + $(this).dialog('close'); + } + }, + { text: "닫기", class: "cancel_btn", click: function() { $(this).dialog('close'); } } + ], close: function() { if (!isAgreed) { pageApp.allowSystemCheck = true; @@ -520,44 +413,41 @@ } $(this).dialog('destroy').remove(); } - }); + }); - $dialog.html('
내용을 불러오는 중입니다...
'); + $dialog.html('
내용을 불러오는 중입니다...
'); $.ajax({ url: '/kiosk/getConsentForm.do', type: 'POST', - contentType: 'application/json', + contentType: 'application/json', data: JSON.stringify({ - consentFormTypeCd: getConsentFormTypeCd(type), // 코드로 변환하여 전송 - nationalCd: pageApp.data.nationalityCode // 현재 국적 코드 전송 + consentFormTypeCd: getConsentFormTypeCd(type), + nationalCd: pageApp.data.nationalityCode }), success: function(res) { if(res && res.consentFormContent) { $dialog.html(res.consentFormContent); } else { - $dialog.html('
등록된 동의서 내용이 없습니다.
'); + $dialog.html('
등록된 동의서 내용이 없습니다.
'); } }, error: function() { - $dialog.html('
서버 통신 중 오류가 발생했습니다.
'); + $dialog.html('
서버 통신 중 오류가 발생했습니다.
'); } }); - } + } - function getDialogTitle(type) { - const titles = { + function getDialogTitle(type) { + const titles = { 'agreePrivacy': '개인정보 수집 및 이용안내', 'agreeProcedure': '시술동의서', 'agreeTerms': '이용약관', 'refusePhoto': '사진촬영동의서' }; - return titles[type] || '동의서'; - } + return titles[type] || '동의서'; + } - /** - * type 명칭을 DB consentFormTypeCd 코드로 변환 - */ function getConsentFormTypeCd(type) { const typeMapping = { 'agreePrivacy': 'C202601180001', @@ -567,5 +457,63 @@ }; return typeMapping[type] || ''; } + + function validateForm() { + let isValid = true; + $('.validation-msg').text(''); + + // --- 1. 기본 정보 검증 --- + let basicError = ""; + if (!$('input[name="modalUserName"]').val().trim()) { + basicError = "이름 필수"; + } else if (!$('#selectNationality').val()) { + basicError = "국적 선택"; + } else if ($('input[name="modalUserRrn1"]').val().length < 6) { + basicError = "주민번호 확인"; + } else if (!$('input[name="modalPhoneNumber"]').val().trim()) { + basicError = "연락처 필수"; + } else if (!$('#address').val().trim()) { + basicError = "주소 검색 필요"; + } + + if (basicError) { + $('#msg-basic').text(" (⚠️ " + basicError + ")"); + isValid = false; + } + + // --- 2. 개인정보 동의 검증 --- + let consentError = ""; + if (!$('#agreePrivacy').is(':checked')) { + consentError = "이용안내 미동의"; + } else if (!$('#agreeProcedure').is(':checked')) { + consentError = "시술동의 미확인"; + } else if (!$('#agreeTerms').is(':checked')) { + consentError = "약관 미동의"; + } else if (!$('#refusePhoto').is(':checked')) { + consentError = "사진촬영 미동의"; + } + + if (consentError) { + $('#msg-consent').text(" (⚠️ " + consentError + ")"); + isValid = false; + } + + if (!isValid) { + const firstErrorField = $('.validation-msg:contains("⚠️")').first(); + if (firstErrorField.length) { + $('html, body').animate({ + scrollTop: firstErrorField.offset().top - 100 + }, 500); + } + } + + return isValid; + } + + $('input, select, textarea').on('input change', function() { + $(this).closest('section').find('.validation-msg').text(''); + }); + + }); })(); \ No newline at end of file diff --git a/src/main/resources/templates/kiosk/new-patient.html b/src/main/resources/templates/kiosk/new-patient.html index 9bea53f..c2c5eda 100644 --- a/src/main/resources/templates/kiosk/new-patient.html +++ b/src/main/resources/templates/kiosk/new-patient.html @@ -38,6 +38,9 @@ @@ -46,7 +49,7 @@
-
기본 정보
+
기본 정보
@@ -111,23 +114,23 @@
- +
- +
- +
- - + +
-
개인정보 수집 및 이용동의
+
개인정보 수집 및 이용동의
@@ -179,7 +182,7 @@
-
기타 정보
+
기타 정보
@@ -191,12 +194,9 @@
- +
- - search +
@@ -215,7 +215,7 @@