From 077a0767d0e0193eb2ea824c7bc886fd8555ea0e Mon Sep 17 00:00:00 2001 From: pjs Date: Fri, 16 Jan 2026 00:58:09 +0900 Subject: [PATCH] =?UTF-8?q?kiosk=20=EA=B0=9C=EB=B0=9C=EC=8B=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../madeu/crm/kiosk/ctrl/KioskController.java | 3 +- .../resources/static/js/kiosk/new-patient.js | 548 ++++++++++++++++++ .../templates/kiosk/new-patient.html | 327 +---------- 3 files changed, 554 insertions(+), 324 deletions(-) create mode 100644 src/main/resources/static/js/kiosk/new-patient.js 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 @@
- - + - + \ No newline at end of file