2085 lines
69 KiB
JavaScript
2085 lines
69 KiB
JavaScript
/**
|
|
* =====================
|
|
* 사용페이지 : 마이페이지
|
|
* 작 성 자 : ERP기획팀 moon
|
|
* 최초작성일자: 26.03.20.
|
|
* 2차수정일자: 26.04.21. (영상 모달 로직 추가)
|
|
* =====================
|
|
*/
|
|
|
|
console.log('[add_mypage.js 로드]');
|
|
|
|
|
|
|
|
(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 }
|
|
};
|
|
|
|
// Task B: 탭별 구조화 영상 데이터 저장소(content_id -> videoData)
|
|
const mypageVideoStore = {
|
|
watching: new Map(),
|
|
completed: new Map(),
|
|
saved: new Map()
|
|
};
|
|
|
|
const MOBILE_CONTENT_TAB_ORDER = ['watching', 'completed', 'saved'];
|
|
|
|
// 디버깅/브리지 연동용 전역 노출
|
|
window._mypageVideoStore = mypageVideoStore;
|
|
|
|
let mypageModalManager = null;
|
|
|
|
/**
|
|
* 특정 탭의 페이지 상태를 초기화하는 함수
|
|
* - 탭을 새로 조회할 때 page, loading, isLast 값을 초기화한다.
|
|
*/
|
|
function resetContentListState(tab) {
|
|
if (!contentListState[tab]) return;
|
|
|
|
contentListState[tab].page = 1;
|
|
contentListState[tab].loading = false;
|
|
contentListState[tab].isLast = false;
|
|
|
|
if (mypageVideoStore[tab]) {
|
|
mypageVideoStore[tab].clear();
|
|
}
|
|
}
|
|
|
|
function normalizeMypageVideo(video) {
|
|
if (!video || typeof video !== 'object') return null;
|
|
|
|
const contentId = String(video.content_id || video.id || '').trim();
|
|
if (!contentId) return null;
|
|
|
|
return {
|
|
...video,
|
|
id: String(video.id || contentId),
|
|
content_id: contentId,
|
|
category_code: String(video.category_code || ''),
|
|
category_name: String(video.category_name || ''),
|
|
category: String(video.category_name || video.category || ''),
|
|
subcate: String(video.subcate || ''),
|
|
title: String(video.title || ''),
|
|
description: String(video.description || ''),
|
|
description2: String(video.description2 || ''),
|
|
content_url: String(video.content_url || video.url || ''),
|
|
url: String(video.url || video.content_url || ''),
|
|
thumbnail: String(video.thumbnail || ''),
|
|
watch_tm: Number(video.watch_tm || 0),
|
|
content_tm: Number(video.content_tm || 0),
|
|
all_tm: Number(video.all_tm || 0),
|
|
type: String(video.type || 'main'),
|
|
bookmark: video.bookmark === true || video.is_bookmarked === true,
|
|
is_bookmarked: video.bookmark === true || video.is_bookmarked === true,
|
|
keywords: Array.isArray(video.keywords) ? video.keywords : []
|
|
};
|
|
}
|
|
|
|
function mergeVideosIntoStore(tab, videos, page) {
|
|
if (!mypageVideoStore[tab]) return;
|
|
|
|
if (page === 1) {
|
|
mypageVideoStore[tab].clear();
|
|
}
|
|
|
|
(Array.isArray(videos) ? videos : []).forEach(function (video) {
|
|
const normalized = normalizeMypageVideo(video);
|
|
if (!normalized) return;
|
|
mypageVideoStore[tab].set(normalized.content_id, normalized);
|
|
});
|
|
|
|
syncMypageModalManagerVideos();
|
|
}
|
|
|
|
function getAllMypageVideos() {
|
|
const merged = new Map();
|
|
|
|
['watching', 'completed', 'saved'].forEach(function (tab) {
|
|
mypageVideoStore[tab].forEach(function (video, contentId) {
|
|
if (!merged.has(contentId)) {
|
|
merged.set(contentId, video);
|
|
}
|
|
});
|
|
});
|
|
|
|
return Array.from(merged.values());
|
|
}
|
|
|
|
function ensureMypageModalManager() {
|
|
if (mypageModalManager) {
|
|
return mypageModalManager;
|
|
}
|
|
|
|
if (typeof VideoModalManager === 'undefined') {
|
|
return null;
|
|
}
|
|
|
|
mypageModalManager = new VideoModalManager({
|
|
videos: getAllMypageVideos()
|
|
});
|
|
|
|
return mypageModalManager;
|
|
}
|
|
|
|
function syncMypageModalManagerVideos() {
|
|
const manager = ensureMypageModalManager();
|
|
if (!manager || !manager.config) {
|
|
return;
|
|
}
|
|
|
|
manager.config.videos = getAllMypageVideos();
|
|
}
|
|
|
|
function getVideoByTabAndContentId(tab, contentId) {
|
|
const normalizedTab = String(tab || '').trim();
|
|
const id = String(contentId || '').trim();
|
|
if (!id) return null;
|
|
|
|
if (mypageVideoStore[normalizedTab] && mypageVideoStore[normalizedTab].has(id)) {
|
|
return mypageVideoStore[normalizedTab].get(id);
|
|
}
|
|
|
|
const allVideos = getAllMypageVideos();
|
|
return allVideos.find(function (video) {
|
|
return String(video.content_id || '') === id;
|
|
}) || null;
|
|
}
|
|
|
|
function toBridgeInfo(videoData) {
|
|
const description = [
|
|
String(videoData?.description || '').trim(),
|
|
String(videoData?.description2 || '').trim(),
|
|
].filter(Boolean).join('\n');
|
|
|
|
return {
|
|
contentId: String(videoData?.content_id || videoData?.id || ''),
|
|
videoUrl: String(videoData?.content_url || videoData?.url || ''),
|
|
title: String(videoData?.title || ''),
|
|
description: description,
|
|
categoryName: String(videoData?.category_name || videoData?.category || ''),
|
|
subCategory: String(videoData?.subcate || ''),
|
|
watchTm: Number(videoData?.watch_tm || 0),
|
|
contentTm: Number(videoData?.content_tm || 0),
|
|
bookmark: videoData?.bookmark === true || videoData?.is_bookmarked === true,
|
|
};
|
|
}
|
|
|
|
async function tryBridgeOpen(openFn, bridgeInfo, bridgeName) {
|
|
if (typeof openFn !== 'function') {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await openFn(bridgeInfo);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('[mypage modal] bridge failed:', bridgeName, error?.message || error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function openMypageVideoByCategory(videoData) {
|
|
const categoryCode = String(videoData?.category_code || '').toUpperCase();
|
|
const bridgeInfo = toBridgeInfo(videoData);
|
|
|
|
if (categoryCode === 'CA10006') {
|
|
const opened = await tryBridgeOpen(window._biztrendOpenModal, bridgeInfo, '_biztrendOpenModal');
|
|
if (opened) return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10005') {
|
|
const opened = await tryBridgeOpen(window._insightOpenModal, bridgeInfo, '_insightOpenModal');
|
|
if (opened) return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10004') {
|
|
const opened = await tryBridgeOpen(window._leadershipOpenModal, bridgeInfo, '_leadershipOpenModal');
|
|
if (opened) return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10003') {
|
|
const opened = await tryBridgeOpen(window._learningOpenModal, bridgeInfo, '_learningOpenModal');
|
|
if (opened) return;
|
|
}
|
|
|
|
if (categoryCode === 'CA10002') {
|
|
const opened = await tryBridgeOpen(window._onboardingOpenModal, bridgeInfo, '_onboardingOpenModal');
|
|
if (opened) return;
|
|
}
|
|
|
|
const manager = ensureMypageModalManager();
|
|
if (!manager || typeof manager.openVideo !== 'function') {
|
|
throw new Error('VideoModalManager is not ready');
|
|
}
|
|
|
|
syncMypageModalManagerVideos();
|
|
await manager.openVideo(String(videoData?.content_id || videoData?.id || ''));
|
|
}
|
|
|
|
function bindContentCardOpenAction() {
|
|
document.addEventListener('click', function (event) {
|
|
const cardLink = event.target.closest('.mylist-area .content-card .card-link');
|
|
if (!cardLink) return;
|
|
|
|
// 북마크/삭제 버튼은 카드 오픈 이벤트에서 제외
|
|
if (event.target.closest('.bookmark') || event.target.closest('.btn-remove-card')) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const cardEl = cardLink.closest('.content-card');
|
|
if (!cardEl) return;
|
|
|
|
const contentId = String(cardEl.getAttribute('data-content-id') || '').trim();
|
|
if (!contentId) return;
|
|
|
|
const panelEl = cardEl.closest('.content-panel');
|
|
const tab = panelEl ? String(panelEl.getAttribute('data-panel') || '') : '';
|
|
const videoData = getVideoByTabAndContentId(tab, contentId);
|
|
if (!videoData) {
|
|
console.warn('[mypage modal] videoData not found:', tab, contentId);
|
|
return;
|
|
}
|
|
|
|
openMypageVideoByCategory(videoData).catch(function (error) {
|
|
console.warn('[mypage modal] open by category failed:', error?.message || error);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 마이페이지 초기 진입 시 실행
|
|
* - 연도 변경 이벤트 연결
|
|
* - 학습활동 데이터 로드
|
|
* - 콘텐츠 탭 클릭 시 재렌더링 이벤트 연결
|
|
*/
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
console.log('[DOMContentLoaded 진입]');
|
|
|
|
// 연도 select 변경 이벤트 연결
|
|
bindYearSelectChange();
|
|
|
|
// 학습활동 초기 데이터 조회
|
|
loadMypageInit();
|
|
|
|
// 콘텐츠 탭 클릭 시 리스트 재조회 이벤트 연결
|
|
bindContentTabReload();
|
|
|
|
// 모바일 리스트 탭(콘텐츠/한줄소감 + 화살표) 이벤트 연결
|
|
bindMobileMylistTabs();
|
|
|
|
//초기 실행에 무한스크롤 바인딩 추가
|
|
bindInfiniteScroll();
|
|
|
|
// 콘텐츠 카드 삭제 버튼 이벤트 연결
|
|
bindRemoveCardAction();
|
|
|
|
// 콘텐츠 카드 클릭 모달 오픈 이벤트 연결
|
|
bindContentCardOpenAction();
|
|
|
|
// 북마크 체크박스 이벤트 연결
|
|
bindBookmarkToggleAction();
|
|
|
|
// [제안하기]PC용 전용 폼 검증/제출 연결
|
|
bindOfferFormValidation();
|
|
bindOfferFormSubmit();
|
|
|
|
// [제안하기]모바일 전용
|
|
bindMobileOfferFormValidation();
|
|
bindMobileOfferSubmit();
|
|
|
|
|
|
// ★ 초기 선택년도 기준 한줄소감 / 학습레벨 조회
|
|
const yearSelectEl = document.getElementById('mypage-year-select');
|
|
const initialYear = yearSelectEl ? (yearSelectEl.value || '') : '';
|
|
|
|
loadReviewList(initialYear);
|
|
loadLevelData(initialYear);
|
|
|
|
//프로필이미지 연동
|
|
bindProfileUpload();
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* 모든 리소스 로드 완료 후 실행
|
|
* - 퍼블리싱용 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('/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);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getActiveContentTabName() {
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
return activeTab ? (activeTab.getAttribute('data-tab') || 'completed') : 'completed';
|
|
}
|
|
|
|
function activateContentTabUI(tab) {
|
|
const tabs = document.querySelectorAll('.content-tab');
|
|
const panels = document.querySelectorAll('.content-panel');
|
|
|
|
tabs.forEach(function (btn) {
|
|
const isActive = btn.getAttribute('data-tab') === tab;
|
|
btn.classList.toggle('active', isActive);
|
|
btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
});
|
|
|
|
panels.forEach(function (panel) {
|
|
const isActive = panel.getAttribute('data-panel') === tab;
|
|
panel.classList.toggle('active', isActive);
|
|
});
|
|
}
|
|
|
|
function loadContentTabFirstPage(tab) {
|
|
resetContentListState(tab);
|
|
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList(1);
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList(1);
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList(1);
|
|
}
|
|
}
|
|
|
|
function getMobileContentLabel(tab) {
|
|
if (tab === 'watching') return '시청중';
|
|
if (tab === 'saved') return '저장한 콘텐츠';
|
|
return '시청완료';
|
|
}
|
|
|
|
function renderMobileContentLabel(tab) {
|
|
const labelEl = document.querySelector('#mobileTabContent .mobile-tab-label');
|
|
if (!labelEl) return;
|
|
labelEl.textContent = getMobileContentLabel(tab);
|
|
}
|
|
|
|
function setMobileListMode(mode) {
|
|
const contentTabWrapEl = document.querySelector('.mypage .mylist-area .content-tab-wrap');
|
|
const reviewSectionEl = document.querySelector('.mypage .mylist-area .review-section');
|
|
const mobileTabContentEl = document.getElementById('mobileTabContent');
|
|
const mobileTabReviewEl = document.getElementById('mobileTabReview');
|
|
|
|
if (!contentTabWrapEl || !reviewSectionEl || !mobileTabContentEl || !mobileTabReviewEl) {
|
|
return;
|
|
}
|
|
|
|
const isReview = mode === 'review';
|
|
|
|
contentTabWrapEl.classList.toggle('is-mobile-hidden', isReview);
|
|
reviewSectionEl.classList.toggle('is-mobile-active', isReview);
|
|
|
|
mobileTabContentEl.classList.toggle('active', !isReview);
|
|
mobileTabContentEl.setAttribute('aria-selected', !isReview ? 'true' : 'false');
|
|
|
|
mobileTabReviewEl.classList.toggle('active', isReview);
|
|
mobileTabReviewEl.setAttribute('aria-selected', isReview ? 'true' : 'false');
|
|
}
|
|
|
|
function moveMobileContentTab(step) {
|
|
const currentTab = getActiveContentTabName();
|
|
const currentIdx = MOBILE_CONTENT_TAB_ORDER.indexOf(currentTab);
|
|
const safeCurrentIdx = currentIdx >= 0 ? currentIdx : 1;
|
|
const nextIdx = (safeCurrentIdx + step + MOBILE_CONTENT_TAB_ORDER.length) % MOBILE_CONTENT_TAB_ORDER.length;
|
|
const nextTab = MOBILE_CONTENT_TAB_ORDER[nextIdx];
|
|
|
|
activateContentTabUI(nextTab);
|
|
renderMobileContentLabel(nextTab);
|
|
loadContentTabFirstPage(nextTab);
|
|
|
|
console.log('[모바일 콘텐츠 탭 전환]', currentTab, '->', nextTab);
|
|
}
|
|
|
|
function bindMobileMylistTabs() {
|
|
const mobileTabContentEl = document.getElementById('mobileTabContent');
|
|
const mobileTabReviewEl = document.getElementById('mobileTabReview');
|
|
|
|
if (!mobileTabContentEl || !mobileTabReviewEl) {
|
|
return;
|
|
}
|
|
|
|
renderMobileContentLabel(getActiveContentTabName());
|
|
|
|
mobileTabContentEl.addEventListener('click', function (event) {
|
|
const prevArrow = event.target.closest('.mobile-tab-prev');
|
|
const nextArrow = event.target.closest('.mobile-tab-next');
|
|
const label = event.target.closest('.mobile-tab-label');
|
|
|
|
let direction = 0;
|
|
|
|
if (prevArrow || nextArrow) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
direction = prevArrow ? -1 : 1;
|
|
} else if (!label) {
|
|
const rect = mobileTabContentEl.getBoundingClientRect();
|
|
const clickX = Number(event.clientX || 0) - rect.left;
|
|
|
|
// 화살표 영역 탭 인식 보강: span이 아닌 버튼 타겟으로 잡혀도 동작시킨다.
|
|
if (clickX <= rect.width * 0.28) {
|
|
direction = -1;
|
|
} else if (clickX >= rect.width * 0.72) {
|
|
direction = 1;
|
|
}
|
|
}
|
|
|
|
setMobileListMode('content');
|
|
|
|
if (direction !== 0) {
|
|
moveMobileContentTab(direction);
|
|
}
|
|
});
|
|
|
|
mobileTabReviewEl.addEventListener('click', function () {
|
|
setMobileListMode('review');
|
|
});
|
|
|
|
// 코드 다른 위치에서 content-tab이 바뀌면 모바일 라벨도 동기화
|
|
document.querySelectorAll('.content-tab').forEach(function (tabBtn) {
|
|
tabBtn.addEventListener('click', function () {
|
|
const tab = this.getAttribute('data-tab') || 'completed';
|
|
renderMobileContentLabel(tab);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 현재 활성화된 콘텐츠 탭을 기준으로
|
|
* 해당 탭의 리스트를 다시 렌더링하는 함수
|
|
*
|
|
* - 최초 페이지 진입 시 사용
|
|
* - 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();
|
|
|
|
console.log('[watching] fetch 시작', { page: page, selectedYear: selectedYear });
|
|
|
|
fetch('/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) {
|
|
console.warn('[watching] API 실패', data);
|
|
renderEmptyContentMessage(listEl);
|
|
return;
|
|
}
|
|
|
|
console.log('[watching] API 성공', {
|
|
total_count: data.total_count,
|
|
html_length: (data.html || '').length,
|
|
is_last: data.is_last,
|
|
});
|
|
|
|
mergeVideosIntoStore('watching', data.videos, page);
|
|
|
|
// ★ 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();
|
|
|
|
console.log('[completed] fetch 시작', { page: page, selectedYear: selectedYear });
|
|
|
|
fetch('/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) {
|
|
console.warn('[completed] API 실패', data);
|
|
renderEmptyContentMessage(listEl);
|
|
return;
|
|
}
|
|
|
|
console.log('[completed] API 성공', {
|
|
total_count: data.total_count,
|
|
html_length: (data.html || '').length,
|
|
is_last: data.is_last,
|
|
});
|
|
|
|
mergeVideosIntoStore('completed', data.videos, page);
|
|
|
|
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('/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.warn('[saved] API 실패', data);
|
|
renderEmptyContentMessage(listEl);
|
|
return;
|
|
}
|
|
|
|
console.log('[saved] API 성공', {
|
|
total_count: data.total_count,
|
|
html_length: (data.html || '').length,
|
|
is_last: data.is_last,
|
|
});
|
|
|
|
mergeVideosIntoStore('saved', data.videos, page);
|
|
|
|
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)를 화면에 반영하는 함수
|
|
* - 전달된 값만 갱신하고, 없는 값은 기존 화면값을 유지한다.
|
|
*/
|
|
function renderContentTabCounts(counts) {
|
|
if (!counts) return;
|
|
|
|
const watchingCountEl = document.querySelector('#tab-watching .count');
|
|
const completedCountEl = document.querySelector('#tab-completed .count');
|
|
const savedCountEl = document.querySelector('#tab-saved .count');
|
|
|
|
if (watchingCountEl && counts.watching !== undefined) {
|
|
const watchingCount = parseInt(counts.watching, 10) || 0;
|
|
watchingCountEl.textContent = watchingCount.toLocaleString();
|
|
}
|
|
|
|
if (completedCountEl && counts.completed !== undefined) {
|
|
const completedCount = parseInt(counts.completed, 10) || 0;
|
|
completedCountEl.textContent = completedCount.toLocaleString();
|
|
}
|
|
|
|
if (savedCountEl && counts.saved !== undefined) {
|
|
const savedCount = parseInt(counts.saved, 10) || 0;
|
|
savedCountEl.textContent = savedCount.toLocaleString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 3개 탭의 목록을 최초 로드 시 미리 조회하는 함수
|
|
* - 시청중 / 시청완료 / 저장한 콘텐츠를 모두 한 번씩 조회한다.
|
|
* - 이후 사용자가 탭을 눌렀을 때는 이미 최신 구조가 반영된 상태가 된다.
|
|
*/
|
|
function preloadAllContentLists() {
|
|
// 시청중 콘텐츠 조회
|
|
loadWatchingContentList();
|
|
|
|
// 시청완료 콘텐츠 조회
|
|
loadCompletedContentList();
|
|
|
|
// 저장한 콘텐츠 조회
|
|
loadSavedContentList();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 현재 활성 탭 기준으로 무한스크롤을 처리하는 함수
|
|
* - 각 탭의 리스트(ul) 자체에서 scroll 이벤트를 감지한다.
|
|
*/
|
|
function bindInfiniteScroll() {
|
|
|
|
console.log('bindInfiniteScroll');
|
|
|
|
const watchingEl = document.getElementById('mypage-watching-list');
|
|
const completedEl = document.getElementById('mypage-completed-list');
|
|
const savedEl = document.getElementById('mypage-saved-list');
|
|
|
|
[watchingEl, completedEl, savedEl].forEach(function (listEl) {
|
|
if (!listEl) return;
|
|
|
|
listEl.addEventListener('scroll', function () {
|
|
const scrollTop = listEl.scrollTop;
|
|
const clientHeight = listEl.clientHeight;
|
|
const scrollHeight = listEl.scrollHeight;
|
|
|
|
console.log('[list scroll]', listEl.id, scrollTop, clientHeight, scrollHeight);
|
|
|
|
// 하단 100px 전 도달 시 동작
|
|
if (scrollTop + clientHeight < scrollHeight - 100) return;
|
|
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
if (!activeTab) return;
|
|
|
|
const tab = activeTab.getAttribute('data-tab');
|
|
const state = contentListState[tab];
|
|
|
|
if (!state || state.loading || state.isLast) {
|
|
console.log('[무한스크롤 차단]', tab, state);
|
|
return;
|
|
}
|
|
|
|
console.log('[무한스크롤 실행]', tab, state.page + 1);
|
|
|
|
if (tab === 'watching') {
|
|
loadWatchingContentList(state.page + 1);
|
|
} else if (tab === 'completed') {
|
|
loadCompletedContentList(state.page + 1);
|
|
} else if (tab === 'saved') {
|
|
loadSavedContentList(state.page + 1);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 현재 활성 탭 기준으로 무한스크롤을 처리하는 함수
|
|
* - 화면 하단 근처까지 내려오면
|
|
* 현재 활성 탭의 다음 페이지를 조회한다.
|
|
*/
|
|
function bindInfiniteScroll_old() {
|
|
|
|
console.log('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('/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() {
|
|
|
|
document.addEventListener('change', function (event) {
|
|
|
|
// ★ 최소 수정 핵심
|
|
const checkbox = event.target;
|
|
if (!checkbox || checkbox.type !== 'checkbox') return;
|
|
|
|
// 카드 요소 찾기
|
|
const card = checkbox.closest('.content-card');
|
|
if (!card) return;
|
|
|
|
// 콘텐츠 ID 가져오기
|
|
const contentId = card.getAttribute('data-content-id');
|
|
if (!contentId) return;
|
|
|
|
// 현재 체크 상태 확인
|
|
const isChecked = checkbox.checked;
|
|
|
|
// 서버 저장/해제 요청
|
|
toggleBookmark(contentId, isChecked, checkbox);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* 북마크 저장/해제 요청 함수 (최종 안정 버전)
|
|
* - 서버 기준으로 항상 동기화
|
|
* - saved 탭 상태 꼬임 방지
|
|
*/
|
|
function toggleBookmark(contentId, isChecked, checkboxEl) {
|
|
fetch('/ajax/toggle_content_wishlist.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
content_id: contentId,
|
|
is_checked: isChecked ? '1' : '0'
|
|
})
|
|
})
|
|
.then(function (res) {
|
|
// ★ 오픈 전까지 응답 원문 확인 가능하도록 text로 먼저 받음
|
|
return res.text();
|
|
})
|
|
.then(function (text) {
|
|
console.log('[북마크 응답 원문]', text);
|
|
|
|
let data = null;
|
|
try {
|
|
data = JSON.parse(text);
|
|
} catch (e) {
|
|
console.error('[북마크 JSON 파싱 실패]', text);
|
|
throw e;
|
|
}
|
|
|
|
// 실패 시 종료
|
|
if (!data.success) {
|
|
alert(data.message || '북마크 처리에 실패했습니다.');
|
|
return;
|
|
}
|
|
|
|
// 탭 개수 갱신
|
|
if (data.counts) {
|
|
renderContentTabCounts(data.counts);
|
|
}
|
|
|
|
// 동일 content_id 카드의 체크상태 동기화
|
|
syncBookmarkState(contentId, isChecked);
|
|
|
|
// 현재 활성 탭 확인
|
|
const activeTab = document.querySelector('.content-tab.active');
|
|
const currentTab = activeTab ? activeTab.getAttribute('data-tab') : '';
|
|
|
|
// ★ 최소 수정 핵심:
|
|
// saved 탭은 카드 제거하지 말고 상태 초기화 후 서버 재조회
|
|
if (currentTab === 'saved') {
|
|
resetContentListState('saved');
|
|
loadSavedContentList(1);
|
|
}
|
|
|
|
console.log('[북마크 처리 완료]', contentId, isChecked ? 'save' : 'remove');
|
|
})
|
|
.catch(function (error) {
|
|
console.error('북마크 처리 중 오류가 발생했습니다.', error);
|
|
|
|
// ★ 최소 수정 핵심:
|
|
// DB는 이미 반영됐을 수 있으므로 체크상태를 강제로 원복하지 않음
|
|
// 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.getElementById('mypage-completed-list');
|
|
} else if (tab === 'saved') {
|
|
listEl = document.getElementById('mypage-saved-list');
|
|
}
|
|
|
|
if (!listEl) return;
|
|
|
|
const count = listEl.querySelectorAll('.content-card').length;
|
|
|
|
if (count === 0) {
|
|
listEl.innerHTML = '<li class="content-empty">표시할 콘텐츠가 없습니다.</li>';
|
|
}
|
|
}
|
|
|
|
document.addEventListener('change', function (event) {
|
|
console.log('change 발생:', event.target);
|
|
});
|
|
|
|
/**
|
|
* 동일한 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('/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('/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('/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('/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('/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 || '/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 || '') : '';
|
|
}
|
|
|
|
|
|
function bindProfileUpload() {
|
|
|
|
const trigger = document.getElementById('profile-upload-trigger');
|
|
const fileInput = document.getElementById('profile-file-input');
|
|
const img = document.getElementById('profile-image');
|
|
|
|
if (!trigger || !fileInput) return;
|
|
|
|
trigger.addEventListener('click', function () {
|
|
fileInput.click();
|
|
});
|
|
|
|
fileInput.addEventListener('change', function () {
|
|
|
|
const file = fileInput.files[0];
|
|
if (!file) return;
|
|
|
|
console.log('[프로필] 파일 선택:', file.name, file.size);
|
|
|
|
// 1차 검사: 프런트에서 3MB 제한
|
|
const maxSize = 2 * 1024 * 1024; // 3MB
|
|
if (file.size > maxSize) {
|
|
alert('2MB 이하의 이미지 파일만 업로드 가능합니다.');
|
|
fileInput.value = '';
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('profile_image', file);
|
|
|
|
fetch('/ajax/upload_profile_image.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
|
|
console.log('[프로필 응답]', data);
|
|
|
|
if (!data.success) {
|
|
alert(data.message);
|
|
return;
|
|
}
|
|
|
|
img.src = data.image_url + '?t=' + Date.now();
|
|
})
|
|
.catch(err => {
|
|
console.error('[프로필] 오류:', err);
|
|
alert('업로드 오류');
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
})();
|
|
console.log('[add_mypage.js 끝까지로드]');
|