commit
This commit is contained in:
406
kngil/js/adm.js
Normal file
406
kngil/js/adm.js
Normal file
@@ -0,0 +1,406 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
import { setUserGridMode, loadData } from './adm_comp.js'
|
||||
|
||||
export function destroyGrid(name) {
|
||||
if (w2ui[name]) {
|
||||
w2ui[name].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
Super Admin Grid 생성 (상단)
|
||||
---------------------------------------- */
|
||||
export function createSuperAdminGrid(boxId) {
|
||||
|
||||
destroyGrid('superGrid')
|
||||
userGrid
|
||||
const grid = new w2grid({
|
||||
name: 'superGrid',
|
||||
box: boxId,
|
||||
|
||||
show: {
|
||||
// toolbar: true,
|
||||
footer: true,
|
||||
// toolbarReload: true,
|
||||
// toolbarColumns: true,
|
||||
// toolbarSearch: true
|
||||
},
|
||||
|
||||
// multiSearch: true,
|
||||
|
||||
// searches: [
|
||||
// { field: 'member_id', text: 'ID', type: 'text' },
|
||||
// { field: 'user_nm', text: '회원명', type: 'text' },
|
||||
// { field: 'co_nm', text: '회사명', type: 'text' },
|
||||
// {
|
||||
// field: 'stat_bc',
|
||||
// text: '회원상태',
|
||||
// type: 'list',
|
||||
// options: {
|
||||
// items: [
|
||||
// { id: 'Y', text: '사용' },
|
||||
// { id: 'N', text: '미사용' }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
|
||||
columns: [
|
||||
{ field: 'recid', text: '#', size: '50px', attr: 'align=center', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'member_id', text: 'ID', size: '90px', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'user_nm', text: '회원명', size: '90px', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'tel_no', text: '연락처', size: '120px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'email', text: 'E-mail', size: '180px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'co_nm', text: '회사명', size: '120px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'bs_no',
|
||||
text: '사업자번호',
|
||||
size: '120px',
|
||||
editable: { type: 'text' },
|
||||
resizable: true,
|
||||
sortable: true
|
||||
},
|
||||
{ field: 'join_dt', text: '가입일', size: '100px', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'user_y', text: '사용자수', size: '80px', attr: 'align=right', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'buy_area', text: '제공면적', size: '100px', attr: 'align=right', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'use_area', text: '사용면적', size: '100px', attr: 'align=right', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'rem_area', text: '잔여면적', size: '100px', attr: 'align=right', editable: false, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'stat_bc',
|
||||
text: '회원상태',
|
||||
size: '80px',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
editable: false,
|
||||
render(record) {
|
||||
switch (record.stat_bc) {
|
||||
case 'SA100100':
|
||||
return '사용중'
|
||||
case 'SA100200':
|
||||
return '탈퇴'
|
||||
default:
|
||||
return record.stat_bc || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{ field: 'memo', text: '메모', size: '100px', attr: 'align=right', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
|
||||
],
|
||||
|
||||
records: [],
|
||||
|
||||
/* --------------------------------
|
||||
상단 회사 클릭 → 하단 사용자 Grid 갱신
|
||||
-------------------------------- */
|
||||
onClick(event) {
|
||||
if (!event.detail.recid) return
|
||||
|
||||
const record = this.get(event.detail.recid)
|
||||
if (!record) return
|
||||
|
||||
// 하단 영역 표시
|
||||
document.getElementById('detailCard').style.display = 'block'
|
||||
|
||||
// 하단 사용자 로드
|
||||
// fetch(`/kngil/bbs/adm.php?action=user_list&member_id=${record.member_id}`)
|
||||
// .then(res => res.json())
|
||||
// .then(d => {
|
||||
// if (d.status !== 'success') {
|
||||
// w2alert('사용자 조회 실패')
|
||||
// return
|
||||
// }
|
||||
|
||||
// const g = w2ui.detailGrid
|
||||
// g.clear()
|
||||
// g.add(d.records || [])
|
||||
// })
|
||||
import('./adm_comp.js').then(m => {
|
||||
m.loadUsersByMember(record.member_id)
|
||||
})
|
||||
},
|
||||
onSelect(event) {
|
||||
event.onComplete = () => {
|
||||
const btn = document.getElementById('btnServiceRegister')
|
||||
if (btn) btn.disabled = false
|
||||
}
|
||||
},
|
||||
|
||||
onUnselect(event) {
|
||||
event.onComplete = () => {
|
||||
const btn = document.getElementById('btnServiceRegister')
|
||||
if (btn && !this.getSelection().length) {
|
||||
btn.disabled = true
|
||||
}
|
||||
}
|
||||
},
|
||||
// onEditField(event) {
|
||||
// if (event.field !== 'bs_no') return
|
||||
|
||||
// event.onComplete = () => {
|
||||
// const input = event.input
|
||||
// if (!input) return
|
||||
|
||||
// input.addEventListener('input', () => {
|
||||
// let v = input.value.replace(/\D/g, '').slice(0, 10)
|
||||
|
||||
// if (v.length >= 6) {
|
||||
// v = `${v.slice(0, 3)}-${v.slice(3, 5)}-${v.slice(5)}`
|
||||
// } else if (v.length >= 4) {
|
||||
// v = `${v.slice(0, 3)}-${v.slice(3)}`
|
||||
// }
|
||||
|
||||
// input.value = v
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
onChange(event) {
|
||||
event.onComplete = function (ev) {
|
||||
let rec = grid.get(ev.recid);
|
||||
if (!rec) return;
|
||||
|
||||
let field = grid.columns[ev.column].field;
|
||||
let val = ev.value_new;
|
||||
|
||||
/* ===============================
|
||||
🔴 사업자번호(bs_no) 전용 처리
|
||||
=============================== */
|
||||
if (field === 'bs_no') {
|
||||
|
||||
// 숫자만 추출
|
||||
let digits = String(val || '').replace(/\D/g, '');
|
||||
|
||||
// 🔥 입력 중에는 아무 것도 안 건드린다
|
||||
if (digits.length < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 10자리 초과 방지
|
||||
digits = digits.slice(0, 10);
|
||||
|
||||
// 최종 포맷
|
||||
let formatted = `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
|
||||
|
||||
// 🔥 여기서만 set
|
||||
grid.set(ev.recid, { bs_no: formatted });
|
||||
grid.refreshRow(ev.recid);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
🔵 기존 공통 로직
|
||||
=============================== */
|
||||
|
||||
if (typeof val === "object" && val !== null) {
|
||||
val = val.text;
|
||||
}
|
||||
|
||||
grid.set(ev.recid, { [field]: val });
|
||||
|
||||
if (field === 'quantity' || field === 'unit_price' || field === 'discount') {
|
||||
let qty = parseInt(rec.quantity) || 0;
|
||||
let unit = parseInt(rec.unit_price) || 0;
|
||||
let dc = parseInt(rec.discount) || 0;
|
||||
let total = Math.max(0, qty * unit - dc);
|
||||
|
||||
grid.set(ev.recid, { total_amount: total });
|
||||
}
|
||||
|
||||
grid.refresh();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// onDblClick(event) {
|
||||
// if (!event.detail.recid) return
|
||||
// const record = this.get(event.detail.recid)
|
||||
// if (!record) return
|
||||
|
||||
// import('./adm_service.js').then(m => {
|
||||
// m.openServiceRegisterPopup({
|
||||
// memberId: record.member_id,
|
||||
// memberName: record.user_nm,
|
||||
// company: record.co_nm,
|
||||
// bizNo: record.bs_no
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
})
|
||||
|
||||
loadCompanies()
|
||||
return grid
|
||||
}
|
||||
|
||||
//사업자 번호 포맷
|
||||
function formatBizNo(value) {
|
||||
const digits = String(value || '').replace(/\D/g, '');
|
||||
if (digits.length !== 10) return null;
|
||||
return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
상단 회사 목록 로드
|
||||
---------------------------------------- */
|
||||
function loadCompanies() {
|
||||
fetch('/kngil/bbs/adm.php')
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (!json.records) return
|
||||
|
||||
w2ui.superGrid.clear()
|
||||
w2ui.superGrid.add(json.records)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('회사 목록 로드 실패:', err)
|
||||
})
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 서비스등록 버튼
|
||||
// =========================
|
||||
document.getElementById('btnServiceRegister')
|
||||
.addEventListener('click', () => {
|
||||
|
||||
const g = w2ui.superGrid
|
||||
if (!g) return
|
||||
|
||||
const sel = g.getSelection()
|
||||
if (!sel.length) {
|
||||
w2alert('서비스를 등록할 회원을 선택하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
const r = g.get(sel[0])
|
||||
|
||||
// 신규 행 방지
|
||||
if (!r.member_id || String(r.member_id).startsWith('new_')) {
|
||||
w2alert('저장된 회원만 서비스 등록이 가능합니다.')
|
||||
return
|
||||
}
|
||||
|
||||
import('/kngil/js/adm_service.js').then(m => {
|
||||
m.openServiceRegisterPopup({
|
||||
memberId: r.member_id,
|
||||
memberName: r.user_nm,
|
||||
company: r.co_nm,
|
||||
bizNo: r.bs_no
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
//저장
|
||||
let isSaveBound = false
|
||||
|
||||
export function bindSaveButton() {
|
||||
if (isSaveBound) return
|
||||
isSaveBound = true
|
||||
|
||||
const btn = document.getElementById('btnSave')
|
||||
if (!btn) return
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const g = w2ui.superGrid
|
||||
if (!g) return
|
||||
|
||||
document.activeElement?.blur()
|
||||
g.finishEditing?.()
|
||||
|
||||
const changes = g.getChanges()
|
||||
|
||||
changes.forEach(ch => {
|
||||
// bs_no가 변경 대상이면
|
||||
if ('bs_no' in ch) {
|
||||
const formatted = formatBizNo(ch.bs_no);
|
||||
|
||||
if (!formatted) {
|
||||
w2alert('사업자번호는 숫자 10자리여야 합니다.\n예: 123-45-67890');
|
||||
throw new Error('invalid bs_no');
|
||||
}
|
||||
|
||||
// 🔥 changes 객체를 직접 수정
|
||||
ch.bs_no = formatted;
|
||||
}
|
||||
});
|
||||
|
||||
if (!changes.length) {
|
||||
w2alert('변경된 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
// 🔥 recid 기준으로 원본 record + 변경값 병합
|
||||
const updates = changes.map(c => {
|
||||
const r = g.get(c.recid)
|
||||
return { ...r, ...c }
|
||||
})
|
||||
|
||||
// 🔥 member_id 필수 체크
|
||||
if (!updates[0]?.member_id) {
|
||||
w2alert('member_id가 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
fetch('/kngil/bbs/adm.php?action=save', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
member_id: updates[0].member_id, // ✅ 핵심
|
||||
updates
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error(json.message)
|
||||
}
|
||||
|
||||
updates.forEach(u => {
|
||||
const r = g.get(u.recid);
|
||||
if (r && u.bs_no) {
|
||||
r.bs_no = u.bs_no; // 하이픈 포함 값
|
||||
g.refreshRow(u.recid);
|
||||
}
|
||||
});
|
||||
|
||||
// g.mergeChanges()
|
||||
// changes 강제 제거
|
||||
g.changes = [];
|
||||
|
||||
// 수정 상태 플래그 제거
|
||||
g.records.forEach(r => {
|
||||
if (r.w2ui) delete r.w2ui.changes;
|
||||
});
|
||||
|
||||
// 화면 다시 그리기
|
||||
g.refresh();
|
||||
w2alert('저장되었습니다.')
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
w2alert(err.message || '저장 실패')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function createDetailGrid(boxId) {
|
||||
|
||||
if (w2ui.detailGrid) w2ui.detailGrid.destroy()
|
||||
|
||||
new w2grid({
|
||||
name: 'detailGrid',
|
||||
box: boxId,
|
||||
show: {
|
||||
footer: true
|
||||
},
|
||||
columns: [
|
||||
{ field: 'recid', text: '#', size: '50px' },
|
||||
{ field: 'user_id', text: 'ID', size: '120px' },
|
||||
{ field: 'user_nm', text: '이름', size: '120px' },
|
||||
{ field: 'dept_nm', text: '부서', size: '120px' },
|
||||
{ field: 'email', text: 'E-mail', size: '200px' },
|
||||
{ field: 'use_yn', text: '사용', size: '80px' }
|
||||
],
|
||||
records: []
|
||||
})
|
||||
}
|
||||
163
kngil/js/adm_common.js
Normal file
163
kngil/js/adm_common.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert , w2confirm} from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
export function bindProductPopupEvents() {
|
||||
|
||||
const g = w2ui.productGrid
|
||||
//행 추가
|
||||
document.getElementById('prodAdd').onclick = () => {
|
||||
g.add({
|
||||
recid: g.records.length + 1,
|
||||
name: '',
|
||||
volume: 0,
|
||||
price: 0,
|
||||
use_yn: 'Y'
|
||||
})
|
||||
}
|
||||
//행 삭제
|
||||
document.getElementById('prodRemove').onclick = () => {
|
||||
const sel = g.getSelection()
|
||||
if (!sel.length) {
|
||||
w2alert('삭제할 항목을 선택하세요.')
|
||||
return
|
||||
}
|
||||
g.remove(...sel)
|
||||
}
|
||||
//저장
|
||||
document.getElementById('prodSave').onclick = () => {
|
||||
console.log('상품 저장', g.records)
|
||||
w2alert('저장 처리 예정')
|
||||
}
|
||||
}
|
||||
|
||||
// [추가] 공통 함수
|
||||
export function commonAdd(gridName, defaultData = {}) {
|
||||
const grid = w2ui[gridName];
|
||||
if (!grid) return;
|
||||
|
||||
// 중복되지 않는 recid 생성 (신규 행 식별용)
|
||||
const newRecid = 'new_' + Date.now();
|
||||
|
||||
grid.add({
|
||||
recid: newRecid,
|
||||
is_new: true, // ★ 핵심: 이 플래그가 있어야 commonSave에서 검색됨
|
||||
...defaultData // 아래 2번 단계에서 전달받은 초기값들이 여기에 들어감
|
||||
});
|
||||
|
||||
grid.scrollIntoView(newRecid);
|
||||
}
|
||||
|
||||
// [행 삭제] 공통 함수 (체크박스 선택 기준)
|
||||
export function commonRemove(gridName) {
|
||||
// 인자가 문자열이 아닌 객체로 들어오는지 확인용
|
||||
if (typeof gridName !== 'string') {
|
||||
console.error("그리드 이름은 반드시 문자열이어야 합니다. 전달된 값:", gridName);
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = w2ui[gridName];
|
||||
if (!grid) {
|
||||
console.error(`그리드 '${gridName}'을 찾을 수 없습니다.`, Object.keys(w2ui));
|
||||
return;
|
||||
}
|
||||
|
||||
const sel = grid.getSelection();
|
||||
if (sel.length === 0) {
|
||||
w2alert('삭제할 항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
w2confirm(`${sel.length}건을 삭제하시겠습니까?`).yes(() => {
|
||||
grid.remove(...sel);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// /js/adm_common.js
|
||||
export async function commonRemoveWithParams(gridName, deleteUrl) {
|
||||
const grid = w2ui[gridName];
|
||||
|
||||
// 1. 그리드 존재 확인 (문자열로 잘 들어왔는지 체크)
|
||||
if (!grid) {
|
||||
console.error(`[삭제실패] ${gridName} 그리드를 찾을 수 없습니다.`, Object.keys(w2ui));
|
||||
return;
|
||||
}
|
||||
|
||||
const sel = grid.getSelection();
|
||||
if (sel.length === 0) {
|
||||
w2alert('삭제할 항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
w2confirm(`${sel.length}건을 삭제하시겠습니까?`).yes(async () => {
|
||||
try {
|
||||
grid.lock('삭제 중...', true);
|
||||
|
||||
// 2. 프로시저에 필요한 데이터(member_id, sq_no) 추출
|
||||
const selectedRows = sel.map(id => grid.get(id));
|
||||
|
||||
const response = await fetch(deleteUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ items: selectedRows }) // 통째로 전달
|
||||
});
|
||||
|
||||
const responseText = await response.text(); // JSON 변환 전에 텍스트로 먼저 받음
|
||||
console.log("서버 실제 응답 원문:", responseText); // 여기서 HTML 에러 내용 확인 가능
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
grid.remove(...sel);
|
||||
w2alert('성공적으로 삭제되었습니다.');
|
||||
} else {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
} catch (e) {
|
||||
w2alert('삭제 오류: ' + e.message);
|
||||
console.error("서버 응답이 JSON이 아닙니다:", responseText);
|
||||
w2alert("서버 오류가 발생했습니다. 콘솔을 확인하세요.");
|
||||
} finally {
|
||||
grid.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// [수정 및 저장] 공통 함수
|
||||
export async function commonSave(gridName, saveUrl) {
|
||||
const grid = w2ui[gridName];
|
||||
if (!grid) {
|
||||
console.error(`[저장 실패] '${gridName}' 그리드를 찾을 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
|
||||
grid.save(); // 에디터에서 입력 중인 값 확정
|
||||
|
||||
// 변경된 내용이 있는지 확인
|
||||
if (grid.getChanges().length === 0) {
|
||||
w2alert('변경사항이 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
grid.lock('저장 중...', true);
|
||||
const response = await fetch(saveUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ items: grid.records }) // 전체 데이터를 보낼지 changes만 보낼지 선택
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
w2alert('저장되었습니다.');
|
||||
grid.mergeChanges(); // 그리드의 변경 표시(빨간색) 제거
|
||||
} else {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
} catch (e) {
|
||||
w2alert('저장 오류: ' + e.message);
|
||||
} finally {
|
||||
grid.unlock();
|
||||
}
|
||||
}
|
||||
506
kngil/js/adm_comp copy.js
Normal file
506
kngil/js/adm_comp copy.js
Normal file
@@ -0,0 +1,506 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert, w2confirm } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) {
|
||||
w2ui[name].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function loadBaseCode(mainCd) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error(json.message || '공통코드 로딩 실패')
|
||||
}
|
||||
return json.items
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
기업 관리자 페이지 Grid
|
||||
------------------------------------------------- */
|
||||
export async function createUserGrid(boxId, options = {}) {
|
||||
// 🔥 DB에서 권한 코드 로딩
|
||||
const authItems = await loadBaseCode('BS100')
|
||||
const { loadSummary = true, memberId = null } = options
|
||||
|
||||
destroyGrid('userGrid')
|
||||
|
||||
const grid = new w2grid({
|
||||
name: boxId === '#detailGrid' ? 'detailGrid' : 'userGrid',
|
||||
box: boxId,
|
||||
|
||||
show: {
|
||||
footer: true,
|
||||
selectColumn: true,
|
||||
},
|
||||
|
||||
columns: [
|
||||
{ field: 'recid', text: '#', size: '50px', attr: 'align=center', editable: false, resizable: true, sortable: true },
|
||||
{ field: 'user_id', text: 'ID', size: '90px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'user_pw',
|
||||
text: 'PW',
|
||||
size: '90px',
|
||||
resizable: true,
|
||||
sortable: false,
|
||||
|
||||
editable: {
|
||||
type: 'password' // ✅ 입력 시 ●●●●
|
||||
},
|
||||
|
||||
render() {
|
||||
return '********' // ✅ 항상 마스킹
|
||||
}
|
||||
},
|
||||
{ field: 'user_nm', text: '이름', size: '90px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'tel_no', text: '연락처', size: '120px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'email', text: 'E-mail', size: '180px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'dept_nm', text: '부서', size: '120px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'use_area',
|
||||
text: '사용량(㎡)',
|
||||
size: '120px',
|
||||
attr: 'align=right',
|
||||
editable: false,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
|
||||
render(record) {
|
||||
const v = Number(record.use_area) || 0
|
||||
return v.toLocaleString() // ✅ 1,000단위 콤마
|
||||
}
|
||||
},
|
||||
{ field: 'reg_date', text: '등록일', size: '100px', editable: false, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'use_yn',
|
||||
text: '사용',
|
||||
size: '80px',
|
||||
attr: 'align=center',
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
|
||||
editable: {
|
||||
type: 'list',
|
||||
items: [
|
||||
{ id: 'Y', text: 'Y' },
|
||||
{ id: 'N', text: 'N' }
|
||||
]
|
||||
},
|
||||
|
||||
render(record) {
|
||||
return record.use_yn === 'Y' ? 'Y' : 'N'
|
||||
}
|
||||
},
|
||||
/* ✅ 권한 콤보 (DB 연동) */
|
||||
{
|
||||
field: 'auth_bc',
|
||||
text: '권한',
|
||||
size: '120px',
|
||||
editable: {
|
||||
type: 'list',
|
||||
items: authItems
|
||||
},
|
||||
render(record) {
|
||||
const item = authItems.find(i => i.id === record.auth_bc)
|
||||
return item ? item.text : record.auth_bc
|
||||
}
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '120px', editable: { type: 'text' }, resizable: true, sortable: true }
|
||||
],
|
||||
|
||||
onEditField(event) {
|
||||
|
||||
const pwColIndex = this.getColumn('user_pw').index
|
||||
|
||||
// 🔥 PW 컬럼일 때만 처리
|
||||
if (event.column !== pwColIndex) return
|
||||
|
||||
event.onComplete = function () {
|
||||
|
||||
// 🔥 현재 편집 세션의 input만 정확히 집기
|
||||
const box = event.box
|
||||
if (!box) return
|
||||
|
||||
const input = box.querySelector('input[type="password"]')
|
||||
if (!input) return
|
||||
|
||||
// PW만 초기화
|
||||
input.value = ''
|
||||
input.placeholder = '변경 시에만 입력'
|
||||
}
|
||||
},
|
||||
|
||||
records: [],
|
||||
})
|
||||
|
||||
loadData({
|
||||
loadSummary,
|
||||
memberId
|
||||
})
|
||||
}
|
||||
|
||||
function loadUsers() {
|
||||
fetch('/kngil/bbs/adm_comp.php')
|
||||
.then(res => res.text()) // 🔥 먼저 text로 확인
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
w2ui.userGrid.clear()
|
||||
w2ui.userGrid.add(json.records)
|
||||
} catch (e) {
|
||||
console.error('JSON 파싱 실패:', text)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function loadUsersByMember(member_id) {
|
||||
|
||||
if (!member_id) return
|
||||
|
||||
// 🔥 실제 존재하는 grid 찾기
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
if (!g) {
|
||||
console.error('사용자 grid가 없습니다')
|
||||
return
|
||||
}
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php')
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
g.clear()
|
||||
g.add(json.records || [])
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('사용자 로드 실패', err)
|
||||
})
|
||||
}
|
||||
|
||||
export function setUserGridMode(mode = 'view') {
|
||||
const g = w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
if (mode === 'view') {
|
||||
g.show.toolbar = false
|
||||
g.show.selectColumn = false
|
||||
g.show.toolbarSave = false
|
||||
} else {
|
||||
g.show.toolbar = true
|
||||
g.show.selectColumn = true
|
||||
g.show.toolbarSave = true
|
||||
}
|
||||
|
||||
g.refresh()
|
||||
}
|
||||
|
||||
export function loadData({ loadSummary = true } = {}) {
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php')
|
||||
.then(res => res.json())
|
||||
.then(async d => {
|
||||
|
||||
if (d.status !== 'success') {
|
||||
w2alert('데이터 로딩 실패')
|
||||
return
|
||||
}
|
||||
|
||||
const records = d.records || []
|
||||
const memberId = d.member_id // ⭐ 여기서 확정
|
||||
|
||||
if (loadSummary && memberId) {
|
||||
const totalArea = await loadTotalArea(memberId)
|
||||
|
||||
renderSummaryFromRecords({
|
||||
memberId,
|
||||
records,
|
||||
totalArea
|
||||
})
|
||||
}
|
||||
|
||||
const gridName = w2ui.detailGrid ? 'detailGrid' : 'userGrid'
|
||||
w2ui[gridName].clear()
|
||||
w2ui[gridName].add(records)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
w2alert('서버 통신 오류')
|
||||
})
|
||||
}
|
||||
|
||||
function renderSummaryFromRecords({ memberId, records, totalArea }) {
|
||||
|
||||
if (!records.length) return
|
||||
|
||||
const first = records[0]
|
||||
|
||||
const issuedCnt = Number(first.users_tot) || 0
|
||||
const usedCnt = Number(first.users_y) || 0
|
||||
const term = first.term || ''
|
||||
|
||||
// 사용 중 면적 합계
|
||||
let usedArea = 0
|
||||
records.forEach(r => {
|
||||
if (r.use_yn === 'Y') {
|
||||
usedArea += Number(r.use_area || 0)
|
||||
}
|
||||
})
|
||||
|
||||
const percent = totalArea > 0
|
||||
? Math.floor((usedArea / totalArea) * 100)
|
||||
: 0
|
||||
|
||||
/* -------- 화면 바인딩 -------- */
|
||||
document.getElementById('memberId').textContent = memberId
|
||||
document.getElementById('planName').textContent = 'Silver'
|
||||
document.getElementById('dateRange').textContent = term
|
||||
|
||||
document.getElementById('issuedCnt').textContent = issuedCnt
|
||||
document.getElementById('usedCnt').textContent = usedCnt
|
||||
|
||||
document.getElementById('usedArea').textContent =
|
||||
usedArea.toLocaleString() + '㎡'
|
||||
|
||||
document.getElementById('totalArea').textContent =
|
||||
totalArea.toLocaleString() + '㎡'
|
||||
|
||||
document.getElementById('usedBar').style.width = percent + '%'
|
||||
document.getElementById('usedBar').textContent = percent + '%'
|
||||
document.getElementById('remainBar').style.width =
|
||||
(100 - percent) + '%'
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============================
|
||||
// 저장 버튼
|
||||
// ===============================
|
||||
document.getElementById('btnSave_comp')?.addEventListener('click', () => {
|
||||
|
||||
// 현재 사용 중인 grid
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
const changes = g.getChanges()
|
||||
|
||||
if (!changes.length) {
|
||||
w2alert('변경된 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
const inserts = []
|
||||
const updates = []
|
||||
|
||||
changes.forEach(c => {
|
||||
const rec = g.get(c.recid)
|
||||
if (!rec) return
|
||||
|
||||
// 🔥 핵심: 원본 rec + 변경값 c 병합
|
||||
const merged = { ...rec, ...c }
|
||||
|
||||
if (rec.__isNew) {
|
||||
// INSERT → 전체 row 필요
|
||||
inserts.push(merged)
|
||||
} else {
|
||||
// UPDATE → PK + 변경 컬럼
|
||||
updates.push({
|
||||
member_id : merged.member_id,
|
||||
user_id : merged.user_id,
|
||||
|
||||
user_nm : merged.user_nm,
|
||||
dept_nm : merged.dept_nm,
|
||||
tel_no : merged.tel_no,
|
||||
email : merged.email,
|
||||
use_yn : merged.use_yn,
|
||||
auth_bc : merged.auth_bc,
|
||||
rmks : (merged.memo !== undefined ? merged.memo : '')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
console.log('INSERTS', inserts)
|
||||
console.log('UPDATES', updates)
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'save',
|
||||
member_id: g.records[0]?.member_id,
|
||||
inserts,
|
||||
updates
|
||||
})
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
if (json.status === 'success') {
|
||||
w2alert('저장 완료')
|
||||
loadUsersByMember(g.records[0].member_id)
|
||||
} else {
|
||||
w2alert(json.message || '저장 실패')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(text)
|
||||
w2alert('서버 응답 오류 (JSON 아님)')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
//추가(insert)
|
||||
document.getElementById('btnAdd')?.addEventListener('click', () => {
|
||||
|
||||
const g = w2ui.userGrid || w2ui.detailGrid
|
||||
if (!g) return
|
||||
|
||||
// 신규 row용 recid (음수로 충돌 방지)
|
||||
const newRecid = -Date.now()
|
||||
|
||||
g.add({
|
||||
recid: newRecid,
|
||||
__isNew: true, // ⭐ 신규 플래그
|
||||
user_id: '',
|
||||
user_pw: '',
|
||||
user_nm: '',
|
||||
tel_no: '',
|
||||
email: '',
|
||||
dept_nm: '',
|
||||
use_area: 0,
|
||||
use_yn: 'Y',
|
||||
auth_bc: 'BS100400',
|
||||
memo: ''
|
||||
}, true)
|
||||
|
||||
g.select(newRecid)
|
||||
g.scrollIntoView(newRecid)
|
||||
})
|
||||
|
||||
//삭제
|
||||
document.getElementById('btnDelete')?.addEventListener('click', () => {
|
||||
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
const sel = g.getSelection()
|
||||
|
||||
if (!sel.length) {
|
||||
w2alert('삭제할 사용자를 선택하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
// 선택된 user_id 수집
|
||||
const ids = sel
|
||||
.map(recid => {
|
||||
const r = g.get(recid)
|
||||
return r?.user_id
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
if (!ids.length) {
|
||||
w2alert('삭제 가능한 항목이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
w2confirm(`선택한 ${ids.length}명의 사용자를 삭제하시겠습니까?`)
|
||||
.yes(() => {
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'delete',
|
||||
member_id: g.records[0]?.member_id,
|
||||
ids
|
||||
})
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
if (json.status === 'success') {
|
||||
w2alert('삭제 완료')
|
||||
loadUsersByMember(g.records[0].member_id)
|
||||
} else {
|
||||
w2alert(json.message || '삭제 실패')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(text)
|
||||
w2alert('서버 응답 오류 (JSON 아님)')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function loadTotalArea(memberId) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=total_area&member_id=${memberId}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error('총 구매면적 로딩 실패')
|
||||
}
|
||||
return Number(json.total_area || 0)
|
||||
})
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
|
||||
const keyword = document.getElementById('schKeyword').value.trim()
|
||||
const type = document.getElementById('schType').value
|
||||
const useYn = document.getElementById('schUseYn').value
|
||||
|
||||
let p_user_nm = ''
|
||||
let p_dept_nm = ''
|
||||
|
||||
// DB로 보낼 검색 조건
|
||||
if (type === 'name') {
|
||||
p_user_nm = keyword
|
||||
} else if (type === 'dept') {
|
||||
p_dept_nm = keyword
|
||||
} else if (type === '') {
|
||||
// 전체 검색 → 이름 OR 부서
|
||||
p_user_nm = keyword
|
||||
p_dept_nm = keyword
|
||||
}
|
||||
// ⚠️ type === 'id' 는 DB로 안 보냄
|
||||
|
||||
fetch(`/kngil/bbs/adm_comp.php?action=list`
|
||||
+ `&user_nm=${encodeURIComponent(p_user_nm)}`
|
||||
+ `&dept_nm=${encodeURIComponent(p_dept_nm)}`
|
||||
+ `&use_yn=${useYn}`
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then(d => {
|
||||
|
||||
if (d.status !== 'success') {
|
||||
w2alert('검색 실패')
|
||||
return
|
||||
}
|
||||
|
||||
let records = d.records || []
|
||||
|
||||
// 🔥 ID 검색은 프론트 필터
|
||||
if (type === 'id' && keyword) {
|
||||
records = records.filter(r =>
|
||||
(r.user_id || '').toLowerCase()
|
||||
.includes(keyword.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
g.clear()
|
||||
g.add(records)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('btnSearch')
|
||||
?.addEventListener('click', doSearch)
|
||||
|
||||
document.getElementById('schKeyword')
|
||||
?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') doSearch()
|
||||
})
|
||||
798
kngil/js/adm_comp.js
Normal file
798
kngil/js/adm_comp.js
Normal file
@@ -0,0 +1,798 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert, w2confirm } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
let AUTH_ITEMS = []
|
||||
let CURRENT_MEMBER_ID = null;
|
||||
|
||||
const USE_ITEMS = [
|
||||
{ id: 'Y', text: '사용' },
|
||||
{ id: 'N', text: '미사용' }
|
||||
]
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
|
||||
function getTargetMemberId() {
|
||||
return document.getElementById('targetMemberId')?.value?.trim()
|
||||
}
|
||||
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) {
|
||||
w2ui[name].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function loadBaseCode(mainCd) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error(json.message || '공통코드 로딩 실패')
|
||||
}
|
||||
return json.items
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeAuth(records) {
|
||||
return records.map(r => {
|
||||
const item = AUTH_ITEMS.find(a => a.id === r.auth_bc)
|
||||
return {
|
||||
...r,
|
||||
auth_bc: item || null //객체 복사
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeUseYn(records) {
|
||||
return records.map(r => {
|
||||
const item = USE_ITEMS.find(u => u.id === r.use_yn)
|
||||
return {
|
||||
...r,
|
||||
use_yn: item || null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
기업 관리자 페이지 Grid
|
||||
------------------------------------------------- */
|
||||
export async function createUserGrid(boxId, options = {}) {
|
||||
console.log('🔥 createUserGrid 호출됨:', boxId)
|
||||
// 🔥 DB에서 권한 코드 로딩
|
||||
AUTH_ITEMS = (await loadBaseCode('BS100')).map(r => ({
|
||||
id: r.id,
|
||||
text: r.text
|
||||
}))
|
||||
const { loadSummary = true, memberId = null } = options
|
||||
|
||||
destroyGrid('userGrid')
|
||||
|
||||
const grid = new w2grid({
|
||||
name: boxId === '#detailGrid' ? 'detailGrid' : 'userGrid',
|
||||
box: boxId,
|
||||
|
||||
show: {
|
||||
footer: true,
|
||||
lineNumbers: true,
|
||||
selectColumn: true,
|
||||
},
|
||||
|
||||
columns: [
|
||||
// {
|
||||
// field: 'recid',
|
||||
// text: '#',
|
||||
// size: '50px',
|
||||
// attr: 'align=center',
|
||||
// sortable: true,
|
||||
// },
|
||||
{ field: 'user_id', text: 'ID', size: '90px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'user_pw',
|
||||
text: 'PW',
|
||||
size: '90px',
|
||||
resizable: true,
|
||||
sortable: false,
|
||||
|
||||
editable: {
|
||||
type: 'password' // ✅ 입력 시 ●●●●
|
||||
},
|
||||
|
||||
render() {
|
||||
return '********' // ✅ 항상 마스킹
|
||||
}
|
||||
},
|
||||
{ field: 'user_nm', text: '이름', size: '90px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'tel_no',
|
||||
text: '연락처',
|
||||
size: '120px',
|
||||
editable: { type: 'text' },
|
||||
resizable: true,
|
||||
sortable: true
|
||||
},
|
||||
{ field: 'email', text: 'E-mail', size: '180px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{ field: 'dept_nm', text: '부서', size: '120px', editable: {type : 'text'}, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'use_area',
|
||||
text: '사용량(㎡)',
|
||||
size: '120px',
|
||||
attr: 'align=right',
|
||||
editable: false,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
|
||||
render(record) {
|
||||
const v = Number(record.use_area) || 0
|
||||
return v.toLocaleString() // ✅ 1,000단위 콤마
|
||||
}
|
||||
},
|
||||
{ field: 'cdt', text: '등록일', size: '100px', editable: false, resizable: true, sortable: true },
|
||||
{
|
||||
field: 'use_yn',
|
||||
text: '사용',
|
||||
size: '90px',
|
||||
attr: 'align=center',
|
||||
sortable: true,
|
||||
editable: {
|
||||
type: 'list',
|
||||
items: USE_ITEMS,
|
||||
showAll: true,
|
||||
openOnFocus: true
|
||||
},
|
||||
|
||||
render(record, extra) {
|
||||
return extra?.value?.text || ''
|
||||
}
|
||||
},
|
||||
/* ✅ 권한 콤보 (DB 연동) */
|
||||
{
|
||||
field: 'auth_bc',
|
||||
text: '권한',
|
||||
size: '120px',
|
||||
sortable: true,
|
||||
editable: {
|
||||
type: 'list',
|
||||
items: AUTH_ITEMS,
|
||||
showAll: true,
|
||||
openOnFocus: true
|
||||
},
|
||||
render(record, extra) {
|
||||
return extra?.value?.text || ''
|
||||
}
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '120px', editable: { type: 'text' }, resizable: true, sortable: true }
|
||||
],
|
||||
onEditField(event) {
|
||||
|
||||
const pwColIndex = this.getColumn('user_pw').index
|
||||
|
||||
// 🔥 PW 컬럼일 때만 처리
|
||||
if (event.column !== pwColIndex) return
|
||||
|
||||
event.onComplete = function () {
|
||||
|
||||
// 🔥 현재 편집 세션의 input만 정확히 집기
|
||||
const box = event.box
|
||||
if (!box) return
|
||||
|
||||
const input = box.querySelector('input[type="password"]')
|
||||
if (!input) return
|
||||
|
||||
// PW만 초기화
|
||||
input.value = ''
|
||||
input.placeholder = '변경 시에만 입력'
|
||||
}
|
||||
},
|
||||
onChange(event) {
|
||||
event.onComplete = function (ev) {
|
||||
|
||||
const rec = grid.get(ev.recid);
|
||||
if (!rec) return;
|
||||
|
||||
const field = grid.columns[ev.column].field;
|
||||
let val = ev.value_new;
|
||||
|
||||
/* ===============================
|
||||
📞 전화번호(tel_no) 자동 하이픈
|
||||
=============================== */
|
||||
if (field === 'tel_no') {
|
||||
|
||||
let digits = String(val || '').replace(/\D/g, '');
|
||||
|
||||
// 입력 중이면 건드리지 않음
|
||||
if (digits.length < 9) return;
|
||||
|
||||
let formatted = digits;
|
||||
|
||||
if (digits.length === 11) {
|
||||
formatted =
|
||||
`${digits.slice(0,3)}-${digits.slice(3,7)}-${digits.slice(7)}`;
|
||||
} else if (digits.length === 10) {
|
||||
formatted =
|
||||
`${digits.slice(0,3)}-${digits.slice(3,6)}-${digits.slice(6)}`;
|
||||
}
|
||||
|
||||
// ✅ 화면 값
|
||||
grid.set(ev.recid, { tel_no: formatted });
|
||||
|
||||
// ✅ 변경사항으로 "확정" (이게 핵심)
|
||||
grid.mergeChanges(ev.recid, { tel_no: formatted });
|
||||
|
||||
grid.refreshRow(ev.recid);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
공통 처리
|
||||
=============================== */
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
val = val.text;
|
||||
}
|
||||
|
||||
grid.set(ev.recid, { [field]: val });
|
||||
grid.mergeChanges(ev.recid, { [field]: val });
|
||||
};
|
||||
},
|
||||
records: [],
|
||||
})
|
||||
|
||||
loadData({
|
||||
loadSummary,
|
||||
memberId
|
||||
})
|
||||
}
|
||||
|
||||
export function loadUsersByMember(memberId) {
|
||||
|
||||
if (!memberId) return
|
||||
|
||||
const g = w2ui.detailGrid
|
||||
if (!g) {
|
||||
console.error('detailGrid 없음')
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`/kngil/bbs/adm_comp.php?action=list&member_id=${memberId}`)
|
||||
.then(res => res.json())
|
||||
.then(d => {
|
||||
|
||||
if (d.status !== 'success') {
|
||||
w2alert('사용자 조회 실패')
|
||||
return
|
||||
}
|
||||
const records = normalizeAuth(d.records || [])
|
||||
records = normalizeUseYn(records)
|
||||
g.clear()
|
||||
g.add(d.records)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
export function setUserGridMode(mode = 'view') {
|
||||
const g = w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
if (mode === 'view') {
|
||||
g.show.toolbar = false
|
||||
g.show.selectColumn = false
|
||||
g.show.toolbarSave = false
|
||||
} else {
|
||||
g.show.toolbar = true
|
||||
g.show.selectColumn = true
|
||||
g.show.toolbarSave = true
|
||||
}
|
||||
|
||||
g.refresh()
|
||||
}
|
||||
|
||||
export function loadData({ loadSummary = true } = {}) {
|
||||
fetch('/kngil/bbs/adm_comp.php?action=list')
|
||||
.then(res => res.json())
|
||||
.then(async d => {
|
||||
|
||||
if (d.status !== 'success') return
|
||||
|
||||
let records = normalizeAuth(d.records || [])
|
||||
records = normalizeUseYn(records)
|
||||
const gridName = w2ui.detailGrid ? 'detailGrid' : 'userGrid'
|
||||
|
||||
w2ui[gridName].clear()
|
||||
w2ui[gridName].add(records)
|
||||
|
||||
if (loadSummary && d.member_id) {
|
||||
const totalArea = await loadTotalArea()
|
||||
renderSummaryFromRecords({
|
||||
memberId: d.member_id,
|
||||
records,
|
||||
totalArea
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function renderSummaryFromRecords({ memberId, records, totalArea }) {
|
||||
|
||||
if (!records.length) return
|
||||
|
||||
const first = records[0]
|
||||
|
||||
const issuedCnt = Number(first.users_tot) || 0
|
||||
const usedCnt = Number(first.users_y) || 0
|
||||
const term = first.term || ''
|
||||
|
||||
// 사용 중 면적 합계
|
||||
let usedArea = 0
|
||||
records.forEach(r => {
|
||||
if (r.use_yn?.id === 'Y') {
|
||||
usedArea += Number(r.use_area || 0)
|
||||
}
|
||||
})
|
||||
|
||||
const percent = totalArea > 0
|
||||
? Math.floor((usedArea / totalArea) * 100)
|
||||
: 0
|
||||
|
||||
/* -------- 화면 바인딩 -------- */
|
||||
document.getElementById('memberId').textContent = memberId
|
||||
document.getElementById('planName').textContent = first.itm_nm || '-'
|
||||
document.getElementById('dateRange').textContent = term
|
||||
|
||||
document.getElementById('issuedCnt').textContent = issuedCnt
|
||||
document.getElementById('usedCnt').textContent = usedCnt
|
||||
|
||||
document.getElementById('usedArea').textContent =
|
||||
usedArea.toLocaleString() + '㎡'
|
||||
|
||||
document.getElementById('totalArea').textContent =
|
||||
totalArea.toLocaleString() + '㎡'
|
||||
|
||||
/* -------- 사용량 바 -------- */
|
||||
const bar = document.getElementById('usedBar')
|
||||
bar.style.width = percent + '%'
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============================
|
||||
// 저장 버튼
|
||||
// ===============================
|
||||
document.getElementById('btnSave_comp')?.addEventListener('click', () => {
|
||||
|
||||
// 현재 사용 중인 grid
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
const changes = g.getChanges()
|
||||
|
||||
if (!changes.length) {
|
||||
w2alert('변경된 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
const inserts = []
|
||||
const updates = []
|
||||
|
||||
changes.forEach(c => {
|
||||
const rec = g.get(c.recid)
|
||||
if (!rec) return
|
||||
|
||||
// 🔥 핵심: 원본 rec + 변경값 c 병합
|
||||
const merged = { ...rec, ...c }
|
||||
|
||||
if (rec.__isNew) {
|
||||
// INSERT → 전체 row 필요
|
||||
inserts.push(merged)
|
||||
} else {
|
||||
// UPDATE → PK + 변경 컬럼
|
||||
updates.push({
|
||||
member_id : merged.member_id,
|
||||
user_id : merged.user_id,
|
||||
|
||||
user_nm : merged.user_nm,
|
||||
dept_nm : merged.dept_nm,
|
||||
tel_no : merged.tel_no,
|
||||
email : merged.email,
|
||||
use_yn : merged.use_yn?.id || merged.use_yn,
|
||||
auth_bc : merged.auth_bc?.id || merged.auth_bc,
|
||||
rmks : (merged.memo !== undefined ? merged.memo : '')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
console.log('INSERTS', inserts)
|
||||
console.log('UPDATES', updates)
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'save',
|
||||
// member_id: g.records[0]?.member_id,
|
||||
inserts,
|
||||
updates
|
||||
})
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
if (json.status === 'success') {
|
||||
w2alert('저장 완료')
|
||||
loadData()
|
||||
} else {
|
||||
w2alert(json.message || '저장 실패')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(text)
|
||||
w2alert('서버 응답 오류 (JSON 아님)')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
//추가(insert)
|
||||
document.getElementById('btnAdd')?.addEventListener('click', () => {
|
||||
|
||||
const g = w2ui.userGrid || w2ui.detailGrid
|
||||
if (!g) return
|
||||
const defaultAuth = AUTH_ITEMS.find(a => a.id === 'BS100500') // 일반
|
||||
// 신규 row용 recid (음수로 충돌 방지)
|
||||
const newRecid = -Date.now()
|
||||
|
||||
g.add({
|
||||
recid: newRecid,
|
||||
__isNew: true,
|
||||
user_id: '',
|
||||
user_pw: '',
|
||||
user_nm: '',
|
||||
tel_no: '',
|
||||
email: '',
|
||||
dept_nm: '',
|
||||
use_area: 0,
|
||||
use_yn: USE_ITEMS[0],
|
||||
auth_bc: defaultAuth || null,
|
||||
memo: ''
|
||||
}, true)
|
||||
|
||||
g.select(newRecid)
|
||||
g.scrollIntoView(newRecid)
|
||||
})
|
||||
|
||||
//삭제
|
||||
document.getElementById('btnDelete')?.addEventListener('click', () => {
|
||||
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
if (!g) return
|
||||
|
||||
const sel = g.getSelection()
|
||||
|
||||
if (!sel.length) {
|
||||
w2alert('삭제할 사용자를 선택하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
// 선택된 user_id 수집
|
||||
const ids = sel
|
||||
.map(recid => {
|
||||
const r = g.get(recid)
|
||||
return r?.user_id
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
if (!ids.length) {
|
||||
w2alert('삭제 가능한 항목이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
w2confirm(`선택한 ${ids.length}명의 사용자를 삭제하시겠습니까?`)
|
||||
.yes(() => {
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'delete',
|
||||
// member_id: g.records[0]?.member_id,
|
||||
ids
|
||||
})
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
if (json.status === 'success') {
|
||||
w2alert('삭제 완료')
|
||||
loadData()
|
||||
} else {
|
||||
w2alert(json.message || '삭제 실패')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(text)
|
||||
w2alert('서버 응답 오류 (JSON 아님)')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function loadTotalArea(memberId) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=total_area`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error('총 구매면적 로딩 실패')
|
||||
}
|
||||
return Number(json.total_area || 0)
|
||||
})
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
|
||||
const keyword = document.getElementById('schKeyword').value.trim()
|
||||
const type = document.getElementById('schType').value
|
||||
const useYn = document.getElementById('schUseYn').value
|
||||
|
||||
let p_user_nm = ''
|
||||
let p_dept_nm = ''
|
||||
|
||||
// DB로 보낼 검색 조건
|
||||
if (type === 'name') {
|
||||
p_user_nm = keyword
|
||||
} else if (type === 'dept') {
|
||||
p_dept_nm = keyword
|
||||
} else if (type === '') {
|
||||
// 전체 검색 → 이름 OR 부서
|
||||
p_user_nm = keyword
|
||||
p_dept_nm = keyword
|
||||
}
|
||||
// ⚠️ type === 'id' 는 DB로 안 보냄
|
||||
|
||||
fetch(`/kngil/bbs/adm_comp.php?action=list`
|
||||
+ `&user_nm=${encodeURIComponent(p_user_nm)}`
|
||||
+ `&dept_nm=${encodeURIComponent(p_dept_nm)}`
|
||||
+ `&use_yn=${useYn}`
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then(d => {
|
||||
|
||||
if (d.status !== 'success') {
|
||||
w2alert('검색 실패')
|
||||
return
|
||||
}
|
||||
|
||||
let records = d.records || []
|
||||
|
||||
// ID 검색은 프론트 필터
|
||||
if (type === 'id' && keyword) {
|
||||
records = records.filter(r =>
|
||||
(r.user_id || '').toLowerCase()
|
||||
.includes(keyword.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
const g = w2ui.detailGrid || w2ui.userGrid
|
||||
records = normalizeAuth(records)
|
||||
records = normalizeUseYn(records)
|
||||
g.clear()
|
||||
g.add(records)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('btnSearch')
|
||||
?.addEventListener('click', () => {
|
||||
|
||||
const memberInput = document.getElementById('targetMemberId');
|
||||
const memberId = memberInput ? memberInput.value.trim() : '';
|
||||
|
||||
if (memberInput && memberInput.style.display !== 'none') {
|
||||
CURRENT_MEMBER_ID = memberId; // ⭐ 저장
|
||||
loadDataByMemberId(memberId);
|
||||
return;
|
||||
}
|
||||
|
||||
doSearch();
|
||||
});
|
||||
|
||||
document.getElementById('schKeyword')
|
||||
?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') doSearch()
|
||||
})
|
||||
|
||||
document.getElementById('btnBulkCreate')
|
||||
?.addEventListener('click', () => {
|
||||
|
||||
const memberId = getTargetMemberId()
|
||||
|
||||
if (!memberId) {
|
||||
w2alert('회원ID를 입력하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
// 🔥 여기서 CSV URL 팝업 띄움
|
||||
openBulkCreatePopup(memberId)
|
||||
})
|
||||
|
||||
function openBulkCreatePopup(memberId) {
|
||||
|
||||
// ⭐ 방어: 객체면 member_id만 사용
|
||||
if (typeof memberId === 'object' && memberId !== null) {
|
||||
memberId = memberId.member_id || '';
|
||||
}
|
||||
|
||||
if (!memberId) {
|
||||
w2alert('회원ID가 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
w2popup.open({
|
||||
title: '사용자 일괄 생성 (CSV)',
|
||||
width: 520,
|
||||
height: 220,
|
||||
modal: true,
|
||||
body: `
|
||||
<div style="padding:20px">
|
||||
<p style="margin-bottom:8px">
|
||||
대상 회원ID: <b>${memberId}</b>
|
||||
</p>
|
||||
|
||||
<input type="text"
|
||||
id="csvUrl"
|
||||
placeholder="CSV URL 입력"
|
||||
style="width:100%; padding:6px" />
|
||||
|
||||
<div style="margin-top:15px; text-align:right">
|
||||
<button class="w2ui-btn" id="btnCsvRun">실행</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
document
|
||||
.getElementById('btnCsvRun')
|
||||
.addEventListener('click', () => {
|
||||
|
||||
const url =
|
||||
document.getElementById('csvUrl').value.trim()
|
||||
|
||||
if (!url) {
|
||||
w2alert('CSV URL을 입력하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
runBulkCreate(memberId, url)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function runBulkCreate(memberId, csvUrl) {
|
||||
|
||||
fetch('/kngil/bbs/adm_comp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'bulk_create',
|
||||
member_id: memberId,
|
||||
csv_url: csvUrl
|
||||
})
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(text => {
|
||||
|
||||
let d;
|
||||
try {
|
||||
d = JSON.parse(text);
|
||||
} catch (e) {
|
||||
w2alert({
|
||||
title: '서버 오류',
|
||||
text: `<pre style="white-space:pre-wrap; text-align:left;">${text}</pre>`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const successCnt = Number(d.success_cnt || 0);
|
||||
const failCnt = Number(d.fail_cnt || 0);
|
||||
|
||||
let errors = d.errors || [];
|
||||
// 배열이지만 요소가 객체일 수 있음 → 문자열로 강제
|
||||
errors = errors.map(e => {
|
||||
if (typeof e === 'string') return e;
|
||||
try {
|
||||
return JSON.stringify(e);
|
||||
} catch {
|
||||
return String(e);
|
||||
}
|
||||
});
|
||||
|
||||
// // ⭐ 핵심: object → array 변환
|
||||
// if (!Array.isArray(errors) && typeof errors === 'object') {
|
||||
// errors = Object.values(errors);
|
||||
// }
|
||||
console.log('errors raw =', d.errors);
|
||||
// ❌ 전부 실패
|
||||
if (successCnt === 0) {
|
||||
w2popup.close(); // ⭐ 먼저 CSV 팝업 닫기
|
||||
|
||||
w2alert(`
|
||||
<div style="
|
||||
text-align:center;
|
||||
line-height:1.6;
|
||||
padding:10px;
|
||||
font-size:16px;
|
||||
">
|
||||
<b>일괄 생성 실패</b><br><br>
|
||||
${errors.map(e => `• ${e}`).join('<br>')}
|
||||
</div>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 성공 or 부분 성공
|
||||
let msg = '';
|
||||
msg += `✔ 성공: ${successCnt}명<br>`;
|
||||
msg += `❌ 실패: ${failCnt}명`;
|
||||
|
||||
if (errors.length > 0) {
|
||||
msg += '<hr style="margin:8px 0">';
|
||||
msg += '<div style="text-align:left; max-height:200px; overflow:auto;">';
|
||||
errors.forEach(err => {
|
||||
msg += `• ${err}<br>`;
|
||||
});
|
||||
msg += '</div>';
|
||||
}
|
||||
|
||||
w2alert({
|
||||
title: '일괄 생성 결과',
|
||||
msg: msg
|
||||
});
|
||||
|
||||
if (CURRENT_MEMBER_ID) {
|
||||
loadDataByMemberId(CURRENT_MEMBER_ID);
|
||||
} else {
|
||||
loadData();
|
||||
}
|
||||
w2popup.close();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
w2alert('서버 통신 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function loadDataByMemberId(memberId) {
|
||||
|
||||
if (!memberId) {
|
||||
w2alert('회원ID를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/kngil/bbs/adm_comp.php?action=list&member_id=${encodeURIComponent(memberId)}`)
|
||||
.then(res => res.json())
|
||||
.then(async d => {
|
||||
|
||||
if (d.status !== 'success') {
|
||||
w2alert(d.message || '조회 실패');
|
||||
return;
|
||||
}
|
||||
|
||||
let records = normalizeAuth(d.records || []);
|
||||
records = normalizeUseYn(records);
|
||||
|
||||
const gridName = w2ui.detailGrid ? 'detailGrid' : 'userGrid';
|
||||
w2ui[gridName].clear();
|
||||
w2ui[gridName].add(records);
|
||||
|
||||
// ✅ 상단 요약 갱신
|
||||
const totalArea = await loadTotalArea(memberId);
|
||||
renderSummaryFromRecords({
|
||||
memberId,
|
||||
records,
|
||||
totalArea
|
||||
});
|
||||
});
|
||||
}
|
||||
285
kngil/js/adm_faq_popup.js
Normal file
285
kngil/js/adm_faq_popup.js
Normal file
@@ -0,0 +1,285 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) {
|
||||
w2ui[name].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function loadBaseCode(mainCd) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error(json.message || '공통코드 로딩 실패')
|
||||
}
|
||||
return json.items
|
||||
})
|
||||
}
|
||||
|
||||
/* =================================================
|
||||
faq 팝업 (슈퍼관리자 전용)
|
||||
================================================= */
|
||||
export function openfaqPopup() {
|
||||
|
||||
if (w2ui.faqGrid) {
|
||||
w2ui.faqGrid.destroy()
|
||||
}
|
||||
w2popup.open({
|
||||
title: 'FAQ 내용등록',
|
||||
width: 900,
|
||||
height: 600,
|
||||
modal: true,
|
||||
|
||||
body: `
|
||||
<div class="faq-popup">
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; padding: 10px; gap: 5px;">
|
||||
<button id="faqAdd">추가</button>
|
||||
<button id="faqRemove">삭제</button>
|
||||
<button id="faqSave" class="btn-faq">저장</button>
|
||||
</div>
|
||||
|
||||
<div id="faqGrid" style="height:460px;"></div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
// 1. 그리드를 생성합니다.
|
||||
createfaqGrid('#faqGrid');
|
||||
|
||||
|
||||
// 1. 추가 버튼
|
||||
document.getElementById('faqAdd').onclick = () => {
|
||||
const g = w2ui.faqGrid
|
||||
if (!g) return
|
||||
// 신규 row용 recid (음수로 충돌 방지)
|
||||
const newRecid = -Date.now()
|
||||
g.add({
|
||||
fa_subject: '',
|
||||
recid: newRecid,
|
||||
__isNew: true, // ⭐ 신규 플래그
|
||||
fa_content: ''
|
||||
}, true)
|
||||
g.select(newRecid)
|
||||
g.scrollIntoView(newRecid)
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 삭제 버튼 이벤트 연결
|
||||
const removeBtn = document.getElementById('faqRemove');
|
||||
if (removeBtn) {
|
||||
removeBtn.onclick = () => {
|
||||
const g = w2ui.faqGrid;
|
||||
if (!g) return;
|
||||
|
||||
// 1. 선택된 행 가져오기
|
||||
const sel = g.getSelection();
|
||||
if (!sel.length) {
|
||||
alert('삭제할 항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. faq 추출
|
||||
const ids = sel
|
||||
.map(recid => g.get(recid)?.fa_id)
|
||||
.filter(Boolean);
|
||||
|
||||
if (!ids.length) {
|
||||
alert('삭제 가능한 항목이 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 브라우저 기본 확인창 사용 (가장 확실함)
|
||||
if (confirm(`선택한 ${ids.length}개의 상품을 삭제하시겠습니까?`)) {
|
||||
fetch('/kngil/bbs/adm_faq_popup_delete.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'delete', ids: ids })
|
||||
})
|
||||
.then(res => {
|
||||
// 서버 응답이 오면 일단 텍스트로 받아서 확인
|
||||
return res.text();
|
||||
})
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
if (json.status === 'success') {
|
||||
alert('삭제 완료');
|
||||
loadfaqData(); // 목록 새로고침
|
||||
} else {
|
||||
alert(json.message || '삭제 실패');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('응답 파싱 에러:', text);
|
||||
alert('서버 응답 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('통신 에러:', err);
|
||||
alert('서버와 통신할 수 없습니다.');
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 3. 저장 버튼
|
||||
document.getElementById('faqSave').onclick = () => {
|
||||
|
||||
// 현재 사용 중인 grid
|
||||
const g = w2ui.faqGrid
|
||||
if (!g) return
|
||||
const changes = g.getChanges()
|
||||
if (!changes.length) {
|
||||
w2alert('변경된 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
const inserts = []
|
||||
const updates = []
|
||||
|
||||
changes.forEach(c => {
|
||||
const rec = g.get(c.recid)
|
||||
if (!rec) return
|
||||
// 🔥 핵심: 원본 rec + 변경값 c 병합
|
||||
const merged = { ...rec, ...c }
|
||||
if (rec.__isNew) {
|
||||
// INSERT → 전체 row 필요
|
||||
inserts.push(merged)
|
||||
} else {
|
||||
// UPDATE → PK + 변경 컬럼
|
||||
updates.push({
|
||||
fa_id : merged.fa_id,
|
||||
fa_subject : merged.fa_subject ,
|
||||
fa_content : merged.fa_content ,
|
||||
use_yn : merged.use_yn,
|
||||
sq_no : merged.sq_no
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
console.log('INSERTS', inserts)
|
||||
console.log('UPDATES', updates)
|
||||
|
||||
fetch('/kngil/bbs/adm_faq_popup_save.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'save',
|
||||
inserts,
|
||||
updates
|
||||
})
|
||||
})
|
||||
|
||||
.then(res => {
|
||||
// 서버가 준 응답이 괜찮은지 확인
|
||||
if (!res.ok) throw new Error('서버 응답 오류');
|
||||
return res.json(); // 처음부터 깔끔하게 데이터로 읽기
|
||||
})
|
||||
|
||||
.then(json => {
|
||||
// 이제 json.status를 바로 쓸 수 있습니다.
|
||||
if (json.status === 'success') {
|
||||
w2alert('저장 완료');
|
||||
w2ui.faqGrid.save(); // 빨간 삼각형 없애기
|
||||
loadfaqData(); // 목록 새로고침
|
||||
} else {
|
||||
w2alert(json.message || '저장 실패');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('에러 발생:', err);
|
||||
w2alert('처리 중 오류가 발생했습니다.');
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//faq 그리드
|
||||
export async function createfaqGrid(boxId) {
|
||||
if (w2ui.faqGrid) w2ui.faqGrid.destroy();
|
||||
destroyGrid('faqGrid')
|
||||
|
||||
const authItems = await loadBaseCode('BS210')
|
||||
|
||||
const grid = new w2grid({
|
||||
|
||||
name: 'faqGrid',
|
||||
box: boxId,
|
||||
recordHeight: 80, // ⭐ 행 높이를 80px로 설정 (3줄 정도 높이)
|
||||
show: {
|
||||
footer: true,
|
||||
selectColumn: true,
|
||||
lineNumbers: true // 행 번호를 표시하면 디버깅이 편합니다.
|
||||
},
|
||||
columns: [
|
||||
/*
|
||||
{ field: 'row_status', text: ' ', size: '30px', attr: 'align=center',
|
||||
render: function (record) {
|
||||
// 1. 신규 추가된 행 (recid가 임시값이거나 DB에 없는 경우)
|
||||
if (record.is_new) return '<span style="color: green;">I</span>';
|
||||
|
||||
// 2. 수정된 데이터 (w2ui.changes 객체가 존재하는 경우)
|
||||
if (record.w2ui && record.w2ui.changes) return '<span style="color: blue;">U</span>';
|
||||
// 3. 일반 상태
|
||||
return '<span style="color: gray;"> </span>';
|
||||
}
|
||||
},
|
||||
*/
|
||||
{ field: 'fa_id', text: 'FAID', size: '60',attr: 'align=center',style: 'text-align: center', attr: 'align=center' ,hidden: true,sortable: true}, // name 아님!
|
||||
{ field: 'fa_subject', text: '질문', size: '300', editable: { type: 'text' }
|
||||
,render: function(record) {
|
||||
if (!record.fa_content) return '';
|
||||
// 엔터값(\n)을 브라우저가 인식하는 <br>로 바꿔서 리턴
|
||||
return record.fa_content.replace(/\n/g, '<br>');
|
||||
}
|
||||
}, // name 아님!
|
||||
{ field: 'fa_content', text: '답글', size: '300', editable: { type: 'text' } }, // volume 아님!
|
||||
{ field: 'sq_no', text: '순번', size: '60', attr: 'align=center',style: 'text-align: center',render: 'number:0', editable: { type: 'int' } ,sortable: true}, //
|
||||
{ field: 'use_yn', text: '사용여부', size: '80px',attr: 'align=center',style: 'text-align: center', attr: 'align=center',
|
||||
editable: { type: 'list', items: authItems, filter: false ,showAll: true } ,
|
||||
render(record) {
|
||||
const item = authItems.find(i => i.id === record.use_yn)
|
||||
return item ? item.text : record.use_yn
|
||||
},sortable: true
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '200px', attr: 'align=center', editable: { type: 'text' } } // memo 아님!
|
||||
],
|
||||
records: [] // 처음엔 비워둠
|
||||
});
|
||||
|
||||
// 데이터 호출 함수 실행
|
||||
loadfaqData();
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
|
||||
// 데이터를 서버에서 불러오는 함수
|
||||
async function loadfaqData() {
|
||||
try {
|
||||
w2ui.faqGrid.lock('조회 중...', true);
|
||||
|
||||
const response = await fetch('/kngil/bbs/adm_faq_popup.php'); // PHP 파일 호출
|
||||
const data = await response.json();
|
||||
|
||||
w2ui.faqGrid.clear();
|
||||
w2ui.faqGrid.add(data);
|
||||
w2ui.faqGrid.unlock();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
w2ui.faqGrid.unlock();
|
||||
w2alert('데이터 로드 실패');
|
||||
}
|
||||
}
|
||||
280
kngil/js/adm_product_popup.js
Normal file
280
kngil/js/adm_product_popup.js
Normal file
@@ -0,0 +1,280 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) {
|
||||
w2ui[name].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function loadBaseCode(mainCd) {
|
||||
return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
throw new Error(json.message || '공통코드 로딩 실패')
|
||||
}
|
||||
return json.items
|
||||
})
|
||||
}
|
||||
|
||||
/* =================================================
|
||||
상품등록 팝업 (슈퍼관리자 전용)
|
||||
================================================= */
|
||||
export function openProductPopup() {
|
||||
|
||||
if (w2ui.productGrid) {
|
||||
w2ui.productGrid.destroy()
|
||||
}
|
||||
w2popup.open({
|
||||
title: '상품등록',
|
||||
width: 900,
|
||||
height: 600,
|
||||
modal: true,
|
||||
|
||||
body: `
|
||||
<div class="product-popup">
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; padding: 1S0px; gap: 5px;">
|
||||
<button id="prodAdd">추가</button>
|
||||
<button id="prodRemove">삭제</button>
|
||||
<button id="prodSave" class="btn-product">저장</button>
|
||||
</div>
|
||||
|
||||
<div id="productGrid" style="height:460px;"></div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
// 1. 그리드를 생성합니다.
|
||||
createProductGrid('#productGrid');
|
||||
|
||||
|
||||
// 1. 추가 버튼
|
||||
document.getElementById('prodAdd').onclick = () => {
|
||||
const g = w2ui.productGrid
|
||||
if (!g) return
|
||||
// 신규 row용 recid (음수로 충돌 방지)
|
||||
const newRecid = -Date.now()
|
||||
g.add({
|
||||
row_status: 'I',
|
||||
recid: newRecid,
|
||||
__isNew: true, // ⭐ 신규 플래그
|
||||
use_yn: 'Y'
|
||||
}, true)
|
||||
g.select(newRecid)
|
||||
g.scrollIntoView(newRecid)
|
||||
|
||||
// commonAdd('productGrid', { use_yn: 'Y', itm_amt: 0, area: 0 });
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 삭제 버튼 이벤트 연결
|
||||
const removeBtn = document.getElementById('prodRemove');
|
||||
if (removeBtn) {
|
||||
removeBtn.onclick = () => {
|
||||
const g = w2ui.productGrid;
|
||||
if (!g) return;
|
||||
|
||||
// 1. 선택된 행 가져오기
|
||||
const sel = g.getSelection();
|
||||
if (!sel.length) {
|
||||
alert('삭제할 상품을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 상품코드 추출
|
||||
const ids = sel
|
||||
.map(recid => g.get(recid)?.itm_cd)
|
||||
.filter(Boolean);
|
||||
|
||||
if (!ids.length) {
|
||||
alert('삭제 가능한 항목이 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 브라우저 기본 확인창 사용 (가장 확실함)
|
||||
if (confirm(`선택한 ${ids.length}개의 상품을 삭제하시겠습니까?`)) {
|
||||
fetch('/kngil/bbs/adm_product_popup_delete.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'delete', ids: ids })
|
||||
})
|
||||
.then(res => {
|
||||
// 서버 응답이 오면 일단 텍스트로 받아서 확인
|
||||
return res.text();
|
||||
})
|
||||
.then(text => {
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
if (json.status === 'success') {
|
||||
alert('삭제 완료');
|
||||
loadProductData(); // 목록 새로고침
|
||||
} else {
|
||||
alert(json.message || '삭제 실패');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('응답 파싱 에러:', text);
|
||||
alert('서버 응답 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('통신 에러:', err);
|
||||
alert('서버와 통신할 수 없습니다.');
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 3. 저장 버튼
|
||||
document.getElementById('prodSave').onclick = () => {
|
||||
|
||||
// 현재 사용 중인 grid
|
||||
const g = w2ui.productGrid
|
||||
if (!g) return
|
||||
const changes = g.getChanges()
|
||||
if (!changes.length) {
|
||||
w2alert('변경된 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
const inserts = []
|
||||
const updates = []
|
||||
|
||||
changes.forEach(c => {
|
||||
const rec = g.get(c.recid)
|
||||
if (!rec) return
|
||||
// 🔥 핵심: 원본 rec + 변경값 c 병합
|
||||
const merged = { ...rec, ...c }
|
||||
if (rec.__isNew) {
|
||||
// INSERT → 전체 row 필요
|
||||
inserts.push(merged)
|
||||
} else {
|
||||
// UPDATE → PK + 변경 컬럼
|
||||
updates.push({
|
||||
itm_cd : merged.itm_cd,
|
||||
itm_nm : merged.itm_nm,
|
||||
area : merged.area,
|
||||
itm_amt: merged.itm_amt,
|
||||
use_yn : merged.use_yn,
|
||||
rmks : merged.rmks
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
console.log('INSERTS', inserts)
|
||||
console.log('UPDATES', updates)
|
||||
|
||||
fetch('/kngil/bbs/adm_product_popup_save.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'save',
|
||||
inserts,
|
||||
updates
|
||||
})
|
||||
})
|
||||
|
||||
.then(res => {
|
||||
// 서버가 준 응답이 괜찮은지 확인
|
||||
if (!res.ok) throw new Error('서버 응답 오류');
|
||||
return res.json(); // 처음부터 깔끔하게 데이터로 읽기
|
||||
})
|
||||
|
||||
.then(json => {
|
||||
// 이제 json.status를 바로 쓸 수 있습니다.
|
||||
if (json.status === 'success') {
|
||||
w2alert('저장 완료');
|
||||
w2ui.productGrid.save(); // 빨간 삼각형 없애기
|
||||
loadProductData(); // 목록 새로고침
|
||||
} else {
|
||||
w2alert(json.message || '저장 실패');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('에러 발생:', err);
|
||||
w2alert('처리 중 오류가 발생했습니다.');
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//상품등록 그리드
|
||||
export async function createProductGrid(boxId) {
|
||||
if (w2ui.productGrid) w2ui.productGrid.destroy();
|
||||
destroyGrid('productGrid')
|
||||
|
||||
const authItems = await loadBaseCode('BS210')
|
||||
|
||||
const grid = new w2grid({
|
||||
|
||||
name: 'productGrid',
|
||||
box: boxId,
|
||||
show: {
|
||||
footer: true,
|
||||
selectColumn: true,
|
||||
lineNumbers: true // 행 번호를 표시하면 디버깅이 편합니다.
|
||||
},
|
||||
columns: [
|
||||
/*
|
||||
{ field: 'row_status', text: ' ', size: '30px', attr: 'align=center',
|
||||
render: function (record) {
|
||||
// 1. 신규 추가된 행 (recid가 임시값이거나 DB에 없는 경우)
|
||||
if (record.is_new) return '<span style="color: green;">I</span>';
|
||||
|
||||
// 2. 수정된 데이터 (w2ui.changes 객체가 존재하는 경우)
|
||||
if (record.w2ui && record.w2ui.changes) return '<span style="color: blue;">U</span>';
|
||||
// 3. 일반 상태
|
||||
return '<span style="color: gray;"> </span>';
|
||||
}
|
||||
},
|
||||
*/
|
||||
{ field: 'itm_cd', text: '상품코드', size: '80px',attr: 'align=center',style: 'text-align: center', attr: 'align=center', editable: { type: 'text' } ,sortable: true}, // name 아님!
|
||||
{ field: 'itm_nm', text: '상품명', size: '120px', attr: 'align=center',style: 'text-align: center', editable: { type: 'text' } ,sortable: true}, // name 아님!
|
||||
{ field: 'area', text: '제공량', size: '100px', attr: 'align=center',render: 'number:0' , editable: { type: 'float' } ,sortable: true}, // volume 아님!
|
||||
{ field: 'itm_amt', text: '단가', size: '120px', attr: 'align=center',render: 'number:0', editable: { type: 'int' } ,sortable: true}, //
|
||||
{ field: 'use_yn', text: '사용여부', size: '80px',attr: 'align=center',style: 'text-align: center', attr: 'align=center',
|
||||
editable: { type: 'list', items: authItems, filter: false ,showAll: true } ,
|
||||
render(record) {
|
||||
const item = authItems.find(i => i.id === record.use_yn)
|
||||
return item ? item.text : record.use_yn
|
||||
},sortable: true
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '200px', attr: 'align=center', editable: { type: 'text' } ,sortable: true} // memo 아님!
|
||||
],
|
||||
records: [] // 처음엔 비워둠
|
||||
});
|
||||
|
||||
// 데이터 호출 함수 실행
|
||||
loadProductData();
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
|
||||
// 데이터를 서버에서 불러오는 함수
|
||||
async function loadProductData() {
|
||||
try {
|
||||
w2ui.productGrid.lock('조회 중...', true);
|
||||
|
||||
const response = await fetch('/kngil/bbs/adm_product_popup.php'); // PHP 파일 호출
|
||||
const data = await response.json();
|
||||
|
||||
w2ui.productGrid.clear();
|
||||
w2ui.productGrid.add(data);
|
||||
w2ui.productGrid.unlock();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
w2ui.productGrid.unlock();
|
||||
w2alert('데이터 로드 실패');
|
||||
}
|
||||
}
|
||||
144
kngil/js/adm_purch_popup.js
Normal file
144
kngil/js/adm_purch_popup.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
// 파일이 실제 존재하는지 확인 필수! 존재하지 않으면 아래 코드가 모두 멈춥니다.
|
||||
import { bindProductPopupEvents } from '/kngil/js/adm_common.js'
|
||||
|
||||
|
||||
|
||||
export function openPurchaseHistoryPopup(memberId = '',isSuperAdmin='') {
|
||||
// 기존 그리드 안전하게 제거
|
||||
if (w2ui.purchaseHistoryGrid) {
|
||||
w2ui.purchaseHistoryGrid.destroy();
|
||||
}
|
||||
|
||||
w2popup.open({
|
||||
title: '서비스 구매 이력',
|
||||
width: 1100,
|
||||
height: 600,
|
||||
modal: true,
|
||||
body:
|
||||
/*
|
||||
`
|
||||
<div style="padding:8px">
|
||||
<div id="purchaseHistoryGrid" style="height:520px;"></div>
|
||||
</div>
|
||||
*/
|
||||
`
|
||||
<div id="popupMainContainer" style="display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; box-sizing: border-box;">
|
||||
|
||||
<div id="searchFormArea" style="display: flex; align-items: center; gap: 10px; padding: 15px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<span style="font-weight: bold; color: #333;">회원 ID</span>
|
||||
<input type="text" id="searchMemberId" value="${memberId}"
|
||||
${!isSuperAdmin ? 'disabled' : ''}
|
||||
style="width: 150px; padding: 6px; border: 1px solid #ccc; border-radius: 4px;"
|
||||
placeholder="아이디 입력">
|
||||
|
||||
<button id="btnSearchHistory"
|
||||
style="padding: 6px 20px; background: #1565c0; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||
검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="purchaseHistoryGrid" style="flex: 1; min-height: 450px; border: 1px solid #ddd;"></div>
|
||||
</div>
|
||||
|
||||
`,
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
const searchBtn = document.getElementById('btnSearchHistory');
|
||||
const searchInput = document.getElementById('searchMemberId');
|
||||
|
||||
// 조회 버튼 클릭 시 동작
|
||||
if (searchBtn) {
|
||||
searchBtn.onclick = () => {
|
||||
loadPurchaseHistoryData(searchInput.value);
|
||||
};
|
||||
}
|
||||
|
||||
// 엔터키 지원
|
||||
if (searchInput) {
|
||||
searchInput.onkeydown = (e) => {
|
||||
if (e.key === 'Enter') searchBtn.click();
|
||||
};
|
||||
}
|
||||
|
||||
createPurchaseHistoryGrid(memberId);
|
||||
loadPurchaseHistoryData(memberId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createPurchaseHistoryGrid(memberId) {
|
||||
new w2grid({
|
||||
name: 'purchaseHistoryGrid',
|
||||
box: '#purchaseHistoryGrid',
|
||||
show: {
|
||||
//toolbar: true,
|
||||
footer: true,
|
||||
lineNumbers: true // 행 번호를 표시하면 디버깅이 편합니다.
|
||||
},
|
||||
columns: [
|
||||
// field 이름이 PHP에서 내려주는 JSON 키값과 정확히 일치해야 함!
|
||||
{ field: 'member_id', text: '회원ID', size: '80px',style: 'text-align: center', attr: 'align=center',sortable: true }, // name 아님!
|
||||
{ field: 'sq_no', text: '순번', size: '50px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'user_nm', text: '구매자', size: '80px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'co_nm', text: '회사명', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'bs_no', text: '사업자번호', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'buy_dt', text: '구입일자', size: '80px',style: 'text-align: center', attr: 'align=center',sortable: true }, // name 아님!
|
||||
{ field: 'itm_nm', text: '상품명', size: '80px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'area', text: '상품면적', size: '80px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'itm_qty', text: '수량', size: '80px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'itm_area', text: '면적', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'add_area', text: '추가면적', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'sum_area', text: '합계면적', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'itm_amt', text: '단가', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'dis_rt', text: '할인율', size: '80px', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'buy_amt', text: '공급금액', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'vat_amt', text: '부가세', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'sum_amt', text: '합계', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'end_dt', text: '종료일', size: '80px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'ok_yn', text: '승인여부', size: '80px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'rmks', text: '비고', size: '150px',style: 'text-align: center', attr: 'align=center' ,sortable: true} // name 아님!
|
||||
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPurchaseHistoryData(memberId) {
|
||||
try {
|
||||
const grid = w2ui.purchaseHistoryGrid;
|
||||
if (!grid) return;
|
||||
|
||||
grid.lock('조회 중...', true);
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('member_id', memberId);
|
||||
searchParams.append('member_nm', '');
|
||||
searchParams.append('fbuy_dt', '');
|
||||
searchParams.append('tbuy_dt', '');
|
||||
|
||||
const response = await fetch('/kngil/bbs/adm_purch_popup.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: searchParams
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`서버 응답 오류: ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('응답 데이터 형식 오류');
|
||||
}
|
||||
|
||||
grid.clear();
|
||||
grid.add(data);
|
||||
grid.unlock();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (w2ui.purchaseHistoryGrid) w2ui.purchaseHistoryGrid.unlock();
|
||||
w2alert('구매이력 조회 실패: ' + e.message);
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
237
kngil/js/adm_service copy.js
Normal file
237
kngil/js/adm_service copy.js
Normal file
@@ -0,0 +1,237 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert }
|
||||
from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) w2ui[name].destroy()
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
서비스 등록 팝업
|
||||
------------------------------------------------- */
|
||||
export function openServiceRegisterPopup(ctx) {
|
||||
|
||||
destroyGrid('serviceGrid')
|
||||
destroyGrid('productList')
|
||||
|
||||
w2popup.open({
|
||||
title: '서비스 등록',
|
||||
width: 1300,
|
||||
height: 720,
|
||||
modal: true,
|
||||
body: `
|
||||
<div class="service-popup">
|
||||
|
||||
<!-- 상단 -->
|
||||
<div class="service-top">
|
||||
|
||||
<!-- 좌측 : 회원정보 + 구매일 -->
|
||||
<div class="service-member">
|
||||
<div><b>회원ID</b> : ${ctx.memberId}</div>
|
||||
<div><b>회원명</b> : ${ctx.memberName}</div>
|
||||
<div><b>회사명</b> : ${ctx.company}</div>
|
||||
<div><b>사업자번호</b> : ${ctx.bizNo}</div>
|
||||
|
||||
<div class="purchase-date">
|
||||
<label>구매일</label>
|
||||
<input type="date" id="buyDate">
|
||||
<button id="btnSearchBuy">조회</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우측 : 상품 선택 -->
|
||||
<div class="service-product">
|
||||
<div class="title">상품 선택 (더블클릭)</div>
|
||||
<div id="productList" style="height:160px;"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 중단 -->
|
||||
<div class="service-grid">
|
||||
<div id="serviceGrid" style="height:260px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 하단 -->
|
||||
<div class="service-summary">
|
||||
<div>공급가액 <span id="sumSupply">0</span></div>
|
||||
<div>부가세 <span id="sumVat">0</span></div>
|
||||
<div>결제금액 <span id="sumTotal">0</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`,
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
createServiceGrid()
|
||||
createProductList()
|
||||
|
||||
// ✅ 구매일 조회 버튼 바인딩 (여기서!)
|
||||
document.getElementById('btnSearchBuy').addEventListener('click', () => {
|
||||
const buyDate = document.getElementById('buyDate').value
|
||||
if (!buyDate) {
|
||||
w2alert('구매일을 선택하세요')
|
||||
return
|
||||
}
|
||||
loadExistingPurchase(ctx.memberId, buyDate)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
서비스 Grid
|
||||
------------------------------------------------- */
|
||||
function createServiceGrid() {
|
||||
|
||||
new w2grid({
|
||||
name: 'serviceGrid',
|
||||
box: '#serviceGrid',
|
||||
show: {
|
||||
footer: true,
|
||||
// selectColumn: true
|
||||
},
|
||||
columns: [
|
||||
{ field: 'recid', text: 'NO', size: '50px', attr: 'align=center' },
|
||||
{ field: 'itm_cd', text: '상품명', size: '100px' },
|
||||
{ field: 'itm_area', text: '면적(m²)', size: '100px', editable: { type: 'int' } },
|
||||
{ field: 'itm_amt', text: '금액', size: '100px', attr: 'align=right' },
|
||||
{ field: 'itm_qty', text: '수량', size: '80px', editable: { type: 'int' } },
|
||||
{ field: 'dis_rt', text: '할인율', size: '80px', editable: { type: 'int' } },
|
||||
{ field: 'supply', text: '공급가액', size: '120px', attr: 'align=right' },
|
||||
{ field: 'vat_amt', text: '부가세', size: '100px', attr: 'align=right' },
|
||||
{ field: 'sum_amt', text: '결제금액', size: '120px', attr: 'align=right' },
|
||||
{ field: 'end_dt', text: '만료일자', size: '110px', editable: { type: 'date' } },
|
||||
{ field: 'rmks', text: '비고', size: '150px', editable: { type: 'text' } }
|
||||
],
|
||||
records: [],
|
||||
onChange() {
|
||||
calcSummary()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
상품 목록
|
||||
------------------------------------------------- */
|
||||
function createProductList() {
|
||||
|
||||
new w2grid({
|
||||
name: 'productList',
|
||||
box: '#productList',
|
||||
columns: [
|
||||
{ field: 'name', text: '상품명', size: '120px' },
|
||||
{ field: 'area', text: '제공량', size: '100px', attr: 'align=right' },
|
||||
{ field: 'price', text: '단가', size: '100px', attr: 'align=right' }
|
||||
],
|
||||
records: [
|
||||
{ recid: 1, name: '다이아', area: 10000000, price: 10000000 },
|
||||
{ recid: 2, name: '골드', area: 1000000, price: 5000000 },
|
||||
{ recid: 3, name: '실버', area: 100000, price: 1000000 }
|
||||
],
|
||||
onDblClick(event) {
|
||||
const rec = this.get(event.recid)
|
||||
addServiceFromProduct(rec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
상품 → 서비스 Grid 추가
|
||||
------------------------------------------------- */
|
||||
function addServiceFromProduct(p) {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
const recid = g.records.length + 1
|
||||
|
||||
const supply = p.price
|
||||
const vat = Math.floor(supply * 0.1)
|
||||
const total = supply + vat
|
||||
|
||||
g.add({
|
||||
recid,
|
||||
itm_cd: p.name,
|
||||
itm_area: p.area,
|
||||
itm_amt: p.price,
|
||||
itm_qty: 1,
|
||||
dis_rt: 0,
|
||||
supply,
|
||||
vat_amt: vat,
|
||||
sum_amt: total,
|
||||
end_dt: '',
|
||||
rmks: ''
|
||||
})
|
||||
|
||||
calcSummary()
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
기존 구매 불러오기
|
||||
------------------------------------------------- */
|
||||
function loadExistingPurchase(memberId, buyDate) {
|
||||
|
||||
fetch(`/kngil/bbs/adm_service.php?member_id=${memberId}&buy_date=${buyDate}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
|
||||
if (json.status !== 'success') {
|
||||
w2alert(json.message || '조회 실패')
|
||||
return
|
||||
}
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
g.clear()
|
||||
|
||||
json.records.forEach((r, i) => {
|
||||
g.add({
|
||||
recid: i + 1,
|
||||
itm_cd: r.itm_cd,
|
||||
itm_area: Number(r.itm_area),
|
||||
itm_amt: Number(r.itm_amt),
|
||||
itm_qty: Number(r.itm_qty),
|
||||
dis_rt: Number(r.dis_rt),
|
||||
supply: Number(r.supply),
|
||||
vat_amt: Number(r.vat_amt),
|
||||
sum_amt: Number(r.sum_amt),
|
||||
end_dt: r.end_dt,
|
||||
rmks: r.rmks,
|
||||
_existing: true
|
||||
})
|
||||
|
||||
// 🔒 기존 구매 회색 처리
|
||||
g.set(i + 1, {
|
||||
w2ui: { style: 'background:#f3f3f3;color:#555' }
|
||||
})
|
||||
})
|
||||
|
||||
g.refresh()
|
||||
calcSummary()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
w2alert('서버 오류')
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
합계 계산
|
||||
------------------------------------------------- */
|
||||
function calcSummary() {
|
||||
|
||||
let supply = 0
|
||||
let vat = 0
|
||||
let total = 0
|
||||
|
||||
w2ui.serviceGrid.records.forEach(r => {
|
||||
supply += Number(r.supply || 0)
|
||||
vat += Number(r.vat_amt || 0)
|
||||
total += Number(r.sum_amt || 0)
|
||||
})
|
||||
|
||||
document.getElementById('sumSupply').innerText = supply.toLocaleString()
|
||||
document.getElementById('sumVat').innerText = vat.toLocaleString()
|
||||
document.getElementById('sumTotal').innerText = total.toLocaleString()
|
||||
}
|
||||
678
kngil/js/adm_service.js
Normal file
678
kngil/js/adm_service.js
Normal file
@@ -0,0 +1,678 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert, w2confirm }
|
||||
from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------- */
|
||||
function destroyGrid(name) {
|
||||
if (w2ui[name]) w2ui[name].destroy()
|
||||
}
|
||||
|
||||
function fmtNum(val) {
|
||||
return Number(val || 0).toLocaleString()
|
||||
}
|
||||
|
||||
function fmtPrice(val) {
|
||||
const v = Number(val || 0)
|
||||
return Number.isInteger(v)
|
||||
? v.toLocaleString()
|
||||
: v.toLocaleString(undefined, { minimumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
서비스 등록 팝업
|
||||
------------------------------------------------- */
|
||||
export function openServiceRegisterPopup(ctx) {
|
||||
|
||||
destroyGrid('serviceGrid')
|
||||
destroyGrid('productList')
|
||||
|
||||
w2popup.open({
|
||||
title: '서비스 등록',
|
||||
width: 1300,
|
||||
height: 720,
|
||||
modal: true,
|
||||
body: `
|
||||
<div class="service-popup">
|
||||
|
||||
<!-- 상단 -->
|
||||
<div class="service-top">
|
||||
|
||||
<!-- 좌측 : 회원정보 -->
|
||||
<div class="service-member">
|
||||
<div><b>회원ID</b> : ${ctx.memberId}</div>
|
||||
<div><b>회원명</b> : ${ctx.memberName}</div>
|
||||
<div><b>회사명</b> : ${ctx.company}</div>
|
||||
<div><b>사업자번호</b> : ${ctx.bizNo}</div>
|
||||
|
||||
<div class="purchase-date">
|
||||
<label>구매일</label>
|
||||
<input type="date" id="buyDate">
|
||||
<button id="btnSearchBuy">조회</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우측 : 상품 선택 -->
|
||||
<div class="service-product">
|
||||
<div class="title">상품 선택 (더블클릭)</div>
|
||||
<div id="productList" style="height:160px;"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- 중단 -->
|
||||
<div class="service-save-bar">
|
||||
<div class="right-actions">
|
||||
<button id="btnDeleteRow" class="btn-delete">삭제</button>
|
||||
<button id="btnSaveService" class="btn-save">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-grid">
|
||||
<div id="serviceGrid" style="height:260px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 하단 합계 -->
|
||||
<div class="service-summary">
|
||||
<div>공급가액 <span id="sumSupply">0</span></div>
|
||||
<div>부가세 <span id="sumVat">0</span></div>
|
||||
<div>결제금액 <span id="sumTotal">0</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`,
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
|
||||
createServiceGrid()
|
||||
createProductList()
|
||||
|
||||
const buyDateInput = document.getElementById('buyDate')
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
|
||||
// 기본 구매일 = 오늘
|
||||
buyDateInput.value = today
|
||||
|
||||
// 자동 조회
|
||||
loadExistingPurchase(ctx.memberId, today)
|
||||
|
||||
// 수동 조회 버튼
|
||||
document.getElementById('btnSearchBuy').addEventListener('click', () => {
|
||||
const buyDate = buyDateInput.value
|
||||
if (!buyDate) {
|
||||
w2alert('구매일을 선택하세요')
|
||||
return
|
||||
}
|
||||
loadExistingPurchase(ctx.memberId, buyDate)
|
||||
})
|
||||
|
||||
document.getElementById('btnDeleteRow')
|
||||
.addEventListener('click', deleteSelectedRows)
|
||||
|
||||
document.getElementById('btnSaveService')
|
||||
.addEventListener('click', () => saveService(ctx))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
서비스 Grid
|
||||
------------------------------------------------- */
|
||||
function createServiceGrid() {
|
||||
|
||||
new w2grid({
|
||||
name: 'serviceGrid',
|
||||
box: '#serviceGrid',
|
||||
show: {
|
||||
footer: true,
|
||||
selectColumn: true
|
||||
},
|
||||
multiSelect: false,
|
||||
|
||||
columns: [
|
||||
{ field: 'recid', text: 'NO', size: '50px', attr: 'align=center' },
|
||||
{ field: 'itm_nm', text: '상품명', size: '120px', editable: { type: 'text' } },
|
||||
{ field: 'area', text: '제공량', size: '120px', editable: { type: 'text' } },
|
||||
{ field: 'itm_cd', text: '상품코드', hidden: true },
|
||||
{
|
||||
field: 'itm_amt',
|
||||
text: '단가',
|
||||
size: '100px',
|
||||
attr: 'align=right',
|
||||
render: r => fmtPrice(r.itm_amt)
|
||||
},
|
||||
{
|
||||
field: 'itm_qty',
|
||||
text: '수량',
|
||||
size: '80px',
|
||||
attr: 'align=right',
|
||||
editable: { type: 'int' },
|
||||
render: r => fmtNum(r.itm_qty)
|
||||
},
|
||||
{
|
||||
field: 'dis_rt',
|
||||
text: '할인율',
|
||||
size: '80px',
|
||||
editable: { type: 'int' }
|
||||
},
|
||||
{
|
||||
field: 'supply',
|
||||
text: '공급가액',
|
||||
size: '120px',
|
||||
attr: 'align=right',
|
||||
render: r => fmtNum(r.supply)
|
||||
},
|
||||
{
|
||||
field: 'vat_amt',
|
||||
text: '부가세',
|
||||
size: '100px',
|
||||
attr: 'align=right',
|
||||
render: r => fmtNum(r.vat_amt)
|
||||
},
|
||||
{
|
||||
field: 'sum_amt',
|
||||
text: '결제금액',
|
||||
size: '120px',
|
||||
attr: 'align=right',
|
||||
render: r => fmtNum(r.sum_amt)
|
||||
},
|
||||
{
|
||||
field: 'itm_area',
|
||||
text: '적용면적(m²)',
|
||||
size: '100px',
|
||||
attr: 'align=right',
|
||||
editable: { type: 'int' },
|
||||
render: r => fmtNum(r.itm_area)
|
||||
},
|
||||
{
|
||||
field: 'add_area',
|
||||
text: '추가면적(m²)',
|
||||
size: '110px',
|
||||
attr: 'align=right',
|
||||
editable: { type: 'int' },
|
||||
render: r => fmtNum(r.add_area)
|
||||
},
|
||||
{
|
||||
field: 'sum_area',
|
||||
text: '합계면적(m²)',
|
||||
size: '110px',
|
||||
attr: 'align=right',
|
||||
editable: { type: 'int' },
|
||||
render: r => fmtNum(r.sum_area)
|
||||
},
|
||||
{
|
||||
field: 'end_dt',
|
||||
text: '만료일자',
|
||||
size: '110px',
|
||||
editable: { type: 'date' }
|
||||
},
|
||||
{
|
||||
field: 'ok_yn',
|
||||
text: '승인여부',
|
||||
size: '80px',
|
||||
attr: 'align=center',
|
||||
editable: {
|
||||
type: 'combo',
|
||||
items: [
|
||||
{ id: 'N', text: '미승인' },
|
||||
{ id: 'Y', text: '승인' }
|
||||
],
|
||||
openOnFocus: true
|
||||
},
|
||||
render(record) {
|
||||
if (record.ok_yn === 'Y') return '승인'
|
||||
return '미승인'
|
||||
}
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '150px', editable: { type: 'text' } }
|
||||
],
|
||||
|
||||
records: [],
|
||||
|
||||
/* -----------------------------
|
||||
일반 필드 처리 (date 제외)
|
||||
----------------------------- */
|
||||
onChange(event) {
|
||||
event.onComplete = (ev) => {
|
||||
const g = w2ui.serviceGrid
|
||||
const field = g.columns[ev.column].field
|
||||
|
||||
let val = ev.value_new
|
||||
if (typeof val === 'object' && val !== null) val = val.id
|
||||
|
||||
g.set(ev.recid, { [field]: val })
|
||||
|
||||
if (['itm_qty','dis_rt','add_area'].includes(field)) {
|
||||
recalcRow(ev.recid)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* -----------------------------
|
||||
날짜 전용 처리 (핵심)
|
||||
----------------------------- */
|
||||
onEditField(event) {
|
||||
if (event.field !== 'end_dt') return;
|
||||
|
||||
event.onComplete = () => {
|
||||
const g = w2ui.serviceGrid;
|
||||
const recid = event.recid;
|
||||
|
||||
const input = document.querySelector(
|
||||
`#grid_${g.name}_edit input`
|
||||
);
|
||||
if (!input) return;
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
|
||||
// 🔥 날짜는 문자열 그대로 즉시 확정
|
||||
g.set(recid, {
|
||||
end_dt: input.value // YYYY-MM-DD
|
||||
});
|
||||
|
||||
// 🔥 w2ui 방식으로 edit 종료
|
||||
input.blur();
|
||||
|
||||
g.refreshRow(recid);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
상품 목록
|
||||
------------------------------------------------- */
|
||||
function createProductList() {
|
||||
|
||||
new w2grid({
|
||||
name: 'productList',
|
||||
box: '#productList',
|
||||
url: '/kngil/bbs/adm_product_popup.php',
|
||||
columns: [
|
||||
{ field: 'itm_nm', text: '상품명', size: '120px' },
|
||||
{
|
||||
field: 'area',
|
||||
text: '제공량',
|
||||
size: '100px',
|
||||
attr: 'align=right',
|
||||
render(record) {
|
||||
return Number(record.area || 0).toLocaleString()
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'itm_amt',
|
||||
text: '단가',
|
||||
size: '120px',
|
||||
attr: 'align=right',
|
||||
render(record) {
|
||||
const v = Number(record.itm_amt || 0)
|
||||
// 정수면 소수점 제거
|
||||
return Number.isInteger(v)
|
||||
? v.toLocaleString()
|
||||
: v.toLocaleString(undefined, { minimumFractionDigits: 2 })
|
||||
}
|
||||
}
|
||||
],
|
||||
onDblClick(event) {
|
||||
event.onComplete = () => {
|
||||
const recid = event.detail?.recid
|
||||
if (!recid) return
|
||||
|
||||
const rec = this.get(recid)
|
||||
if (!rec) return
|
||||
|
||||
addServiceFromProduct(rec)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
상품 → 서비스 Grid 추가
|
||||
------------------------------------------------- */
|
||||
function addServiceFromProduct(p) {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
const recid = g.records.length + 1
|
||||
|
||||
const qty = 1
|
||||
const area = Number(p.area || 0)
|
||||
const unit = Number(p.itm_amt || 0)
|
||||
|
||||
const today = new Date()
|
||||
const endDt = `${today.getFullYear()}-12-31`
|
||||
|
||||
g.add({
|
||||
recid,
|
||||
itm_cd: p.itm_cd,
|
||||
itm_nm: p.itm_nm,
|
||||
|
||||
area: area, // 제공량
|
||||
itm_qty: qty,
|
||||
itm_amt: unit,
|
||||
dis_rt: 0,
|
||||
|
||||
itm_area: area * qty,
|
||||
add_area: 0,
|
||||
sum_area: area * qty,
|
||||
|
||||
supply: unit * qty,
|
||||
vat_amt: Math.floor(unit * qty * 0.1),
|
||||
sum_amt: Math.floor(unit * qty * 1.1),
|
||||
|
||||
end_dt: endDt,
|
||||
ok_yn: 'N',
|
||||
rmks: '',
|
||||
|
||||
_new: true
|
||||
})
|
||||
|
||||
recalcRow(recid)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
행 삭제
|
||||
------------------------------------------------- */
|
||||
function deleteSelectedRows() {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
const sel = g.getSelection()
|
||||
|
||||
if (!sel.length) {
|
||||
w2alert('삭제할 서비스를 선택하세요')
|
||||
return
|
||||
}
|
||||
|
||||
const recid = sel[0]
|
||||
const row = g.get(recid)
|
||||
|
||||
if (!row || !row.sq_no) {
|
||||
w2alert('삭제할 수 없는 항목입니다.')
|
||||
return
|
||||
}
|
||||
|
||||
if (row.ok_yn === 'Y') {
|
||||
w2alert('승인된 항목은 삭제할 수 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
w2confirm('선택한 서비스를 삭제하시겠습니까?')
|
||||
.yes(() => {
|
||||
deleteServiceImmediately(row)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteServiceImmediately(row) {
|
||||
console.log({
|
||||
action: 'delete',
|
||||
member_id: row.member_id,
|
||||
sq_no: row.sq_no
|
||||
})
|
||||
|
||||
fetch('/kngil/bbs/adm_service.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'delete',
|
||||
member_id: row.member_id, // ✅ 여기 중요
|
||||
sq_no: row.sq_no
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('삭제되었습니다.')
|
||||
w2ui.serviceGrid.remove(row.recid)
|
||||
calcSummary()
|
||||
} else {
|
||||
w2alert(res.message || '삭제 실패')
|
||||
}
|
||||
})
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
}
|
||||
|
||||
|
||||
function isServiceItem(r) {
|
||||
return r.itm_cd && r.itm_cd.startsWith('ZET01')
|
||||
|| r.itm_nm === '서비스'
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
기존 구매 불러오기
|
||||
------------------------------------------------- */
|
||||
function loadExistingPurchase(memberId, buyDate) {
|
||||
|
||||
fetch('/kngil/bbs/adm_service.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'list',
|
||||
member_id: memberId,
|
||||
buy_date: buyDate
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
|
||||
if (json.status !== 'success') {
|
||||
w2alert(json.message || '조회 실패')
|
||||
return
|
||||
}
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
g.clear()
|
||||
|
||||
json.records.forEach((r, i) => {
|
||||
g.add({
|
||||
recid: i + 1,
|
||||
member_id: memberId,
|
||||
sq_no: r.sq_no,
|
||||
|
||||
itm_cd: r.itm_cd,
|
||||
itm_nm: r.itm_nm,
|
||||
itm_area: Number(r.itm_area || 0),
|
||||
add_area: Number(r.add_area || 0),
|
||||
itm_amt: Number(r.itm_amt),
|
||||
itm_qty: Number(r.itm_qty),
|
||||
|
||||
// area
|
||||
area: isServiceItem(r)
|
||||
? 1
|
||||
: (r.itm_qty ? Number(r.itm_area) / Number(r.itm_qty) : 0),
|
||||
|
||||
dis_rt: Number(r.dis_rt || 0), // 문자열 → 숫자
|
||||
supply: Number(r.buy_amt),
|
||||
vat_amt: Number(r.vat_amt),
|
||||
sum_amt: Number(r.sum_amt),
|
||||
|
||||
end_dt: r.end_dt || '',
|
||||
ok_yn: r.ok_yn || 'N',
|
||||
rmks: r.rmks ?? '', // undefined 방지
|
||||
|
||||
_existing: true
|
||||
})
|
||||
|
||||
g.set(i + 1, {
|
||||
w2ui: { style: 'background:#f3f3f3;color:#555' }
|
||||
})
|
||||
})
|
||||
|
||||
g.refresh()
|
||||
// 🔥 모든 행 재계산
|
||||
g.records.forEach(r => {
|
||||
if (!r._deleted) {
|
||||
recalcRow(r.recid)
|
||||
}
|
||||
})
|
||||
calcSummary()
|
||||
})
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
행 단위 재계산
|
||||
------------------------------------------------- */
|
||||
function recalcRow(recid) {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
const r = g.get(recid)
|
||||
if (!r) return
|
||||
|
||||
const qty = Number(r.itm_qty || 0)
|
||||
const unit = Number(r.itm_amt || 0)
|
||||
const rate = Number(r.dis_rt || 0)
|
||||
const area = Number(r.area || 0)
|
||||
const add = Number(r.add_area || 0)
|
||||
|
||||
// 🔥 적용면적 = 제공량 * 수량
|
||||
r.itm_area = area * qty
|
||||
|
||||
// 🔥 공급가액 = 단가 * 수량 * (1 - 할인율)
|
||||
const base = unit * qty
|
||||
const discount = Math.floor(base * (rate / 100))
|
||||
r.supply = base - discount
|
||||
|
||||
// 🔥 부가세 / 결제금액
|
||||
r.vat_amt = Math.floor(r.supply * 0.1)
|
||||
r.sum_amt = r.supply + r.vat_amt
|
||||
|
||||
// 🔥 합계면적
|
||||
r.sum_area = r.itm_area + add
|
||||
|
||||
g.refreshRow(recid)
|
||||
calcSummary()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
합계 계산
|
||||
------------------------------------------------- */
|
||||
function calcSummary() {
|
||||
|
||||
let supply = 0
|
||||
let vat = 0
|
||||
let total = 0
|
||||
|
||||
w2ui.serviceGrid.records.forEach(r => {
|
||||
if (r._deleted) return
|
||||
supply += Number(r.supply || 0)
|
||||
vat += Number(r.vat_amt || 0)
|
||||
total += Number(r.sum_amt || 0)
|
||||
})
|
||||
|
||||
document.getElementById('sumSupply').innerText = supply.toLocaleString()
|
||||
document.getElementById('sumVat').innerText = vat.toLocaleString()
|
||||
document.getElementById('sumTotal').innerText = total.toLocaleString()
|
||||
}
|
||||
|
||||
function saveService(ctx) {
|
||||
|
||||
const buyDate = document.getElementById('buyDate').value
|
||||
if (!buyDate) {
|
||||
w2alert('구매일을 선택하세요')
|
||||
return
|
||||
}
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
|
||||
// 🔥🔥🔥 핵심: 현재 편집중인 셀 강제 반영
|
||||
g.save()
|
||||
|
||||
const items = g.records.map(r => ({
|
||||
sq_no: r.sq_no || null,
|
||||
itm_cd: r.itm_cd,
|
||||
itm_area: r.itm_area,
|
||||
add_area: r.add_area || 0,
|
||||
itm_amt: r.itm_amt,
|
||||
itm_qty: r.itm_qty,
|
||||
|
||||
dis_rt: r.dis_rt,
|
||||
buy_amt: r.supply,
|
||||
vat_amt: r.vat_amt,
|
||||
sum_amt: r.sum_amt,
|
||||
end_dt: dateToYMD(r.end_dt),
|
||||
ok_yn: r.ok_yn || 'N',
|
||||
rmks: r.rmks,
|
||||
|
||||
_new: r._new || false,
|
||||
_existing: r._existing || false,
|
||||
_deleted: r._deleted || false
|
||||
}))
|
||||
|
||||
fetch('/kngil/bbs/adm_service.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'save',
|
||||
member_id: ctx.memberId,
|
||||
buy_date: buyDate,
|
||||
items
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('저장되었습니다.')
|
||||
loadExistingPurchase(ctx.memberId, buyDate)
|
||||
} else {
|
||||
w2alert(res.message || '저장 실패')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeDate(val) {
|
||||
if (!val) return null
|
||||
|
||||
// 이미 YYYY-MM-DD면 그대로
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
||||
return val
|
||||
}
|
||||
|
||||
// MM/DD/YYYY → YYYY-MM-DD
|
||||
const d = new Date(val)
|
||||
if (isNaN(d)) return null
|
||||
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
|
||||
function dateToYMD(val) {
|
||||
if (!val) return null
|
||||
|
||||
// Date 객체
|
||||
if (val instanceof Date) {
|
||||
return [
|
||||
val.getFullYear(),
|
||||
String(val.getMonth() + 1).padStart(2, '0'),
|
||||
String(val.getDate()).padStart(2, '0')
|
||||
].join('-')
|
||||
}
|
||||
|
||||
// 문자열 (MM/DD/YYYY or YYYY-MM-DD)
|
||||
if (typeof val === 'string') {
|
||||
|
||||
// 이미 YYYY-MM-DD
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
||||
return val
|
||||
}
|
||||
|
||||
// MM/DD/YYYY
|
||||
const d = new Date(val)
|
||||
if (!isNaN(d)) {
|
||||
return [
|
||||
d.getFullYear(),
|
||||
String(d.getMonth() + 1).padStart(2, '0'),
|
||||
String(d.getDate()).padStart(2, '0')
|
||||
].join('-')
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
158
kngil/js/adm_use_history.js
Normal file
158
kngil/js/adm_use_history.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert } from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
// 파일이 실제 존재하는지 확인 필수! 존재하지 않으면 아래 코드가 모두 멈춥니다.
|
||||
import { bindProductPopupEvents } from '/kngil/js/adm_common.js'
|
||||
|
||||
|
||||
|
||||
export function openuseHistoryPopup(memberId = '',isSuperAdmin='') {
|
||||
// 기존 그리드 안전하게 제거
|
||||
if (w2ui.UseHistoryGrid) {
|
||||
w2ui.UseHistoryGrid.destroy();
|
||||
}
|
||||
|
||||
w2popup.open({
|
||||
title: '서비스 사용 이력',
|
||||
width: 1100,
|
||||
height: 600,
|
||||
modal: true,
|
||||
body:
|
||||
/*
|
||||
`
|
||||
<div style="padding:8px">
|
||||
<div id="UseHistoryGrid" style="height:520px;"></div>
|
||||
</div>
|
||||
*/
|
||||
`
|
||||
<div id="popupMainContainer" style="display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; box-sizing: border-box;">
|
||||
|
||||
<div id="searchFormArea" style="display: flex; align-items: center; gap: 20px; padding: 15px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px;">
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-weight: bold; color: #333; white-space: nowrap;">회원 ID</span>
|
||||
<input type="text" id="searchMemberId" value="${memberId}"
|
||||
${!isSuperAdmin ? 'disabled' : ''}
|
||||
style="width: 120px; padding: 6px; border: 1px solid #ccc; border-radius: 4px;"
|
||||
placeholder="아이디">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-weight: bold; color: #333; white-space: nowrap;">성명</span>
|
||||
<input type="text" id="searchUserNm"
|
||||
style="width: 120px; padding: 6px; border: 1px solid #ccc; border-radius: 4px;"
|
||||
placeholder="성명 입력">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-weight: bold; color: #333; white-space: nowrap;">부서</span>
|
||||
<input type="text" id="searchDeptNm"
|
||||
style="width: 150px; padding: 6px; border: 1px solid #ccc; border-radius: 4px;"
|
||||
placeholder="부서명 입력">
|
||||
</div>
|
||||
|
||||
<button id="btnSearchHistory"
|
||||
style="padding: 6px 20px; background: #1565c0; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; margin-left: auto;">
|
||||
검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="UseHistoryGrid" style="flex: 1; min-height: 450px; border: 1px solid #ddd;"></div>
|
||||
</div>
|
||||
|
||||
`,
|
||||
onOpen(event) {
|
||||
event.onComplete = () => {
|
||||
const searchBtn = document.getElementById('btnSearchHistory');
|
||||
const searchInput = document.getElementById('searchMemberId');
|
||||
const inputUnm = document.getElementById('searchUserNm');
|
||||
const inputDnm = document.getElementById('searchDeptNm');
|
||||
|
||||
// 조회 버튼 클릭 시 동작
|
||||
if (searchBtn) {
|
||||
searchBtn.onclick = () => {
|
||||
loadUseHistoryData(searchInput.value,inputUnm,inputDnm);
|
||||
};
|
||||
}
|
||||
|
||||
// 엔터키 지원
|
||||
if (searchInput,inputUnm,inputDnm) {
|
||||
searchInput.onkeydown = (e) => {
|
||||
if (e.key === 'Enter') searchBtn.click();
|
||||
};
|
||||
}
|
||||
|
||||
createUseHistoryGrid(memberId);
|
||||
loadUseHistoryData(memberId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createUseHistoryGrid(memberId) {
|
||||
new w2grid({
|
||||
name: 'UseHistoryGrid',
|
||||
box: '#UseHistoryGrid',
|
||||
show: {
|
||||
//toolbar: true,
|
||||
footer: true,
|
||||
lineNumbers: true // 행 번호를 표시하면 디버깅이 편합니다.
|
||||
},
|
||||
columns: [
|
||||
// field 이름이 PHP에서 내려주는 JSON 키값과 정확히 일치해야 함!
|
||||
{ field: 'member_id', text: '회원ID', size: '100px',style: 'text-align: center', attr: 'align=center',hidden: true }, // name 아님!
|
||||
{ field: 'use_dt', text: '사용일자', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'user_id', text: '사용자ID', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'sq_no', text: '순번', size: '100px',style: 'text-align: center', attr: 'align=center' ,hidden: true}, // name 아님!
|
||||
{ field: 'user_nm', text: '성명', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'dept_nm', text: '부서', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'posit_nm', text: '직위', size: '100px',style: 'text-align: center', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'use_yn', text: '사용여부', size: '80px', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'use_area', text: '사용면적', size: '100px',render: 'number:0' , attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
{ field: 'ser_bc', text: '서비스구분', size: '150px', attr: 'align=center' ,sortable: true}, // name 아님!
|
||||
|
||||
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function loadUseHistoryData(memberId = ''){
|
||||
try {
|
||||
const grid = w2ui.UseHistoryGrid;
|
||||
if (!grid) return;
|
||||
|
||||
// DOM에서 현재 입력된 값을 실시간으로 읽어옴
|
||||
const sMid = document.getElementById('searchMemberId')?.value || '';
|
||||
const sUnm = document.getElementById('searchUserNm')?.value || '';
|
||||
const sDnm = document.getElementById('searchDeptNm')?.value || '';
|
||||
|
||||
grid.lock('조회 중...', true);
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('member_id', sMid);
|
||||
searchParams.append('user_nm', sUnm);
|
||||
searchParams.append('dept_nm', sDnm);
|
||||
|
||||
const response = await fetch('/kngil/bbs/adm_use_history.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: searchParams
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`서버 응답 오류: ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('응답 데이터 형식 오류');
|
||||
}
|
||||
|
||||
grid.clear();
|
||||
grid.add(data);
|
||||
grid.unlock();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (w2ui.UseHistoryGrid) w2ui.UseHistoryGrid.unlock();
|
||||
w2alert('사용이력 조회 실패: ' + e.message);
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
4289
kngil/js/common.js
Normal file
4289
kngil/js/common.js
Normal file
File diff suppressed because one or more lines are too long
1325
kngil/js/faq/faq_common.js
Normal file
1325
kngil/js/faq/faq_common.js
Normal file
File diff suppressed because it is too large
Load Diff
256
kngil/js/faq/faq_popup.js
Normal file
256
kngil/js/faq/faq_popup.js
Normal file
@@ -0,0 +1,256 @@
|
||||
window.onload = function() {
|
||||
$.ajax({
|
||||
url: "some_api_endpoint",
|
||||
success: function(response) {
|
||||
},
|
||||
complete: function() {
|
||||
|
||||
// 팝업 닫기버튼
|
||||
$(document).ready(function() {
|
||||
$('.btn_close').click(function() {
|
||||
$('.popup_wrap').hide();
|
||||
$('body').css('overflow', ''); // 기본 스크롤 상태로 복귀
|
||||
lenis.start();
|
||||
console.log('lenis 재시작')
|
||||
});
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$('.btn_map_close').click(function() {
|
||||
$('.popup_sitemap').hide();
|
||||
$('body').css('overflow', ''); // 기본 스크롤 상태로 복귀
|
||||
lenis.start();
|
||||
console.log('lenis 재시작')
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 이메일 직접입력
|
||||
$(document).ready(function() {
|
||||
$('#domain-list').change(function() {
|
||||
if ($(this).val() === 'type') {
|
||||
$('#custom-domain').show().focus();
|
||||
} else {
|
||||
$('#custom-domain').hide().val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 인증번호 타이머
|
||||
$(document).ready(function() {
|
||||
// 인증번호 버튼 클릭 시
|
||||
$('.cert_number').click(function() {
|
||||
$('.code').show();
|
||||
});
|
||||
|
||||
// 확인 버튼 클릭 시
|
||||
$('.check').click(function() {
|
||||
$(this).hide();
|
||||
$('.check.complete').show();
|
||||
clearInterval(interval); // 타이머 멈춤
|
||||
$('.timer').remove(); // 타이머 요소 삭제
|
||||
});
|
||||
|
||||
// 타이머 함수
|
||||
var interval;
|
||||
function startTimer(duration, display) {
|
||||
clearInterval(interval);
|
||||
var timer = duration, minutes, seconds;
|
||||
|
||||
function updateTimer() {
|
||||
minutes = parseInt(timer / 60, 10);
|
||||
seconds = parseInt(timer % 60, 10);
|
||||
|
||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||
seconds = seconds < 10 ? "0" + seconds : seconds;
|
||||
|
||||
display.text(minutes + ":" + seconds);
|
||||
|
||||
if (--timer < 0) {
|
||||
clearInterval(interval);
|
||||
display.text("00:00");
|
||||
}
|
||||
}
|
||||
|
||||
updateTimer();
|
||||
interval = setInterval(updateTimer, 1000);
|
||||
}
|
||||
|
||||
var threeMinutes = 60 * 3,
|
||||
display = $('.timer');
|
||||
|
||||
startTimer(threeMinutes, display);
|
||||
|
||||
$('.cert_number').click(function() {
|
||||
startTimer(threeMinutes, display);
|
||||
});
|
||||
});
|
||||
|
||||
// 아이디찾기
|
||||
$(document).ready(function(){
|
||||
$('.find_email').click(function(){
|
||||
$('.find_ph').removeClass('on').prop('checked', false);
|
||||
$(this).addClass('on').prop('checked', true);
|
||||
$('.ph').hide();
|
||||
$('.email').show();
|
||||
});
|
||||
|
||||
$('.find_ph').click(function(){
|
||||
$('.find_email').removeClass('on').prop('checked', false);
|
||||
$(this).addClass('on').prop('checked', true);
|
||||
$('.email').hide();
|
||||
$('.ph').show();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.btn_id').on('click', function() {
|
||||
$('.btn_id').addClass('on');
|
||||
$('.btn_pw').removeClass('on');
|
||||
$('.content.id').show();
|
||||
$('.content.pw').hide();
|
||||
});
|
||||
|
||||
$('.btn_pw').on('click', function() {
|
||||
$('.btn_pw').addClass('on');
|
||||
$('.btn_id').removeClass('on');
|
||||
$('.content.pw').show();
|
||||
$('.content.id').hide();
|
||||
});
|
||||
|
||||
$('#domain-list').on('change', function() {
|
||||
if ($(this).val() === 'type') {
|
||||
$('#custom-domain').show();
|
||||
} else {
|
||||
$('#custom-domain').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 전체약관동의
|
||||
// $(document).ready(function() {
|
||||
// function toggleJoinButton() {
|
||||
// // 모든 개별 체크박스가 체크되었는지 확인
|
||||
// var allChecked = $('.terms_wrap input[type="checkbox"]').length === $('.terms_wrap input[type="checkbox"]:checked').length;
|
||||
|
||||
// // '약관에 모두 동의합니다' 체크박스 상태에 따라 조정
|
||||
// $('.checkbox_wrap.all input[type="checkbox"]').prop('checked', allChecked);
|
||||
|
||||
// // 모든 체크박스가 체크되지 않은 경우 버튼에 'none' 클래스 추가하고 disabled 속성 추가
|
||||
// if (allChecked) {
|
||||
// $('.join_btn_wrap').removeClass('none');
|
||||
// $('.join_btn_wrap button').prop('disabled', false);
|
||||
// } else {
|
||||
// $('.join_btn_wrap').addClass('none');
|
||||
// $('.join_btn_wrap button').prop('disabled', true);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // '약관에 모두 동의합니다' 체크박스의 변경 이벤트
|
||||
// $('.checkbox_wrap.all input[type="checkbox"]').on('change', function() {
|
||||
// var isChecked = $(this).is(':checked');
|
||||
// $('.terms_wrap input[type="checkbox"]').prop('checked', isChecked);
|
||||
// toggleJoinButton(); // 버튼 상태 업데이트
|
||||
// });
|
||||
|
||||
// // 각 terms_wrap의 개별 체크박스 변경 이벤트
|
||||
// $('.terms_wrap input[type="checkbox"]').on('change', function() {
|
||||
// toggleJoinButton(); // 버튼 상태 업데이트
|
||||
// });
|
||||
|
||||
// // 초기 상태 설정
|
||||
// toggleJoinButton();
|
||||
// });
|
||||
|
||||
// 전체약관동의 수정 250813
|
||||
(function ($) {
|
||||
if (window.__agreeBound) return; // 중복 바인딩 방지
|
||||
window.__agreeBound = true;
|
||||
|
||||
const $container = $('#pop_agreement');
|
||||
const $items = $container.find('.terms_wrap input[type="checkbox"]'); // agree11, agree21
|
||||
const $all = $container.find('.checkbox_wrap.all input[type="checkbox"]');
|
||||
const $btn = $container.find('#btn_agree');
|
||||
|
||||
function syncAll() {
|
||||
const allChecked = $items.length > 0 && $items.filter(':checked').length === $items.length;
|
||||
$all.prop('checked', allChecked);
|
||||
// 버튼은 disable 하지 않음 (스타일만 조정하고 싶다면 클래스만 토글)
|
||||
// $('.join_btn_wrap').toggleClass('none', !allChecked); <-- 필요 없으면 제거
|
||||
}
|
||||
|
||||
// 전체동의 → 개별
|
||||
$all.on('change', function () {
|
||||
const on = $(this).is(':checked');
|
||||
$items.prop('checked', on);
|
||||
syncAll();
|
||||
});
|
||||
|
||||
// 개별 → 전체동의 동기화
|
||||
$items.on('change', syncAll);
|
||||
|
||||
// 동의 버튼 클릭 시에만 검사
|
||||
$btn.off('click.agree').on('click.agree', function (e) {
|
||||
e.preventDefault();
|
||||
const allChecked = $items.filter(':checked').length === $items.length;
|
||||
if (!allChecked) {
|
||||
alert('약관에 모두 동의해주세요.');
|
||||
return false;
|
||||
}
|
||||
// 통과 시 다음 단계로 진행(필요 시 주석 해제)
|
||||
// $('#pop_agreement').hide();
|
||||
// $('#pop_register_form').show();
|
||||
// $('body').css('overflow','hidden');
|
||||
});
|
||||
|
||||
// 외부에서 팝업 열 때 상태 초기화가 필요하면 이 함수 호출
|
||||
window.resetAgreementUI = function () {
|
||||
$items.prop('checked', false);
|
||||
$all.prop('checked', false);
|
||||
syncAll();
|
||||
// 버튼은 항상 활성
|
||||
$('#btn_agree, #fregister button[type=submit]')
|
||||
.prop('disabled', false)
|
||||
.css('pointer-events', 'auto');
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
||||
// 가입완료
|
||||
$(document).ready(function() {
|
||||
$('.join.completion .join_btn_wrap button').click(function() {
|
||||
$('.pop_input_wrap form').children().not('.messages').hide();
|
||||
$('.messages').show();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.join.completion .join_btn_wrap button').click(function() {
|
||||
$('.pop_input_wrap form').children().not('.messages').hide();
|
||||
$('.messages').show();
|
||||
|
||||
// 세 번째 단계에 'on' 클래스 추가하고, 다른 단계에서 'on' 클래스 제거
|
||||
$('.join_progress .join_step').removeClass('on');
|
||||
$('.join_progress .join_step').eq(2).addClass('on');
|
||||
});
|
||||
});
|
||||
|
||||
// 개인정보 보호정책 스크립트
|
||||
$(document).ready(function() {
|
||||
$('.tab_privacy').on('click', function() {
|
||||
$(this).addClass('on');
|
||||
$('.tab_agreement').removeClass('on');
|
||||
$('.content.pri').addClass('show').removeClass('hide');
|
||||
$('.content.agr').removeClass('show').addClass('hide');
|
||||
});
|
||||
$('.tab_agreement').on('click', function() {
|
||||
$(this).addClass('on');
|
||||
$('.tab_privacy').removeClass('on');
|
||||
$('.content.agr').addClass('show').removeClass('hide');
|
||||
$('.content.pri').removeClass('show').addClass('hide');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
5
kngil/js/faq/jquery-1.12.4.min.js
vendored
Normal file
5
kngil/js/faq/jquery-1.12.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
kngil/js/faq/jquery-3.6.1.min.js
vendored
Normal file
2
kngil/js/faq/jquery-3.6.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
kngil/js/faq/jquery-migrate-1.4.1.min.js
vendored
Normal file
2
kngil/js/faq/jquery-migrate-1.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1374
kngil/js/faq/jquery.bxslider.js
Normal file
1374
kngil/js/faq/jquery.bxslider.js
Normal file
File diff suppressed because it is too large
Load Diff
109
kngil/js/faq/jquery.menu.js
Normal file
109
kngil/js/faq/jquery.menu.js
Normal file
@@ -0,0 +1,109 @@
|
||||
$(function(){
|
||||
var hide_menu = false;
|
||||
var mouse_event = false;
|
||||
var oldX = oldY = 0;
|
||||
|
||||
$(document).mousemove(function(e) {
|
||||
if(oldX == 0) {
|
||||
oldX = e.pageX;
|
||||
oldY = e.pageY;
|
||||
}
|
||||
|
||||
if(oldX != e.pageX || oldY != e.pageY) {
|
||||
mouse_event = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 주메뉴
|
||||
var $gnb = $(".gnb_1dli > a");
|
||||
$gnb.mouseover(function() {
|
||||
if(mouse_event) {
|
||||
$("#hd").addClass("hd_zindex");
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
$(this).parent().addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).parent());
|
||||
hide_menu = false;
|
||||
}
|
||||
});
|
||||
|
||||
$gnb.mouseout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$(".gnb_2dli").mouseover(function() {
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$(".gnb_2dli").mouseout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$gnb.focusin(function() {
|
||||
$("#hd").addClass("hd_zindex");
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
$(this).parent().addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).parent());
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$gnb.focusout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$(".gnb_2da").focusin(function() {
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
var $gnb_li = $(this).closest(".gnb_1dli").addClass("gnb_1dli_over gnb_1dli_on");
|
||||
menu_rearrange($(this).closest(".gnb_1dli"));
|
||||
hide_menu = false;
|
||||
});
|
||||
|
||||
$(".gnb_2da").focusout(function() {
|
||||
hide_menu = true;
|
||||
});
|
||||
|
||||
$('#gnb_1dul>li').bind('mouseleave',function(){
|
||||
submenu_hide();
|
||||
});
|
||||
|
||||
$(document).bind('click focusin',function(){
|
||||
if(hide_menu) {
|
||||
submenu_hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function submenu_hide() {
|
||||
$("#hd").removeClass("hd_zindex");
|
||||
$(".gnb_1dli").removeClass("gnb_1dli_over gnb_1dli_over2 gnb_1dli_on");
|
||||
}
|
||||
|
||||
function menu_rearrange(el)
|
||||
{
|
||||
var width = $("#gnb_1dul").width();
|
||||
var left = w1 = w2 = 0;
|
||||
var idx = $(".gnb_1dli").index(el);
|
||||
var max_menu_count = 0;
|
||||
var $gnb_1dli;
|
||||
|
||||
for(i=0; i<=idx; i++) {
|
||||
$gnb_1dli = $(".gnb_1dli:eq("+i+")");
|
||||
w1 = $gnb_1dli.outerWidth();
|
||||
|
||||
if($gnb_1dli.find(".gnb_2dul").length)
|
||||
w2 = $gnb_1dli.find(".gnb_2dli > a").outerWidth(true);
|
||||
else
|
||||
w2 = w1;
|
||||
|
||||
if((left + w2) > width) {
|
||||
if(max_menu_count == 0)
|
||||
max_menu_count = i + 1;
|
||||
}
|
||||
|
||||
if(max_menu_count > 0 && (idx + 1) % max_menu_count == 0) {
|
||||
el.removeClass("gnb_1dli_over").addClass("gnb_1dli_over2");
|
||||
left = 0;
|
||||
} else {
|
||||
left += w1;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
kngil/js/faq/jquery.mousewheel.min.js
vendored
Normal file
5
kngil/js/faq/jquery.mousewheel.min.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* jQuery Mousewheel 3.1.13
|
||||
* Copyright OpenJS Foundation and other contributors
|
||||
*/
|
||||
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e:e(jQuery)}(function(a){var u,r,e=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],t="onwheel"in window.document||9<=window.document.documentMode?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],f=Array.prototype.slice;if(a.event.fixHooks)for(var n=e.length;n;)a.event.fixHooks[e[--n]]=a.event.mouseHooks;var d=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var e=t.length;e;)this.addEventListener(t[--e],i,!1);else this.onmousewheel=i;a.data(this,"mousewheel-line-height",d.getLineHeight(this)),a.data(this,"mousewheel-page-height",d.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var e=t.length;e;)this.removeEventListener(t[--e],i,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(e){var t=a(e),e=t["offsetParent"in a.fn?"offsetParent":"parent"]();return e.length||(e=a("body")),parseInt(e.css("fontSize"),10)||parseInt(t.css("fontSize"),10)||16},getPageHeight:function(e){return a(e).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};function i(e){var t,n=e||window.event,i=f.call(arguments,1),o=0,l=0,s=0,h=0;if((e=a.event.fix(n)).type="mousewheel","detail"in n&&(s=-1*n.detail),"wheelDelta"in n&&(s=n.wheelDelta),"wheelDeltaY"in n&&(s=n.wheelDeltaY),"wheelDeltaX"in n&&(l=-1*n.wheelDeltaX),"axis"in n&&n.axis===n.HORIZONTAL_AXIS&&(l=-1*s,s=0),o=0===s?l:s,"deltaY"in n&&(o=s=-1*n.deltaY),"deltaX"in n&&(l=n.deltaX,0===s&&(o=-1*l)),0!==s||0!==l)return 1===n.deltaMode?(o*=t=a.data(this,"mousewheel-line-height"),s*=t,l*=t):2===n.deltaMode&&(o*=t=a.data(this,"mousewheel-page-height"),s*=t,l*=t),h=Math.max(Math.abs(s),Math.abs(l)),(!r||h<r)&&c(n,r=h)&&(r/=40),c(n,h)&&(o/=40,l/=40,s/=40),o=Math[1<=o?"floor":"ceil"](o/r),l=Math[1<=l?"floor":"ceil"](l/r),s=Math[1<=s?"floor":"ceil"](s/r),d.settings.normalizeOffset&&this.getBoundingClientRect&&(h=this.getBoundingClientRect(),e.offsetX=e.clientX-h.left,e.offsetY=e.clientY-h.top),e.deltaX=l,e.deltaY=s,e.deltaFactor=r,e.deltaMode=0,i.unshift(e,o,l,s),u&&window.clearTimeout(u),u=window.setTimeout(w,200),(a.event.dispatch||a.event.handle).apply(this,i)}function w(){r=null}function c(e,t){return d.settings.adjustOldDeltas&&"mousewheel"===e.type&&t%120==0}a.fn.extend({mousewheel:function(e){return e?this.on("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.off("mousewheel",e)}})});
|
||||
7
kngil/js/faq/owl.carousel.min.js
vendored
Normal file
7
kngil/js/faq/owl.carousel.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
186
kngil/js/faq/owlcarousel/owl.carousel.css
Normal file
186
kngil/js/faq/owlcarousel/owl.carousel.css
Normal file
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Owl Carousel v2.3.4
|
||||
* Copyright 2013-2018 David Deutsch
|
||||
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
|
||||
*/
|
||||
/*
|
||||
* Owl Carousel - Core
|
||||
*/
|
||||
.owl-carousel {
|
||||
display: none;
|
||||
width: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
/* position relative and z-index fix webkit rendering fonts issue */
|
||||
position: relative;
|
||||
z-index: 1; }
|
||||
.owl-carousel .owl-stage {
|
||||
position: relative;
|
||||
-ms-touch-action: pan-Y;
|
||||
touch-action: manipulation;
|
||||
-moz-backface-visibility: hidden;
|
||||
/* fix firefox animation glitch */ }
|
||||
.owl-carousel .owl-stage:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
line-height: 0;
|
||||
height: 0; }
|
||||
.owl-carousel .owl-stage-outer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* fix for flashing background */
|
||||
-webkit-transform: translate3d(0px, 0px, 0px); }
|
||||
.owl-carousel .owl-wrapper,
|
||||
.owl-carousel .owl-item {
|
||||
-webkit-backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
-ms-backface-visibility: hidden;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-moz-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0); }
|
||||
.owl-carousel .owl-item {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
float: left;
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none; }
|
||||
.owl-carousel .owl-item img {
|
||||
display: block;
|
||||
width: 100%; }
|
||||
.owl-carousel .owl-nav.disabled,
|
||||
.owl-carousel .owl-dots.disabled {
|
||||
display: none; }
|
||||
.owl-carousel .owl-nav .owl-prev,
|
||||
.owl-carousel .owl-nav .owl-next,
|
||||
.owl-carousel .owl-dot {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
.owl-carousel .owl-nav button.owl-prev,
|
||||
.owl-carousel .owl-nav button.owl-next,
|
||||
.owl-carousel button.owl-dot {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0 !important;
|
||||
font: inherit; }
|
||||
.owl-carousel.owl-loaded {
|
||||
display: block; }
|
||||
.owl-carousel.owl-loading {
|
||||
opacity: 0;
|
||||
display: block; }
|
||||
.owl-carousel.owl-hidden {
|
||||
opacity: 0; }
|
||||
.owl-carousel.owl-refresh .owl-item {
|
||||
visibility: hidden; }
|
||||
.owl-carousel.owl-drag .owl-item {
|
||||
-ms-touch-action: pan-y;
|
||||
touch-action: pan-y;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
.owl-carousel.owl-grab {
|
||||
cursor: move;
|
||||
cursor: grab; }
|
||||
.owl-carousel.owl-rtl {
|
||||
direction: rtl; }
|
||||
.owl-carousel.owl-rtl .owl-item {
|
||||
float: right; }
|
||||
|
||||
/* No Js */
|
||||
.no-js .owl-carousel {
|
||||
display: block; }
|
||||
|
||||
/*
|
||||
* Owl Carousel - Animate Plugin
|
||||
*/
|
||||
.owl-carousel .animated {
|
||||
animation-duration: 1000ms;
|
||||
animation-fill-mode: both; }
|
||||
|
||||
.owl-carousel .owl-animated-in {
|
||||
z-index: 0; }
|
||||
|
||||
.owl-carousel .owl-animated-out {
|
||||
z-index: 1; }
|
||||
|
||||
.owl-carousel .fadeOut {
|
||||
animation-name: fadeOut; }
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
100% {
|
||||
opacity: 0; } }
|
||||
|
||||
/*
|
||||
* Owl Carousel - Auto Height Plugin
|
||||
*/
|
||||
.owl-height {
|
||||
transition: height 500ms ease-in-out; }
|
||||
|
||||
/*
|
||||
* Owl Carousel - Lazy Load Plugin
|
||||
*/
|
||||
.owl-carousel .owl-item {
|
||||
/**
|
||||
This is introduced due to a bug in IE11 where lazy loading combined with autoheight plugin causes a wrong
|
||||
calculation of the height of the owl-item that breaks page layouts
|
||||
*/ }
|
||||
.owl-carousel .owl-item .owl-lazy {
|
||||
opacity: 0;
|
||||
transition: opacity 400ms ease; }
|
||||
.owl-carousel .owl-item .owl-lazy[src^=""], .owl-carousel .owl-item .owl-lazy:not([src]) {
|
||||
max-height: 0; }
|
||||
.owl-carousel .owl-item img.owl-lazy {
|
||||
transform-style: preserve-3d; }
|
||||
|
||||
/*
|
||||
* Owl Carousel - Video Plugin
|
||||
*/
|
||||
.owl-carousel .owl-video-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: #000; }
|
||||
|
||||
.owl-carousel .owl-video-play-icon {
|
||||
position: absolute;
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -40px;
|
||||
margin-top: -40px;
|
||||
background: url("owl.video.play.png") no-repeat;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
-webkit-backface-visibility: hidden;
|
||||
transition: transform 100ms ease; }
|
||||
|
||||
.owl-carousel .owl-video-play-icon:hover {
|
||||
-ms-transform: scale(1.3, 1.3);
|
||||
transform: scale(1.3, 1.3); }
|
||||
|
||||
.owl-carousel .owl-video-playing .owl-video-tn,
|
||||
.owl-carousel .owl-video-playing .owl-video-play-icon {
|
||||
display: none; }
|
||||
|
||||
.owl-carousel .owl-video-tn {
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
transition: opacity 400ms ease; }
|
||||
|
||||
.owl-carousel .owl-video-frame {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
width: 100%; }
|
||||
3448
kngil/js/faq/owlcarousel/owl.carousel.js
Normal file
3448
kngil/js/faq/owlcarousel/owl.carousel.js
Normal file
File diff suppressed because it is too large
Load Diff
6
kngil/js/faq/owlcarousel/owl.carousel.min.css
vendored
Normal file
6
kngil/js/faq/owlcarousel/owl.carousel.min.css
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Owl Carousel v2.3.4
|
||||
* Copyright 2013-2018 David Deutsch
|
||||
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
|
||||
*/
|
||||
.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;touch-action:manipulation;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel .owl-nav button.owl-next,.owl-carousel .owl-nav button.owl-prev,.owl-carousel button.owl-dot{background:0 0;color:inherit;border:none;padding:0!important;font:inherit}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item .owl-lazy:not([src]),.owl-carousel .owl-item .owl-lazy[src^=""]{max-height:0}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.png) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%}
|
||||
7
kngil/js/faq/owlcarousel/owl.carousel.min.js
vendored
Normal file
7
kngil/js/faq/owlcarousel/owl.carousel.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
kngil/js/faq/owlcarousel/owl.video.play.png
Normal file
BIN
kngil/js/faq/owlcarousel/owl.video.play.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
25
kngil/js/faq/placeholders.min.js
vendored
Normal file
25
kngil/js/faq/placeholders.min.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/* Placeholders.js v4.0.1 */
|
||||
/*!
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2012 James Allardice
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
!function(a){"use strict";function b(){}function c(){try{return document.activeElement}catch(a){}}function d(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return!0;return!1}function e(a,b,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent?a.attachEvent("on"+b,c):void 0}function f(a,b){var c;a.createTextRange?(c=a.createTextRange(),c.move("character",b),c.select()):a.selectionStart&&(a.focus(),a.setSelectionRange(b,b))}function g(a,b){try{return a.type=b,!0}catch(c){return!1}}function h(a,b){if(a&&a.getAttribute(B))b(a);else for(var c,d=a?a.getElementsByTagName("input"):N,e=a?a.getElementsByTagName("textarea"):O,f=d?d.length:0,g=e?e.length:0,h=f+g,i=0;h>i;i++)c=f>i?d[i]:e[i-f],b(c)}function i(a){h(a,k)}function j(a){h(a,l)}function k(a,b){var c=!!b&&a.value!==b,d=a.value===a.getAttribute(B);if((c||d)&&"true"===a.getAttribute(C)){a.removeAttribute(C),a.value=a.value.replace(a.getAttribute(B),""),a.className=a.className.replace(A,"");var e=a.getAttribute(I);parseInt(e,10)>=0&&(a.setAttribute("maxLength",e),a.removeAttribute(I));var f=a.getAttribute(D);return f&&(a.type=f),!0}return!1}function l(a){var b=a.getAttribute(B);if(""===a.value&&b){a.setAttribute(C,"true"),a.value=b,a.className+=" "+z;var c=a.getAttribute(I);c||(a.setAttribute(I,a.maxLength),a.removeAttribute("maxLength"));var d=a.getAttribute(D);return d?a.type="text":"password"===a.type&&g(a,"text")&&a.setAttribute(D,"password"),!0}return!1}function m(a){return function(){P&&a.value===a.getAttribute(B)&&"true"===a.getAttribute(C)?f(a,0):k(a)}}function n(a){return function(){l(a)}}function o(a){return function(){i(a)}}function p(a){return function(b){return v=a.value,"true"===a.getAttribute(C)&&v===a.getAttribute(B)&&d(x,b.keyCode)?(b.preventDefault&&b.preventDefault(),!1):void 0}}function q(a){return function(){k(a,v),""===a.value&&(a.blur(),f(a,0))}}function r(a){return function(){a===c()&&a.value===a.getAttribute(B)&&"true"===a.getAttribute(C)&&f(a,0)}}function s(a){var b=a.form;b&&"string"==typeof b&&(b=document.getElementById(b),b.getAttribute(E)||(e(b,"submit",o(b)),b.setAttribute(E,"true"))),e(a,"focus",m(a)),e(a,"blur",n(a)),P&&(e(a,"keydown",p(a)),e(a,"keyup",q(a)),e(a,"click",r(a))),a.setAttribute(F,"true"),a.setAttribute(B,T),(P||a!==c())&&l(a)}var t=document.createElement("input"),u=void 0!==t.placeholder;if(a.Placeholders={nativeSupport:u,disable:u?b:i,enable:u?b:j},!u){var v,w=["text","search","url","tel","email","password","number","textarea"],x=[27,33,34,35,36,37,38,39,40,8,46],y="#ccc",z="placeholdersjs",A=new RegExp("(?:^|\\s)"+z+"(?!\\S)"),B="data-placeholder-value",C="data-placeholder-active",D="data-placeholder-type",E="data-placeholder-submit",F="data-placeholder-bound",G="data-placeholder-focus",H="data-placeholder-live",I="data-placeholder-maxlength",J=100,K=document.getElementsByTagName("head")[0],L=document.documentElement,M=a.Placeholders,N=document.getElementsByTagName("input"),O=document.getElementsByTagName("textarea"),P="false"===L.getAttribute(G),Q="false"!==L.getAttribute(H),R=document.createElement("style");R.type="text/css";var S=document.createTextNode("."+z+" {color:"+y+";}");R.styleSheet?R.styleSheet.cssText=S.nodeValue:R.appendChild(S),K.insertBefore(R,K.firstChild);for(var T,U,V=0,W=N.length+O.length;W>V;V++)U=V<N.length?N[V]:O[V-N.length],T=U.attributes.placeholder,T&&(T=T.nodeValue,T&&d(w,U.type)&&s(U));var X=setInterval(function(){for(var a=0,b=N.length+O.length;b>a;a++)U=a<N.length?N[a]:O[a-N.length],T=U.attributes.placeholder,T?(T=T.nodeValue,T&&d(w,U.type)&&(U.getAttribute(F)||s(U),(T!==U.getAttribute(B)||"password"===U.type&&!U.getAttribute(D))&&("password"===U.type&&!U.getAttribute(D)&&g(U,"text")&&U.setAttribute(D,"password"),U.value===U.getAttribute(B)&&(U.value=T),U.setAttribute(B,T)))):U.getAttribute(C)&&(k(U),U.removeAttribute(B));Q||clearInterval(X)},J);e(a,"beforeunload",function(){M.disable()})}}(this);
|
||||
371
kngil/js/faq/wrest.js
Normal file
371
kngil/js/faq/wrest.js
Normal file
@@ -0,0 +1,371 @@
|
||||
var wrestMsg = "";
|
||||
var wrestFld = null;
|
||||
var wrestFldDefaultColor = "";
|
||||
//var wrestFldBackColor = "#ff3061";
|
||||
|
||||
// subject 속성값을 얻어 return, 없으면 tag의 name을 넘김
|
||||
function wrestItemname(fld)
|
||||
{
|
||||
//return fld.getAttribute("title") ? fld.getAttribute("title") : ( fld.getAttribute("alt") ? fld.getAttribute("alt") : fld.name );
|
||||
var id = fld.getAttribute("id");
|
||||
var labels = document.getElementsByTagName("label");
|
||||
var el = null;
|
||||
|
||||
for(i=0; i<labels.length; i++) {
|
||||
if(id == labels[i].htmlFor) {
|
||||
el = labels[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(el != null) {
|
||||
var text = el.innerHTML.replace(/[<].*[>].*[<]\/+.*[>]/gi, "");
|
||||
|
||||
if(text == '') {
|
||||
return fld.getAttribute("title") ? fld.getAttribute("title") : ( fld.getAttribute("placeholder") ? fld.getAttribute("placeholder") : fld.name );
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
} else {
|
||||
return fld.getAttribute("title") ? fld.getAttribute("title") : ( fld.getAttribute("placeholder") ? fld.getAttribute("placeholder") : fld.name );
|
||||
}
|
||||
}
|
||||
|
||||
// 양쪽 공백 없애기
|
||||
function wrestTrim(fld)
|
||||
{
|
||||
var pattern = /(^\s+)|(\s+$)/g; // \s 공백 문자
|
||||
return fld.value.replace(pattern, "");
|
||||
}
|
||||
|
||||
// 필수 입력 검사
|
||||
function wrestRequired(fld)
|
||||
{
|
||||
if (wrestTrim(fld) == "") {
|
||||
if (wrestFld == null) {
|
||||
// 셀렉트박스일 경우에도 필수 선택 검사합니다.
|
||||
wrestMsg = wrestItemname(fld) + " : 필수 "+(fld.type=="select-one"?"선택":"입력")+"입니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 김선용 2006.3 - 전화번호(휴대폰) 형식 검사 : 123-123(4)-5678
|
||||
function wrestTelNum(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}$/;
|
||||
if(!pattern.test(fld.value)){
|
||||
if(wrestFld == null){
|
||||
wrestMsg = wrestItemname(fld)+" : 전화번호 형식이 올바르지 않습니다.\n\n하이픈(-)을 포함하여 입력하세요.\n";
|
||||
wrestFld = fld;
|
||||
fld.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 이메일주소 형식 검사
|
||||
function wrestEmail(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
//var pattern = /(\S+)@(\S+)\.(\S+)/; 이메일주소에 한글 사용시
|
||||
var pattern = /([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)/;
|
||||
if (!pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + " : 이메일주소 형식이 아닙니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 한글인지 검사 (자음, 모음 조합된 한글만 가능)
|
||||
function wrestHangul(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
//var pattern = /([^가-힣\x20])/i;
|
||||
var pattern = /([^가-힣\x20])/;
|
||||
|
||||
if (pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + ' : 한글이 아닙니다. (자음, 모음 조합된 한글만 가능)\n';
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 한글인지 검사2 (자음, 모음만 있는 한글도 가능)
|
||||
function wrestHangul2(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /([^가-힣ㄱ-ㅎㅏ-ㅣ\x20])/i;
|
||||
//var pattern = /([^가-힣ㄱ-ㅎㅏ-ㅣ\x20])/;
|
||||
|
||||
if (pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + ' : 한글이 아닙니다.\n';
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 한글,영문,숫자인지 검사3
|
||||
function wrestHangulAlNum(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /([^가-힣\x20^a-z^A-Z^0-9])/i;
|
||||
|
||||
if (pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + ' : 한글, 영문, 숫자가 아닙니다.\n';
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 한글,영문 인지 검사
|
||||
function wrestHangulAlpha(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /([^가-힣\x20^a-z^A-Z])/i;
|
||||
|
||||
if (pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + ' : 한글, 영문이 아닙니다.\n';
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 숫자인지검사
|
||||
// 배부른꿀꿀이님 추가 (http://dasir.com) 2003-06-24
|
||||
function wrestNumeric(fld)
|
||||
{
|
||||
if (fld.value.length > 0) {
|
||||
for (i = 0; i < fld.value.length; i++) {
|
||||
if (fld.value.charAt(i) < '0' || fld.value.charAt(i) > '9') {
|
||||
wrestMsg = wrestItemname(fld) + " : 숫자가 아닙니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 영문자 검사
|
||||
// 배부른꿀꿀이님 추가 (http://dasir.com) 2003-06-24
|
||||
function wrestAlpha(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /(^[a-zA-Z]+$)/;
|
||||
|
||||
if (!pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + " : 영문이 아닙니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 영문자와 숫자 검사
|
||||
// 배부른꿀꿀이님 추가 (http://dasir.com) 2003-07-07
|
||||
function wrestAlNum(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /(^[a-zA-Z0-9]+$)/;
|
||||
|
||||
if (!pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + " : 영문 또는 숫자가 아닙니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 영문자와 숫자 그리고 _ 검사
|
||||
function wrestAlNum_(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /(^[a-zA-Z0-9\_]+$)/;
|
||||
|
||||
if (!pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + " : 영문, 숫자, _ 가 아닙니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 최소 길이 검사
|
||||
function wrestMinLength(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var minlength = fld.getAttribute("minlength");
|
||||
|
||||
if (wrestFld == null) {
|
||||
if (fld.value.length < parseInt(minlength)) {
|
||||
wrestMsg = wrestItemname(fld) + " : 최소 "+minlength+"글자 이상 입력하세요.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 이미지 확장자
|
||||
function wrestImgExt(fld)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var pattern = /\.(gif|jpg|png)$/i; // jpeg 는 제외
|
||||
if(!pattern.test(fld.value)){
|
||||
if(wrestFld == null){
|
||||
wrestMsg = wrestItemname(fld)+" : 이미지 파일이 아닙니다.\n.gif .jpg .png 파일만 가능합니다.\n";
|
||||
wrestFld = fld;
|
||||
fld.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 확장자
|
||||
function wrestExtension(fld, css)
|
||||
{
|
||||
if (!wrestTrim(fld)) return;
|
||||
|
||||
var str = css.split("="); // ext=?? <-- str[1]
|
||||
var src = fld.value.split(".");
|
||||
var ext = src[src.length - 1];
|
||||
|
||||
if (wrestFld == null) {
|
||||
if (ext.toLowerCase() < str[1].toLowerCase()) {
|
||||
wrestMsg = wrestItemname(fld) + " : ."+str[1]+" 파일만 가능합니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 공백 검사후 공백을 "" 로 변환
|
||||
function wrestNospace(fld)
|
||||
{
|
||||
var pattern = /(\s)/g; // \s 공백 문자
|
||||
|
||||
if (pattern.test(fld.value)) {
|
||||
if (wrestFld == null) {
|
||||
wrestMsg = wrestItemname(fld) + " : 공백이 없어야 합니다.\n";
|
||||
wrestFld = fld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// submit 할 때 속성을 검사한다.
|
||||
function wrestSubmit()
|
||||
{
|
||||
wrestMsg = "";
|
||||
wrestFld = null;
|
||||
|
||||
var attr = null;
|
||||
|
||||
// 해당폼에 대한 요소의 개수만큼 돌려라
|
||||
for (var i=0; i<this.elements.length; i++) {
|
||||
var el = this.elements[i];
|
||||
|
||||
// Input tag 의 type 이 text, file, password 일때만
|
||||
// 셀렉트 박스일때도 필수 선택 검사합니다. select-one
|
||||
if (el.type=="text" || el.type=="hidden" || el.type=="file" || el.type=="password" || el.type=="select-one" || el.type=="textarea") {
|
||||
if (el.getAttribute("required") != null) {
|
||||
wrestRequired(el);
|
||||
}
|
||||
|
||||
if (el.getAttribute("minlength") != null) {
|
||||
wrestMinLength(el);
|
||||
}
|
||||
|
||||
var array_css = el.className.split(" "); // class 를 공백으로 나눔
|
||||
|
||||
el.style.backgroundColor = wrestFldDefaultColor;
|
||||
|
||||
// 배열의 길이만큼 돌려라
|
||||
for (var k=0; k<array_css.length; k++) {
|
||||
var css = array_css[k];
|
||||
switch (css) {
|
||||
case "required" : wrestRequired(el); break;
|
||||
case "trim" : wrestTrim(el); break;
|
||||
case "email" : wrestEmail(el); break;
|
||||
case "hangul" : wrestHangul(el); break;
|
||||
case "hangul2" : wrestHangul2(el); break;
|
||||
case "hangulalpha" : wrestHangulAlpha(el); break;
|
||||
case "hangulalnum" : wrestHangulAlNum(el); break;
|
||||
case "nospace" : wrestNospace(el); break;
|
||||
case "numeric" : wrestNumeric(el); break;
|
||||
case "alpha" : wrestAlpha(el); break;
|
||||
case "alnum" : wrestAlNum(el); break;
|
||||
case "alnum_" : wrestAlNum_(el); break;
|
||||
case "telnum" : wrestTelNum(el); break; // 김선용 2006.3 - 전화번호 형식 검사
|
||||
case "imgext" : wrestImgExt(el); break;
|
||||
default :
|
||||
if (/^extension\=/.test(css)) {
|
||||
wrestExtension(el, css); break;
|
||||
}
|
||||
} // switch (css)
|
||||
} // for (k)
|
||||
} // if (el)
|
||||
} // for (i)
|
||||
|
||||
// 필드가 null 이 아니라면 오류메세지 출력후 포커스를 해당 오류 필드로 옮김
|
||||
// 오류 필드는 배경색상을 바꾼다.
|
||||
if (wrestFld != null) {
|
||||
// 경고메세지 출력
|
||||
alert(wrestMsg);
|
||||
|
||||
if (wrestFld.style.display != "none") {
|
||||
var id = wrestFld.getAttribute("id");
|
||||
|
||||
// 오류메세지를 위한 element 추가
|
||||
var msg_el = document.createElement("strong");
|
||||
msg_el.id = "msg_"+id;
|
||||
msg_el.className = "msg_sound_only";
|
||||
msg_el.innerHTML = wrestMsg;
|
||||
wrestFld.parentNode.insertBefore(msg_el, wrestFld);
|
||||
|
||||
var new_href = document.location.href.replace(/#msg.+$/, "")+"#msg_"+id;
|
||||
|
||||
document.location.href = new_href;
|
||||
|
||||
//wrestFld.style.backgroundColor = wrestFldBackColor;
|
||||
if (typeof(wrestFld.select) != "undefined")
|
||||
wrestFld.select();
|
||||
wrestFld.focus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.oldsubmit && this.oldsubmit() == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 초기에 onsubmit을 가로채도록 한다.
|
||||
function wrestInitialized()
|
||||
{
|
||||
for (var i = 0; i < document.forms.length; i++) {
|
||||
// onsubmit 이벤트가 있다면 저장해 놓는다.
|
||||
if (document.forms[i].onsubmit) {
|
||||
document.forms[i].oldsubmit = document.forms[i].onsubmit;
|
||||
}
|
||||
document.forms[i].onsubmit = wrestSubmit;
|
||||
}
|
||||
}
|
||||
|
||||
// 폼필드 자동검사
|
||||
$(document).ready(function(){
|
||||
// onload
|
||||
wrestInitialized();
|
||||
});
|
||||
523
kngil/js/index.js
Normal file
523
kngil/js/index.js
Normal file
@@ -0,0 +1,523 @@
|
||||
/**
|
||||
* Main Page Video Player & Navigation Controller
|
||||
* 모듈화된 구조로 리팩토링
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ============================================
|
||||
// Configuration
|
||||
// ============================================
|
||||
const CONFIG = {
|
||||
TOTAL_PAGES: 5,
|
||||
INTRO_DELAY: 2800,
|
||||
VISITED_STORAGE_KEY: 'visited',
|
||||
VIDEO_BASE_PATH: '/kngil/img/video',
|
||||
PAGE_LINKS: {
|
||||
1: './value.html',
|
||||
2: './provided.html',
|
||||
3: './primary.html',
|
||||
4: './analysis.html',
|
||||
5: './results.html'
|
||||
},
|
||||
SELECTORS: {
|
||||
video: '#video_play',
|
||||
videoLink: '#main_video_link',
|
||||
pagination: '.main-pagination',
|
||||
paginationItem: '.main-pagination div',
|
||||
intro: '.intro-wrap',
|
||||
mainMask: '.main-mask',
|
||||
footer: 'footer',
|
||||
footerClose: '.footer-close',
|
||||
main: '.main'
|
||||
},
|
||||
CLASSES: {
|
||||
pageOn: 'page-on',
|
||||
footerOn: 'on',
|
||||
footerOff: ' ',
|
||||
skip: 'skip'
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
const Utils = {
|
||||
/**
|
||||
* DOM 요소 선택 (jQuery 대체)
|
||||
*/
|
||||
$(selector) {
|
||||
return document.querySelector(selector);
|
||||
},
|
||||
|
||||
/**
|
||||
* DOM 요소들 선택 (jQuery 대체)
|
||||
*/
|
||||
$$(selector) {
|
||||
return document.querySelectorAll(selector);
|
||||
},
|
||||
|
||||
/**
|
||||
* 숫자 유효성 검사
|
||||
*/
|
||||
isValidPageNum(pageNum) {
|
||||
return Number.isInteger(pageNum) &&
|
||||
pageNum >= 1 &&
|
||||
pageNum <= CONFIG.TOTAL_PAGES;
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전한 이벤트 리스너 추가
|
||||
*/
|
||||
safeAddEventListener(element, event, handler) {
|
||||
if (element && typeof handler === 'function') {
|
||||
element.addEventListener(event, handler);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 클래스 토글 헬퍼
|
||||
*/
|
||||
toggleClass(element, className, force) {
|
||||
if (!element) return;
|
||||
if (force === undefined) {
|
||||
element.classList.toggle(className);
|
||||
} else {
|
||||
element.classList.toggle(className, force);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Video Player Module
|
||||
// ============================================
|
||||
const VideoPlayer = {
|
||||
currentPage: 1,
|
||||
videoElement: null,
|
||||
sourceElement: null,
|
||||
|
||||
init() {
|
||||
this.videoElement = Utils.$(CONFIG.SELECTORS.video);
|
||||
|
||||
if (!this.videoElement) {
|
||||
console.warn('[VideoPlayer] Video element not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.sourceElement = this.videoElement.querySelector('source');
|
||||
if (!this.sourceElement) {
|
||||
console.warn('[VideoPlayer] Source element not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setupEventListeners();
|
||||
return true;
|
||||
},
|
||||
|
||||
setupEventListeners() {
|
||||
// 영상 종료 후 다음 영상 실행
|
||||
Utils.safeAddEventListener(this.videoElement, 'ended', () => {
|
||||
this.playNext();
|
||||
});
|
||||
},
|
||||
|
||||
loadVideo(pageNum) {
|
||||
if (!Utils.isValidPageNum(pageNum)) {
|
||||
console.warn(`[VideoPlayer] Invalid page number: ${pageNum}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const videoSource = `${CONFIG.VIDEO_BASE_PATH}/main_${pageNum}.mp4`;
|
||||
|
||||
try {
|
||||
this.sourceElement.src = videoSource;
|
||||
this.videoElement.load();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[VideoPlayer] Failed to load video:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
play() {
|
||||
if (!this.videoElement) return false;
|
||||
|
||||
return this.videoElement.play()
|
||||
.then(() => true)
|
||||
.catch(err => {
|
||||
console.warn('[VideoPlayer] Play failed:', err);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
pause() {
|
||||
if (this.videoElement) {
|
||||
this.videoElement.pause();
|
||||
}
|
||||
},
|
||||
|
||||
playNext() {
|
||||
this.currentPage = this.currentPage < CONFIG.TOTAL_PAGES
|
||||
? this.currentPage + 1
|
||||
: 1;
|
||||
|
||||
if (this.loadVideo(this.currentPage)) {
|
||||
Pagination.updateState(this.currentPage);
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Pagination Module
|
||||
// ============================================
|
||||
const Pagination = {
|
||||
paginationElement: null,
|
||||
items: null,
|
||||
|
||||
init() {
|
||||
this.paginationElement = Utils.$(CONFIG.SELECTORS.pagination);
|
||||
|
||||
if (!this.paginationElement) {
|
||||
console.warn('[Pagination] Pagination element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
this.items = Utils.$$(CONFIG.SELECTORS.paginationItem);
|
||||
this.setupClickHandlers();
|
||||
},
|
||||
|
||||
setupClickHandlers() {
|
||||
// 이벤트 위임 사용
|
||||
Utils.safeAddEventListener(
|
||||
this.paginationElement,
|
||||
'click',
|
||||
this.handleClick.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
handleClick(e) {
|
||||
// 클릭된 요소 또는 그 부모 요소에서 data-page 속성을 찾음
|
||||
let target = e.target.closest('[data-page]');
|
||||
|
||||
// 만약 li 요소를 직접 클릭한 경우, 그 안의 div를 찾음
|
||||
if (!target) {
|
||||
const liElement = e.target.closest('li');
|
||||
if (liElement) {
|
||||
target = liElement.querySelector('[data-page]');
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const pageNum = parseInt(target.getAttribute('data-page'), 10);
|
||||
if (Utils.isValidPageNum(pageNum)) {
|
||||
this.handlePageClick(pageNum);
|
||||
}
|
||||
},
|
||||
|
||||
handlePageClick(pageNum) {
|
||||
VideoPlayer.currentPage = pageNum;
|
||||
|
||||
if (VideoPlayer.loadVideo(pageNum)) {
|
||||
VideoPlayer.play();
|
||||
this.updateState(pageNum);
|
||||
this.updateLink(pageNum);
|
||||
}
|
||||
},
|
||||
|
||||
updateState(pageNum) {
|
||||
if (!this.items || !Utils.isValidPageNum(pageNum)) return;
|
||||
|
||||
// 모든 아이템에서 활성 클래스 제거
|
||||
this.items.forEach(item => {
|
||||
item.classList.remove(CONFIG.CLASSES.pageOn);
|
||||
});
|
||||
|
||||
// 해당 페이지 아이템에 활성 클래스 추가
|
||||
const targetItem = Array.from(this.items).find(item =>
|
||||
item.classList.contains(`page-0${pageNum}`)
|
||||
);
|
||||
|
||||
if (targetItem) {
|
||||
targetItem.classList.add(CONFIG.CLASSES.pageOn);
|
||||
}
|
||||
},
|
||||
|
||||
updateLink(pageNum) {
|
||||
if (!Utils.isValidPageNum(pageNum)) return;
|
||||
|
||||
const linkUrl = CONFIG.PAGE_LINKS[pageNum];
|
||||
const linkElement = Utils.$(CONFIG.SELECTORS.videoLink);
|
||||
|
||||
if (linkElement && linkUrl) {
|
||||
linkElement.href = linkUrl;
|
||||
}
|
||||
},
|
||||
|
||||
show() {
|
||||
if (this.paginationElement) {
|
||||
this.paginationElement.style.display = '';
|
||||
}
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this.paginationElement) {
|
||||
this.paginationElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Intro Controller Module
|
||||
// ============================================
|
||||
const IntroController = {
|
||||
init() {
|
||||
const visited = sessionStorage.getItem(CONFIG.VISITED_STORAGE_KEY);
|
||||
const intro = Utils.$(CONFIG.SELECTORS.intro);
|
||||
const mainMask = Utils.$(CONFIG.SELECTORS.mainMask);
|
||||
|
||||
if (visited) {
|
||||
this.hideIntro(intro, mainMask);
|
||||
} else {
|
||||
// 첫 방문 시 세션 스토리지에 저장
|
||||
setTimeout(() => {
|
||||
try {
|
||||
sessionStorage.setItem(CONFIG.VISITED_STORAGE_KEY, 'true');
|
||||
} catch (error) {
|
||||
console.warn('[IntroController] Failed to set sessionStorage:', error);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
hideIntro(intro, mainMask) {
|
||||
if (intro) {
|
||||
intro.style.display = 'none';
|
||||
}
|
||||
if (mainMask) {
|
||||
mainMask.classList.add(CONFIG.CLASSES.skip);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Cursor Text Controller Module
|
||||
// ============================================
|
||||
const CursorTextController = {
|
||||
cursorTextElement: null,
|
||||
|
||||
init() {
|
||||
// 메인 페이지인지 확인 (.wrap.main 클래스 존재 여부)
|
||||
const mainElement = Utils.$(CONFIG.SELECTORS.main);
|
||||
if (!mainElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 커서 따라다니는 텍스트 요소 생성
|
||||
this.cursorTextElement = document.createElement('div');
|
||||
this.cursorTextElement.textContent = 'Click!';
|
||||
this.cursorTextElement.style.cssText = `
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transform: translate(-0%, -50%);
|
||||
transition: opacity 0.3s;
|
||||
opacity: 0;
|
||||
`;
|
||||
document.body.appendChild(this.cursorTextElement);
|
||||
|
||||
this.setupEventListeners();
|
||||
},
|
||||
|
||||
setupEventListeners() {
|
||||
// 마우스 움직임 추적
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
this.handleMouseMove(e);
|
||||
});
|
||||
|
||||
// 마우스가 페이지를 벗어나면 숨김
|
||||
document.addEventListener('mouseleave', () => {
|
||||
if (this.cursorTextElement) {
|
||||
this.cursorTextElement.style.opacity = '0';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseenter', () => {
|
||||
if (this.cursorTextElement) {
|
||||
this.cursorTextElement.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleMouseMove(e) {
|
||||
if (!this.cursorTextElement) return;
|
||||
|
||||
// 마우스 위치의 요소 확인
|
||||
const elementUnderMouse = document.elementFromPoint(e.clientX, e.clientY);
|
||||
|
||||
// 특정 영역인지 확인
|
||||
const isInFooter = elementUnderMouse?.closest('footer');
|
||||
const isInPopup = elementUnderMouse?.closest('.popup_wrap') || elementUnderMouse?.closest('.popup-wrap');
|
||||
const isInFloating = elementUnderMouse?.closest('.floating_menu');
|
||||
const isInPagination = elementUnderMouse?.closest('.main-pagination');
|
||||
const isInHeader = elementUnderMouse?.closest('.header');
|
||||
const isInSitemap = elementUnderMouse?.closest('.popup_sitemap') || elementUnderMouse?.closest('.sitemap');
|
||||
|
||||
// 특정 영역이면 숨김
|
||||
if (isInFooter || isInPopup || isInFloating || isInPagination || isInHeader || isInSitemap) {
|
||||
this.cursorTextElement.style.opacity = '0';
|
||||
} else {
|
||||
this.cursorTextElement.style.opacity = '1';
|
||||
// 커서 오른쪽 아래에 위치
|
||||
this.cursorTextElement.style.left = (e.clientX + 15) + 'px';
|
||||
this.cursorTextElement.style.top = (e.clientY + 15) + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Footer Controller Module
|
||||
// ============================================
|
||||
const FooterController = {
|
||||
footerElement: null,
|
||||
mainElement: null,
|
||||
footerCloseElement: null,
|
||||
|
||||
init() {
|
||||
this.footerElement = Utils.$(CONFIG.SELECTORS.footer);
|
||||
this.mainElement = Utils.$(CONFIG.SELECTORS.main);
|
||||
this.footerCloseElement = Utils.$(CONFIG.SELECTORS.footerClose);
|
||||
|
||||
if (!this.footerElement || !this.mainElement) {
|
||||
console.warn('[FooterController] Required elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupMousewheelHandler();
|
||||
this.setupCloseHandler();
|
||||
},
|
||||
|
||||
setupMousewheelHandler() {
|
||||
// jQuery mousewheel 이벤트 대신 wheel 이벤트 사용
|
||||
Utils.safeAddEventListener(
|
||||
this.mainElement,
|
||||
'wheel',
|
||||
this.handleWheel.bind(this),
|
||||
{ passive: true }
|
||||
);
|
||||
},
|
||||
|
||||
handleWheel(e) {
|
||||
// deltaY가 양수면 아래로 스크롤 (footer 표시 - on 클래스 추가)
|
||||
// deltaY가 음수면 위로 스크롤 (footer 숨김 - on 클래스 제거)
|
||||
if (e.deltaY > 0) {
|
||||
this.show();
|
||||
} else if (e.deltaY < 0) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
setupCloseHandler() {
|
||||
if (this.footerCloseElement) {
|
||||
Utils.safeAddEventListener(
|
||||
this.footerCloseElement,
|
||||
'click',
|
||||
() => this.hide()
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
show() {
|
||||
if (!this.footerElement) return;
|
||||
|
||||
this.footerElement.classList.add(CONFIG.CLASSES.footerOn);
|
||||
|
||||
// footerOff가 유효한 클래스 이름인 경우에만 제거
|
||||
const footerOff = CONFIG.CLASSES.footerOff.trim();
|
||||
if (footerOff) {
|
||||
this.footerElement.classList.remove(footerOff);
|
||||
}
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (!this.footerElement) return;
|
||||
|
||||
// footerOff가 유효한 클래스 이름인 경우에만 추가
|
||||
const footerOff = CONFIG.CLASSES.footerOff.trim();
|
||||
if (footerOff) {
|
||||
this.footerElement.classList.add(footerOff);
|
||||
}
|
||||
|
||||
this.footerElement.classList.remove(CONFIG.CLASSES.footerOn);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Main Initialization
|
||||
// ============================================
|
||||
const MainController = {
|
||||
init() {
|
||||
// DOM이 준비되면 초기화
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.start();
|
||||
});
|
||||
} else {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
// Video Player 초기화
|
||||
if (!VideoPlayer.init()) {
|
||||
console.warn('[MainController] Video player initialization failed');
|
||||
return;
|
||||
}
|
||||
|
||||
// 초기 비디오 일시정지 및 페이지네이션 숨김
|
||||
VideoPlayer.pause();
|
||||
Pagination.hide();
|
||||
|
||||
// 방문 여부 확인 및 비디오 재생
|
||||
const visited = sessionStorage.getItem(CONFIG.VISITED_STORAGE_KEY);
|
||||
|
||||
if (visited) {
|
||||
this.startVideoPlayback();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.startVideoPlayback();
|
||||
}, CONFIG.INTRO_DELAY);
|
||||
}
|
||||
|
||||
// 모듈 초기화
|
||||
Pagination.init();
|
||||
IntroController.init();
|
||||
FooterController.init();
|
||||
CursorTextController.init();
|
||||
|
||||
// 초기 링크 설정
|
||||
Pagination.updateLink(VideoPlayer.currentPage);
|
||||
},
|
||||
|
||||
startVideoPlayback() {
|
||||
VideoPlayer.play();
|
||||
Pagination.show();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Start Application
|
||||
// ============================================
|
||||
MainController.init();
|
||||
|
||||
})();
|
||||
326
kngil/js/join copy.js
Normal file
326
kngil/js/join copy.js
Normal file
@@ -0,0 +1,326 @@
|
||||
/* =========================
|
||||
전역 상태값
|
||||
========================= */
|
||||
let isIdChecked = false;
|
||||
let checkedUserId = '';
|
||||
let isPhoneVerified = false;
|
||||
let authTimer = null;
|
||||
|
||||
const REGEX = {
|
||||
id: /^[a-zA-Z][a-zA-Z0-9]{3,11}$/,
|
||||
password: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/,
|
||||
name: /^[가-힣a-zA-Z\s]+$/,
|
||||
phone: /^\d{3}-\d{4}-\d{4}$/
|
||||
};
|
||||
|
||||
/* =========================
|
||||
DOM 로드 후 초기화
|
||||
========================= */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMemberType();
|
||||
initEmailDomain();
|
||||
initIdCheck();
|
||||
initPhoneAuth();
|
||||
initFormSubmit();
|
||||
initPasswordValidation(); // 비밀번호 규칙
|
||||
initNameValidation(); // 이름 특수문자 차단
|
||||
initPhoneFormat(); // 휴대폰 자동 하이픈
|
||||
// initFormValidation(); // submit 최종 검증
|
||||
});
|
||||
|
||||
/* =========================
|
||||
1. 회원유형
|
||||
========================= */
|
||||
function initMemberType() {
|
||||
const radios = document.querySelectorAll('input[name="memberType"]');
|
||||
const companyRow = document.querySelector('.company-group');
|
||||
|
||||
radios.forEach(radio => {
|
||||
radio.addEventListener('change', () => {
|
||||
if (radio.value === '1') {
|
||||
companyRow.style.display = '';
|
||||
} else {
|
||||
companyRow.style.display = 'none';
|
||||
companyRow.querySelectorAll('input').forEach(i => i.value = '');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
2. 이메일 도메인
|
||||
========================= */
|
||||
function initEmailDomain() {
|
||||
const select = document.getElementById('domain_list');
|
||||
const custom = document.getElementById('custom_domain');
|
||||
|
||||
select.addEventListener('change', () => {
|
||||
if (select.value === 'type') {
|
||||
custom.classList.remove('d-none');
|
||||
custom.required = true;
|
||||
} else {
|
||||
custom.classList.add('d-none');
|
||||
custom.required = false;
|
||||
custom.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
3. 아이디 중복확인
|
||||
========================= */
|
||||
function initIdCheck() {
|
||||
const input = document.getElementById('user_id');
|
||||
const btn = document.getElementById('btn_id_check');
|
||||
|
||||
if (!btn || !input) {
|
||||
console.error('ID input 또는 중복확인 버튼 없음');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 아이디 입력값 변경 시 → 중복확인 무효화
|
||||
input.addEventListener('input', () => {
|
||||
isIdChecked = false;
|
||||
checkedUserId = '';
|
||||
input.readOnly = false;
|
||||
});
|
||||
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.preventDefault(); // ⭐ 중요 (폼 submit 방지)
|
||||
|
||||
const userId = input.value.trim();
|
||||
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9]{3,11}$/.test(userId)) {
|
||||
alert('아이디 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/kngil/bbs/join.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'check_id',
|
||||
userId: userId
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.available === true) {
|
||||
alert('사용 가능한 아이디입니다.');
|
||||
|
||||
isIdChecked = true;
|
||||
checkedUserId = userId;
|
||||
|
||||
// UX: 아이디 수정 못 하게
|
||||
input.readOnly = true;
|
||||
} else {
|
||||
alert(data.message || '이미 사용 중인 아이디입니다.');
|
||||
|
||||
isIdChecked = false;
|
||||
checkedUserId = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* =========================
|
||||
4. 휴대폰 인증번호
|
||||
========================= */
|
||||
function initPhoneAuth() {
|
||||
const btn = document.querySelector('.btn-code');
|
||||
const phoneInput = document.getElementById('user_phone');
|
||||
const timerEl = document.querySelector('.timer');
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
const phone = phoneInput.value.trim();
|
||||
if (!/^\d{3}-\d{4}-\d{4}$/.test(phone)) {
|
||||
alert('휴대폰 번호 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
await fetch('/api/send_sms.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone })
|
||||
});
|
||||
|
||||
startTimer(timerEl, 180);
|
||||
alert('인증번호가 발송되었습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
function startTimer(el, seconds) {
|
||||
clearInterval(authTimer);
|
||||
el.classList.remove('d-none');
|
||||
|
||||
let remain = seconds;
|
||||
authTimer = setInterval(() => {
|
||||
const m = String(Math.floor(remain / 60)).padStart(2, '0');
|
||||
const s = String(remain % 60).padStart(2, '0');
|
||||
el.textContent = `${m}:${s}`;
|
||||
remain--;
|
||||
|
||||
if (remain < 0) {
|
||||
clearInterval(authTimer);
|
||||
el.textContent = '만료';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
5. 비밀번호 검증
|
||||
========================= */
|
||||
function validatePassword() {
|
||||
const pw = document.getElementById('user_password').value;
|
||||
const pw2 = document.getElementById('user_password_confirm').value;
|
||||
|
||||
if (!PASSWORD_REGEX.test(pw)) {
|
||||
alert('비밀번호는 영문, 숫자, 특수기호를 포함한 8자 이상이어야 합니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pw !== pw2) {
|
||||
alert('비밀번호가 일치하지 않습니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
6. 가입하기 submit
|
||||
========================= */
|
||||
function initFormSubmit() {
|
||||
const form = document.forms.joinForm;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!isIdChecked) {
|
||||
alert('아이디 중복확인을 해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validatePassword()) return;
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
const email =
|
||||
formData.get('userEmail') + '@' +
|
||||
(formData.get('emailDomain') === 'type'
|
||||
? formData.get('customDomain')
|
||||
: formData.get('emailDomain'));
|
||||
|
||||
const payload = {
|
||||
memberType: formData.get('memberType'),
|
||||
userId: formData.get('userId'),
|
||||
password: formData.get('userPassword'),
|
||||
userName: formData.get('userName'),
|
||||
email,
|
||||
phone: formData.get('userPhone'),
|
||||
company: formData.get('companyName'),
|
||||
department: formData.get('departmentName')
|
||||
};
|
||||
|
||||
const res = await fetch('/kngil/bbs/join.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
form.style.display = 'none';
|
||||
document.querySelector('.messages').style.display = 'block';
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
회원 가입 유효성 체크
|
||||
========================= */
|
||||
function initPasswordValidation() {
|
||||
const pw = document.getElementById('user_password');
|
||||
const pw2 = document.getElementById('user_password_confirm');
|
||||
const msg = document.getElementById('password_help');
|
||||
|
||||
function validate() {
|
||||
if (!pw.value) return;
|
||||
|
||||
if (!PASSWORD_REGEX.test(pw.value)) {
|
||||
msg.textContent = '영문 + 숫자 + 특수기호 포함 8자 이상';
|
||||
msg.classList.remove('success');
|
||||
msg.classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pw2.value && pw.value !== pw2.value) {
|
||||
msg.textContent = '비밀번호가 일치하지 않습니다.';
|
||||
msg.classList.remove('success');
|
||||
msg.classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
msg.textContent = '사용 가능한 비밀번호입니다.';
|
||||
msg.classList.remove('error');
|
||||
msg.classList.add('success');
|
||||
}
|
||||
|
||||
pw.addEventListener('input', validate);
|
||||
pw2.addEventListener('input', validate);
|
||||
}
|
||||
|
||||
|
||||
// function initFormValidation() {
|
||||
// const form = document.forms.joinForm;
|
||||
|
||||
// form.addEventListener('submit', e => {
|
||||
// e.preventDefault();
|
||||
|
||||
// const id = form.userId.value.trim();
|
||||
// const pw = form.userPassword.value;
|
||||
// const pw2 = form.userPasswordConfirm.value;
|
||||
// const name = form.userName.value.trim();
|
||||
// const email = form.userEmail.value.trim();
|
||||
// const phone = form.userPhone.value.trim();
|
||||
|
||||
// if (!isIdChecked || checkedUserId !== form.userId.value.trim()) {
|
||||
// alert('아이디 중복확인을 해주세요.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (!id || !pw || !pw2 || !name || !email || !phone) {
|
||||
// alert('필수 항목을 모두 입력하세요.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (!REGEX.password.test(pw)) {
|
||||
// alert('비밀번호 규칙을 확인하세요.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (pw !== pw2) {
|
||||
// alert('비밀번호가 일치하지 않습니다.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (!REGEX.name.test(name)) {
|
||||
// alert('이름에 특수문자는 사용할 수 없습니다.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (!REGEX.phone.test(phone)) {
|
||||
// alert('휴대전화번호 형식이 올바르지 않습니다.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 여기까지 왔으면 → 서버 signup 요청
|
||||
// submitSignup();
|
||||
// });
|
||||
// }
|
||||
308
kngil/js/join.js
Normal file
308
kngil/js/join.js
Normal file
@@ -0,0 +1,308 @@
|
||||
/* =========================
|
||||
전역 상태값
|
||||
========================= */
|
||||
let isIdChecked = false;
|
||||
let checkedUserId = '';
|
||||
|
||||
const REGEX = {
|
||||
id: /^[a-zA-Z][a-zA-Z0-9]{3,11}$/,
|
||||
password: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/,
|
||||
name: /^[가-힣a-zA-Z\s]+$/,
|
||||
phone: /^\d{3}-\d{4}-\d{4}$/
|
||||
};
|
||||
|
||||
|
||||
/* =========================
|
||||
DOM 로드 후 초기화
|
||||
========================= */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMemberType();
|
||||
initEmailDomain();
|
||||
initIdCheck();
|
||||
initPasswordValidation();
|
||||
// initNameValidation();
|
||||
initPhoneFormat();
|
||||
initFormSubmit();
|
||||
});
|
||||
|
||||
/* =========================
|
||||
1. 회원유형
|
||||
========================= */
|
||||
function initMemberType() {
|
||||
const radios = document.querySelectorAll('input[name="memberType"]');
|
||||
const companyRow = document.querySelector('.company-group');
|
||||
|
||||
radios.forEach(radio => {
|
||||
radio.addEventListener('change', () => {
|
||||
if (radio.value === '1') {
|
||||
companyRow.style.display = '';
|
||||
} else {
|
||||
companyRow.style.display = 'none';
|
||||
companyRow.querySelectorAll('input').forEach(i => i.value = '');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
2. 이메일 도메인
|
||||
========================= */
|
||||
function initEmailDomain() {
|
||||
const select = document.getElementById('domain_list');
|
||||
const custom = document.getElementById('custom_domain');
|
||||
|
||||
select.addEventListener('change', () => {
|
||||
if (select.value === 'type') {
|
||||
custom.classList.remove('d-none');
|
||||
custom.required = true;
|
||||
} else {
|
||||
custom.classList.add('d-none');
|
||||
custom.required = false;
|
||||
custom.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
3. 아이디 중복확인
|
||||
========================= */
|
||||
function initIdCheck() {
|
||||
const input = document.getElementById('user_id');
|
||||
const btn = document.getElementById('btn_id_check');
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
isIdChecked = false;
|
||||
checkedUserId = '';
|
||||
input.readOnly = false;
|
||||
});
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
const userId = input.value.trim();
|
||||
|
||||
if (!userId) {
|
||||
alert('아이디를 입력해주세요.');
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!REGEX.id.test(userId)) {
|
||||
alert('아이디 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/kngil/bbs/join.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'check_id', userId })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.available) {
|
||||
alert('사용 가능한 아이디입니다.');
|
||||
isIdChecked = true;
|
||||
checkedUserId = userId;
|
||||
input.readOnly = true;
|
||||
} else {
|
||||
alert(data.message || '이미 사용 중인 아이디입니다.');
|
||||
isIdChecked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* =========================
|
||||
4. 휴대폰 인증번호
|
||||
========================= */
|
||||
function initPhoneAuth() {
|
||||
const btn = document.querySelector('.btn-code');
|
||||
const phoneInput = document.getElementById('user_phone');
|
||||
const timerEl = document.querySelector('.timer');
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
const phone = phoneInput.value.trim();
|
||||
if (!/^\d{3}-\d{4}-\d{4}$/.test(phone)) {
|
||||
alert('휴대폰 번호 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
await fetch('/api/send_sms.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone })
|
||||
});
|
||||
|
||||
startTimer(timerEl, 180);
|
||||
alert('인증번호가 발송되었습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
function startTimer(el, seconds) {
|
||||
clearInterval(authTimer);
|
||||
el.classList.remove('d-none');
|
||||
|
||||
let remain = seconds;
|
||||
authTimer = setInterval(() => {
|
||||
const m = String(Math.floor(remain / 60)).padStart(2, '0');
|
||||
const s = String(remain % 60).padStart(2, '0');
|
||||
el.textContent = `${m}:${s}`;
|
||||
remain--;
|
||||
|
||||
if (remain < 0) {
|
||||
clearInterval(authTimer);
|
||||
el.textContent = '만료';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
5. 비밀번호 검증
|
||||
========================= */
|
||||
function validatePassword() {
|
||||
const pw = document.getElementById('user_password').value;
|
||||
const pw2 = document.getElementById('user_password_confirm').value;
|
||||
|
||||
if (!PASSWORD_REGEX.test(pw)) {
|
||||
alert('비밀번호는 영문, 숫자, 특수기호를 포함한 8자 이상이어야 합니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pw !== pw2) {
|
||||
alert('비밀번호가 일치하지 않습니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
6. 가입하기 submit
|
||||
========================= */
|
||||
function initFormSubmit() {
|
||||
const form = document.forms.joinForm;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!isIdChecked || checkedUserId !== form.userId.value.trim()) {
|
||||
alert('아이디 중복확인을 해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!REGEX.password.test(form.userPassword.value)) {
|
||||
alert('비밀번호 규칙을 확인하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (form.userPassword.value !== form.userPasswordConfirm.value) {
|
||||
alert('비밀번호가 일치하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!REGEX.name.test(form.userName.value.trim())) {
|
||||
alert('이름에 특수문자는 사용할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!REGEX.phone.test(form.userPhone.value.trim())) {
|
||||
// alert('휴대전화번호 형식이 올바르지 않습니다.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// ✅ 서버 전송
|
||||
const formData = new FormData(form);
|
||||
|
||||
const email =
|
||||
formData.get('userEmail') + '@' +
|
||||
(formData.get('emailDomain') === 'type'
|
||||
? formData.get('customDomain')
|
||||
: formData.get('emailDomain'));
|
||||
|
||||
const payload = {
|
||||
action: 'signup',
|
||||
memberType: formData.get('memberType'),
|
||||
userId: formData.get('userId'),
|
||||
password: formData.get('userPassword'),
|
||||
userName: formData.get('userName'),
|
||||
email,
|
||||
phone: formData.get('userPhone'),
|
||||
company: formData.get('companyName') || null,
|
||||
department: formData.get('departmentName') || null
|
||||
};
|
||||
|
||||
const res = await fetch('/kngil/bbs/join.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
form.style.display = 'none';
|
||||
document.querySelector('.messages').style.display = 'block';
|
||||
} else {
|
||||
alert(data.message || '회원가입 실패');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* =========================
|
||||
회원 가입 유효성 체크
|
||||
========================= */
|
||||
function initPasswordValidation() {
|
||||
const pw = document.getElementById('user_password');
|
||||
const pw2 = document.getElementById('user_password_confirm');
|
||||
const msg = document.getElementById('password_help');
|
||||
|
||||
function validate() {
|
||||
if (!pw.value) {
|
||||
msg.textContent = '영문+숫자+특수기호 8자 이상';
|
||||
msg.className = 'info-msg';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!REGEX.password.test(pw.value)) {
|
||||
msg.textContent = '비밀번호 규칙을 만족하지 않습니다.';
|
||||
msg.className = 'info-msg error';
|
||||
return;
|
||||
}
|
||||
|
||||
if (pw2.value && pw.value !== pw2.value) {
|
||||
msg.textContent = '비밀번호가 일치하지 않습니다.';
|
||||
msg.className = 'info-msg error';
|
||||
return;
|
||||
}
|
||||
|
||||
msg.textContent = '사용 가능한 비밀번호입니다.';
|
||||
msg.className = 'info-msg success';
|
||||
}
|
||||
|
||||
pw.addEventListener('input', validate);
|
||||
pw2.addEventListener('input', validate);
|
||||
}
|
||||
|
||||
function initPhoneFormat() {
|
||||
const phone = document.getElementById('user_phone');
|
||||
|
||||
phone.addEventListener('input', () => {
|
||||
let v = phone.value.replace(/\D/g, '');
|
||||
|
||||
if (v.length <= 3) phone.value = v;
|
||||
else if (v.length <= 7) phone.value = `${v.slice(0,3)}-${v.slice(3)}`;
|
||||
else phone.value = `${v.slice(0,3)}-${v.slice(3,7)}-${v.slice(7,11)}`;
|
||||
});
|
||||
}
|
||||
|
||||
// function initNameValidation() {
|
||||
// const nameInput = document.getElementById('user_name');
|
||||
// if (!nameInput) return;
|
||||
|
||||
// nameInput.addEventListener('input', () => {
|
||||
// nameInput.value = nameInput.value.replace(/[^가-힣a-zA-Z\s]/g, '');
|
||||
// });
|
||||
// }
|
||||
1533
kngil/js/lib/MotionPathPlugin.min.js
vendored
Normal file
1533
kngil/js/lib/MotionPathPlugin.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
kngil/js/lib/aos.min.js
vendored
Normal file
1
kngil/js/lib/aos.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
kngil/js/lib/gsap.min.js
vendored
Normal file
10
kngil/js/lib/gsap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
kngil/js/lib/jquery-3.6.1.min.js
vendored
Normal file
2
kngil/js/lib/jquery-3.6.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
kngil/js/lib/jquery.mCustomScrollbar.concat.min.js
vendored
Normal file
4
kngil/js/lib/jquery.mCustomScrollbar.concat.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
kngil/js/lib/jquery.mousewheel.min.js
vendored
Normal file
5
kngil/js/lib/jquery.mousewheel.min.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* jQuery Mousewheel 3.1.13
|
||||
* Copyright OpenJS Foundation and other contributors
|
||||
*/
|
||||
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e:e(jQuery)}(function(a){var u,r,e=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],t="onwheel"in window.document||9<=window.document.documentMode?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],f=Array.prototype.slice;if(a.event.fixHooks)for(var n=e.length;n;)a.event.fixHooks[e[--n]]=a.event.mouseHooks;var d=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var e=t.length;e;)this.addEventListener(t[--e],i,!1);else this.onmousewheel=i;a.data(this,"mousewheel-line-height",d.getLineHeight(this)),a.data(this,"mousewheel-page-height",d.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var e=t.length;e;)this.removeEventListener(t[--e],i,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(e){var t=a(e),e=t["offsetParent"in a.fn?"offsetParent":"parent"]();return e.length||(e=a("body")),parseInt(e.css("fontSize"),10)||parseInt(t.css("fontSize"),10)||16},getPageHeight:function(e){return a(e).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};function i(e){var t,n=e||window.event,i=f.call(arguments,1),o=0,l=0,s=0,h=0;if((e=a.event.fix(n)).type="mousewheel","detail"in n&&(s=-1*n.detail),"wheelDelta"in n&&(s=n.wheelDelta),"wheelDeltaY"in n&&(s=n.wheelDeltaY),"wheelDeltaX"in n&&(l=-1*n.wheelDeltaX),"axis"in n&&n.axis===n.HORIZONTAL_AXIS&&(l=-1*s,s=0),o=0===s?l:s,"deltaY"in n&&(o=s=-1*n.deltaY),"deltaX"in n&&(l=n.deltaX,0===s&&(o=-1*l)),0!==s||0!==l)return 1===n.deltaMode?(o*=t=a.data(this,"mousewheel-line-height"),s*=t,l*=t):2===n.deltaMode&&(o*=t=a.data(this,"mousewheel-page-height"),s*=t,l*=t),h=Math.max(Math.abs(s),Math.abs(l)),(!r||h<r)&&c(n,r=h)&&(r/=40),c(n,h)&&(o/=40,l/=40,s/=40),o=Math[1<=o?"floor":"ceil"](o/r),l=Math[1<=l?"floor":"ceil"](l/r),s=Math[1<=s?"floor":"ceil"](s/r),d.settings.normalizeOffset&&this.getBoundingClientRect&&(h=this.getBoundingClientRect(),e.offsetX=e.clientX-h.left,e.offsetY=e.clientY-h.top),e.deltaX=l,e.deltaY=s,e.deltaFactor=r,e.deltaMode=0,i.unshift(e,o,l,s),u&&window.clearTimeout(u),u=window.setTimeout(w,200),(a.event.dispatch||a.event.handle).apply(this,i)}function w(){r=null}function c(e,t){return d.settings.adjustOldDeltas&&"mousewheel"===e.type&&t%120==0}a.fn.extend({mousewheel:function(e){return e?this.on("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.off("mousewheel",e)}})});
|
||||
2
kngil/js/lib/lenis.min.js
vendored
Normal file
2
kngil/js/lib/lenis.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
kngil/js/lib/lottie.min.js
vendored
Normal file
15
kngil/js/lib/lottie.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
kngil/js/lib/scrollToPlugin.min.js
vendored
Normal file
10
kngil/js/lib/scrollToPlugin.min.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/*!
|
||||
* ScrollToPlugin 3.12.5
|
||||
* https://gsap.com
|
||||
*
|
||||
* @license Copyright 2024, GreenSock. All rights reserved.
|
||||
* Subject to the terms at https://gsap.com/standard-license or for Club GSAP members, the agreement issued with that membership.
|
||||
* @author: Jack Doyle, jack@greensock.com
|
||||
*/
|
||||
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).window=e.window||{})}(this,function(e){"use strict";function l(){return"undefined"!=typeof window}function m(){return f||l()&&(f=window.gsap)&&f.registerPlugin&&f}function n(e){return"string"==typeof e}function o(e){return"function"==typeof e}function p(e,t){var o="x"===t?"Width":"Height",n="scroll"+o,r="client"+o;return e===T||e===i||e===c?Math.max(i[n],c[n])-(T["inner"+o]||i[r]||c[r]):e[n]-e["offset"+o]}function q(e,t){var o="scroll"+("x"===t?"Left":"Top");return e===T&&(null!=e.pageXOffset?o="page"+t.toUpperCase()+"Offset":e=null!=i[o]?i:c),function(){return e[o]}}function s(e,t){if(!(e=y(e)[0])||!e.getBoundingClientRect)return console.warn("scrollTo target doesn't exist. Using 0")||{x:0,y:0};var o=e.getBoundingClientRect(),n=!t||t===T||t===c,r=n?{top:i.clientTop-(T.pageYOffset||i.scrollTop||c.scrollTop||0),left:i.clientLeft-(T.pageXOffset||i.scrollLeft||c.scrollLeft||0)}:t.getBoundingClientRect(),l={x:o.left-r.left,y:o.top-r.top};return!n&&t&&(l.x+=q(t,"x")(),l.y+=q(t,"y")()),l}function t(e,t,o,r,l){return isNaN(e)||"object"==typeof e?n(e)&&"="===e.charAt(1)?parseFloat(e.substr(2))*("-"===e.charAt(0)?-1:1)+r-l:"max"===e?p(t,o)-l:Math.min(p(t,o),s(e,t)[o]-l):parseFloat(e)-l}function u(){f=m(),l()&&f&&"undefined"!=typeof document&&document.body&&(T=window,c=document.body,i=document.documentElement,y=f.utils.toArray,f.config({autoKillThreshold:7}),h=f.config(),a=1)}var f,a,T,i,c,y,h,v,r={version:"3.12.5",name:"scrollTo",rawVars:1,register:function register(e){f=e,u()},init:function init(e,r,l,s,i){a||u();var p=this,c=f.getProperty(e,"scrollSnapType");p.isWin=e===T,p.target=e,p.tween=l,r=function _clean(e,t,r,l){if(o(e)&&(e=e(t,r,l)),"object"!=typeof e)return n(e)&&"max"!==e&&"="!==e.charAt(1)?{x:e,y:e}:{y:e};if(e.nodeType)return{y:e,x:e};var s,i={};for(s in e)i[s]="onAutoKill"!==s&&o(e[s])?e[s](t,r,l):e[s];return i}(r,s,e,i),p.vars=r,p.autoKill=!!r.autoKill,p.getX=q(e,"x"),p.getY=q(e,"y"),p.x=p.xPrev=p.getX(),p.y=p.yPrev=p.getY(),v=v||f.core.globals().ScrollTrigger,"smooth"===f.getProperty(e,"scrollBehavior")&&f.set(e,{scrollBehavior:"auto"}),c&&"none"!==c&&(p.snap=1,p.snapInline=e.style.scrollSnapType,e.style.scrollSnapType="none"),null!=r.x?(p.add(p,"x",p.x,t(r.x,e,"x",p.x,r.offsetX||0),s,i),p._props.push("scrollTo_x")):p.skipX=1,null!=r.y?(p.add(p,"y",p.y,t(r.y,e,"y",p.y,r.offsetY||0),s,i),p._props.push("scrollTo_y")):p.skipY=1},render:function render(e,t){for(var o,n,r,l,s,i=t._pt,c=t.target,u=t.tween,f=t.autoKill,a=t.xPrev,y=t.yPrev,d=t.isWin,g=t.snap,x=t.snapInline;i;)i.r(e,i.d),i=i._next;o=d||!t.skipX?t.getX():a,r=(n=d||!t.skipY?t.getY():y)-y,l=o-a,s=h.autoKillThreshold,t.x<0&&(t.x=0),t.y<0&&(t.y=0),f&&(!t.skipX&&(s<l||l<-s)&&o<p(c,"x")&&(t.skipX=1),!t.skipY&&(s<r||r<-s)&&n<p(c,"y")&&(t.skipY=1),t.skipX&&t.skipY&&(u.kill(),t.vars.onAutoKill&&t.vars.onAutoKill.apply(u,t.vars.onAutoKillParams||[]))),d?T.scrollTo(t.skipX?o:t.x,t.skipY?n:t.y):(t.skipY||(c.scrollTop=t.y),t.skipX||(c.scrollLeft=t.x)),!g||1!==e&&0!==e||(n=c.scrollTop,o=c.scrollLeft,x?c.style.scrollSnapType=x:c.style.removeProperty("scroll-snap-type"),c.scrollTop=n+1,c.scrollLeft=o+1,c.scrollTop=n,c.scrollLeft=o),t.xPrev=t.x,t.yPrev=t.y,v&&v.update()},kill:function kill(e){var t="scrollTo"===e,o=this._props.indexOf(e);return!t&&"scrollTo_x"!==e||(this.skipX=1),!t&&"scrollTo_y"!==e||(this.skipY=1),-1<o&&this._props.splice(o,1),!this._props.length}};r.max=p,r.getOffset=s,r.buildGetter=q,m()&&f.registerPlugin(r),e.ScrollToPlugin=r,e.default=r;if (typeof(window)==="undefined"||window!==e){Object.defineProperty(e,"__esModule",{value:!0})} else {delete e.default}});
|
||||
10
kngil/js/lib/scrolltrigger.min.js
vendored
Normal file
10
kngil/js/lib/scrolltrigger.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
kngil/js/lib/swiper11.min.js
vendored
Normal file
14
kngil/js/lib/swiper11.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
36
kngil/js/login.js
Normal file
36
kngil/js/login.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const form = document.querySelector('.tab-content.id form')
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const id = document.getElementById('login_id').value.trim()
|
||||
const pw = document.getElementById('login_password').value.trim()
|
||||
|
||||
if (!id || !pw) {
|
||||
alert('아이디와 비밀번호를 입력하세요.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/login.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, pw })
|
||||
})
|
||||
console.log('login.js loaded')
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status === 'success') {
|
||||
console.log('LOGIN OK, reload')
|
||||
document.getElementById('pop_login').style.display = 'none'
|
||||
location.reload()
|
||||
} else {
|
||||
alert(json.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert('로그인 중 오류가 발생했습니다.')
|
||||
}
|
||||
})
|
||||
}
|
||||
102
kngil/js/login_sms.js
Normal file
102
kngil/js/login_sms.js
Normal file
@@ -0,0 +1,102 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const smsBtn = document.getElementById('sms_button');
|
||||
const phoneInp = document.getElementById('login_phone');
|
||||
|
||||
if (!smsBtn || !phoneInp) return;
|
||||
|
||||
const form = smsBtn.closest('form');
|
||||
const infoBox = form.querySelector('.info-box');
|
||||
const timerEl = form.querySelector('.timer');
|
||||
|
||||
let pollTimer = null;
|
||||
let timerIntv = null;
|
||||
let remain = 180; // 3분
|
||||
|
||||
function startTimer() {
|
||||
clearInterval(timerIntv);
|
||||
remain = 180;
|
||||
|
||||
timerEl.classList.remove('d-none');
|
||||
updateTimer();
|
||||
|
||||
timerIntv = setInterval(() => {
|
||||
remain--;
|
||||
updateTimer();
|
||||
|
||||
if (remain <= 0) {
|
||||
clearInterval(timerIntv);
|
||||
smsBtn.disabled = false;
|
||||
timerEl.textContent = '00:00';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function updateTimer() {
|
||||
const m = String(Math.floor(remain / 60)).padStart(2, '0');
|
||||
const s = String(remain % 60).padStart(2, '0');
|
||||
timerEl.textContent = `${m}:${s}`;
|
||||
}
|
||||
|
||||
smsBtn.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const phone = phoneInp.value.trim();
|
||||
if (!phone) {
|
||||
alert('휴대폰 번호를 입력하세요.');
|
||||
phoneInp.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
smsBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/login_sms.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
mode: 'request',
|
||||
phone
|
||||
})
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (json.status !== 'success') {
|
||||
alert(json.message || '인증 링크 요청 실패');
|
||||
smsBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// UI 처리
|
||||
infoBox.classList.remove('d-none');
|
||||
startTimer();
|
||||
startPolling();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('인증 요청 중 오류');
|
||||
smsBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
function startPolling() {
|
||||
clearInterval(pollTimer);
|
||||
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/login_sms.php?mode=status');
|
||||
const json = await res.json();
|
||||
|
||||
if (json.status === 'success') {
|
||||
clearInterval(pollTimer);
|
||||
clearInterval(timerIntv);
|
||||
location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
});
|
||||
457
kngil/js/mypage.js
Normal file
457
kngil/js/mypage.js
Normal file
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* mypage.js
|
||||
* 마이페이지 관련 모든 모달 흐름 관리
|
||||
*/
|
||||
|
||||
/* =========================
|
||||
공통 유틸
|
||||
========================= */
|
||||
function show(id) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) el.style.display = 'block'
|
||||
}
|
||||
|
||||
function hide(id) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) el.style.display = 'none'
|
||||
}
|
||||
|
||||
/* =========================
|
||||
마이페이지 1단계 (비밀번호 인증)
|
||||
- 헤더에서 호출
|
||||
========================= */
|
||||
window.mypage01 = function () {
|
||||
if (!window.IS_LOGIN) {
|
||||
if (typeof window.login === 'function') {
|
||||
window.login()
|
||||
}
|
||||
return
|
||||
}
|
||||
show('pop_mypage01')
|
||||
}
|
||||
|
||||
/* =========================
|
||||
마이페이지 2단계 (실제 마이페이지)
|
||||
========================= */
|
||||
let currentPage = 1
|
||||
|
||||
window.mypage02 = async function (page = 1) {
|
||||
currentPage = page
|
||||
hide('pop_mypage01')
|
||||
|
||||
try {
|
||||
const res = await fetch(`/kngil/bbs/mypage02.php?page=${page}`)
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status !== 'success') {
|
||||
alert(json.message || '데이터 로드 실패')
|
||||
return
|
||||
}
|
||||
|
||||
renderMyPage02(json.user)
|
||||
renderMyPageHistory(json.history)
|
||||
renderPagination(json.pagination)
|
||||
|
||||
show('pop_mypage02')
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert('마이페이지 로딩 오류')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderMyPage02(data) {
|
||||
|
||||
// 이름 / ID
|
||||
document.getElementById('mp_user_nm').textContent = data.user_nm
|
||||
document.getElementById('mp_user_id').textContent = `(${data.user_id})`
|
||||
|
||||
// 연락처
|
||||
document.getElementById('mp_tel').textContent = data.tel_no || '-'
|
||||
document.getElementById('mp_email').textContent = data.email || '-'
|
||||
|
||||
// 회사 정보
|
||||
document.getElementById('mp_co_nm').textContent = data.co_nm || '-'
|
||||
document.getElementById('mp_dept_nm').textContent = data.dept_nm || '-'
|
||||
|
||||
// 사용량
|
||||
document.getElementById('mp_tot_use').textContent =
|
||||
Number(data.tot_use || 0).toLocaleString()
|
||||
|
||||
document.getElementById('mp_year_use').textContent =
|
||||
Number(data.year_use || 0).toLocaleString()
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const d = new Date(dateStr)
|
||||
const yy = String(d.getFullYear()).slice(2)
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
return `${yy}-${mm}-${dd}`
|
||||
}
|
||||
|
||||
function renderMyPageHistory(list) {
|
||||
const tbody = document.getElementById('mp_history_body')
|
||||
tbody.innerHTML = ''
|
||||
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" style="text-align:center;">사용 이력이 없습니다.</td>
|
||||
</tr>
|
||||
`
|
||||
return
|
||||
}
|
||||
|
||||
list.forEach((row, idx) => {
|
||||
const tr = document.createElement('tr')
|
||||
tr.innerHTML = `
|
||||
<td>${(currentPage - 1) * 5 + idx + 1}</td>
|
||||
<td class="tit">${row.ser_bc}</td>
|
||||
<td>${formatDate(row.use_dt)}</td>
|
||||
<td><em>${Number(row.use_area).toLocaleString()}</em></td>
|
||||
`
|
||||
tbody.appendChild(tr)
|
||||
})
|
||||
}
|
||||
|
||||
function renderPagination(p) {
|
||||
|
||||
const ul = document.querySelector('.pagination-list')
|
||||
if (!ul) return
|
||||
|
||||
ul.innerHTML = ''
|
||||
|
||||
for (let i = 1; i <= p.totalPages; i++) {
|
||||
const li = document.createElement('li')
|
||||
li.className = (i === p.page) ? 'on' : ''
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = '#'
|
||||
a.textContent = i
|
||||
a.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
mypage02(i)
|
||||
})
|
||||
|
||||
li.appendChild(a)
|
||||
ul.appendChild(li)
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
마이페이지 3단계 (마이페이지 정보수정)
|
||||
========================= */
|
||||
|
||||
window.mypage03 = async function () {
|
||||
|
||||
// ✅ 비밀번호 변경 영역 초기 상태 강제
|
||||
const rowCurrent = document.getElementById('row-pw-current')
|
||||
const rowNew = document.getElementById('row-pw-new')
|
||||
const rowView = document.querySelector('.btn-sm.change')?.closest('tr')
|
||||
|
||||
rowCurrent && (rowCurrent.style.display = 'none')
|
||||
rowNew && (rowNew.style.display = 'none')
|
||||
rowView && (rowView.style.display = '')
|
||||
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/mypage03.php')
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status !== 'success') {
|
||||
alert(json.message)
|
||||
return
|
||||
}
|
||||
|
||||
fillMyPage03(json.data)
|
||||
bindPasswordValidation()
|
||||
hide('pop_mypage02')
|
||||
show('pop_mypage03')
|
||||
bindPasswordChangeUI()
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert('회원정보 조회 오류')
|
||||
}
|
||||
};
|
||||
|
||||
;(function bindMyPage03Save() {
|
||||
|
||||
const popup = document.getElementById('pop_mypage03')
|
||||
if (!popup) return
|
||||
|
||||
const btnSave = popup.querySelector('.btn-full')
|
||||
|
||||
btnSave.addEventListener('click', async () => {
|
||||
|
||||
const password = popup.querySelector('#new_pw')?.value.trim() || ''
|
||||
|
||||
const emailId = popup.querySelector('.e-id')?.value.trim()
|
||||
const domainSel = popup.querySelector('#domain_list')
|
||||
const customDomain = popup.querySelector('#custom_domain')
|
||||
|
||||
let email = ''
|
||||
if (emailId) {
|
||||
const domain =
|
||||
domainSel.value === 'type'
|
||||
? customDomain.value.trim()
|
||||
: domainSel.options[domainSel.selectedIndex].text
|
||||
email = `${emailId}@${domain}`
|
||||
}
|
||||
|
||||
const telNo = popup.querySelector('#user_phone')?.value.trim()
|
||||
const deptNm = popup.querySelector('#department_name')?.value.trim()
|
||||
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/mypage03.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
password,
|
||||
email,
|
||||
tel_no: telNo,
|
||||
dept_nm: deptNm
|
||||
})
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status === 'success') {
|
||||
alert('정보가 수정되었습니다.')
|
||||
hide('pop_mypage03')
|
||||
mypage02()
|
||||
} else {
|
||||
alert(json.message)
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert('저장 중 오류 발생')
|
||||
}
|
||||
})
|
||||
|
||||
})();
|
||||
|
||||
|
||||
function fillMyPage03(data) {
|
||||
|
||||
const popup = document.getElementById('pop_mypage03')
|
||||
if (!popup) return
|
||||
|
||||
/* =========================
|
||||
아이디
|
||||
========================= */
|
||||
const idInput = popup.querySelector('input[readonly]')
|
||||
if (idInput) idInput.value = data.user_id || ''
|
||||
|
||||
/* =========================
|
||||
이름
|
||||
========================= */
|
||||
const nameInput = popup.querySelector('input[placeholder="이지빔"]')
|
||||
if (nameInput) nameInput.value = data.user_nm || ''
|
||||
|
||||
/* =========================
|
||||
이메일
|
||||
========================= */
|
||||
if (data.email) {
|
||||
const [emailId, domain] = data.email.split('@')
|
||||
|
||||
const emailInput = popup.querySelector('.e-id')
|
||||
const domainList = popup.querySelector('#domain_list')
|
||||
const customDomain = popup.querySelector('#custom_domain')
|
||||
|
||||
if (emailInput) emailInput.value = emailId
|
||||
|
||||
// 도메인 목록에 있으면 선택
|
||||
const option = [...domainList.options].find(opt => opt.text === domain)
|
||||
|
||||
if (option) {
|
||||
domainList.value = option.value
|
||||
customDomain.classList.add('d-none')
|
||||
customDomain.value = ''
|
||||
} else {
|
||||
domainList.value = 'type'
|
||||
customDomain.classList.remove('d-none')
|
||||
customDomain.value = domain
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
부서명
|
||||
========================= */
|
||||
const deptInput = popup.querySelector('#department_name')
|
||||
if (deptInput) deptInput.value = data.dept_nm || ''
|
||||
|
||||
/* =========================
|
||||
휴대폰 (표시만)
|
||||
========================= */
|
||||
const phoneInput = popup.querySelector('#user_phone')
|
||||
if (phoneInput) phoneInput.value = data.tel_no || ''
|
||||
}
|
||||
|
||||
|
||||
/* =========================
|
||||
비밀번호 재인증 처리
|
||||
========================= */
|
||||
;(function bindPasswordVerify() {
|
||||
|
||||
const popup = document.getElementById('pop_mypage01')
|
||||
if (!popup) return
|
||||
|
||||
const form = popup.querySelector('.tab-content.id form')
|
||||
if (!form) return
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const pwInput = popup.querySelector('#login_password')
|
||||
const pw = pwInput?.value.trim()
|
||||
|
||||
if (!pw) {
|
||||
alert('비밀번호를 입력하세요.')
|
||||
pwInput?.focus()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/kngil/bbs/mypage01.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ pw })
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
|
||||
if (json.status === 'success') {
|
||||
pwInput.value = ''
|
||||
window.mypage02()
|
||||
} else {
|
||||
alert(json.message || '비밀번호가 올바르지 않습니다.')
|
||||
pwInput.focus()
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('[mypage verify error]', err)
|
||||
alert('인증 중 오류가 발생했습니다.')
|
||||
}
|
||||
})
|
||||
|
||||
})(); // ✅ IIFE 종료
|
||||
|
||||
/* =========================
|
||||
공통 닫기
|
||||
========================= */
|
||||
window.closeMyPage = function () {
|
||||
hide('pop_mypage01')
|
||||
hide('pop_mypage02')
|
||||
hide('pop_mypage03')
|
||||
}
|
||||
|
||||
/* =========================
|
||||
새로고침 / bfcache 대응 (script-only)
|
||||
========================= */
|
||||
window.addEventListener('pageshow', () => {
|
||||
['pop_mypage01','pop_mypage02','pop_mypage03']
|
||||
.forEach(hide)
|
||||
})
|
||||
|
||||
|
||||
/* =========================
|
||||
비밀번호 변경 UI 토글
|
||||
========================= */
|
||||
function bindPasswordChangeUI() {
|
||||
|
||||
const rowOriginal = document.getElementById('row-pw-original')
|
||||
const rowNew = document.getElementById('row-pw-new')
|
||||
const btnChange = document.getElementById('btnPwChange')
|
||||
const btnCancel = document.getElementById('btnPwCancel')
|
||||
|
||||
if (!rowOriginal || !rowNew || !btnChange || !btnCancel) {
|
||||
console.warn('[mypage] password toggle elements missing')
|
||||
return
|
||||
}
|
||||
|
||||
// 초기 상태
|
||||
rowNew.style.display = 'none'
|
||||
|
||||
// 비밀번호 변경
|
||||
btnChange.addEventListener('click', () => {
|
||||
rowOriginal.style.display = 'none'
|
||||
rowNew.style.display = ''
|
||||
})
|
||||
|
||||
// 취소
|
||||
btnCancel.addEventListener('click', () => {
|
||||
rowOriginal.style.display = ''
|
||||
rowNew.style.display = 'none'
|
||||
|
||||
document.getElementById('current_pw').value = ''
|
||||
document.getElementById('new_pw').value = ''
|
||||
document.getElementById('new_pw_confirm').value = ''
|
||||
})
|
||||
}
|
||||
/* =========================
|
||||
회원정보 유효성 체크
|
||||
========================= */
|
||||
function bindPasswordValidation() {
|
||||
const pw = document.getElementById('new_pw')
|
||||
const pwConfirm = document.getElementById('new_pw_confirm')
|
||||
const msg = document.getElementById('pwPolicyMsg')
|
||||
|
||||
if (!pw || !pwConfirm || !msg) return
|
||||
|
||||
function updateMessage(text, isError = true) {
|
||||
msg.textContent = text
|
||||
msg.classList.remove('d-none')
|
||||
msg.style.color = isError ? '#e60000' : '#2e7d32'
|
||||
}
|
||||
|
||||
function validate() {
|
||||
const v = pw.value
|
||||
const c = pwConfirm.value
|
||||
|
||||
if (!v && !c) {
|
||||
msg.classList.add('d-none')
|
||||
return
|
||||
}
|
||||
|
||||
const policy = checkPasswordPolicy(v)
|
||||
|
||||
if (!policy.length) {
|
||||
updateMessage('비밀번호는 8자 이상이어야 합니다.')
|
||||
return
|
||||
}
|
||||
if (!policy.eng) {
|
||||
updateMessage('영문자를 포함해야 합니다.')
|
||||
return
|
||||
}
|
||||
if (!policy.num) {
|
||||
updateMessage('숫자를 포함해야 합니다.')
|
||||
return
|
||||
}
|
||||
if (!policy.special) {
|
||||
updateMessage('특수문자를 포함해야 합니다.')
|
||||
return
|
||||
}
|
||||
if (c && v !== c) {
|
||||
updateMessage('비밀번호가 일치하지 않습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
updateMessage('사용 가능한 비밀번호입니다.', false)
|
||||
}
|
||||
|
||||
pw.addEventListener('input', validate)
|
||||
pwConfirm.addEventListener('input', validate)
|
||||
}
|
||||
|
||||
function checkPasswordPolicy(pw) {
|
||||
return {
|
||||
length: pw.length >= 8,
|
||||
eng: /[a-zA-Z]/.test(pw),
|
||||
num: /[0-9]/.test(pw),
|
||||
special: /[^a-zA-Z0-9]/.test(pw)
|
||||
}
|
||||
}
|
||||
680
kngil/js/popup.js
Normal file
680
kngil/js/popup.js
Normal file
@@ -0,0 +1,680 @@
|
||||
/**
|
||||
* Popup Controller Module
|
||||
* 팝업 관련 기능을 관리하는 모듈
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ============================================
|
||||
// Configuration
|
||||
// ============================================
|
||||
const CONFIG = {
|
||||
SELECTORS: {
|
||||
btnClose: '.btn-close',
|
||||
btnMapClose: '.btn-map-close',
|
||||
popupWrap: '.popup-wrap',
|
||||
popupSitemap: '.popup-sitemap',
|
||||
domainList: '#domain-list',
|
||||
customDomain: '#custom-domain',
|
||||
certNumber: '.cert-number',
|
||||
code: '.code',
|
||||
check: '.check',
|
||||
checkComplete: '.check.complete',
|
||||
timer: '.timer',
|
||||
findEmail: '.find-email',
|
||||
findPh: '.find-ph',
|
||||
btnId: '.btn-id',
|
||||
btnPw: '.btn-pw',
|
||||
contentId: '.content.id',
|
||||
contentPw: '.content.pw',
|
||||
termsWrap: '.terms-wrap',
|
||||
checkboxWrap: '.checkbox-wrap.all',
|
||||
joinBtnWrap: '.join-btn-wrap',
|
||||
popInputWrap: '.pop-input-wrap',
|
||||
joinProgress: '.join-progress',
|
||||
joinStep: '.join-step',
|
||||
tabPrivacy: '.tab-privacy',
|
||||
tabAgreement: '.tab-agreement',
|
||||
tabContentPri: '.tab-content.pri',
|
||||
tabContentAgr: '.tab-content.agr',
|
||||
tabWrap: '.tab-menu',
|
||||
contentsWrap: '.contents_wrap',
|
||||
messages: '.messages'
|
||||
},
|
||||
TIMER: {
|
||||
DURATION: 60 * 3, // 3분
|
||||
INTERVAL: 1000 // 1초
|
||||
},
|
||||
CLASSES: {
|
||||
on: 'on',
|
||||
none: 'none',
|
||||
show: 'show',
|
||||
hide: 'hide',
|
||||
complete: 'complete'
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
const Utils = {
|
||||
/**
|
||||
* Lenis 스크롤 시작
|
||||
*/
|
||||
startLenis() {
|
||||
if (typeof lenis !== 'undefined' && lenis) {
|
||||
lenis.start();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 스크롤 복원
|
||||
*/
|
||||
restoreScroll() {
|
||||
$('body').css('overflow', '');
|
||||
this.startLenis();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Popup Close Controller
|
||||
// ============================================
|
||||
const PopupCloseController = {
|
||||
init() {
|
||||
// 팝업 닫기 버튼
|
||||
$(document).on('click', CONFIG.SELECTORS.btnClose, () => {
|
||||
$(CONFIG.SELECTORS.popupWrap).hide();
|
||||
Utils.restoreScroll();
|
||||
});
|
||||
|
||||
// 사이트맵 닫기 버튼
|
||||
$(document).on('click', CONFIG.SELECTORS.btnMapClose, () => {
|
||||
$(CONFIG.SELECTORS.popupSitemap).hide();
|
||||
Utils.restoreScroll();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Email Domain Controller
|
||||
// ============================================
|
||||
const EmailDomainController = {
|
||||
init() {
|
||||
// ID 기반 선택자 (기존 호환성)
|
||||
$(document).on('change', CONFIG.SELECTORS.domainList, function() {
|
||||
const $select = $(this);
|
||||
const $selectBox = $select.closest('.select-box');
|
||||
const $customDomain = $selectBox.find(CONFIG.SELECTORS.customDomain);
|
||||
|
||||
if ($select.val() === 'type') {
|
||||
// 직접입력 선택 시 d-none 클래스 제거
|
||||
$customDomain.removeClass('d-none').focus();
|
||||
} else {
|
||||
// 다른 선택지 선택 시 d-none 클래스 추가 및 값 초기화
|
||||
$customDomain.addClass('d-none').val('');
|
||||
}
|
||||
});
|
||||
|
||||
// 클래스 기반 선택자 (.domain-list)
|
||||
$(document).on('change', '.domain-list', function() {
|
||||
const $select = $(this);
|
||||
const $inputBox = $select.closest('.input-box');
|
||||
const $customDomain = $inputBox.find('.domain-domain');
|
||||
|
||||
if ($select.val() === 'type') {
|
||||
// 직접입력 선택 시 d-none 클래스 제거
|
||||
$customDomain.removeClass('d-none').focus();
|
||||
} else {
|
||||
// 다른 선택지 선택 시 d-none 클래스 추가 및 값 초기화
|
||||
$customDomain.addClass('d-none').val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Timer Controller
|
||||
// ============================================
|
||||
// const TimerController = {
|
||||
// interval: null,
|
||||
// display: null,
|
||||
|
||||
// init() {
|
||||
// this.display = $(CONFIG.SELECTORS.timer);
|
||||
|
||||
// // 인증번호 버튼 클릭 시
|
||||
// $(document).on('click', CONFIG.SELECTORS.certNumber, () => {
|
||||
// $(CONFIG.SELECTORS.code).show();
|
||||
// this.start();
|
||||
// });
|
||||
|
||||
// // 확인 버튼 클릭 시
|
||||
// $(document).on('click', CONFIG.SELECTORS.check, function() {
|
||||
// $(this).hide();
|
||||
// $(CONFIG.SELECTORS.checkComplete).show();
|
||||
// TimerController.stop();
|
||||
// $(CONFIG.SELECTORS.timer).remove();
|
||||
// });
|
||||
|
||||
// // 로그인 페이지 휴대폰 인증 폼의 인증 링크 요청 버튼 클릭 시
|
||||
// $(document).on('submit', '#pop_login .tab-content.phone form', (e) => {
|
||||
// e.preventDefault();
|
||||
// const $form = $(e.target);
|
||||
// const $timer = $form.find('.timer');
|
||||
// const $infoBox = $form.find('.info-box');
|
||||
|
||||
// // 타이머와 info-box 표시
|
||||
// // $timer.removeClass('hide');
|
||||
// $timer.removeClass('d-none');
|
||||
// $infoBox.removeClass('d-none');
|
||||
|
||||
// // 타이머 시작 (해당 폼 내의 타이머만)
|
||||
// if ($timer.length) {
|
||||
// this.stop();
|
||||
// this.display = $timer;
|
||||
// this.start();
|
||||
// }
|
||||
// });
|
||||
|
||||
// // 초기 타이머 시작 (다른 페이지에서 사용하는 경우를 위해)
|
||||
// // 숨겨지지 않은 타이머만 자동 시작
|
||||
// const $visibleTimers = $(CONFIG.SELECTORS.timer).not('.hide').not('.d-none');
|
||||
// if ($visibleTimers.length > 0) {
|
||||
// this.display = $visibleTimers.first();
|
||||
// this.start();
|
||||
// }
|
||||
// },
|
||||
|
||||
// start() {
|
||||
// this.stop();
|
||||
// this.startTimer(CONFIG.TIMER.DURATION, this.display);
|
||||
// },
|
||||
|
||||
// stop() {
|
||||
// if (this.interval) {
|
||||
// clearInterval(this.interval);
|
||||
// this.interval = null;
|
||||
// }
|
||||
// },
|
||||
|
||||
// startTimer(duration, display) {
|
||||
// let timer = duration;
|
||||
|
||||
// const updateTimer = () => {
|
||||
// const minutes = Math.floor(timer / 60);
|
||||
// const seconds = timer % 60;
|
||||
// const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;
|
||||
// const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
|
||||
|
||||
// if (display && display.length) {
|
||||
// display.text(`${formattedMinutes}:${formattedSeconds}`);
|
||||
// }
|
||||
|
||||
// if (--timer < 0) {
|
||||
// this.stop();
|
||||
// if (display && display.length) {
|
||||
// display.text('00:00');
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// updateTimer();
|
||||
// this.interval = setInterval(updateTimer, CONFIG.TIMER.INTERVAL);
|
||||
// }
|
||||
// };
|
||||
|
||||
// ============================================
|
||||
// Find Account Controller
|
||||
// ============================================
|
||||
const FindAccountController = {
|
||||
/**
|
||||
* 라디오 버튼 전환 처리
|
||||
* @param {jQuery} $radio - 클릭된 라디오 버튼
|
||||
* @param {string} type - 'ph' 또는 'email'
|
||||
*/
|
||||
switchRadio($radio, type) {
|
||||
const $radioWrap = $radio.closest('.radio-wrap');
|
||||
const $tabContent = $radioWrap.closest('.tab-content');
|
||||
|
||||
// 같은 라디오 그룹의 다른 라디오 버튼들
|
||||
const $otherRadio = type === 'ph'
|
||||
? $radioWrap.find(CONFIG.SELECTORS.findEmail)
|
||||
: $radioWrap.find(CONFIG.SELECTORS.findPh);
|
||||
|
||||
// 라디오 버튼 상태 변경
|
||||
$otherRadio.removeClass(CONFIG.CLASSES.on).prop('checked', false);
|
||||
$radio.addClass(CONFIG.CLASSES.on).prop('checked', true);
|
||||
|
||||
// 테이블 표시/숨김 (해당 탭 콘텐츠 내에서만)
|
||||
if (type === 'ph') {
|
||||
$tabContent.find('table.email').hide();
|
||||
$tabContent.find('table.ph').show();
|
||||
} else {
|
||||
$tabContent.find('table.ph').hide();
|
||||
$tabContent.find('table.email').show();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 초기 상태 설정
|
||||
*/
|
||||
initRadioState() {
|
||||
// 각 탭 콘텐츠별로 초기 상태 설정
|
||||
$('.tab-content').each(function() {
|
||||
const $tabContent = $(this);
|
||||
const $checkedRadio = $tabContent.find('.radio-wrap input[type="radio"]:checked');
|
||||
|
||||
if ($checkedRadio.length) {
|
||||
if ($checkedRadio.hasClass('find-ph')) {
|
||||
$tabContent.find('table.ph').show();
|
||||
$tabContent.find('table.email').hide();
|
||||
} else if ($checkedRadio.hasClass('find-email')) {
|
||||
$tabContent.find('table.email').show();
|
||||
$tabContent.find('table.ph').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
// 이메일 찾기 라디오 버튼 클릭
|
||||
$(document).on('click', CONFIG.SELECTORS.findEmail, function(e) {
|
||||
e.preventDefault();
|
||||
FindAccountController.switchRadio($(this), 'email');
|
||||
});
|
||||
|
||||
// 전화번호 찾기 라디오 버튼 클릭
|
||||
$(document).on('click', CONFIG.SELECTORS.findPh, function(e) {
|
||||
e.preventDefault();
|
||||
FindAccountController.switchRadio($(this), 'ph');
|
||||
});
|
||||
|
||||
// 라디오 버튼 변경 이벤트 (체크 상태 동기화)
|
||||
$(document).on('change', '.radio-wrap input[type="radio"]', function() {
|
||||
const $radio = $(this);
|
||||
if ($radio.hasClass('find-ph')) {
|
||||
FindAccountController.switchRadio($radio, 'ph');
|
||||
} else if ($radio.hasClass('find-email')) {
|
||||
FindAccountController.switchRadio($radio, 'email');
|
||||
}
|
||||
});
|
||||
|
||||
// 초기 상태 설정
|
||||
this.initRadioState();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Login Tab Controller
|
||||
// ============================================
|
||||
const LoginTabController = {
|
||||
init() {
|
||||
// ID 찾기 탭
|
||||
$(document).on('click', CONFIG.SELECTORS.btnId, () => {
|
||||
$(CONFIG.SELECTORS.btnId).addClass(CONFIG.CLASSES.on);
|
||||
$(CONFIG.SELECTORS.btnPw).removeClass(CONFIG.CLASSES.on);
|
||||
$(CONFIG.SELECTORS.contentId).show();
|
||||
$(CONFIG.SELECTORS.contentPw).hide();
|
||||
});
|
||||
|
||||
// 비밀번호 찾기 탭
|
||||
$(document).on('click', CONFIG.SELECTORS.btnPw, () => {
|
||||
$(CONFIG.SELECTORS.btnPw).addClass(CONFIG.CLASSES.on);
|
||||
$(CONFIG.SELECTORS.btnId).removeClass(CONFIG.CLASSES.on);
|
||||
$(CONFIG.SELECTORS.contentPw).show();
|
||||
$(CONFIG.SELECTORS.contentId).hide();
|
||||
});
|
||||
|
||||
// 도메인 선택 (중복 제거)
|
||||
$(CONFIG.SELECTORS.domainList).on('change', function() {
|
||||
const $customDomain = $(CONFIG.SELECTORS.customDomain);
|
||||
|
||||
if ($(this).val() === 'type') {
|
||||
$customDomain.show();
|
||||
} else {
|
||||
$customDomain.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Terms Agreement Controller
|
||||
// ============================================
|
||||
const TermsAgreementController = {
|
||||
init() {
|
||||
// 약관 동의 상태 업데이트
|
||||
this.updateJoinButton();
|
||||
|
||||
// 전체 동의 체크박스 (기존 호환성)
|
||||
$(CONFIG.SELECTORS.checkboxWrap).find('input[type="checkbox"]').on('change', (e) => {
|
||||
const isChecked = $(e.target).is(':checked');
|
||||
$(CONFIG.SELECTORS.termsWrap).find('input[type="checkbox"]').prop('checked', isChecked);
|
||||
this.updateJoinButton();
|
||||
});
|
||||
|
||||
// .chk-all 클래스를 가진 체크박스 (전체 동의)
|
||||
$(document).on('change click', '.chk-all', function(e) {
|
||||
e.stopPropagation(); // 이벤트 전파 중지
|
||||
const $chkAll = $(this);
|
||||
const isChecked = $chkAll.is(':checked');
|
||||
const $termsWrap = $chkAll.closest('.terms-wrap');
|
||||
|
||||
// .chk-all을 제외한 모든 체크박스 선택/해제
|
||||
$termsWrap.find('input[type="checkbox"]:not(.chk-all)').prop('checked', isChecked);
|
||||
|
||||
// updateJoinButton 호출 시 무한 루프 방지를 위해 플래그 설정
|
||||
TermsAgreementController._updatingFromChkAll = true;
|
||||
TermsAgreementController.updateJoinButton();
|
||||
TermsAgreementController._updatingFromChkAll = false;
|
||||
});
|
||||
|
||||
// 개별 체크박스
|
||||
$(CONFIG.SELECTORS.termsWrap).find('input[type="checkbox"]').on('change', () => {
|
||||
this.updateJoinButton();
|
||||
});
|
||||
|
||||
// .terms-wrap 내의 개별 체크박스 (chk-all 제외)
|
||||
$(document).on('change', '.terms-wrap input[type="checkbox"]:not(.chk-all)', () => {
|
||||
this.updateJoinButton();
|
||||
});
|
||||
},
|
||||
|
||||
updateJoinButton() {
|
||||
// .terms-wrap 내의 모든 체크박스 확인 (chk-all 제외)
|
||||
const $allTermsWraps = $('.terms-wrap');
|
||||
let allChecked = true;
|
||||
|
||||
$allTermsWraps.each(function() {
|
||||
const $checkboxes = $(this).find('input[type="checkbox"]:not(.chk-all)');
|
||||
const $checked = $checkboxes.filter(':checked');
|
||||
if ($checkboxes.length > 0 && $checkboxes.length !== $checked.length) {
|
||||
allChecked = false;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
|
||||
// .chk-all 체크박스 상태 업데이트 (무한 루프 방지)
|
||||
if (!TermsAgreementController._updatingFromChkAll) {
|
||||
$('.chk-all').prop('checked', allChecked);
|
||||
}
|
||||
|
||||
// 전체 동의 체크박스 상태 업데이트 (기존 호환성)
|
||||
$(CONFIG.SELECTORS.checkboxWrap).find('input[type="checkbox"]').prop('checked', allChecked);
|
||||
|
||||
// 버튼 상태 업데이트
|
||||
const $joinBtnWrap = $(CONFIG.SELECTORS.joinBtnWrap);
|
||||
if (allChecked) {
|
||||
$joinBtnWrap.removeClass(CONFIG.CLASSES.none);
|
||||
$joinBtnWrap.find('button').prop('disabled', false);
|
||||
} else {
|
||||
$joinBtnWrap.addClass(CONFIG.CLASSES.none);
|
||||
$joinBtnWrap.find('button').prop('disabled', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Join Completion Controller
|
||||
// ============================================
|
||||
const JoinCompletionController = {
|
||||
init() {
|
||||
// 전송완료
|
||||
$(document).on('click', '.pw ' + CONFIG.SELECTORS.joinBtnWrap + ' button', () => {
|
||||
$(CONFIG.SELECTORS.contentsWrap).children().not(CONFIG.SELECTORS.messages).hide();
|
||||
$(CONFIG.SELECTORS.messages).show();
|
||||
});
|
||||
|
||||
// 가입완료
|
||||
$(document).on('click', '.join.completion ' + CONFIG.SELECTORS.joinBtnWrap + ' button', () => {
|
||||
$(CONFIG.SELECTORS.popInputWrap).find('form').children().not(CONFIG.SELECTORS.messages).hide();
|
||||
$(CONFIG.SELECTORS.messages).show();
|
||||
|
||||
// 진행 단계 업데이트
|
||||
$(CONFIG.SELECTORS.joinProgress).find(CONFIG.SELECTORS.joinStep).removeClass(CONFIG.CLASSES.on);
|
||||
$(CONFIG.SELECTORS.joinProgress).find(CONFIG.SELECTORS.joinStep).eq(2).addClass(CONFIG.CLASSES.on);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Universal Tab Controller
|
||||
// ============================================
|
||||
const TabController = {
|
||||
/**
|
||||
* 탭 클래스명에서 콘텐츠 클래스명 추출
|
||||
* @param {jQuery} $tab - 탭 요소
|
||||
* @returns {string} 콘텐츠 클래스명
|
||||
*/
|
||||
getContentClass($tab) {
|
||||
// 탭 클래스명에서 tab- 접두사 제거
|
||||
const tabClasses = $tab.attr('class').split(' ');
|
||||
let contentClass = '';
|
||||
|
||||
for (let i = 0; i < tabClasses.length; i++) {
|
||||
const className = tabClasses[i];
|
||||
if (className.startsWith('tab-')) {
|
||||
const tabName = className.replace('tab-', '');
|
||||
|
||||
// 특수 케이스 매핑
|
||||
const specialCases = {
|
||||
'privacy': 'pri',
|
||||
'agreement': 'agr'
|
||||
};
|
||||
|
||||
contentClass = specialCases[tabName] || tabName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return contentClass;
|
||||
},
|
||||
|
||||
/**
|
||||
* 슬라이딩 인디케이터 위치 업데이트 (round 타입 탭 메뉴용)
|
||||
* @param {jQuery} $tabMenu - 탭 메뉴 요소
|
||||
* @param {jQuery} $activeTab - 활성화된 탭 요소
|
||||
*/
|
||||
updateSliderPosition($tabMenu, $activeTab) {
|
||||
// round 클래스가 있는 경우에만 처리
|
||||
if (!$tabMenu.hasClass('round')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $tabs = $tabMenu.find('li');
|
||||
const activeIndex = $tabs.index($activeTab);
|
||||
const tabCount = $tabs.length;
|
||||
|
||||
if (tabCount === 0) return;
|
||||
|
||||
// 탭 메뉴의 실제 너비와 패딩 확인
|
||||
const tabMenuWidth = $tabMenu.width();
|
||||
const padding = 0; // padding: 4px
|
||||
const gap = 4; // gap: 4px
|
||||
|
||||
// 사용 가능한 너비 (패딩 제외)
|
||||
const availableWidth = tabMenuWidth - (padding * 2);
|
||||
|
||||
// 각 탭의 너비 계산 (gap 포함)
|
||||
// gap은 탭 사이에만 있으므로, 탭 개수 - 1개의 gap이 있음
|
||||
const totalGapWidth = gap * (tabCount - 1);
|
||||
const tabWidth = (availableWidth - totalGapWidth) / tabCount;
|
||||
|
||||
// 인디케이터 위치 계산 (패딩 + 탭 너비 * 인덱스 + gap * 인덱스)
|
||||
const indicatorPosition = padding + (tabWidth + gap) * activeIndex;
|
||||
|
||||
// CSS 변수로 위치 설정 (픽셀 단위)
|
||||
$tabMenu[0].style.setProperty('--tab-indicator-position', `${indicatorPosition}px`);
|
||||
|
||||
// 인디케이터 너비도 동적으로 설정
|
||||
$tabMenu[0].style.setProperty('--tab-indicator-width', `${tabWidth}px`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 탭 전환 처리
|
||||
* @param {jQuery} $clickedTab - 클릭된 탭 요소
|
||||
*/
|
||||
switchTab($clickedTab) {
|
||||
const $tabMenu = $clickedTab.closest(CONFIG.SELECTORS.tabWrap);
|
||||
const $allTabs = $tabMenu.find('li');
|
||||
|
||||
// 콘텐츠 컨테이너 찾기 (pop-contents 또는 contents-wrap)
|
||||
const $contentContainer = $clickedTab.closest('.pop-contents, .contents-wrap');
|
||||
const $allContents = $contentContainer.find('.tab-content');
|
||||
|
||||
// 모든 탭에서 on 클래스 제거
|
||||
$allTabs.removeClass(CONFIG.CLASSES.on);
|
||||
|
||||
// 클릭된 탭에 on 클래스 추가
|
||||
$clickedTab.addClass(CONFIG.CLASSES.on);
|
||||
|
||||
// 슬라이딩 인디케이터 위치 업데이트 (round 타입인 경우)
|
||||
this.updateSliderPosition($tabMenu, $clickedTab);
|
||||
|
||||
// 모든 콘텐츠 숨기기
|
||||
$allContents.removeClass(CONFIG.CLASSES.show);
|
||||
|
||||
// 해당하는 콘텐츠만 표시
|
||||
const contentClass = this.getContentClass($clickedTab);
|
||||
if (contentClass) {
|
||||
const $targetContent = $contentContainer.find('.tab-content.' + contentClass);
|
||||
if ($targetContent.length) {
|
||||
$targetContent.addClass(CONFIG.CLASSES.show);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 탭 초기 상태 설정
|
||||
*/
|
||||
initTabState() {
|
||||
// 모든 tab-menu 찾기
|
||||
$(CONFIG.SELECTORS.tabWrap).each(function() {
|
||||
const $tabMenu = $(this);
|
||||
const $tabs = $tabMenu.find('li');
|
||||
const $contentContainer = $tabMenu.closest('.pop-contents, .contents-wrap');
|
||||
|
||||
if ($tabs.length === 0 || !$contentContainer.length) return;
|
||||
|
||||
// 활성화된 탭 찾기
|
||||
const $activeTab = $tabs.filter('.' + CONFIG.CLASSES.on);
|
||||
|
||||
if ($activeTab.length > 0) {
|
||||
// 활성화된 탭이 있으면 해당 콘텐츠 표시
|
||||
TabController.switchTab($activeTab);
|
||||
} else {
|
||||
// 활성화된 탭이 없으면 첫 번째 탭 활성화
|
||||
const $firstTab = $tabs.first();
|
||||
$firstTab.addClass(CONFIG.CLASSES.on);
|
||||
TabController.switchTab($firstTab);
|
||||
}
|
||||
|
||||
// 리사이즈 시 인디케이터 위치 재계산
|
||||
if ($tabMenu.hasClass('round')) {
|
||||
$(window).on('resize.tabSlider', function() {
|
||||
const $active = $tabMenu.find('li.' + CONFIG.CLASSES.on);
|
||||
if ($active.length) {
|
||||
TabController.updateSliderPosition($tabMenu, $active);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
// 모든 tab-menu의 탭 클릭 이벤트
|
||||
$(document).on('click', CONFIG.SELECTORS.tabWrap + ' li', function(e) {
|
||||
e.preventDefault();
|
||||
TabController.switchTab($(this));
|
||||
});
|
||||
|
||||
// 키보드 접근성 지원 (탭 + 엔터/스페이스)
|
||||
$(document).on('keydown', CONFIG.SELECTORS.tabWrap + ' li', function(e) {
|
||||
// Enter 또는 Space 키
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
TabController.switchTab($(this));
|
||||
}
|
||||
|
||||
// 화살표 키로 탭 전환
|
||||
const $tabMenu = $(this).closest(CONFIG.SELECTORS.tabWrap);
|
||||
const $tabs = $tabMenu.find('li');
|
||||
const currentIndex = $tabs.index(this);
|
||||
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
let targetIndex;
|
||||
|
||||
if (e.key === 'ArrowLeft') {
|
||||
targetIndex = currentIndex > 0 ? currentIndex - 1 : $tabs.length - 1;
|
||||
} else {
|
||||
targetIndex = currentIndex < $tabs.length - 1 ? currentIndex + 1 : 0;
|
||||
}
|
||||
|
||||
const $targetTab = $tabs.eq(targetIndex);
|
||||
$targetTab.focus();
|
||||
TabController.switchTab($targetTab);
|
||||
}
|
||||
});
|
||||
|
||||
// 초기 상태 설정
|
||||
this.initTabState();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Privacy Tab Controller (하위 호환성 유지)
|
||||
// ============================================
|
||||
const PrivacyTabController = {
|
||||
switchTab: function($clickedTab) {
|
||||
return TabController.switchTab.call(TabController, $clickedTab);
|
||||
},
|
||||
init: function() {
|
||||
return TabController.init.call(TabController);
|
||||
},
|
||||
initTabState: function() {
|
||||
return TabController.initTabState.call(TabController);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Main Initialization
|
||||
// ============================================
|
||||
const PopupController = {
|
||||
init() {
|
||||
// DOM이 준비되면 초기화
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.start();
|
||||
});
|
||||
} else {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
PopupCloseController.init();
|
||||
EmailDomainController.init();
|
||||
TimerController.init();
|
||||
FindAccountController.init();
|
||||
LoginTabController.init();
|
||||
TermsAgreementController.init();
|
||||
JoinCompletionController.init();
|
||||
TabController.init();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Start Application
|
||||
// ============================================
|
||||
PopupController.init();
|
||||
|
||||
// ============================================
|
||||
// Global Functions
|
||||
// ============================================
|
||||
// 전역에서 접근 가능하도록 함수 노출
|
||||
window.PopupController = PopupController;
|
||||
window.TabController = TabController;
|
||||
window.PrivacyTabController = PrivacyTabController; // 하위 호환성 유지
|
||||
|
||||
})();
|
||||
2
kngil/js/qa/jquery-3.6.1.min.js
vendored
Normal file
2
kngil/js/qa/jquery-3.6.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
kngil/js/qa/jquery.mousewheel.min.js
vendored
Normal file
5
kngil/js/qa/jquery.mousewheel.min.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* jQuery Mousewheel 3.1.13
|
||||
* Copyright OpenJS Foundation and other contributors
|
||||
*/
|
||||
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e:e(jQuery)}(function(a){var u,r,e=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],t="onwheel"in window.document||9<=window.document.documentMode?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],f=Array.prototype.slice;if(a.event.fixHooks)for(var n=e.length;n;)a.event.fixHooks[e[--n]]=a.event.mouseHooks;var d=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var e=t.length;e;)this.addEventListener(t[--e],i,!1);else this.onmousewheel=i;a.data(this,"mousewheel-line-height",d.getLineHeight(this)),a.data(this,"mousewheel-page-height",d.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var e=t.length;e;)this.removeEventListener(t[--e],i,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(e){var t=a(e),e=t["offsetParent"in a.fn?"offsetParent":"parent"]();return e.length||(e=a("body")),parseInt(e.css("fontSize"),10)||parseInt(t.css("fontSize"),10)||16},getPageHeight:function(e){return a(e).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};function i(e){var t,n=e||window.event,i=f.call(arguments,1),o=0,l=0,s=0,h=0;if((e=a.event.fix(n)).type="mousewheel","detail"in n&&(s=-1*n.detail),"wheelDelta"in n&&(s=n.wheelDelta),"wheelDeltaY"in n&&(s=n.wheelDeltaY),"wheelDeltaX"in n&&(l=-1*n.wheelDeltaX),"axis"in n&&n.axis===n.HORIZONTAL_AXIS&&(l=-1*s,s=0),o=0===s?l:s,"deltaY"in n&&(o=s=-1*n.deltaY),"deltaX"in n&&(l=n.deltaX,0===s&&(o=-1*l)),0!==s||0!==l)return 1===n.deltaMode?(o*=t=a.data(this,"mousewheel-line-height"),s*=t,l*=t):2===n.deltaMode&&(o*=t=a.data(this,"mousewheel-page-height"),s*=t,l*=t),h=Math.max(Math.abs(s),Math.abs(l)),(!r||h<r)&&c(n,r=h)&&(r/=40),c(n,h)&&(o/=40,l/=40,s/=40),o=Math[1<=o?"floor":"ceil"](o/r),l=Math[1<=l?"floor":"ceil"](l/r),s=Math[1<=s?"floor":"ceil"](s/r),d.settings.normalizeOffset&&this.getBoundingClientRect&&(h=this.getBoundingClientRect(),e.offsetX=e.clientX-h.left,e.offsetY=e.clientY-h.top),e.deltaX=l,e.deltaY=s,e.deltaFactor=r,e.deltaMode=0,i.unshift(e,o,l,s),u&&window.clearTimeout(u),u=window.setTimeout(w,200),(a.event.dispatch||a.event.handle).apply(this,i)}function w(){r=null}function c(e,t){return d.settings.adjustOldDeltas&&"mousewheel"===e.type&&t%120==0}a.fn.extend({mousewheel:function(e){return e?this.on("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.off("mousewheel",e)}})});
|
||||
1325
kngil/js/qa/qa_common.js
Normal file
1325
kngil/js/qa/qa_common.js
Normal file
File diff suppressed because it is too large
Load Diff
198
kngil/js/qa/qa_index.js
Normal file
198
kngil/js/qa/qa_index.js
Normal file
@@ -0,0 +1,198 @@
|
||||
$(function () {
|
||||
var obj = document.getElementById("video_play");
|
||||
var video = $("#video_play").get(0);
|
||||
var i = 1;
|
||||
|
||||
// 영상 소스를 비율에 맞게 설정하는 함수
|
||||
function updateVideoSource() {
|
||||
var width = $(window).width();
|
||||
var height = $(window).height();
|
||||
var ratio = width / height;
|
||||
|
||||
// 비율이 가로가 더 길면 기본 영상, 세로가 더 길면 '_v'가 붙은 영상
|
||||
if (ratio > 1) {
|
||||
$("#video_play").attr("src", "img/main_" + i + ".mp4");
|
||||
$("ul.pagination_main").removeClass("m");
|
||||
} else {
|
||||
$("#video_play").attr("src", "img/main_" + i + "_v.mp4");
|
||||
$("ul.pagination_main").addClass("m");
|
||||
}
|
||||
|
||||
// 영상 로드 및 자동 재생
|
||||
video.load();
|
||||
video.play();
|
||||
}
|
||||
|
||||
// 페이지 로드 시 비율에 맞는 영상 설정
|
||||
updateVideoSource();
|
||||
|
||||
// 화면 사이즈가 변경될 때마다 비율에 맞는 영상 설정
|
||||
$(window).resize(function () {
|
||||
updateVideoSource();
|
||||
});
|
||||
console.log("리사이징 완료");
|
||||
|
||||
// 인트로 페이지 종료 후 첫 영상 실행
|
||||
video.pause();
|
||||
$(".pagination_main").hide();
|
||||
|
||||
if (sessionStorage.getItem("visited")) {
|
||||
video.play();
|
||||
$(".pagination_main").show();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
video.play();
|
||||
$(".pagination_main").show();
|
||||
}, 2800);
|
||||
}
|
||||
|
||||
// 페이지 네이션 - 클릭하면 해당 영상 실행
|
||||
function setPageVideo(pageNum) {
|
||||
i = pageNum;
|
||||
updateVideoSource(); // 비율에 맞는 영상으로 설정
|
||||
$(".page_0" + i).addClass("page_on");
|
||||
$(".pagination_main div")
|
||||
.not(".page_0" + i)
|
||||
.removeClass("page_on");
|
||||
$(".main_link_0" + i).addClass("link_on");
|
||||
$(".main_link a")
|
||||
.not(".main_link_0" + i)
|
||||
.removeClass("link_on");
|
||||
}
|
||||
|
||||
// 각 페이지 클릭 시 영상 변경
|
||||
$(".page_01").click(function () {
|
||||
setPageVideo(1);
|
||||
console.log("영상1 재생");
|
||||
});
|
||||
$(".page_02").click(function () {
|
||||
setPageVideo(2);
|
||||
console.log("영상2 재생");
|
||||
});
|
||||
$(".page_03").click(function () {
|
||||
setPageVideo(3);
|
||||
console.log("영상3 재생");
|
||||
});
|
||||
$(".page_04").click(function () {
|
||||
setPageVideo(4);
|
||||
console.log("영상4 재생");
|
||||
});
|
||||
$(".page_05").click(function () {
|
||||
setPageVideo(5);
|
||||
console.log("영상5 재생");
|
||||
});
|
||||
|
||||
// 영상 종료 후 다음 영상 실행
|
||||
$("#video_play").on("ended", function () {
|
||||
if (i < 5) {
|
||||
i = i + 1;
|
||||
updateVideoSource();
|
||||
$(".page_0" + i).addClass("page_on");
|
||||
$(".pagination_main div")
|
||||
.not(".page_0" + i)
|
||||
.removeClass("page_on");
|
||||
$(".main_link_0" + i).addClass("link_on");
|
||||
$(".main_link a")
|
||||
.not(".main_link_0" + i)
|
||||
.removeClass("link_on");
|
||||
} else {
|
||||
i = 1;
|
||||
updateVideoSource();
|
||||
$(".page_0" + i).addClass("page_on");
|
||||
$(".pagination_main div")
|
||||
.not(".page_0" + i)
|
||||
.removeClass("page_on");
|
||||
$(".main_link_0" + i).addClass("link_on");
|
||||
$(".main_link a")
|
||||
.not(".main_link_0" + i)
|
||||
.removeClass("link_on");
|
||||
}
|
||||
video.play(); // 영상 자동 재생
|
||||
});
|
||||
});
|
||||
|
||||
// index footer 동작
|
||||
$(function () {
|
||||
let currentMode = null;
|
||||
|
||||
// 디바이스 체크 함수
|
||||
function getDeviceType() {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isMobile =
|
||||
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(ua);
|
||||
const isTablet = /(ipad|tablet|playbook|silk)|(android(?!.*mobile))/i.test(
|
||||
ua
|
||||
);
|
||||
|
||||
// 터치 지원 여부도 체크
|
||||
const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// 모바일 또는 태블릿이면서 터치 지원
|
||||
if ((isMobile || isTablet) && hasTouch) {
|
||||
return "mo";
|
||||
}
|
||||
|
||||
return "pc";
|
||||
}
|
||||
|
||||
function setupFooterEvents() {
|
||||
const newMode = getDeviceType();
|
||||
|
||||
// 모드가 변경되지 않았으면 리턴
|
||||
if (currentMode === newMode) return;
|
||||
|
||||
currentMode = newMode;
|
||||
|
||||
// 기존 이벤트 제거
|
||||
$(".main").off("wheel mousewheel touchmove");
|
||||
|
||||
if (newMode === "pc") {
|
||||
// PC: 기본 footer_off 상태
|
||||
$("footer").addClass("footer_off").removeClass("footer_on");
|
||||
|
||||
// wheel 이벤트로 토글
|
||||
$(".main").on("wheel", function (e) {
|
||||
if (e.originalEvent.deltaY < 0) {
|
||||
// 위로 스크롤
|
||||
$("footer").addClass("footer_off").removeClass("footer_on");
|
||||
} else {
|
||||
// 아래로 스크롤
|
||||
$("footer").addClass("footer_on").removeClass("footer_off");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 모바일/태블릿: touchmove 시 footer_on
|
||||
$(".main").on("touchmove", function () {
|
||||
$("footer").addClass("footer_on").removeClass("footer_off");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 설정
|
||||
setupFooterEvents();
|
||||
|
||||
// footer 닫기
|
||||
$(".footer_close")
|
||||
.off("click")
|
||||
.on("click", function () {
|
||||
$("footer").addClass("footer_off").removeClass("footer_on");
|
||||
});
|
||||
});
|
||||
|
||||
// 인트로 없애기
|
||||
// 1. 홈페이지에 들어오면 sessionStorage에 visited 추가
|
||||
// 2. visited가 있는 동안에는 인트로 삭제
|
||||
// 3. 브라우저 종료 or 탭 닫으면 visited 자동 삭제
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const intro = document.querySelector(".intro_wrap");
|
||||
|
||||
if (sessionStorage.getItem("visited")) {
|
||||
console.log("visited");
|
||||
intro.style.display = "none";
|
||||
document.querySelector(".main_mask").classList.add("skip");
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
sessionStorage.setItem("visited", "true");
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
256
kngil/js/qa/qa_popup.js
Normal file
256
kngil/js/qa/qa_popup.js
Normal file
@@ -0,0 +1,256 @@
|
||||
window.onload = function() {
|
||||
$.ajax({
|
||||
url: "some_api_endpoint",
|
||||
success: function(response) {
|
||||
},
|
||||
complete: function() {
|
||||
|
||||
// 팝업 닫기버튼
|
||||
$(document).ready(function() {
|
||||
$('.btn_close').click(function() {
|
||||
$('.popup_wrap').hide();
|
||||
$('body').css('overflow', ''); // 기본 스크롤 상태로 복귀
|
||||
lenis.start();
|
||||
console.log('lenis 재시작')
|
||||
});
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$('.btn_map_close').click(function() {
|
||||
$('.popup_sitemap').hide();
|
||||
$('body').css('overflow', ''); // 기본 스크롤 상태로 복귀
|
||||
lenis.start();
|
||||
console.log('lenis 재시작')
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 이메일 직접입력
|
||||
$(document).ready(function() {
|
||||
$('#domain-list').change(function() {
|
||||
if ($(this).val() === 'type') {
|
||||
$('#custom-domain').show().focus();
|
||||
} else {
|
||||
$('#custom-domain').hide().val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 인증번호 타이머
|
||||
$(document).ready(function() {
|
||||
// 인증번호 버튼 클릭 시
|
||||
$('.cert_number').click(function() {
|
||||
$('.code').show();
|
||||
});
|
||||
|
||||
// 확인 버튼 클릭 시
|
||||
$('.check').click(function() {
|
||||
$(this).hide();
|
||||
$('.check.complete').show();
|
||||
clearInterval(interval); // 타이머 멈춤
|
||||
$('.timer').remove(); // 타이머 요소 삭제
|
||||
});
|
||||
|
||||
// 타이머 함수
|
||||
var interval;
|
||||
function startTimer(duration, display) {
|
||||
clearInterval(interval);
|
||||
var timer = duration, minutes, seconds;
|
||||
|
||||
function updateTimer() {
|
||||
minutes = parseInt(timer / 60, 10);
|
||||
seconds = parseInt(timer % 60, 10);
|
||||
|
||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||
seconds = seconds < 10 ? "0" + seconds : seconds;
|
||||
|
||||
display.text(minutes + ":" + seconds);
|
||||
|
||||
if (--timer < 0) {
|
||||
clearInterval(interval);
|
||||
display.text("00:00");
|
||||
}
|
||||
}
|
||||
|
||||
updateTimer();
|
||||
interval = setInterval(updateTimer, 1000);
|
||||
}
|
||||
|
||||
var threeMinutes = 60 * 3,
|
||||
display = $('.timer');
|
||||
|
||||
startTimer(threeMinutes, display);
|
||||
|
||||
$('.cert_number').click(function() {
|
||||
startTimer(threeMinutes, display);
|
||||
});
|
||||
});
|
||||
|
||||
// 아이디찾기
|
||||
$(document).ready(function(){
|
||||
$('.find_email').click(function(){
|
||||
$('.find_ph').removeClass('on').prop('checked', false);
|
||||
$(this).addClass('on').prop('checked', true);
|
||||
$('.ph').hide();
|
||||
$('.email').show();
|
||||
});
|
||||
|
||||
$('.find_ph').click(function(){
|
||||
$('.find_email').removeClass('on').prop('checked', false);
|
||||
$(this).addClass('on').prop('checked', true);
|
||||
$('.email').hide();
|
||||
$('.ph').show();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.btn_id').on('click', function() {
|
||||
$('.btn_id').addClass('on');
|
||||
$('.btn_pw').removeClass('on');
|
||||
$('.content.id').show();
|
||||
$('.content.pw').hide();
|
||||
});
|
||||
|
||||
$('.btn_pw').on('click', function() {
|
||||
$('.btn_pw').addClass('on');
|
||||
$('.btn_id').removeClass('on');
|
||||
$('.content.pw').show();
|
||||
$('.content.id').hide();
|
||||
});
|
||||
|
||||
$('#domain-list').on('change', function() {
|
||||
if ($(this).val() === 'type') {
|
||||
$('#custom-domain').show();
|
||||
} else {
|
||||
$('#custom-domain').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 전체약관동의
|
||||
// $(document).ready(function() {
|
||||
// function toggleJoinButton() {
|
||||
// // 모든 개별 체크박스가 체크되었는지 확인
|
||||
// var allChecked = $('.terms_wrap input[type="checkbox"]').length === $('.terms_wrap input[type="checkbox"]:checked').length;
|
||||
|
||||
// // '약관에 모두 동의합니다' 체크박스 상태에 따라 조정
|
||||
// $('.checkbox_wrap.all input[type="checkbox"]').prop('checked', allChecked);
|
||||
|
||||
// // 모든 체크박스가 체크되지 않은 경우 버튼에 'none' 클래스 추가하고 disabled 속성 추가
|
||||
// if (allChecked) {
|
||||
// $('.join_btn_wrap').removeClass('none');
|
||||
// $('.join_btn_wrap button').prop('disabled', false);
|
||||
// } else {
|
||||
// $('.join_btn_wrap').addClass('none');
|
||||
// $('.join_btn_wrap button').prop('disabled', true);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // '약관에 모두 동의합니다' 체크박스의 변경 이벤트
|
||||
// $('.checkbox_wrap.all input[type="checkbox"]').on('change', function() {
|
||||
// var isChecked = $(this).is(':checked');
|
||||
// $('.terms_wrap input[type="checkbox"]').prop('checked', isChecked);
|
||||
// toggleJoinButton(); // 버튼 상태 업데이트
|
||||
// });
|
||||
|
||||
// // 각 terms_wrap의 개별 체크박스 변경 이벤트
|
||||
// $('.terms_wrap input[type="checkbox"]').on('change', function() {
|
||||
// toggleJoinButton(); // 버튼 상태 업데이트
|
||||
// });
|
||||
|
||||
// // 초기 상태 설정
|
||||
// toggleJoinButton();
|
||||
// });
|
||||
|
||||
// 전체약관동의 수정 250813
|
||||
(function ($) {
|
||||
if (window.__agreeBound) return; // 중복 바인딩 방지
|
||||
window.__agreeBound = true;
|
||||
|
||||
const $container = $('#pop_agreement');
|
||||
const $items = $container.find('.terms_wrap input[type="checkbox"]'); // agree11, agree21
|
||||
const $all = $container.find('.checkbox_wrap.all input[type="checkbox"]');
|
||||
const $btn = $container.find('#btn_agree');
|
||||
|
||||
function syncAll() {
|
||||
const allChecked = $items.length > 0 && $items.filter(':checked').length === $items.length;
|
||||
$all.prop('checked', allChecked);
|
||||
// 버튼은 disable 하지 않음 (스타일만 조정하고 싶다면 클래스만 토글)
|
||||
// $('.join_btn_wrap').toggleClass('none', !allChecked); <-- 필요 없으면 제거
|
||||
}
|
||||
|
||||
// 전체동의 → 개별
|
||||
$all.on('change', function () {
|
||||
const on = $(this).is(':checked');
|
||||
$items.prop('checked', on);
|
||||
syncAll();
|
||||
});
|
||||
|
||||
// 개별 → 전체동의 동기화
|
||||
$items.on('change', syncAll);
|
||||
|
||||
// 동의 버튼 클릭 시에만 검사
|
||||
$btn.off('click.agree').on('click.agree', function (e) {
|
||||
e.preventDefault();
|
||||
const allChecked = $items.filter(':checked').length === $items.length;
|
||||
if (!allChecked) {
|
||||
alert('약관에 모두 동의해주세요.');
|
||||
return false;
|
||||
}
|
||||
// 통과 시 다음 단계로 진행(필요 시 주석 해제)
|
||||
// $('#pop_agreement').hide();
|
||||
// $('#pop_register_form').show();
|
||||
// $('body').css('overflow','hidden');
|
||||
});
|
||||
|
||||
// 외부에서 팝업 열 때 상태 초기화가 필요하면 이 함수 호출
|
||||
window.resetAgreementUI = function () {
|
||||
$items.prop('checked', false);
|
||||
$all.prop('checked', false);
|
||||
syncAll();
|
||||
// 버튼은 항상 활성
|
||||
$('#btn_agree, #fregister button[type=submit]')
|
||||
.prop('disabled', false)
|
||||
.css('pointer-events', 'auto');
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
||||
// 가입완료
|
||||
$(document).ready(function() {
|
||||
$('.join.completion .join_btn_wrap button').click(function() {
|
||||
$('.pop_input_wrap form').children().not('.messages').hide();
|
||||
$('.messages').show();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.join.completion .join_btn_wrap button').click(function() {
|
||||
$('.pop_input_wrap form').children().not('.messages').hide();
|
||||
$('.messages').show();
|
||||
|
||||
// 세 번째 단계에 'on' 클래스 추가하고, 다른 단계에서 'on' 클래스 제거
|
||||
$('.join_progress .join_step').removeClass('on');
|
||||
$('.join_progress .join_step').eq(2).addClass('on');
|
||||
});
|
||||
});
|
||||
|
||||
// 개인정보 보호정책 스크립트
|
||||
$(document).ready(function() {
|
||||
$('.tab_privacy').on('click', function() {
|
||||
$(this).addClass('on');
|
||||
$('.tab_agreement').removeClass('on');
|
||||
$('.content.pri').addClass('show').removeClass('hide');
|
||||
$('.content.agr').removeClass('show').addClass('hide');
|
||||
});
|
||||
$('.tab_agreement').on('click', function() {
|
||||
$(this).addClass('on');
|
||||
$('.tab_privacy').removeClass('on');
|
||||
$('.content.agr').addClass('show').removeClass('hide');
|
||||
$('.content.pri').removeClass('show').addClass('hide');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
109
kngil/js/qa_write.js
Normal file
109
kngil/js/qa_write.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// /kngil/js/qa/qa_write.js
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
/* ==========================
|
||||
* 1. CKEditor 초기화
|
||||
* ========================== */
|
||||
let editorInstance = null;
|
||||
|
||||
const contentEl = document.querySelector('#content');
|
||||
if (contentEl) {
|
||||
ClassicEditor
|
||||
.create(contentEl, {
|
||||
toolbar: [
|
||||
'heading','|',
|
||||
'bold','italic','link',
|
||||
'bulletedList','numberedList','|',
|
||||
'fontColor','fontBackgroundColor','fontSize',
|
||||
'|','blockQuote','insertTable','imageUpload',
|
||||
'undo','redo'
|
||||
],
|
||||
language: 'ko',
|
||||
ckfinder: {
|
||||
uploadUrl: '/kngil/bbs/qa_img_upload.php'
|
||||
}
|
||||
})
|
||||
.then(editor => {
|
||||
editorInstance = editor;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
* 2. 폼 submit 검증
|
||||
* ========================== */
|
||||
const form = document.getElementById('qaForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
if (!editorInstance) return;
|
||||
|
||||
const data = editorInstance.getData().trim();
|
||||
if (!data) {
|
||||
e.preventDefault();
|
||||
alert('내용을 입력해주세요.');
|
||||
editorInstance.editing.view.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
contentEl.value = data; // textarea에 반영
|
||||
});
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
* 3. 첨부파일 Drag & Drop
|
||||
* ========================== */
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const fileInput = document.getElementById('attach');
|
||||
const fileList = document.getElementById('file-list');
|
||||
|
||||
if (dropZone && fileInput && fileList) {
|
||||
|
||||
let uploadFiles = [];
|
||||
|
||||
dropZone.addEventListener('click', () => fileInput.click());
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('dragover');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', () => {
|
||||
dropZone.classList.remove('dragover');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('dragover');
|
||||
addFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
addFiles(fileInput.files);
|
||||
});
|
||||
|
||||
function addFiles(files) {
|
||||
for (const file of files) {
|
||||
uploadFiles.push(file);
|
||||
}
|
||||
render();
|
||||
syncInput();
|
||||
}
|
||||
|
||||
function render() {
|
||||
fileList.innerHTML = '';
|
||||
uploadFiles.forEach(file => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = `${file.name} (${(file.size / 1024).toFixed(1)} KB)`;
|
||||
fileList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function syncInput() {
|
||||
const dt = new DataTransfer();
|
||||
uploadFiles.forEach(f => dt.items.add(f));
|
||||
fileInput.files = dt.files;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
96
kngil/js/viewimageresize.js
Normal file
96
kngil/js/viewimageresize.js
Normal file
@@ -0,0 +1,96 @@
|
||||
(function($) {
|
||||
$.fn.viewimageresize = function(selector)
|
||||
{
|
||||
var cfg = {
|
||||
selector: "img"
|
||||
};
|
||||
|
||||
if(typeof selector == "object") {
|
||||
cfg = $.extend(cfg, selector);
|
||||
} else {
|
||||
if(selector) {
|
||||
cfg = $.extend({ selector: selector });
|
||||
}
|
||||
}
|
||||
|
||||
var $img = this.find(cfg.selector);
|
||||
var $this = this;
|
||||
|
||||
$img.removeAttr("height")
|
||||
.css("height", "");
|
||||
|
||||
function image_resize()
|
||||
{
|
||||
var width = $this.width();
|
||||
|
||||
$img.each(function() {
|
||||
if($(this).data("width") == undefined)
|
||||
$(this).data("width", $(this).width());
|
||||
|
||||
if($(this).data("width") > width) {
|
||||
$(this).removeAttr("width")
|
||||
.removeAttr("height")
|
||||
.css("width","")
|
||||
.css("height", "");
|
||||
|
||||
if($(this).data("width") > width) {
|
||||
$(this).css("width", "100%");
|
||||
}
|
||||
} else {
|
||||
$(this).attr("width", $(this).data("width"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on("load", function() {
|
||||
image_resize();
|
||||
});
|
||||
|
||||
$(window).on("resize", function() {
|
||||
image_resize();
|
||||
});
|
||||
}
|
||||
|
||||
$.fn.viewimageresize2 = function(selector)
|
||||
{
|
||||
var cfg = {
|
||||
selector: "img"
|
||||
};
|
||||
|
||||
if(typeof selector == "object") {
|
||||
cfg = $.extend(cfg, selector);
|
||||
} else {
|
||||
if(selector) {
|
||||
cfg = $.extend({ selector: selector });
|
||||
}
|
||||
}
|
||||
|
||||
var $img = this.find(cfg.selector);
|
||||
var $this = this;
|
||||
|
||||
function image_resize()
|
||||
{
|
||||
var width = $this.width();
|
||||
|
||||
$img.each(function() {
|
||||
$(this).removeAttr("width")
|
||||
.removeAttr("height")
|
||||
.css("width","")
|
||||
.css("height", "");
|
||||
|
||||
if($(this).data("width") == undefined)
|
||||
$(this).data("width", $(this).width());
|
||||
|
||||
if($(this).data("width") > width) {
|
||||
$(this).css("width", "100%");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on("resize", function() {
|
||||
image_resize();
|
||||
});
|
||||
|
||||
image_resize();
|
||||
}
|
||||
}(jQuery));
|
||||
Reference in New Issue
Block a user