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 ecc01f6..712f5e0 100644
--- a/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java
+++ b/src/main/java/com/madeu/crm/kiosk/ctrl/KioskController.java
@@ -5,6 +5,7 @@ import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@@ -34,7 +35,7 @@ public class KioskController extends ManagerDraftAction {
/**
* 키오스크 메인화면 화면으로 이동.
*/
- @GetMapping("/kiosk/MainIntro.do")
+ @RequestMapping("/kiosk")
public ModelAndView selectMainIntro(HttpServletRequest request, HttpServletResponse response) {
log.debug("KioskController selectMainIntro START");
log.debug("KioskController selectMainIntro END");
diff --git a/src/main/resources/static/js/kiosk/new-patient.js b/src/main/resources/static/js/kiosk/new-patient.js
new file mode 100644
index 0000000..2a96a6e
--- /dev/null
+++ b/src/main/resources/static/js/kiosk/new-patient.js
@@ -0,0 +1,548 @@
+(function() {
+ 'use strict';
+
+ class NewPatientPage {
+ constructor() {
+ this.choicesInstances = new Map();
+ this.data = {
+ nationality: '',
+ nationalityCode: '',
+ userType: '',
+ visitPath: '',
+ treatment: ''
+ };
+ this.datepickerInstance = null;
+ this.isInitialized = false;
+ this.userModalRetryCount = 0;
+ }
+
+ init() {
+ if (this.isInitialized) return;
+
+ if (!this.validateDependencies()) {
+ this.scheduleRetry();
+ return;
+ }
+
+ if (!this.validateDOM()) {
+ this.scheduleRetry();
+ return;
+ }
+
+ this.initializeComponents();
+ this.isInitialized = true;
+ console.log('✅ NewPatientPage 초기화 완료');
+ }
+
+ validateDependencies() {
+ return !!($ && window.Choices);
+ }
+
+ validateDOM() {
+ const requiredElements = [
+ '#selectNationality', '#selectUserType', '#selectTreatment',
+ '#selectChannel', '#selectIdentification', '#newPatientForm'
+ ];
+ return requiredElements.every(selector => document.querySelector(selector));
+ }
+
+ scheduleRetry() {
+ setTimeout(() => this.init(), 150);
+ }
+
+ initializeComponents() {
+ this.initChoices();
+ this.initUserIntroModal(); // 내장 모달 초기화
+ this.loadCommonCategories();
+ this.initDatepicker();
+ this.bindEvents();
+ }
+
+ initChoices() {
+ const choicesConfig = {
+ searchEnabled: true,
+ searchPlaceholderValue: '검색...',
+ noResultsText: '결과 없음',
+ noChoicesText: '선택 가능한 항목이 없습니다',
+ itemSelectText: '선택',
+ removeItemButton: false,
+ shouldSort: false,
+ placeholderValue: '선택하세요'
+ };
+
+ const selectors = {
+ nationality: { id: '#selectNationality', key: 'nationality', placeholder: '국적 선택' },
+ userType: { id: '#selectUserType', key: 'userType', placeholder: '고객구분 선택' },
+ treatment: { id: '#selectTreatment', key: 'treatment', placeholder: '관심진료 선택' },
+ channel: { id: '#selectChannel', key: 'channel', placeholder: '방문경로 선택' },
+ identification: {
+ id: '#selectIdentification',
+ key: 'identification',
+ config: { searchEnabled: false, placeholder: false }
+ }
+ };
+
+ Object.entries(selectors).forEach(([key, config]) => {
+ try {
+ const instance = new Choices(config.id, {
+ ...choicesConfig,
+ placeholder: true,
+ placeholderValue: config.placeholder,
+ ...(config.config || {})
+ });
+ this.choicesInstances.set(config.key, instance);
+ } catch (e) {
+ console.error(`Choices 초기화 실패 (${key}):`, e);
+ }
+ });
+
+ this.toggleForeignerBox(false);
+ this.bindChoicesEvents();
+ }
+
+ // 🔥 내장형 userIntroSelectModal
+ initUserIntroModal() {
+ window.userIntroSelectModal = {
+ callback: null,
+ dataList: null,
+
+ popup(callback) {
+ this.callback = callback;
+ this.initModal();
+ setTimeout(() => $('#userIntroSelectModal').modal('show'), 200);
+ },
+
+ initModal() {
+ if ($('#userIntroSelectModal').length) {
+ $('#userIntroSelectModal').remove();
+ }
+ $('body').append(this.htmlTemplate);
+ this.bindModalEvents();
+ },
+
+ bindModalEvents() {
+ $('#userIntroSelectModal .btnCancle').off().on('click', () => this.close());
+ $('#searchIntroUserBtn').off().on('click', () => this.search());
+ $('#introUserSearchKeyword').off('keypress').on('keypress', (e) => {
+ if (e.key === 'Enter') this.search();
+ });
+ },
+
+ search() {
+ const keyword = $('#introUserSearchKeyword').val()?.trim();
+ if (keyword.length < 2) {
+ this.showAlert('검색어는 2자 이상 입력하세요.');
+ return;
+ }
+
+ $.ajax({
+ url: '/kiosk/getUserOptionList.do',
+ method: 'POST',
+ data: { userSearchKeywordParam: keyword },
+ dataType: 'json',
+ success: (data) => {
+ if (data.msgCode === '0') {
+ this.dataList = data.rows || [];
+ this.renderResults(this.dataList);
+ } else {
+ this.showAlert('검색 결과가 없습니다.');
+ }
+ },
+ error: () => this.showAlert('검색 중 오류가 발생했습니다.')
+ });
+ },
+
+ renderResults(users) {
+ const $tbody = $('#userIntroSelectModal tbody');
+ $tbody.empty();
+
+ if (users.length === 0) {
+ $tbody.html('
| 검색결과가 없습니다. |
');
+ return;
+ }
+
+ users.forEach((user, index) => {
+ const $row = $(`
+
+ | ${user.username || ''} |
+ ${this.formatDate(user.birthday)} |
+ ${user.gender || ''} |
+ ${this.formatPhone(user.phonenumber)} |
+ ${user.lastvisitdate || ''} |
+
+ `);
+ $tbody.append($row);
+ });
+
+ $('.user-row').off('click').on('click', (e) => {
+ const index = $(e.currentTarget).data('index');
+ this.selectUser(index);
+ });
+ },
+
+ selectUser(index) {
+ if (this.dataList?.[index]) {
+ this.callback(this.dataList[index]);
+ }
+ this.close();
+ },
+
+ formatDate(dateStr) {
+ if (!dateStr) return '';
+ try {
+ const date = new Date(dateStr.match(/\d{4}-\d{2}-\d{2}/)?.[0]);
+ if (isNaN(date)) return '';
+ return date.toISOString().slice(0, 10).replace(/-/g, '.');
+ } catch {
+ return '';
+ }
+ },
+
+ formatPhone(phone) {
+ if (!phone) return '';
+ return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
+ },
+
+ showAlert(message) {
+ if (window.modalEvent) {
+ window.modalEvent.warning('', message);
+ } else {
+ alert(message);
+ }
+ },
+
+ close() {
+ $('#userIntroSelectModal').modal('hide');
+ setTimeout(() => $('#userIntroSelectModal').remove(), 500);
+ },
+
+ htmlTemplate: `
+
+
+
+
+
+
+
+
+
+
+ | 성함 | 생년월일 | 성별 | 연락처 | 최근내원 |
+
+
+
+ | 검색어를 입력하세요. |
+
+
+
+
+
+
+
+
`
+ };
+ }
+
+ bindChoicesEvents() {
+ const idSelect = document.getElementById('selectIdentification');
+ if (idSelect) {
+ idSelect.addEventListener('change', (e) => {
+ const isPassport = e.target.value === 'pno';
+ $('.passport_number_box').toggle(isPassport);
+ $('.foreigner_number_box').toggle(!isPassport);
+ });
+ }
+
+ const eventMap = {
+ 'selectNationality': (e) => this.handleNationalityChange(e),
+ 'selectUserType': (e) => this.data.userType = e.target.value,
+ 'selectTreatment': (e) => this.data.treatment = e.target.value,
+ 'selectChannel': (e) => this.handleChannelChange(e)
+ };
+
+ Object.entries(eventMap).forEach(([id, handler]) => {
+ const element = document.getElementById(id);
+ if (element) element.addEventListener('change', handler);
+ });
+ }
+
+ handleNationalityChange(event) {
+ this.data.nationalityCode = event.target.value;
+ const selectedText = event.target.options[event.target.selectedIndex]?.text || '';
+ const isKorean = selectedText.includes('대한민국') || event.target.value.includes('KR') || event.target.value === 'Local';
+ this.data.nationality = isKorean ? 'Local' : 'Foreigner';
+ this.toggleForeignerBox(!isKorean);
+ }
+
+ handleChannelChange(event) {
+ this.data.visitPath = event.target.value;
+ const selectedText = event.target.options[event.target.selectedIndex]?.text || '';
+ if (selectedText.includes('소개')) {
+ $('.searchIntroUser').show();
+ } else {
+ $('input[name="modalRecommendId"]').val('').removeAttr('data-userid');
+ }
+ }
+
+ toggleForeignerBox(show) {
+ $('.foreigner_box').toggle(show);
+ $('.local_box').toggle(!show);
+ }
+
+ loadCommonCategories() {
+ const categories = [
+ { code: 'C202404110001', key: 'nationality' },
+ { code: 'C202404110002', key: 'userType' },
+ { code: 'C202404110003', key: 'channel' }
+ ];
+ categories.forEach(({ code, key }) => this.fetchCategory(code, key));
+ this.fetchTreatmentList();
+ }
+
+ fetchCategory(code, choicesKey) {
+ $.ajax({
+ url: '/kiosk/getCategoryItem.do',
+ type: 'POST',
+ data: { categoryCode: code },
+ success: (data) => {
+ if (data.rows?.length > 0) {
+ const choices = data.rows.map(item => ({
+ value: item.categoryitemcode || item.commonCode || '',
+ label: item.categoryitemname || item.codeName || ''
+ }));
+ const instance = this.choicesInstances.get(choicesKey);
+ if (instance) {
+ instance.setChoices(choices, 'value', 'label', true);
+ if (choicesKey === 'nationality') {
+ const defaultItem = choices.find(c => c.label.includes('대한민국'));
+ if (defaultItem) {
+ instance.setChoiceByValue(defaultItem.value);
+ this.data.nationalityCode = defaultItem.value;
+ this.data.nationality = 'Local';
+ }
+ }
+ }
+ }
+ },
+ error: (xhr) => console.error(`카테고리 조회 실패 (${code}):`, xhr.responseText)
+ });
+ }
+
+ fetchTreatmentList() {
+ $.ajax({
+ url: '/kiosk/getTreatmentOptionList.do',
+ type: 'POST',
+ success: (data) => {
+ if (data.rows?.length > 0) {
+ const choices = data.rows.map(item => ({
+ value: item.mutreatmentid || '',
+ label: item.treatmentname || ''
+ }));
+ const instance = this.choicesInstances.get('treatment');
+ if (instance) instance.setChoices(choices, 'value', 'label', true);
+ }
+ }
+ });
+ }
+
+ initDatepicker() {
+ const birthdayInput = document.querySelector('input[name="modalBirthday"]');
+ if (!birthdayInput) return;
+
+ this.datepickerInstance = flatpickr(birthdayInput, {
+ locale: "ko",
+ dateFormat: "Y-m-d",
+ maxDate: "today",
+ disableMobile: true,
+ onChange: (selectedDates) => {
+ if (selectedDates.length > 0) {
+ this.calculateAgeAndRrn(selectedDates[0]);
+ }
+ }
+ });
+
+ $('.date-input-wrapper').off('click.datepicker').on('click.datepicker', (e) => {
+ e.stopPropagation();
+ this.datepickerInstance?.open();
+ });
+ }
+
+ calculateAgeAndRrn(birthDate) {
+ const today = new Date();
+ let age = today.getFullYear() - birthDate.getFullYear();
+ const m = today.getMonth() - birthDate.getMonth();
+ if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) age--;
+ $('.txtAge').text(`만 ${age}세`);
+
+ const yy = String(birthDate.getFullYear()).substring(2);
+ const mm = String(birthDate.getMonth() + 1).padStart(2, '0');
+ const dd = String(birthDate.getDate()).padStart(2, '0');
+ $('input[name="modalUserRrn1"]').val(`${yy}${mm}${dd}`);
+ }
+
+ bindEvents() {
+ $('.cancel_btn').off('click.newPatient').on('click.newPatient', () => history.back());
+ $('.registration_bth').off('click.newPatient').on('click.newPatient', () => this.save());
+
+ $('input[name="modalPhoneNumber"], input[name="modalPhoneNumber2"]')
+ .off('input.phoneFormat').on('input.phoneFormat', function() {
+ this.value = this.value.replace(/[^0-9]/g, '')
+ .replace(/(\d{3})(\d{3,4})(\d{4})/, '$1-$2-$3').substring(0, 13);
+ });
+
+ $('.searchIntroUser').off('click.newPatient').on('click.newPatient', () => {
+ window.userIntroSelectModal?.popup((obj) => {
+ $('input[name="modalRecommendId"]').val(obj.username || '').attr('data-userid', obj.muuserid || '');
+ });
+ });
+ }
+
+ async save() {
+ const formData = this.collectFormData();
+ if (!this.validateForm(formData)) return;
+
+ try {
+ const response = await this.submitForm(formData);
+ this.handleSaveResponse(response);
+ } catch (error) {
+ this.showError('통신 중 오류가 발생했습니다.');
+ }
+ }
+
+ collectFormData() {
+ return {
+ userName: $('input[name="modalUserName"]').val()?.trim() || '',
+ phone: $('input[name="modalPhoneNumber"]').val().replace(/-/g, '') || '',
+ phone2: $('input[name="modalPhoneNumber2"]').val().replace(/-/g, '') || '',
+ birthday: $('input[name="modalBirthday"]').val() || '',
+ gender: $('input[name="modalGender"]:checked').val() || '',
+ smsYn: $('input[name="modalSmsYn"]:checked').val() || '',
+ refusePhoto: $('input[name="modalRefusePhotoYn"]:checked').val() || '',
+ rrn1: $('input[name="modalUserRrn1"]').val() || '',
+ rrn2: $('input[name="modalUserRrn2"]').val() || '',
+ pno: $('input[name="modalUserPno"]').val() || '',
+ arc1: $('input[name="modalUserArc1"]').val() || '',
+ arc2: $('input[name="modalUserArc2"]').val() || '',
+ memo: $('textarea[name="modalMemo"]').val() || '',
+ etc: $('textarea[name="modalEtc"]').val() || '',
+ introUserId: $('input[name="modalRecommendId"]').attr('data-userid') || '',
+ ...this.data
+ };
+ }
+
+ validateForm(data) {
+ const errors = {
+ userName: !data.userName && '고객명을 입력해주세요.',
+ phone: (!data.phone || data.phone.length < 10) && '올바른 연락처를 입력해주세요.',
+ privacy: !$('#agree_privacy').is(':checked') && '개인정보 수집 이용에 동의해야 합니다.'
+ };
+
+ const errorMsg = Object.values(errors).find(Boolean);
+ if (errorMsg && window.modalEvent) {
+ window.modalEvent.warning('알림', errorMsg);
+ return false;
+ }
+ return true;
+ }
+
+ submitForm(data) {
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ url: '/kiosk/putUser.do',
+ type: 'POST',
+ data: {
+ nationality: data.nationality,
+ nationalityCode: data.nationalityCode,
+ userName: data.userName,
+ phoneNumber: data.phone,
+ phoneNumber2: data.phone2,
+ birthday: data.birthday,
+ gender: data.gender,
+ userRrn1: data.rrn1,
+ userRrn2: data.rrn2,
+ userPno: data.pno,
+ userArc1: data.arc1,
+ userArc2: data.arc2,
+ userTypeCode: data.userType,
+ channelCode: data.visitPath,
+ muGroupId: data.treatment,
+ introUserId: data.introUserId,
+ memo: data.memo,
+ etc: data.etc,
+ smsYn: data.smsYn,
+ refusePhotoYn: data.refusePhoto
+ },
+ success: resolve,
+ error: reject
+ });
+ });
+ }
+
+ handleSaveResponse(data) {
+ if (data.msgCode === '0') {
+ if (window.modalEvent) {
+ window.modalEvent.success('등록 성공', '신규 고객 등록이 완료되었습니다.', () => {
+ location.href = '/kiosk/kiosk_main';
+ });
+ } else {
+ location.href = '/kiosk/kiosk_main';
+ }
+ } else if (window.modalEvent) {
+ window.modalEvent.danger('오류', data.msgDesc || '등록에 실패했습니다.');
+ }
+ }
+
+ showError(message) {
+ if (window.modalEvent) {
+ window.modalEvent.danger('오류', message);
+ } else {
+ alert(message);
+ }
+ }
+
+ destroy() {
+ this.choicesInstances.forEach(instance => instance?.destroy());
+ this.choicesInstances.clear();
+ this.datepickerInstance?.destroy();
+ $('#userIntroSelectModal')?.remove();
+ this.isInitialized = false;
+ }
+ }
+
+ // 싱글톤 인스턴스 관리
+ let instance = null;
+ window.newPatientPage = {
+ init() {
+ if (instance) return;
+ instance = new NewPatientPage();
+ instance.init();
+ },
+ destroy() {
+ instance?.destroy();
+ instance = null;
+ },
+ getInstance() {
+ return instance;
+ }
+ };
+
+ // 자동 초기화
+ const init = () => window.newPatientPage.init();
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ setTimeout(init, 50);
+ }
+ window.addEventListener('load', () => setTimeout(init, 100));
+})();
diff --git a/src/main/resources/templates/kiosk/new-patient.html b/src/main/resources/templates/kiosk/new-patient.html
index 4e96a30..cd162da 100644
--- a/src/main/resources/templates/kiosk/new-patient.html
+++ b/src/main/resources/templates/kiosk/new-patient.html
@@ -26,9 +26,9 @@
-
+
-
+
@@ -223,328 +223,9 @@
-
-
+
-
+