1571 lines
52 KiB
JavaScript
1571 lines
52 KiB
JavaScript
/**
|
|
* =====================
|
|
* 사용페이지 : 마이페이지
|
|
* 작 성 자 : ERP기획팀 moon
|
|
* 최초작성일자: 26.03.20.
|
|
* =====================
|
|
*/
|
|
|
|
(function () {
|
|
|
|
/**
|
|
* 콘텐츠 탭별 페이지 상태 관리
|
|
* - page: 현재까지 불러온 페이지 번호
|
|
* - loading: 현재 요청 중 여부
|
|
* - isLast: 마지막 페이지 여부
|
|
*/
|
|
const contentListState = {
|
|
watching: { page: 1, loading: false, isLast: false },
|
|
completed: { page: 1, loading: false, isLast: false },
|
|
saved: { page: 1, loading: false, isLast: false }
|
|
};
|
|
|
|
/**
|
|
* 특정 탭의 페이지 상태를 초기화하는 함수
|
|
* - 탭을 새로 조회할 때 page, loading, isLast 값을 초기화한다.
|
|
*/
|
|
function resetContentListState(tab) {
|
|
if (!contentListState[tab]) return;
|
|
|
|
contentListState[tab].page = 1;
|
|
contentListState[tab].loading = false;
|
|
contentListState[tab].isLast = false;
|
|
}
|
|
|
|
/**
|
|
* 마이페이지 초기 진입 시 실행
|
|
* - 연도 변경 이벤트 연결
|
|
* - 학습활동 데이터 로드
|
|
* - 콘텐츠 탭 클릭 시 재렌더링 이벤트 연결
|
|
*/
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
// 연도 select 변경 이벤트 연결
|
|
bindYearSelectChange();
|
|
|
|
// 학습활동 초기 데이터 조회
|
|
loadMypageInit();
|
|
|
|
// 콘텐츠 탭 클릭 시 리스트 재조회 이벤트 연결
|
|
bindContentTabReload();
|
|
|
|
//초기 실행에 무한스크롤 바인딩 추가
|
|
bindInfiniteScroll();
|
|
|
|
// 콘텐츠 카드 삭제 버튼 이벤트 연결
|
|
bindRemoveCardAction();
|
|
|
|
// 북마크 체크박스 이벤트 연결
|
|
bindBookmarkToggleAction();
|
|
|
|
// [제안하기]PC용 전용 폼 검증/제출 연결
|
|
bindOfferFormValidation();
|
|
bindOfferFormSubmit();
|
|
|
|
// [제안하기]모바일 전용
|
|
bindMobileOfferFormValidation();
|
|
bindMobileOfferSubmit();
|
|
|
|
|
|
// ★ 초기 선택년도 기준 한줄소감 / 학습레벨 조회
|
|
const yearSelectEl = document.getElementById('mypage-year-select');
|
|
const initialYear = yearSelectEl ? (yearSelectEl.value || '') : '';
|
|
|
|
loadReviewList(initialYear);
|
|
loadLevelData(initialYear);
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* 모든 리소스 로드 완료 후 실행
|
|
* - 퍼블리싱용 mypage.js 초기화가 끝난 뒤
|
|
* 현재 활성 탭 클릭을 한 번 강제 실행하고,
|
|
* 3개 콘텐츠 탭을 모두 미리 조회한다.
|
|
*/
|
|
window.addEventListener("load", function () {
|
|
setTimeout(function () {
|
|
// 현재 활성 탭 강제 클릭
|
|
triggerInitialActiveTabClick();
|
|
|
|
// 3개 탭 목록 모두 미리 조회
|
|
// preloadAllContentLists();
|
|
}, 200);
|
|
});
|
|
|
|
|
|
/**
|
|
* 최초 로드시 현재 활성 탭 버튼을 강제로 한 번 클릭시키는 함수
|
|
* - 퍼블리싱용 mypage.js의 탭 전환 로직을 그대로 사용한다.
|
|
* - 이후 bindContentTabReload()에 연결된 재렌더링 로직이 함께 실행된다.
|
|
*/
|
|
function triggerInitialActiveTabClick() {
|
|
// 현재 active 상태의 탭 버튼 찾기
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
if (!activeTab) return;
|
|
|
|
console.log('[초기 활성 탭 강제 클릭]', activeTab.getAttribute('data-tab'));
|
|
|
|
// 실제 클릭 이벤트 발생
|
|
activeTab.click();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 초기 콘텐츠 리스트 렌더링 안정화 함수
|
|
* - 최초 로드 시 mypage.js가 DOM을 다시 건드리는 경우가 있어
|
|
* 현재 활성 탭 기준으로 일정 횟수 재렌더링을 수행한다.
|
|
* - 카드가 정상적으로 들어오면 자동 중단한다.
|
|
*/
|
|
/*
|
|
function stabilizeInitialContentRendering() {
|
|
let tryCount = 0;
|
|
const maxTry = 8;
|
|
|
|
const timer = setInterval(function () {
|
|
tryCount++;
|
|
|
|
|
|
// 시청중 리스트에 카드가 실제로 들어왔는지 확인
|
|
const watchingList = document.getElementById('mypage-watching-list');
|
|
const cardCount = watchingList ? watchingList.querySelectorAll('.content-card').length : 0;
|
|
|
|
console.log('[초기 복구 시도]', tryCount, 'watching card count:', cardCount);
|
|
|
|
// 카드가 정상 삽입되었으면 중단
|
|
if (cardCount > 0) {
|
|
clearInterval(timer);
|
|
}
|
|
|
|
// 최대 시도 횟수 초과 시 중단
|
|
if (tryCount >= maxTry) {
|
|
clearInterval(timer);
|
|
}
|
|
}, 250);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* 마이페이지 초기 데이터 로드 함수
|
|
* - 서버에서 학습활동 관련 데이터를 가져온다
|
|
* - 연도 select, 총 학습시간, 전체평균, 카테고리별 수치를 화면에 반영한다.
|
|
*/
|
|
function loadMypageInit(year = '') {
|
|
fetch('/edu/ajax/get_init_data_for_mypage.php?year=' + year)
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
|
|
console.log('loadMypageInit');
|
|
console.log(data);
|
|
|
|
// 실패 시 종료
|
|
if (!data.success) return;
|
|
|
|
// 1. 연도 select 구성
|
|
renderYearSelect(data.year_list, data.selected_year);
|
|
|
|
// 2. 총 학습시간 표시
|
|
renderMyTotalMinutes(data.my_total_minutes);
|
|
|
|
// 3. 모바일 총 학습시간 표시
|
|
renderMobileMyTotalMinutes(data.my_total_minutes);
|
|
|
|
// 4. 전체평균 표시
|
|
renderAvgTotalMinutes(data.avg_total_minutes);
|
|
|
|
// 5. 상단 총 학습시간 게이지 표시
|
|
renderMyTotalGauge(data.my_total_minutes);
|
|
|
|
// 6. 마이클래스 항목 표시
|
|
renderMyClassActivity(data.activity_items);
|
|
|
|
// 7. 온보딩 학습활동 데이터를 화면에 반영
|
|
renderOnboardingActivity(data.activity_items);
|
|
|
|
// 8. 법정교육 학습활동 데이터를 화면에 반영
|
|
renderLegalEducationActivity(data.activity_items);
|
|
|
|
// 9. 리더십 학습활동 데이터를 화면에 반영
|
|
renderLeadershipActivity(data.activity_items);
|
|
|
|
// 10. 인사이트 학습활동 데이터를 화면에 반영
|
|
renderInsightActivity(data.activity_items);
|
|
|
|
// 11. 비즈트렌드 학습활동 데이터를 화면에 반영
|
|
renderBizTrendActivity(data.activity_items);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('학습활동 데이터를 불러오지 못했습니다.', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 연도 select 옵션을 구성하는 함수
|
|
* - 서버에서 받은 year_list를 기준으로 option 생성
|
|
* - selectedYear와 일치하는 값을 selected 처리
|
|
*/
|
|
function renderYearSelect(yearList, selectedYear) {
|
|
const select = document.getElementById('mypage-year-select');
|
|
if (!select) return;
|
|
|
|
select.innerHTML = '';
|
|
|
|
yearList.forEach(function (year) {
|
|
const option = document.createElement('option');
|
|
option.value = year;
|
|
option.textContent = year + '년';
|
|
|
|
if (year == selectedYear) {
|
|
option.selected = true;
|
|
}
|
|
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 총 학습시간(분)을 화면에 표시하는 함수
|
|
* - 서버에서 받은 my_total_minutes 값을
|
|
* #mypage-total-minutes 영역에 표시한다.
|
|
* - 숫자는 천단위 콤마 포맷 적용
|
|
*/
|
|
function renderMyTotalMinutes(totalMinutes) {
|
|
const el = document.getElementById('mypage-total-minutes');
|
|
if (!el) return;
|
|
|
|
const value = parseInt(totalMinutes, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(value / 60);
|
|
el.textContent = totalWatchTmMin.toLocaleString();
|
|
}
|
|
|
|
/**
|
|
* 모바일 총 학습시간(분)을 화면에 표시하는 함수
|
|
* - 서버에서 받은 my_total_minutes 값을
|
|
* #mobile-mypage-total-minutes 영역에 표시한다.
|
|
* - 숫자는 천단위 콤마 포맷 적용
|
|
*/
|
|
function renderMobileMyTotalMinutes(totalMinutes) {
|
|
const el = document.getElementById('mobile-mypage-total-minutes');
|
|
if (!el) return;
|
|
|
|
const value = parseInt(totalMinutes, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(value / 60);
|
|
el.textContent = totalWatchTmMin.toLocaleString();
|
|
}
|
|
|
|
/**
|
|
* 전체평균 학습시간(분)을 화면에 표시하는 함수
|
|
* - 서버에서 받은 avg_total_minutes 값을
|
|
* #mypage-avg-minutes 영역에 표시한다.
|
|
* - 숫자는 천단위 콤마 포맷 적용
|
|
*/
|
|
function renderAvgTotalMinutes(avgTotalMinutes) {
|
|
const el = document.getElementById('mypage-avg-minutes');
|
|
if (!el) return;
|
|
|
|
const value = parseInt(avgTotalMinutes, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(value / 60);
|
|
|
|
el.textContent = totalWatchTmMin.toLocaleString();
|
|
}
|
|
|
|
/**
|
|
* 총 학습시간 게이지를 화면에 반영하는 함수
|
|
* - 서버에서 받은 my_total_minutes 값을 기준으로
|
|
* #mypage-total-gauge의 width 값을 변경한다.
|
|
* - 최대 기준은 40시간(2400분)으로 계산한다.
|
|
* - 2400분을 초과해도 게이지는 100%를 넘지 않도록 제한한다.
|
|
*/
|
|
function renderMyTotalGauge(totalMinutes) {
|
|
const el = document.getElementById('mypage-total-gauge');
|
|
if (!el) return;
|
|
|
|
const value = parseInt(totalMinutes, 10) || 0;
|
|
const maxMinutes = 2400;
|
|
|
|
let percent = Math.round((value / maxMinutes) * 100);
|
|
|
|
if (percent < 0) percent = 0;
|
|
if (percent > 100) percent = 100;
|
|
|
|
el.style.width = percent + '%';
|
|
}
|
|
|
|
|
|
/**
|
|
* 연도 select 변경 이벤트를 연결하는 함수
|
|
* - 사용자가 연도를 변경하면
|
|
* 학습활동, 한줄소감, 학습레벨, 콘텐츠 탭을 함께 다시 조회한다.
|
|
*/
|
|
function bindYearSelectChange() {
|
|
const select = document.getElementById('mypage-year-select');
|
|
if (!select) return;
|
|
|
|
select.addEventListener('change', function () {
|
|
const selectedYear = this.value || '';
|
|
|
|
// ★ 학습활동 재조회
|
|
loadMypageInit(selectedYear);
|
|
|
|
// ★ 한줄소감 재조회
|
|
loadReviewList(selectedYear);
|
|
|
|
// ★ 학습레벨 재조회
|
|
loadLevelData(selectedYear);
|
|
|
|
// ★ 콘텐츠 탭 상태 초기화
|
|
resetContentListState('watching');
|
|
resetContentListState('completed');
|
|
resetContentListState('saved');
|
|
|
|
// ★ 콘텐츠 3개 탭 재조회
|
|
loadWatchingContentList(1);
|
|
loadCompletedContentList(1);
|
|
loadSavedContentList(1);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* 마이클래스(CA10001) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - activity_items 배열에서 마이클래스 항목만 찾아서
|
|
* 해당 li[data-category="CA10001"] 영역의 수치를 갱신한다.
|
|
* - 학습시간, 퍼센트, 게이지, 전체 콘텐츠 개수를 반영한다.
|
|
*/
|
|
function renderMyClassActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10001';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10001"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분 (' + percent + '%)';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalCount.toLocaleString() + '개';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 온보딩(CA10002) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - 학습시간, 퍼센트, 게이지, 분모를 표시
|
|
* - 온보딩 기간일 경우에만 "필수 시청 날짜"를 노출한다.
|
|
*/
|
|
function renderOnboardingActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10002';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10002"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분 (' + percent + '%)';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalCount.toLocaleString() + '개';
|
|
}
|
|
|
|
const noteEl = li.querySelector('.activity-note');
|
|
const dateEl = li.querySelector('.onboarding-due-date');
|
|
|
|
if (noteEl && dateEl) {
|
|
if (item.onboarding_due_date) {
|
|
const d = new Date(item.onboarding_due_date);
|
|
if (!isNaN(d.getTime())) {
|
|
const yy = String(d.getFullYear()).slice(2);
|
|
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(d.getDate()).padStart(2, '0');
|
|
dateEl.textContent = yy + '.' + mm + '.' + dd;
|
|
}
|
|
}
|
|
|
|
if (item.is_onboarding_period === 'Y') {
|
|
noteEl.style.display = '';
|
|
} else {
|
|
noteEl.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 법정교육(CA10003) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - 학습시간, 퍼센트, 게이지, 분모를 표시한다.
|
|
* - activity_items 배열에서 법정교육 항목만 찾아
|
|
* 해당 li[data-category="CA10003"] 영역을 갱신한다.
|
|
*/
|
|
function renderLegalEducationActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10003';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10003"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분 (' + percent + '%)';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalCount.toLocaleString() + '개';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 리더십(CA10004) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - 학습시간, 게이지, 분모(분)를 표시한다.
|
|
* - activity_items 배열에서 리더십 항목만 찾아
|
|
* 해당 li[data-category="CA10004"] 영역을 갱신한다.
|
|
*/
|
|
function renderLeadershipActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10004';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10004"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalContentTm.toLocaleString() + '분';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 인사이트(CA10005) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - 학습시간, 게이지, 분모(분)를 표시한다.
|
|
* - activity_items 배열에서 인사이트 항목만 찾아
|
|
* 해당 li[data-category="CA10005"] 영역을 갱신한다.
|
|
*/
|
|
function renderInsightActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10005';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10005"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalContentTm.toLocaleString() + '분';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 비즈트렌드(CA10006) 학습활동 데이터를 화면에 반영하는 함수
|
|
* - 학습시간, 게이지, 분모(분)를 표시한다.
|
|
* - activity_items 배열에서 비즈트렌드 항목만 찾아
|
|
* 해당 li[data-category="CA10006"] 영역을 갱신한다.
|
|
*/
|
|
function renderBizTrendActivity(activityItems) {
|
|
if (!Array.isArray(activityItems)) return;
|
|
|
|
const item = activityItems.find(function (row) {
|
|
return row.category_code === 'CA10006';
|
|
});
|
|
if (!item) return;
|
|
|
|
const li = document.querySelector('.activity-list li[data-category="CA10006"]');
|
|
if (!li) return;
|
|
|
|
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
|
// 2. 분 단위 변환 (소수점 버림 처리 예시)
|
|
const totalWatchTmMin = Math.floor(totalWatchTm / 60);
|
|
|
|
const percent = parseInt(item.percent, 10) || 0;
|
|
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
|
|
|
const valueEl = li.querySelector('.activity-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '<em>' + totalWatchTmMin.toLocaleString() + '</em>분';
|
|
}
|
|
|
|
const gaugeEl = li.querySelector('.gauge-fill');
|
|
if (gaugeEl) {
|
|
gaugeEl.style.width = percent + '%';
|
|
}
|
|
|
|
const endEl = li.querySelector('.gauge-end');
|
|
if (endEl) {
|
|
endEl.textContent = totalContentTm.toLocaleString() + '분';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 탭 클릭 이벤트 감지 후
|
|
* 해당 탭에 맞는 데이터를 다시 렌더링하는 함수
|
|
*
|
|
* - mypage.js가 탭 전환을 처리한 이후 실행된다
|
|
* - 각 탭(watching / completed / saved)에 따라
|
|
* AJAX 호출을 다시 수행하여 리스트를 덮어쓴다
|
|
*/
|
|
function bindContentTabReload() {
|
|
const tabs = document.querySelectorAll('.content-tab');
|
|
|
|
tabs.forEach(function (tabBtn) {
|
|
tabBtn.addEventListener('click', function () {
|
|
const tab = this.getAttribute('data-tab');
|
|
|
|
setTimeout(function () {
|
|
// 탭 전환 시 페이지 상태 초기화
|
|
resetContentListState(tab);
|
|
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList(1);
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList(1);
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList(1);
|
|
}
|
|
|
|
console.log('[탭 재렌더링]', tab);
|
|
}, 100);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 현재 활성화된 콘텐츠 탭을 기준으로
|
|
* 해당 탭의 리스트를 다시 렌더링하는 함수
|
|
*
|
|
* - 최초 페이지 진입 시 사용
|
|
* - mypage.js가 탭/패널 초기화를 끝낸 뒤 실행되어야 한다
|
|
*/
|
|
/*
|
|
function reloadActiveContentTab() {
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
if (!activeTab) return;
|
|
|
|
const tab = activeTab.getAttribute('data-tab');
|
|
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList();
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList();
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList();
|
|
}
|
|
|
|
console.log('[초기 활성 탭 재렌더링]', tab);
|
|
}
|
|
*/
|
|
|
|
|
|
/**
|
|
* 시청중인 콘텐츠 목록을 서버에서 조회하여 화면에 표시하는 함수
|
|
* - page=1이면 목록 전체를 교체한다.
|
|
* - page>1이면 기존 목록 아래에 이어붙인다.
|
|
* - 선택된 년도 기준으로 데이터를 조회한다.
|
|
*/
|
|
function loadWatchingContentList(page = 1) {
|
|
const listEl = document.getElementById('mypage-watching-list');
|
|
if (!listEl) return;
|
|
|
|
const state = contentListState.watching;
|
|
if (state.loading || state.isLast) return;
|
|
|
|
// ★ 요청 시작 상태 반영
|
|
state.loading = true;
|
|
|
|
// ★ 현재 선택년도 수집
|
|
const selectedYear = getSelectedMypageYear();
|
|
|
|
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=watching&page=' + page + '&selected_year=' + encodeURIComponent(selectedYear))
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data.success) return;
|
|
|
|
// ★ page 1이면 전체 교체, 그 외는 이어붙이기
|
|
if (page === 1) {
|
|
if ((data.html || '').trim() === '') {
|
|
renderEmptyContentMessage(listEl);
|
|
} else {
|
|
listEl.innerHTML = data.html || '';
|
|
}
|
|
} else {
|
|
listEl.insertAdjacentHTML('beforeend', data.html || '');
|
|
}
|
|
|
|
// ★ 탭 개수 갱신
|
|
renderContentTabCounts(data.counts);
|
|
|
|
// ★ 상태 갱신
|
|
state.page = page;
|
|
state.isLast = !!data.is_last;
|
|
|
|
console.log('[watching] 렌더링 완료:', page, state.isLast);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('시청중 콘텐츠 목록을 불러오지 못했습니다.', error);
|
|
})
|
|
.finally(function () {
|
|
state.loading = false;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 시청완료 콘텐츠 목록 로드
|
|
* - page=1이면 목록 전체를 교체한다.
|
|
* - page>1이면 기존 목록 아래에 이어붙인다.
|
|
* - 선택된 년도 기준으로 데이터를 조회한다.
|
|
*/
|
|
function loadCompletedContentList(page = 1) {
|
|
const listEl = document.getElementById('mypage-completed-list');
|
|
if (!listEl) return;
|
|
|
|
const state = contentListState.completed;
|
|
if (state.loading || state.isLast) return;
|
|
|
|
state.loading = true;
|
|
|
|
const selectedYear = getSelectedMypageYear();
|
|
|
|
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=completed&page=' + page + '&selected_year=' + encodeURIComponent(selectedYear))
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data.success) return;
|
|
|
|
if (page === 1) {
|
|
if ((data.html || '').trim() === '') {
|
|
renderEmptyContentMessage(listEl);
|
|
} else {
|
|
listEl.innerHTML = data.html || '';
|
|
}
|
|
} else {
|
|
listEl.insertAdjacentHTML('beforeend', data.html || '');
|
|
}
|
|
|
|
renderContentTabCounts(data.counts);
|
|
|
|
state.page = page;
|
|
state.isLast = !!data.is_last;
|
|
|
|
console.log('[completed] 렌더링 완료:', page, state.isLast);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('시청완료 콘텐츠 목록을 불러오지 못했습니다.', error);
|
|
})
|
|
.finally(function () {
|
|
state.loading = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 저장한 콘텐츠 목록 로드
|
|
* - page=1이면 목록 전체를 교체한다.
|
|
* - page>1이면 기존 목록 아래에 이어붙인다.
|
|
* - 선택된 년도 기준으로 데이터를 조회한다.
|
|
*/
|
|
function loadSavedContentList(page = 1) {
|
|
console.log('[saved] 함수진입, page =', page);
|
|
|
|
const listEl = document.getElementById('mypage-saved-list');
|
|
console.log('[saved] listEl =', listEl);
|
|
|
|
const state = contentListState.saved;
|
|
console.log('[saved] state before =', JSON.stringify(state));
|
|
|
|
if (!listEl) {
|
|
console.log('[saved] 중단: listEl 없음');
|
|
return;
|
|
}
|
|
|
|
if (state.loading || state.isLast) {
|
|
console.log('[saved] 중단: loading/isLast', state.loading, state.isLast);
|
|
return;
|
|
}
|
|
|
|
state.loading = true;
|
|
console.log('[saved] fetch 시작');
|
|
|
|
const selectedYear = getSelectedMypageYear();
|
|
|
|
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=saved&page=' + page + '&selected_year=' + encodeURIComponent(selectedYear))
|
|
.then(function (res) {
|
|
console.log('[saved] fetch 응답 도착', res.status);
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
console.log('[saved] 응답 data =', data);
|
|
|
|
if (!data.success) {
|
|
console.log('[saved] 중단: data.success=false');
|
|
return;
|
|
}
|
|
|
|
if (page === 1) {
|
|
if ((data.html || '').trim() === '') {
|
|
console.log('[saved] html 비어있음');
|
|
renderEmptyContentMessage(listEl);
|
|
} else {
|
|
console.log('[saved] html 길이 =', (data.html || '').length);
|
|
listEl.innerHTML = data.html || '';
|
|
}
|
|
} else {
|
|
listEl.insertAdjacentHTML('beforeend', data.html || '');
|
|
}
|
|
|
|
renderContentTabCounts(data.counts);
|
|
|
|
state.page = page;
|
|
state.isLast = !!data.is_last;
|
|
|
|
console.log('[saved] 렌더링 완료:', page, state.isLast);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('[saved] catch:', error);
|
|
})
|
|
.finally(function () {
|
|
state.loading = false;
|
|
console.log('[saved] finally, loading=false');
|
|
});
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 콘텐츠 탭의 개수(count)를 화면에 반영하는 함수
|
|
* - 서버에서 받은 counts 값을
|
|
* 시청중 / 시청완료 / 저장한 콘텐츠 탭에 표시한다.
|
|
*/
|
|
function renderContentTabCounts(counts) {
|
|
// counts 객체가 없으면 종료
|
|
if (!counts) return;
|
|
|
|
// 각 탭 버튼의 count 영역 찾기
|
|
const watchingCountEl = document.querySelector('#tab-watching .count');
|
|
const completedCountEl = document.querySelector('#tab-completed .count');
|
|
const savedCountEl = document.querySelector('#tab-saved .count');
|
|
|
|
// 숫자 변환 및 예외 방어
|
|
const watchingCount = parseInt(counts.watching, 10) || 0;
|
|
const completedCount = parseInt(counts.completed, 10) || 0;
|
|
const savedCount = parseInt(counts.saved, 10) || 0;
|
|
|
|
// 화면 반영
|
|
if (watchingCountEl) {
|
|
watchingCountEl.textContent = watchingCount.toLocaleString();
|
|
}
|
|
|
|
if (completedCountEl) {
|
|
completedCountEl.textContent = completedCount.toLocaleString();
|
|
}
|
|
|
|
if (savedCountEl) {
|
|
savedCountEl.textContent = savedCount.toLocaleString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 3개 탭의 목록을 최초 로드 시 미리 조회하는 함수
|
|
* - 시청중 / 시청완료 / 저장한 콘텐츠를 모두 한 번씩 조회한다.
|
|
* - 이후 사용자가 탭을 눌렀을 때는 이미 최신 구조가 반영된 상태가 된다.
|
|
*/
|
|
function preloadAllContentLists() {
|
|
// 시청중 콘텐츠 조회
|
|
loadWatchingContentList();
|
|
|
|
// 시청완료 콘텐츠 조회
|
|
loadCompletedContentList();
|
|
|
|
// 저장한 콘텐츠 조회
|
|
loadSavedContentList();
|
|
}
|
|
|
|
|
|
/**
|
|
* 현재 활성 탭 기준으로 무한스크롤을 처리하는 함수
|
|
* - 화면 하단 근처까지 내려오면
|
|
* 현재 활성 탭의 다음 페이지를 조회한다.
|
|
*/
|
|
function bindInfiniteScroll() {
|
|
window.addEventListener('scroll', function () {
|
|
// 현재 문서 스크롤 위치 계산
|
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
const windowHeight = window.innerHeight;
|
|
const docHeight = document.documentElement.scrollHeight;
|
|
|
|
// 하단 200px 전 도달 시 동작
|
|
if (scrollTop + windowHeight < docHeight - 200) return;
|
|
|
|
// 현재 활성 탭 찾기
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
if (!activeTab) return;
|
|
|
|
const tab = activeTab.getAttribute('data-tab');
|
|
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList(contentListState.watching.page + 1);
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList(contentListState.completed.page + 1);
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList(contentListState.saved.page + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 카드의 삭제 버튼 클릭 이벤트를 연결하는 함수
|
|
* - 동적으로 생성된 카드에도 동작하도록 이벤트 위임 방식으로 처리한다.
|
|
* - 삭제 성공 시 현재 탭 목록을 1페이지부터 다시 조회한다.
|
|
*/
|
|
function bindRemoveCardAction() {
|
|
// 문서 전체에 클릭 이벤트 연결
|
|
document.addEventListener('click', function (event) {
|
|
// 삭제 버튼이 아니면 종료
|
|
const removeBtn = event.target.closest('.btn-remove-card');
|
|
if (!removeBtn) return;
|
|
|
|
// 기본 동작 차단
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
// 카드 요소 찾기
|
|
const cardEl = removeBtn.closest('.content-card');
|
|
if (!cardEl) return;
|
|
|
|
// 콘텐츠 ID 가져오기
|
|
const contentId = cardEl.getAttribute('data-content-id');
|
|
if (!contentId) {
|
|
alert('콘텐츠 ID를 찾을 수 없습니다.');
|
|
return;
|
|
}
|
|
|
|
// 현재 활성 탭 확인
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
const tab = activeTab ? activeTab.getAttribute('data-tab') : 'watching';
|
|
|
|
// 사용자 확인
|
|
const isConfirmed = window.confirm('해당 콘텐츠를 목록에서 제거하시겠습니까?');
|
|
if (!isConfirmed) return;
|
|
|
|
// 삭제 요청 실행
|
|
removeContentCard(contentId, tab);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 삭제 요청 함수
|
|
* - 서버에 content_id와 현재 탭 정보를 전달하여 삭제 처리한다.
|
|
* - 삭제 성공 시 해당 탭의 목록을 1페이지부터 다시 조회한다.
|
|
*/
|
|
function removeContentCard(contentId, tab) {
|
|
fetch('/edu/ajax/delete_mypage_content_item.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
content_id: contentId,
|
|
tab: tab
|
|
})
|
|
})
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
// 실패 시 종료
|
|
if (!data.success) {
|
|
alert(data.message || '삭제에 실패했습니다.');
|
|
return;
|
|
}
|
|
|
|
// 현재 탭 상태 초기화
|
|
resetContentListState(tab);
|
|
|
|
// 현재 탭 목록 다시 조회
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList(1);
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList(1);
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList(1);
|
|
}
|
|
|
|
console.log('[삭제 완료]', contentId, tab);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('삭제 중 오류가 발생했습니다.', error);
|
|
alert('삭제 중 오류가 발생했습니다.');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 카드의 북마크 체크박스 변경 이벤트를 연결하는 함수
|
|
* - 동적으로 생성된 카드에도 동작하도록 이벤트 위임 방식으로 처리한다.
|
|
* - 체크 시 저장, 체크 해제 시 저장 취소 요청을 보낸다.
|
|
*/
|
|
function bindBookmarkToggleAction() {
|
|
// 문서 전체에 change 이벤트 연결
|
|
document.addEventListener('change', function (event) {
|
|
// 북마크 체크박스가 아니면 종료
|
|
const checkbox = event.target.closest('.bookmark input[type="checkbox"]');
|
|
if (!checkbox) return;
|
|
|
|
// 카드 요소 찾기
|
|
const cardEl = checkbox.closest('.content-card');
|
|
if (!cardEl) return;
|
|
|
|
// 콘텐츠 ID 가져오기
|
|
const contentId = cardEl.getAttribute('data-content-id');
|
|
if (!contentId) {
|
|
console.error('content_id가 없습니다.');
|
|
checkbox.checked = !checkbox.checked;
|
|
return;
|
|
}
|
|
|
|
// 현재 체크 상태 확인
|
|
const isChecked = checkbox.checked;
|
|
|
|
// 서버 저장/해제 요청
|
|
toggleBookmark(contentId, isChecked, checkbox);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 북마크 저장/해제 요청 함수
|
|
* - 체크 여부에 따라 서버에 저장 또는 해제 요청을 보낸다.
|
|
* - 실패 시 체크 상태를 원복한다.
|
|
* - saved 탭에서 체크 해제 시에는 현재 카드를 즉시 화면에서 제거한다.
|
|
*/
|
|
function toggleBookmark(contentId, isChecked, checkboxEl) {
|
|
fetch('/edu/ajax/toggle_content_wishlist.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
content_id: contentId,
|
|
is_checked: isChecked ? 'Y' : 'N'
|
|
})
|
|
})
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
// 실패 시 체크 상태 원복
|
|
if (!data.success) {
|
|
alert(data.message || '북마크 처리에 실패했습니다.');
|
|
checkboxEl.checked = !isChecked;
|
|
return;
|
|
}
|
|
|
|
// 탭 개수 갱신
|
|
if (data.counts) {
|
|
renderContentTabCounts(data.counts);
|
|
}
|
|
|
|
//추가된 핵심
|
|
syncBookmarkState(contentId, isChecked);
|
|
|
|
// 현재 활성 탭 확인
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
const currentTab = activeTab ? activeTab.getAttribute('data-tab') : '';
|
|
|
|
// saved 탭에서 체크 해제한 경우:
|
|
// 현재 카드만 즉시 제거하여 UX 개선
|
|
if (currentTab === 'saved' && isChecked === false) {
|
|
const cardEl = checkboxEl.closest('.content-card');
|
|
if (cardEl) {
|
|
cardEl.remove();
|
|
}
|
|
// 리스트가 비었으면 안내 문구 표시
|
|
const savedListEl = document.querySelector('#panel-saved .content-grid');
|
|
if (savedListEl && savedListEl.querySelectorAll('.content-card').length === 0) {
|
|
renderEmptyContentMessage( savedListEl);
|
|
}
|
|
}
|
|
|
|
console.log('[북마크 처리 완료]', contentId, isChecked ? 'save' : 'remove');
|
|
})
|
|
.catch(function (error) {
|
|
console.error('북마크 처리 중 오류가 발생했습니다.', error);
|
|
checkboxEl.checked = !isChecked;
|
|
alert('북마크 처리 중 오류가 발생했습니다.');
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* 콘텐츠 리스트가 비었는지 확인하고,
|
|
* 비어 있으면 안내 문구를 표시하는 함수
|
|
* - 현재는 saved 탭에서 북마크 해제 후 마지막 카드 제거 상황에 사용한다.
|
|
*/
|
|
function renderEmptyContentMessageIfNeeded(tab) {
|
|
let listEl = null;
|
|
|
|
if (tab === 'watching') {
|
|
listEl = document.getElementById('mypage-watching-list');
|
|
} else if (tab === 'completed') {
|
|
listEl = document.querySelector('#panel-completed .content-grid');
|
|
} else if (tab === 'saved') {
|
|
listEl = document.querySelector('#panel-saved .content-grid');
|
|
}
|
|
|
|
if (!listEl) return;
|
|
|
|
const cardCount = listEl.querySelectorAll('.content-card').length;
|
|
if (cardCount > 0) return;
|
|
|
|
listEl.innerHTML = '<li class="content-empty">표시할 콘텐츠가 없습니다.</li>';
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 동일한 content_id를 가진 모든 카드의 북마크 상태를 동기화하는 함수
|
|
* - 한 탭에서 체크/해제 시 다른 탭의 카드에도 즉시 반영한다.
|
|
*/
|
|
function syncBookmarkState(contentId, isChecked) {
|
|
// 동일 content_id를 가진 모든 카드 찾기
|
|
const cards = document.querySelectorAll('.content-card[data-content-id="' + contentId + '"]');
|
|
|
|
cards.forEach(function (cardEl) {
|
|
const checkbox = cardEl.querySelector('.bookmark input[type="checkbox"]');
|
|
if (!checkbox) return;
|
|
|
|
checkbox.checked = isChecked;
|
|
});
|
|
|
|
console.log('[북마크 동기화]', contentId, isChecked);
|
|
}
|
|
|
|
/**
|
|
* PC 제안하기 폼 제출 이벤트를 연결하는 함수
|
|
* - 기본 submit을 막고 AJAX로 제안 등록을 처리한다.
|
|
* - 저장 성공 시 입력값 초기화 및 버튼 비활성화를 수행한다.
|
|
*/
|
|
function bindOfferFormSubmit() {
|
|
const formEl = document.getElementById('offerForm');
|
|
if (!formEl) return;
|
|
|
|
formEl.addEventListener('submit', function (event) {
|
|
// 기본 submit 막기
|
|
event.preventDefault();
|
|
|
|
// AJAX 제출 실행
|
|
submitOfferFormAjax();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* PC 제안하기 폼의 입력값 상태에 따라 버튼 활성화 여부를 제어하는 함수
|
|
* - URL, 추천이유가 모두 입력된 경우에만 버튼을 활성화한다.
|
|
*/
|
|
function bindOfferFormValidation() {
|
|
const formEl = document.getElementById('offerForm');
|
|
if (!formEl) return;
|
|
|
|
const urlEl = formEl.querySelector('#suggest-url');
|
|
const reasonEl = formEl.querySelector('#suggest-reason');
|
|
const submitBtn = formEl.querySelector('button[type="submit"]');
|
|
|
|
if (!urlEl || !reasonEl || !submitBtn) return;
|
|
|
|
function updateOfferSubmitButtonState() {
|
|
const hasUrl = (urlEl.value || '').trim() !== '';
|
|
const hasReason = (reasonEl.value || '').trim() !== '';
|
|
|
|
submitBtn.disabled = !(hasUrl && hasReason);
|
|
}
|
|
|
|
// 입력 이벤트 연결
|
|
urlEl.addEventListener('input', updateOfferSubmitButtonState);
|
|
reasonEl.addEventListener('input', updateOfferSubmitButtonState);
|
|
|
|
// 초기 상태 반영
|
|
updateOfferSubmitButtonState();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 제안하기 폼을 AJAX로 제출하는 함수
|
|
* - FormData를 사용하여 insert_offer.php로 전송한다.
|
|
* - 성공 시 입력값 초기화, 버튼 비활성화, 제안현황 갱신을 수행한다.
|
|
*/
|
|
function submitOfferFormAjax() {
|
|
const formEl = document.getElementById('offerForm');
|
|
if (!formEl) return;
|
|
|
|
const submitBtn = formEl.querySelector('button[type="submit"]');
|
|
const urlEl = formEl.querySelector('#suggest-url');
|
|
const reasonEl = formEl.querySelector('#suggest-reason');
|
|
|
|
if (!submitBtn || !urlEl || !reasonEl) return;
|
|
|
|
// ★ 중복 클릭 방지
|
|
submitBtn.disabled = true;
|
|
|
|
const formData = new FormData(formEl);
|
|
|
|
fetch('/edu/ajax/insert_offer.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function (res) {
|
|
// ★ JSON 파싱 전에 원문 확인
|
|
return res.text();
|
|
})
|
|
.then(function (text) {
|
|
console.log('[insert_offer.php 응답 원문]', text);
|
|
|
|
let data = null;
|
|
|
|
try {
|
|
data = JSON.parse(text);
|
|
} catch (e) {
|
|
throw new Error('JSON 파싱 실패: ' + text);
|
|
}
|
|
|
|
if (!data.success) {
|
|
alert(data.message || '제안 등록에 실패했습니다.');
|
|
submitBtn.disabled = false;
|
|
return;
|
|
}
|
|
|
|
// 성공 안내
|
|
alert(data.message || '제안이 등록되었습니다.');
|
|
|
|
// 입력값 초기화
|
|
urlEl.value = '';
|
|
reasonEl.value = '';
|
|
|
|
// 버튼 비활성화
|
|
submitBtn.disabled = true;
|
|
|
|
// 제안현황 다시 조회
|
|
loadOfferHistory();
|
|
})
|
|
.catch(function (error) {
|
|
console.error('제안 등록 중 오류가 발생했습니다.', error);
|
|
alert('제안 등록 중 오류가 발생했습니다.');
|
|
submitBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 제안현황 목록을 다시 조회하여 화면에 반영하는 함수
|
|
* - 제안 등록 성공 후 최신 제안현황을 다시 불러온다.
|
|
*/
|
|
function loadOfferHistory() {
|
|
const listEl = document.querySelector('.suggest-status-list');
|
|
if (!listEl) return;
|
|
|
|
fetch('/edu/ajax/get_offer_history.php')
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data.success) return;
|
|
|
|
listEl.innerHTML = data.html || '';
|
|
})
|
|
.catch(function (error) {
|
|
console.error('제안현황을 불러오지 못했습니다.', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 모바일 제안하기 입력값 상태에 따라 확인 버튼 활성화 여부를 제어하는 함수
|
|
* - URL, 추천이유가 모두 입력된 경우에만 버튼을 활성화한다.
|
|
*/
|
|
function bindMobileOfferFormValidation() {
|
|
const urlEl = document.getElementById('sheet-url');
|
|
const reasonEl = document.getElementById('sheet-reason');
|
|
const confirmBtn = document.getElementById('btnSheetConfirm');
|
|
|
|
if (!urlEl || !reasonEl || !confirmBtn) return;
|
|
|
|
function updateMobileOfferButtonState() {
|
|
const hasUrl = (urlEl.value || '').trim() !== '';
|
|
const hasReason = (reasonEl.value || '').trim() !== '';
|
|
|
|
confirmBtn.disabled = !(hasUrl && hasReason);
|
|
}
|
|
|
|
urlEl.addEventListener('input', updateMobileOfferButtonState);
|
|
reasonEl.addEventListener('input', updateMobileOfferButtonState);
|
|
|
|
updateMobileOfferButtonState();
|
|
}
|
|
|
|
/**
|
|
* 모바일 제안하기 내용을 AJAX로 저장하는 함수
|
|
* - sheet-url, sheet-reason 값을 insert_offer.php로 전송한다.
|
|
* - 성공 시 입력값 초기화, 버튼 비활성화, 바텀시트 닫기, 제안현황 갱신을 수행한다.
|
|
*/
|
|
function submitMobileOfferAjax() {
|
|
const urlEl = document.getElementById('sheet-url');
|
|
const reasonEl = document.getElementById('sheet-reason');
|
|
const confirmBtn = document.getElementById('btnSheetConfirm');
|
|
|
|
if (!urlEl || !reasonEl || !confirmBtn) return;
|
|
|
|
const referenceUrl = (urlEl.value || '').trim();
|
|
const reason = (reasonEl.value || '').trim();
|
|
|
|
if (referenceUrl === '') {
|
|
alert('URL을 입력해주세요.');
|
|
return;
|
|
}
|
|
|
|
if (reason === '') {
|
|
alert('추천이유를 입력해주세요.');
|
|
return;
|
|
}
|
|
|
|
confirmBtn.disabled = true;
|
|
|
|
const formData = new FormData();
|
|
formData.append('type_code', 'OF10001');
|
|
formData.append('status_code', 'OF10002');
|
|
formData.append('reference_url', referenceUrl);
|
|
formData.append('reason', reason);
|
|
|
|
fetch('/edu/ajax/insert_offer.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function (res) {
|
|
return res.text();
|
|
})
|
|
.then(function (text) {
|
|
console.log('[모바일 insert_offer.php 응답 원문]', text);
|
|
|
|
let data = null;
|
|
|
|
try {
|
|
data = JSON.parse(text);
|
|
} catch (e) {
|
|
throw new Error('JSON 파싱 실패: ' + text);
|
|
}
|
|
|
|
if (!data.success) {
|
|
alert(data.message || '제안 등록에 실패했습니다.');
|
|
confirmBtn.disabled = false;
|
|
return;
|
|
}
|
|
|
|
alert(data.message || '제안이 등록되었습니다.');
|
|
|
|
// 입력값 초기화
|
|
urlEl.value = '';
|
|
reasonEl.value = '';
|
|
|
|
// 버튼 비활성화
|
|
confirmBtn.disabled = true;
|
|
|
|
// 제안현황 다시 조회
|
|
loadOfferHistory();
|
|
|
|
// 모바일 바텀시트 닫기
|
|
closeMobileSuggestSheet();
|
|
})
|
|
.catch(function (error) {
|
|
console.error('모바일 제안 등록 중 오류가 발생했습니다.', error);
|
|
alert('제안 등록 중 오류가 발생했습니다.');
|
|
confirmBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* 모바일 제안하기 확인 버튼 클릭 이벤트를 연결하는 함수
|
|
* - 클릭 시 AJAX 저장을 수행한다.
|
|
*/
|
|
function bindMobileOfferSubmit() {
|
|
const confirmBtn = document.getElementById('btnSheetConfirm');
|
|
if (!confirmBtn) return;
|
|
|
|
confirmBtn.addEventListener('click', function (event) {
|
|
event.preventDefault();
|
|
submitMobileOfferAjax();
|
|
});
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 모바일 제안하기 바텀시트를 닫는 함수
|
|
* - hidden 속성을 다시 설정하여 닫힘 상태로 만든다.
|
|
*/
|
|
function closeMobileSuggestSheet() {
|
|
const sheetEl = document.getElementById('mobileSuggestSheet');
|
|
if (!sheetEl) return;
|
|
|
|
sheetEl.hidden = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* 콘텐츠 리스트가 비어 있을 때 빈 상태 문구를 렌더링하는 함수
|
|
* - 탭 종류에 따라 적절한 안내 문구를 표시한다.
|
|
*/
|
|
/*
|
|
function renderEmptyContentMessage(tab, listEl) {
|
|
if (!listEl) return;
|
|
|
|
let message = '표시할 콘텐츠가 없습니다.';
|
|
|
|
if (tab === 'watching') {
|
|
message = '시청중인 콘텐츠가 없습니다.';
|
|
} else if (tab === 'completed') {
|
|
message = '시청완료 콘텐츠가 없습니다.';
|
|
} else if (tab === 'saved') {
|
|
message = '저장한 콘텐츠가 없습니다.';
|
|
}
|
|
|
|
listEl.innerHTML = '<li class="content-empty">' + message + '</li>';
|
|
}
|
|
*/
|
|
|
|
|
|
/**
|
|
* 콘텐츠 리스트가 비어 있을 때 빈 상태 문구를 렌더링하는 함수
|
|
* - 현재 활성 탭 기준으로 메시지를 자동 결정한다.
|
|
*/
|
|
function renderEmptyContentMessage(listEl) {
|
|
if (!listEl) return;
|
|
|
|
// ★ 현재 활성 탭 가져오기
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
const tab = activeTab ? activeTab.getAttribute('data-tab') : '';
|
|
|
|
let message = '표시할 콘텐츠가 없습니다.';
|
|
|
|
if (tab === 'watching') {
|
|
message = '시청중인 콘텐츠가 없습니다.';
|
|
} else if (tab === 'completed') {
|
|
message = '시청완료 콘텐츠가 없습니다.';
|
|
} else if (tab === 'saved') {
|
|
message = '저장한 콘텐츠가 없습니다.';
|
|
}
|
|
|
|
listEl.innerHTML = '<li class="content-empty">' + message + '</li>';
|
|
}
|
|
|
|
|
|
/**
|
|
* 한줄소감 리스트를 서버에서 조회하여 화면에 반영하는 함수
|
|
* - 선택년도 기준으로 한줄소감 HTML 조각과 건수를 받아온다.
|
|
*/
|
|
function loadReviewList(selectedYear = '') {
|
|
const listEl = document.getElementById('mypage-review-list');
|
|
if (!listEl) return;
|
|
|
|
fetch('/edu/ajax/get_review_list_for_mypage.php?selected_year=' + encodeURIComponent(selectedYear))
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data.success) return;
|
|
|
|
// ★ 리스트 HTML 반영
|
|
listEl.innerHTML = data.html || '';
|
|
|
|
// ★ 건수 반영
|
|
renderReviewCount(data.total_count);
|
|
|
|
console.log('[한줄소감] 렌더링 완료:', data.total_count);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('한줄소감 리스트를 불러오지 못했습니다.', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 한줄소감 건수를 화면에 반영하는 함수
|
|
* - 제목 우측 count 값을 변경한다.
|
|
*/
|
|
function renderReviewCount(totalCount) {
|
|
const countEl = document.getElementById('mypage-review-count');
|
|
if (!countEl) return;
|
|
|
|
const value = parseInt(totalCount, 10) || 0;
|
|
countEl.textContent = value.toLocaleString();
|
|
}
|
|
|
|
|
|
/**
|
|
* 학습레벨 정보를 서버에서 조회하여 화면에 반영하는 함수
|
|
* - 선택년도 기준으로 학습레벨 정보를 가져온다.
|
|
*/
|
|
function loadLevelData(selectedYear = '') {
|
|
fetch('/edu/ajax/get_level_data_for_mypage.php?selected_year=' + encodeURIComponent(selectedYear))
|
|
.then(function (res) {
|
|
return res.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data.success) return;
|
|
|
|
renderLevelData(data);
|
|
|
|
console.log('[학습레벨] 렌더링 완료:', data.learning_level);
|
|
})
|
|
.catch(function (error) {
|
|
console.error('학습레벨 정보를 불러오지 못했습니다.', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 학습레벨 정보를 화면에 반영하는 함수
|
|
* - wrapper class, 아이콘, 레벨 텍스트를 변경한다.
|
|
*/
|
|
function renderLevelData(data) {
|
|
const boxEl = document.getElementById('mypage-level-box');
|
|
const iconEl = document.getElementById('mypage-level-icon');
|
|
const labelEl = document.getElementById('mypage-level-label');
|
|
|
|
if (!boxEl || !iconEl || !labelEl) return;
|
|
|
|
const levelClass = (data.level_class || 'rookie').toString().trim();
|
|
const levelIcon = (data.level_icon || '/edu/img/ico/ico_level_rookie.svg').toString().trim();
|
|
const levelLabel = (data.learning_level || 'Rookie').toString().trim();
|
|
|
|
// ★ badge-level 기본 클래스 유지 + 레벨 클래스만 교체
|
|
boxEl.className = 'badge-level ' + levelClass;
|
|
|
|
// ★ 아이콘 교체
|
|
iconEl.src = levelIcon;
|
|
|
|
// ★ 텍스트 교체
|
|
labelEl.textContent = levelLabel;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 현재 선택된 년도를 반환하는 함수
|
|
*/
|
|
function getSelectedMypageYear() {
|
|
const yearEl = document.getElementById('mypage-year-select');
|
|
return yearEl ? (yearEl.value || '') : '';
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
})();
|
|
|
|
|