kiosk 개발시작

This commit is contained in:
pjs
2026-01-16 00:58:09 +09:00
parent 521aa41a18
commit 077a0767d0
3 changed files with 554 additions and 324 deletions

View File

@@ -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('<tr><td colspan="5" class="text-center p-3">검색결과가 없습니다.</td></tr>');
return;
}
users.forEach((user, index) => {
const $row = $(`
<tr class="user-row" data-index="${index}" style="cursor: pointer;">
<td>${user.username || ''}</td>
<td>${this.formatDate(user.birthday)}</td>
<td>${user.gender || ''}</td>
<td>${this.formatPhone(user.phonenumber)}</td>
<td>${user.lastvisitdate || ''}</td>
</tr>
`);
$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: `
<div id="userIntroSelectModal" class="modal fade list1_diagnosis" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header list1_modal_header">
<h5 class="modal-title">고객조회</h5>
</div>
<div class="modal-body list1_modal_body">
<div class="input-group mb-3">
<div class="search_box flex-grow-1">
<img src="/image/web/search_B.svg" style="width:20px;margin-right:8px;">
<input id="introUserSearchKeyword" class="form-control" placeholder="성함, 연락처, 생년월일">
</div>
<button id="searchIntroUserBtn" class="btn btn-primary ms-2">검색</button>
</div>
<div class="list1_modal_table">
<table class="table table-sm">
<thead class="table-light">
<tr>
<th>성함</th><th>생년월일</th><th>성별</th><th>연락처</th><th>최근내원</th>
</tr>
</thead>
<tbody>
<tr><td colspan="5" class="text-center">검색어를 입력하세요.</td></tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer list1_modal_footer">
<button class="btn btn-secondary btnCancle">취소</button>
</div>
</div>
</div>
</div>`
};
}
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));
})();