추천인 검색, 서명패드 추가
This commit is contained in:
@@ -632,3 +632,125 @@ button {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* 고객 검색 모달 커스텀 스타일 */
|
||||
.user-select-dialog {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
border-radius: 12px !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.1) !important;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
padding: 24px 24px 16px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
background: #f1f3f5;
|
||||
border-radius: 10px;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input-wrapper:focus-within {
|
||||
background: #ffffff;
|
||||
border-color: #4dabf7;
|
||||
box-shadow: 0 0 0 4px rgba(77, 171, 247, 0.1);
|
||||
}
|
||||
|
||||
.search-input-wrapper input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 10px 12px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: #228be6;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0 20px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-search:hover { background: #1c7ed6; }
|
||||
|
||||
.table-wrapper {
|
||||
padding: 0 24px 20px;
|
||||
max-height: 380px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.user-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #ffffff;
|
||||
padding: 12px 10px;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
color: #868e96;
|
||||
border-bottom: 2px solid #f1f3f5;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.user-table tbody tr {
|
||||
cursor: pointer;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.user-table tbody tr:hover { background: #f8f9fa; }
|
||||
|
||||
.user-table tbody td {
|
||||
padding: 16px 10px;
|
||||
font-size: 15px;
|
||||
color: #343a40;
|
||||
border-bottom: 1px solid #f1f3f5;
|
||||
}
|
||||
|
||||
.user-name { font-weight: 700; color: #212529; }
|
||||
|
||||
.gender-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.gender-M { background: #e7f5ff; color: #1971c2; }
|
||||
.gender-F { background: #fff0f6; color: #d6336c; }
|
||||
|
||||
.modal-footer-ui {
|
||||
padding: 16px 24px;
|
||||
background: #f8f9fa;
|
||||
text-align: right;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.btn-close-ui {
|
||||
background: #dee2e6;
|
||||
color: #495057;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
this.loadCommonCategories();
|
||||
this.bindEvents();
|
||||
this.isInitialized = true;
|
||||
|
||||
}
|
||||
|
||||
validateDependencies() { return !!($ && window.Choices); }
|
||||
@@ -63,6 +64,8 @@
|
||||
alert('동의서 보기 버튼을 클릭하여 내용을 확인하신 후 [동의함] 버튼을 눌러주세요.');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
updateUIByNationality(selectedCode) {
|
||||
@@ -128,17 +131,340 @@
|
||||
}
|
||||
|
||||
openAddrSearch() {
|
||||
// 1. 다이얼로그를 담을 컨테이너 생성 (없으면 생성)
|
||||
let $addrDialog = $('#addrSearchDialog');
|
||||
if ($addrDialog.length === 0) {
|
||||
$addrDialog = $('<div id="addrSearchDialog" style="display:none; overflow:hidden;"></div>').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();
|
||||
}
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [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 = '<tr><td colspan="3" style="text-align: center; padding: 40px 0; color: #adb5bd;">조회 결과가 없습니다.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
rows.forEach((user, index) => {
|
||||
const genderClass = (user.gender === '남' || user.gender === 'M') ? 'gender-M' : 'gender-F';
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td class="user-name">${user.userName}</td>
|
||||
<td>${this.convertDateFormat(user.birthday)}</td>
|
||||
<td><span class="gender-tag ${genderClass}">${user.gender}</span></td>
|
||||
`;
|
||||
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 `
|
||||
<div id="${this.modalId}" style="display:none;">
|
||||
<div class="search-header">
|
||||
<div class="search-input-wrapper">
|
||||
<input id="introUserSearchKeyword" type="text" placeholder="고객명 또는 생년월일" />
|
||||
<button id="searchIntroUserBtn" class="btn-search">검색</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table class="user-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>성함</th>
|
||||
<th>생년월일</th>
|
||||
<th>성별</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="3" style="text-align: center; padding: 40px 0; color: #adb5bd;">검색어를 입력하세요.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer-ui">
|
||||
<button type="button" class="btn-close-ui btnCancle">닫기</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [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 `
|
||||
<div id="${this.modalId}" title="서명 확인" style="display:none; padding:0;">
|
||||
<style>
|
||||
.signature-ui-dialog { border-radius: 12px; overflow: hidden; border: none; box-shadow: 0 5px 20px rgba(0,0,0,0.2); }
|
||||
.sig-header { padding: 15px; background: #339af0; color: #fff; font-weight: bold; }
|
||||
.sig-body { padding: 20px; text-align: center; }
|
||||
.canvas-area { border: 1px solid #dee2e6; background: #f8f9fa; border-radius: 4px; margin-bottom: 10px; }
|
||||
.canvas-area canvas { width: 100%; height: 200px; cursor: crosshair; }
|
||||
.sig-footer { padding: 15px; background: #f8f9fa; text-align: right; border-top: 1px solid #eee; }
|
||||
.sig-btn { padding: 8px 16px; border-radius: 6px; border: none; cursor: pointer; font-weight: 600; margin-left: 5px; }
|
||||
.sig-btn-primary { background: #339af0; color: #fff; }
|
||||
.sig-btn-secondary { background: #adb5bd; color: #fff; }
|
||||
</style>
|
||||
<div class="sig-header">본인 서명 확인</div>
|
||||
<div class="sig-body">
|
||||
<p style="margin-bottom: 10px; color: #495057;">아래 영역에 서명해 주세요.</p>
|
||||
<div class="canvas-area">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<button type="button" class="sig-btn sig-btn-secondary btnReset" style="font-size: 12px; padding: 4px 10px;">지우기</button>
|
||||
</div>
|
||||
<div class="sig-footer">
|
||||
<button type="button" class="sig-btn sig-btn-secondary btnCancle">취소</button>
|
||||
<button type="button" class="sig-btn sig-btn-primary btnSave">등록 완료</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(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();
|
||||
window.newPatientPage = pageApp;
|
||||
pageApp.init();
|
||||
@@ -147,7 +473,22 @@
|
||||
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);
|
||||
// }
|
||||
|
||||
}, { type: "recommend" }); // 필요 시 요청 파라미터 전달
|
||||
});
|
||||
function openConsentDialog(type) {
|
||||
if ($('#dynamicConsentDialog').length) $('#dynamicConsentDialog').dialog('destroy').remove();
|
||||
|
||||
@@ -209,7 +550,7 @@
|
||||
'agreePrivacy': '개인정보 수집 및 이용안내',
|
||||
'agreeProcedure': '시술동의서',
|
||||
'agreeTerms': '이용약관',
|
||||
'refusePhoto': '사진촬영거부동의서'
|
||||
'refusePhoto': '사진촬영동의서'
|
||||
};
|
||||
return titles[type] || '동의서';
|
||||
}
|
||||
|
||||
@@ -1,548 +0,0 @@
|
||||
(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));
|
||||
})();
|
||||
@@ -11,6 +11,7 @@
|
||||
<script src="/js/web/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||
<script src="/js/web/jquery-ui.js"></script>
|
||||
<script src="/js/web/signature_pad.js"></script>
|
||||
|
||||
|
||||
<!-- Choices.js -->
|
||||
@@ -153,10 +154,10 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row checkbox-row">
|
||||
<label for="refusePhoto">사진촬영거부동의서</label>
|
||||
<label for="refusePhoto">사진촬영동의서</label>
|
||||
<div class="checkbox-line">
|
||||
<input type="checkbox" id="refusePhoto" name="refusePhoto" onclick="return false;" />
|
||||
<a href="#" class="consent-link" data-type="refusePhoto">사진촬영거부동의서 동의서 보기</a>
|
||||
<a href="#" class="consent-link" data-type="refusePhoto">사진촬영 동의서 보기</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user