Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
-- =====================================
|
||||
-- 영상 모달 JS (클릭 → 모달 열기 / 추천영상 / 댓글)
|
||||
-- =====================================
|
||||
*/
|
||||
$(function () {
|
||||
var API_BASE = '/bbs/api';
|
||||
var $modal = $('#leadershipVideoModal');
|
||||
var currentContentId = null;
|
||||
var editingCommentId = null;
|
||||
|
||||
// ── YouTube IFrame API 시청 추적 변수 ──
|
||||
var ytPlayer = null;
|
||||
var trackingInterval = null;
|
||||
var lastSaveTime = 0;
|
||||
var lastCurrentTime = 0;
|
||||
|
||||
// YouTube IFrame API 스크립트 로드
|
||||
if (!document.getElementById('youtube-iframe-api')) {
|
||||
var ytScript = document.createElement('script');
|
||||
ytScript.id = 'youtube-iframe-api';
|
||||
ytScript.src = 'https://www.youtube.com/iframe_api';
|
||||
ytScript.async = true;
|
||||
document.head.appendChild(ytScript);
|
||||
}
|
||||
|
||||
// ── YouTube 비디오 ID 추출 ──
|
||||
function extractVideoId(url) {
|
||||
if (!url) return '';
|
||||
var videoId = url;
|
||||
if (url.indexOf('youtube.com/watch') > -1) {
|
||||
try { videoId = new URL(url).searchParams.get('v') || url; } catch (e) {}
|
||||
} else if (url.indexOf('youtu.be/') > -1) {
|
||||
videoId = url.split('youtu.be/')[1].split(/[?&#]/)[0];
|
||||
} else if (url.indexOf('youtube.com/embed/') > -1) {
|
||||
videoId = url.split('youtube.com/embed/')[1].split(/[?&#]/)[0];
|
||||
}
|
||||
if (videoId && videoId.indexOf('/') === -1 && videoId.indexOf('.') === -1) {
|
||||
return videoId;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// ── YT.Player 생성 ──
|
||||
function initYTPlayer(videoId, startSeconds) {
|
||||
destroyYTPlayer();
|
||||
startSeconds = Math.floor(startSeconds || 0);
|
||||
|
||||
var createPlayer = function () {
|
||||
ytPlayer = new YT.Player('videoPlayer', {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
videoId: videoId,
|
||||
playerVars: {
|
||||
autoplay: 1,
|
||||
controls: 1,
|
||||
rel: 0,
|
||||
modestbranding: 1,
|
||||
enablejsapi: 1,
|
||||
start: startSeconds
|
||||
},
|
||||
events: {
|
||||
'onReady': function (event) {
|
||||
if (event.target && typeof event.target.playVideo === 'function') {
|
||||
event.target.playVideo();
|
||||
}
|
||||
},
|
||||
'onStateChange': function (event) {
|
||||
handlePlayerStateChange(event);
|
||||
},
|
||||
'onError': function (event) {
|
||||
console.error('[Leadership] Player Error:', event.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
|
||||
createPlayer();
|
||||
} else {
|
||||
// YT API 아직 로드 안 됨 → 대기
|
||||
var checkYt = setInterval(function () {
|
||||
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
|
||||
clearInterval(checkYt);
|
||||
createPlayer();
|
||||
}
|
||||
}, 500);
|
||||
setTimeout(function () { clearInterval(checkYt); }, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 플레이어 파괴 ──
|
||||
function destroyYTPlayer() {
|
||||
stopTracking();
|
||||
if (ytPlayer && typeof ytPlayer.destroy === 'function') {
|
||||
try { ytPlayer.destroy(); } catch (e) {}
|
||||
}
|
||||
ytPlayer = null;
|
||||
// YT.Player가 div를 iframe으로 교체하므로 다시 div를 복원
|
||||
var $box = $modal.find('.video-box');
|
||||
if (!$box.find('#videoPlayer').length) {
|
||||
$box.html('<div id="videoPlayer"></div>');
|
||||
}
|
||||
}
|
||||
|
||||
// ── 플레이어 상태 변경 핸들러 (VideoModalBase 패턴) ──
|
||||
function handlePlayerStateChange(event) {
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
startTracking();
|
||||
} else {
|
||||
stopTracking();
|
||||
if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
lastSaveTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 시청 추적 시작 (1초 주기) ──
|
||||
function startTracking() {
|
||||
if (trackingInterval) return;
|
||||
lastSaveTime = 0;
|
||||
|
||||
trackingInterval = setInterval(function () {
|
||||
if (!ytPlayer || typeof ytPlayer.getCurrentTime !== 'function') return;
|
||||
|
||||
var playerState = (typeof ytPlayer.getPlayerState === 'function')
|
||||
? ytPlayer.getPlayerState() : null;
|
||||
if (playerState !== YT.PlayerState.PLAYING) return;
|
||||
|
||||
var currentTime = ytPlayer.getCurrentTime();
|
||||
var diff = currentTime - lastCurrentTime;
|
||||
if (Math.abs(diff) <= 5) {
|
||||
lastSaveTime += 1;
|
||||
}
|
||||
lastCurrentTime = currentTime;
|
||||
|
||||
// 5초마다 서버에 heartbeat 전송
|
||||
if (lastSaveTime >= 5) {
|
||||
saveLearningProgress(lastSaveTime);
|
||||
lastSaveTime = 0;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── 시청 추적 중지 ──
|
||||
function stopTracking() {
|
||||
if (trackingInterval) {
|
||||
clearInterval(trackingInterval);
|
||||
trackingInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 서버에 학습 진행상황 저장 (save_learning.php) ──
|
||||
function saveLearningProgress(heartbeatSeconds, isWatching) {
|
||||
if (!ytPlayer || !currentContentId) return;
|
||||
if (typeof ytPlayer.getCurrentTime !== 'function') return;
|
||||
|
||||
var currentTime = Math.floor(ytPlayer.getCurrentTime());
|
||||
var duration = Math.floor(ytPlayer.getDuration());
|
||||
var incrementalWatch = Math.max(0, Math.floor(heartbeatSeconds || 0));
|
||||
|
||||
var params = new URLSearchParams({
|
||||
content_id: String(currentContentId),
|
||||
watch_tm: String(Math.max(0, currentTime)),
|
||||
content_tm: String(Math.max(0, duration)),
|
||||
all_tm_increment: String(incrementalWatch),
|
||||
is_watching: isWatching === 'N' ? 'N' : 'Y'
|
||||
});
|
||||
|
||||
fetch(API_BASE + '/save_learning.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: params.toString(),
|
||||
keepalive: true
|
||||
}).then(function (r) { return r.json(); }).then(function (result) {
|
||||
if (!result.success) {
|
||||
console.error('[Leadership] Save Learning Failed:', result.message);
|
||||
}
|
||||
}).catch(function (e) {
|
||||
console.error('[Leadership] Save Learning Error:', e);
|
||||
});
|
||||
}
|
||||
|
||||
// ── 영상 카드 클릭 → 모달 열기 ──
|
||||
$(document).on('click', '#video-list .video-item .card-link', function (e) {
|
||||
e.preventDefault();
|
||||
var $item = $(this).closest('.video-item');
|
||||
|
||||
var contentId = $item.data('content-id') || $item.attr('data-content-id') || '';
|
||||
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
|
||||
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
|
||||
var desc = $item.data('description') || '';
|
||||
var catName = $item.data('category-name') || $item.find('.item-category').text().trim() || '리더십';
|
||||
var subCat = $item.data('sub-category') || '';
|
||||
var watchTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
|
||||
var contentTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
|
||||
|
||||
var categoryCode = $item.data('category-code') || $item.attr('data-category-code') || '';
|
||||
|
||||
console.warn('[VideoModal] category-code='+categoryCode);
|
||||
|
||||
if (!contentId) {
|
||||
console.warn('[VideoModal] content-id가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
openModal({
|
||||
contentId: contentId,
|
||||
videoUrl: videoUrl,
|
||||
title: title,
|
||||
description: desc,
|
||||
categoryName: catName,
|
||||
subCategory: subCat,
|
||||
watchTm: watchTm,
|
||||
contentTm: contentTm
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
$(document).on('click', '#video-list .video-item .card-link', function (e) {
|
||||
e.preventDefault();
|
||||
var $item = $(this).closest('.video-item');
|
||||
|
||||
var contentId = $item.data('content-id') || $item.attr('data-content-id') || '';
|
||||
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
|
||||
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
|
||||
var desc = $item.data('description') || '';
|
||||
var catName = $item.data('category-name') || $item.find('.item-category').text().trim() || '리더십';
|
||||
var subCat = $item.data('sub-category') || '';
|
||||
var watchTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
|
||||
var contentTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
|
||||
|
||||
if (!contentId) {
|
||||
console.warn('[VideoModal] content-id가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
openModal({
|
||||
contentId: contentId,
|
||||
videoUrl: videoUrl,
|
||||
title: title,
|
||||
description: desc,
|
||||
categoryName: catName,
|
||||
subCategory: subCat,
|
||||
watchTm: watchTm,
|
||||
contentTm: contentTm
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// ── 모달 열기 ──
|
||||
function openModal(info) {
|
||||
currentContentId = info.contentId;
|
||||
editingCommentId = null;
|
||||
lastSaveTime = 0;
|
||||
lastCurrentTime = Math.floor(info.watchTm || 0);
|
||||
|
||||
// YouTube IFrame API 플레이어 세팅
|
||||
var videoId = extractVideoId(info.videoUrl);
|
||||
if (videoId) {
|
||||
initYTPlayer(videoId, info.watchTm || 0);
|
||||
}
|
||||
|
||||
// 영상 정보
|
||||
$modal.find('#modalTitle').text(info.title);
|
||||
// 설명: 줄바꿈을 <br>로 변환하여 표시
|
||||
var descHtml = $('<div>').text(info.description || '').html().replace(/\n/g, '<br>');
|
||||
$modal.find('#modalDesc').html(descHtml);
|
||||
$modal.find('#modalCategoryName').text(info.categoryName || '리더십');
|
||||
$modal.find('#modalSubCategory').text(info.subCategory || '');
|
||||
$modal.find('#modalBadge').text(info.categoryName || '리더십');
|
||||
|
||||
// 추천영상 리스트
|
||||
buildRecommendedList(info.contentId);
|
||||
|
||||
// 댓글 로드
|
||||
loadComments(info.contentId);
|
||||
resetCommentForm();
|
||||
|
||||
// 모달 표시
|
||||
$modal.show();
|
||||
$('body').css('overflow', 'hidden');
|
||||
}
|
||||
|
||||
// ── 모달 닫기 (진행상황 저장 후 닫기) ──
|
||||
function closeModal() {
|
||||
// 마지막 시청 진행상황 저장
|
||||
stopTracking();
|
||||
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
}
|
||||
lastSaveTime = 0;
|
||||
|
||||
destroyYTPlayer();
|
||||
$modal.hide();
|
||||
$('body').css('overflow', '');
|
||||
currentContentId = null;
|
||||
editingCommentId = null;
|
||||
}
|
||||
|
||||
$modal.on('click', '.close', closeModal);
|
||||
$modal.on('click', function (e) {
|
||||
if ($(e.target).hasClass('modal')) closeModal();
|
||||
});
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.key === 'Escape' && $modal.is(':visible')) closeModal();
|
||||
});
|
||||
|
||||
// ── 추천영상 리스트 구성 (현재 페이지 영상 목록 활용) ──
|
||||
function buildRecommendedList(excludeContentId) {
|
||||
var $list = $modal.find('#recommendedList');
|
||||
$list.empty();
|
||||
var count = 0;
|
||||
|
||||
$('#video-list .video-item').each(function () {
|
||||
var $item = $(this);
|
||||
var cid = $item.data('content-id') || $item.attr('data-content-id') || '';
|
||||
if (!cid || String(cid) === String(excludeContentId)) return;
|
||||
if (count >= 10) return false; // 최대 10개
|
||||
|
||||
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
|
||||
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
|
||||
var catName = $item.data('category-name') || $item.find('.item-category').text().trim() || '리더십';
|
||||
var thumbSrc = $item.find('.item-thumb img, .thumb img').attr('src') || '/img/video/img_thumb_01.png';
|
||||
var desc = String($item.data('description') || '');
|
||||
var subCat = $item.data('sub-category') || '';
|
||||
var wTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
|
||||
var cTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
|
||||
|
||||
var li =
|
||||
'<li>' +
|
||||
'<a href="#" class="list" ' +
|
||||
'data-content-id="' + cid + '" ' +
|
||||
'data-content-url="' + videoUrl.replace(/"/g, '"') + '" ' +
|
||||
'data-title="' + title.replace(/"/g, '"') + '" ' +
|
||||
'data-description="' + desc.replace(/"/g, '"') + '" ' +
|
||||
'data-category-name="' + catName.replace(/"/g, '"') + '" ' +
|
||||
'data-sub-category="' + subCat.replace(/"/g, '"') + '" ' +
|
||||
'data-watch-tm="' + wTm + '" ' +
|
||||
'data-content-tm="' + cTm + '">' +
|
||||
'<div class="thumb"><img src="' + thumbSrc + '" alt="" /></div>' +
|
||||
'<div class="txt-box">' +
|
||||
'<div class="category leader">' + catName + '</div>' +
|
||||
'<div class="title">' + title + '</div>' +
|
||||
'</div>' +
|
||||
'</a>' +
|
||||
'</li>';
|
||||
$list.append(li);
|
||||
count++;
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
$list.html('<li class="empty">추천 영상이 없습니다.</li>');
|
||||
}
|
||||
}
|
||||
|
||||
// ── 추천영상 클릭 → 영상 전환 ──
|
||||
$modal.on('click', '#recommendedList .list', function (e) {
|
||||
e.preventDefault();
|
||||
var $a = $(this);
|
||||
// 현재 영상 진행상황 저장 후 전환
|
||||
stopTracking();
|
||||
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
}
|
||||
lastSaveTime = 0;
|
||||
openModal({
|
||||
contentId: $a.data('content-id'),
|
||||
videoUrl: $a.data('content-url') || '',
|
||||
title: $a.data('title') || $a.find('.title').text().trim(),
|
||||
description: $a.data('description') || '',
|
||||
categoryName: $a.data('category-name') || '리더십',
|
||||
subCategory: $a.data('sub-category') || '',
|
||||
watchTm: parseInt($a.data('watch-tm') || '0', 10),
|
||||
contentTm: parseInt($a.data('content-tm') || '0', 10)
|
||||
});
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// 댓글 CRUD
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
// ── 댓글 목록 로드 ──
|
||||
function loadComments(contentId) {
|
||||
var $commentList = $modal.find('.comment-list');
|
||||
if (!contentId) return;
|
||||
|
||||
$.getJSON(API_BASE + '/get_comments.php', { content_id: contentId })
|
||||
.done(function (data) {
|
||||
if (data.success && Array.isArray(data.data)) {
|
||||
renderComments(data.data);
|
||||
} else {
|
||||
$commentList.html('<li class="empty">등록된 댓글이 없습니다.</li>');
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
$commentList.html('<li class="empty">댓글을 불러올 수 없습니다.</li>');
|
||||
});
|
||||
}
|
||||
|
||||
// ── 댓글 렌더링 (온보딩 모달 동일 구조) ──
|
||||
function renderComments(comments) {
|
||||
var $commentList = $modal.find('.comment-list');
|
||||
if (!comments.length) {
|
||||
$commentList.html('<li class="empty">등록된 댓글이 없습니다.</li>');
|
||||
return;
|
||||
}
|
||||
|
||||
var userIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z"/></svg>';
|
||||
|
||||
var html = comments.map(function (c) {
|
||||
var safeComment = (c.comment || '')
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br>');
|
||||
var safeName = (c.member_name || '익명')
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
var buttons = c.is_author
|
||||
? '<div class="comment-actions">' +
|
||||
'<button type="button" class="btn-edit" data-id="' + c.id + '">수정</button>' +
|
||||
'<button type="button" class="btn-delete" data-id="' + c.id + '">삭제</button>' +
|
||||
'</div>'
|
||||
: '';
|
||||
|
||||
return '<li data-comment-id="' + c.id + '">' +
|
||||
'<div class="comment-header">' +
|
||||
'<span class="user-icon">' + userIconSvg + '</span>' +
|
||||
'<span class="comment-author">' + safeName + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="comment-body">' +
|
||||
'<div class="comment-content">' + safeComment + '</div>' +
|
||||
buttons +
|
||||
'</div>' +
|
||||
'<div class="comment-footer">' +
|
||||
'<span class="comment-date">' + (c.created_at || '') + '</span>' +
|
||||
'</div>' +
|
||||
'</li>';
|
||||
}).join('');
|
||||
|
||||
$commentList.html(html);
|
||||
}
|
||||
|
||||
// ── 댓글 수정 ──
|
||||
$modal.on('click', '.btn-edit', function () {
|
||||
var $li = $(this).closest('li');
|
||||
var content = $li.find('.comment-content').text();
|
||||
editingCommentId = $(this).data('id');
|
||||
|
||||
var $textarea = $modal.find('.comment-box textarea');
|
||||
$textarea.val(content).focus();
|
||||
$modal.find('.btn-cancel').prop('disabled', false);
|
||||
$modal.find('.btn-save').prop('disabled', false).text('수정');
|
||||
});
|
||||
|
||||
// ── 댓글 삭제 ──
|
||||
$modal.on('click', '.btn-delete', function () {
|
||||
if (!confirm('댓글을 삭제하시겠습니까?')) return;
|
||||
var commentId = $(this).data('id');
|
||||
|
||||
$.ajax({
|
||||
url: API_BASE + '/delete_comment.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ id: commentId }),
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
loadComments(currentContentId);
|
||||
} else {
|
||||
alert(data.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
},
|
||||
error: function () { alert('삭제 중 오류가 발생했습니다.'); }
|
||||
});
|
||||
});
|
||||
|
||||
// ── 댓글 작성/수정 저장 ──
|
||||
$modal.on('click', '.comment-box .btn-save', function () {
|
||||
var $textarea = $modal.find('.comment-box textarea');
|
||||
var comment = $textarea.val().trim();
|
||||
if (!comment || !currentContentId) return;
|
||||
|
||||
var payload = { content_id: currentContentId, comment: comment };
|
||||
if (editingCommentId) payload.id = editingCommentId;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASE + '/save_comment.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(payload),
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
resetCommentForm();
|
||||
loadComments(currentContentId);
|
||||
} else {
|
||||
alert(data.message || '저장에 실패했습니다.');
|
||||
}
|
||||
},
|
||||
error: function () { alert('저장 중 오류가 발생했습니다.'); }
|
||||
});
|
||||
});
|
||||
|
||||
// ── 댓글 취소 ──
|
||||
$modal.on('click', '.comment-box .btn-cancel', function () {
|
||||
resetCommentForm();
|
||||
});
|
||||
|
||||
// ── textarea 입력 → 버튼 활성화 ──
|
||||
$modal.on('input', '.comment-box textarea', function () {
|
||||
var hasText = $(this).val().trim().length > 0;
|
||||
$modal.find('.btn-cancel').prop('disabled', !hasText);
|
||||
$modal.find('.btn-save').prop('disabled', !hasText);
|
||||
});
|
||||
|
||||
function resetCommentForm() {
|
||||
$modal.find('.comment-box textarea').val('');
|
||||
$modal.find('.btn-cancel').prop('disabled', true);
|
||||
$modal.find('.btn-save').prop('disabled', true).text('등록');
|
||||
editingCommentId = null;
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
-- =====================================
|
||||
-- 영상 모달 JS (클릭 → 모달 열기 / 추천영상 / 댓글)
|
||||
-- =====================================
|
||||
*/
|
||||
$(function () {
|
||||
var API_BASE = '/edu/bbs/api';
|
||||
var $modal = $('#leadershipVideoModal');
|
||||
var currentContentId = null;
|
||||
var editingCommentId = null;
|
||||
|
||||
// ── YouTube IFrame API 시청 추적 변수 ──
|
||||
var ytPlayer = null;
|
||||
var trackingInterval = null;
|
||||
var lastSaveTime = 0;
|
||||
var lastCurrentTime = 0;
|
||||
|
||||
// YouTube IFrame API 스크립트 로드
|
||||
if (!document.getElementById('youtube-iframe-api')) {
|
||||
var ytScript = document.createElement('script');
|
||||
ytScript.id = 'youtube-iframe-api';
|
||||
ytScript.src = 'https://www.youtube.com/iframe_api';
|
||||
ytScript.async = true;
|
||||
document.head.appendChild(ytScript);
|
||||
}
|
||||
|
||||
// ── YouTube 비디오 ID 추출 ──
|
||||
function extractVideoId(url) {
|
||||
if (!url) return '';
|
||||
var videoId = url;
|
||||
if (url.indexOf('youtube.com/watch') > -1) {
|
||||
try { videoId = new URL(url).searchParams.get('v') || url; } catch (e) {}
|
||||
} else if (url.indexOf('youtu.be/') > -1) {
|
||||
videoId = url.split('youtu.be/')[1].split(/[?&#]/)[0];
|
||||
} else if (url.indexOf('youtube.com/embed/') > -1) {
|
||||
videoId = url.split('youtube.com/embed/')[1].split(/[?&#]/)[0];
|
||||
}
|
||||
if (videoId && videoId.indexOf('/') === -1 && videoId.indexOf('.') === -1) {
|
||||
return videoId;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// ── YT.Player 생성 ──
|
||||
function initYTPlayer(videoId, startSeconds) {
|
||||
destroyYTPlayer();
|
||||
startSeconds = Math.floor(startSeconds || 0);
|
||||
|
||||
var createPlayer = function () {
|
||||
ytPlayer = new YT.Player('videoPlayer', {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
videoId: videoId,
|
||||
playerVars: {
|
||||
autoplay: 1,
|
||||
controls: 1,
|
||||
rel: 0,
|
||||
modestbranding: 1,
|
||||
enablejsapi: 1,
|
||||
start: startSeconds
|
||||
},
|
||||
events: {
|
||||
'onReady': function (event) {
|
||||
if (event.target && typeof event.target.playVideo === 'function') {
|
||||
event.target.playVideo();
|
||||
}
|
||||
},
|
||||
'onStateChange': function (event) {
|
||||
handlePlayerStateChange(event);
|
||||
},
|
||||
'onError': function (event) {
|
||||
console.error('[Leadership] Player Error:', event.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
|
||||
createPlayer();
|
||||
} else {
|
||||
// YT API 아직 로드 안 됨 → 대기
|
||||
var checkYt = setInterval(function () {
|
||||
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
|
||||
clearInterval(checkYt);
|
||||
createPlayer();
|
||||
}
|
||||
}, 500);
|
||||
setTimeout(function () { clearInterval(checkYt); }, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 플레이어 파괴 ──
|
||||
function destroyYTPlayer() {
|
||||
stopTracking();
|
||||
if (ytPlayer && typeof ytPlayer.destroy === 'function') {
|
||||
try { ytPlayer.destroy(); } catch (e) {}
|
||||
}
|
||||
ytPlayer = null;
|
||||
// YT.Player가 div를 iframe으로 교체하므로 다시 div를 복원
|
||||
var $box = $modal.find('.video-box');
|
||||
if (!$box.find('#videoPlayer').length) {
|
||||
$box.html('<div id="videoPlayer"></div>');
|
||||
}
|
||||
}
|
||||
|
||||
// ── 플레이어 상태 변경 핸들러 (VideoModalBase 패턴) ──
|
||||
function handlePlayerStateChange(event) {
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
startTracking();
|
||||
} else {
|
||||
stopTracking();
|
||||
if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
lastSaveTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 시청 추적 시작 (1초 주기) ──
|
||||
function startTracking() {
|
||||
if (trackingInterval) return;
|
||||
lastSaveTime = 0;
|
||||
|
||||
trackingInterval = setInterval(function () {
|
||||
if (!ytPlayer || typeof ytPlayer.getCurrentTime !== 'function') return;
|
||||
|
||||
var playerState = (typeof ytPlayer.getPlayerState === 'function')
|
||||
? ytPlayer.getPlayerState() : null;
|
||||
if (playerState !== YT.PlayerState.PLAYING) return;
|
||||
|
||||
var currentTime = ytPlayer.getCurrentTime();
|
||||
var diff = currentTime - lastCurrentTime;
|
||||
if (Math.abs(diff) <= 5) {
|
||||
lastSaveTime += 1;
|
||||
}
|
||||
lastCurrentTime = currentTime;
|
||||
|
||||
// 5초마다 서버에 heartbeat 전송
|
||||
if (lastSaveTime >= 5) {
|
||||
saveLearningProgress(lastSaveTime);
|
||||
lastSaveTime = 0;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── 시청 추적 중지 ──
|
||||
function stopTracking() {
|
||||
if (trackingInterval) {
|
||||
clearInterval(trackingInterval);
|
||||
trackingInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 서버에 학습 진행상황 저장 (save_learning.php) ──
|
||||
function saveLearningProgress(heartbeatSeconds, isWatching) {
|
||||
if (!ytPlayer || !currentContentId) return;
|
||||
if (typeof ytPlayer.getCurrentTime !== 'function') return;
|
||||
|
||||
var currentTime = Math.floor(ytPlayer.getCurrentTime());
|
||||
var duration = Math.floor(ytPlayer.getDuration());
|
||||
var incrementalWatch = Math.max(0, Math.floor(heartbeatSeconds || 0));
|
||||
|
||||
var params = new URLSearchParams({
|
||||
content_id: String(currentContentId),
|
||||
watch_tm: String(Math.max(0, currentTime)),
|
||||
content_tm: String(Math.max(0, duration)),
|
||||
all_tm_increment: String(incrementalWatch),
|
||||
is_watching: isWatching === 'N' ? 'N' : 'Y'
|
||||
});
|
||||
|
||||
fetch(API_BASE + '/save_learning.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: params.toString(),
|
||||
keepalive: true
|
||||
}).then(function (r) { return r.json(); }).then(function (result) {
|
||||
if (!result.success) {
|
||||
console.error('[Leadership] Save Learning Failed:', result.message);
|
||||
}
|
||||
}).catch(function (e) {
|
||||
console.error('[Leadership] Save Learning Error:', e);
|
||||
});
|
||||
}
|
||||
|
||||
// ── 영상 카드 클릭 → 모달 열기 ──
|
||||
$(document).on('click', '#video-list .video-item .card-link', function (e) {
|
||||
e.preventDefault();
|
||||
var $item = $(this).closest('.video-item');
|
||||
|
||||
var contentId = $item.data('content-id') || $item.attr('data-content-id') || '';
|
||||
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
|
||||
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
|
||||
var desc = $item.data('description') || '';
|
||||
var catName = $item.data('category-name') || $item.find('.item-category').text().trim() || '리더십';
|
||||
var subCat = $item.data('sub-category') || '';
|
||||
var watchTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
|
||||
var contentTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
|
||||
|
||||
if (!contentId) {
|
||||
console.warn('[VideoModal] content-id가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
openModal({
|
||||
contentId: contentId,
|
||||
videoUrl: videoUrl,
|
||||
title: title,
|
||||
description: desc,
|
||||
categoryName: catName,
|
||||
subCategory: subCat,
|
||||
watchTm: watchTm,
|
||||
contentTm: contentTm
|
||||
});
|
||||
});
|
||||
|
||||
// ── 모달 열기 ──
|
||||
function openModal(info) {
|
||||
currentContentId = info.contentId;
|
||||
editingCommentId = null;
|
||||
lastSaveTime = 0;
|
||||
lastCurrentTime = Math.floor(info.watchTm || 0);
|
||||
|
||||
// YouTube IFrame API 플레이어 세팅
|
||||
var videoId = extractVideoId(info.videoUrl);
|
||||
if (videoId) {
|
||||
initYTPlayer(videoId, info.watchTm || 0);
|
||||
}
|
||||
|
||||
// 영상 정보
|
||||
$modal.find('#modalTitle').text(info.title);
|
||||
// 설명: 줄바꿈을 <br>로 변환하여 표시
|
||||
var descHtml = $('<div>').text(info.description || '').html().replace(/\n/g, '<br>');
|
||||
$modal.find('#modalDesc').html(descHtml);
|
||||
$modal.find('#modalCategoryName').text(info.categoryName || '리더십');
|
||||
$modal.find('#modalSubCategory').text(info.subCategory || '');
|
||||
$modal.find('#modalBadge').text(info.categoryName || '리더십');
|
||||
|
||||
// 추천영상 리스트
|
||||
buildRecommendedList(info.contentId);
|
||||
|
||||
// 댓글 로드
|
||||
loadComments(info.contentId);
|
||||
resetCommentForm();
|
||||
|
||||
// 모달 표시
|
||||
$modal.show();
|
||||
$('body').css('overflow', 'hidden');
|
||||
}
|
||||
|
||||
// ── 모달 닫기 (진행상황 저장 후 닫기) ──
|
||||
function closeModal() {
|
||||
// 마지막 시청 진행상황 저장
|
||||
stopTracking();
|
||||
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
}
|
||||
lastSaveTime = 0;
|
||||
|
||||
destroyYTPlayer();
|
||||
$modal.hide();
|
||||
$('body').css('overflow', '');
|
||||
currentContentId = null;
|
||||
editingCommentId = null;
|
||||
}
|
||||
|
||||
$modal.on('click', '.close', closeModal);
|
||||
$modal.on('click', function (e) {
|
||||
if ($(e.target).hasClass('modal')) closeModal();
|
||||
});
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.key === 'Escape' && $modal.is(':visible')) closeModal();
|
||||
});
|
||||
|
||||
// ── 추천영상 리스트 구성 (현재 페이지 영상 목록 활용) ──
|
||||
function buildRecommendedList(excludeContentId) {
|
||||
var $list = $modal.find('#recommendedList');
|
||||
$list.empty();
|
||||
var count = 0;
|
||||
|
||||
$('#video-list .video-item').each(function () {
|
||||
var $item = $(this);
|
||||
var cid = $item.data('content-id') || $item.attr('data-content-id') || '';
|
||||
if (!cid || String(cid) === String(excludeContentId)) return;
|
||||
if (count >= 10) return false; // 최대 10개
|
||||
|
||||
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
|
||||
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
|
||||
var catName = $item.data('category-name') || $item.find('.item-category').text().trim() || '리더십';
|
||||
var thumbSrc = $item.find('.item-thumb img, .thumb img').attr('src') || '/edu/img/video/img_thumb_01.png';
|
||||
var desc = String($item.data('description') || '');
|
||||
var subCat = $item.data('sub-category') || '';
|
||||
var wTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
|
||||
var cTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
|
||||
|
||||
var li =
|
||||
'<li>' +
|
||||
'<a href="#" class="list" ' +
|
||||
'data-content-id="' + cid + '" ' +
|
||||
'data-content-url="' + videoUrl.replace(/"/g, '"') + '" ' +
|
||||
'data-title="' + title.replace(/"/g, '"') + '" ' +
|
||||
'data-description="' + desc.replace(/"/g, '"') + '" ' +
|
||||
'data-category-name="' + catName.replace(/"/g, '"') + '" ' +
|
||||
'data-sub-category="' + subCat.replace(/"/g, '"') + '" ' +
|
||||
'data-watch-tm="' + wTm + '" ' +
|
||||
'data-content-tm="' + cTm + '">' +
|
||||
'<div class="thumb"><img src="' + thumbSrc + '" alt="" /></div>' +
|
||||
'<div class="txt-box">' +
|
||||
'<div class="category leader">' + catName + '</div>' +
|
||||
'<div class="title">' + title + '</div>' +
|
||||
'</div>' +
|
||||
'</a>' +
|
||||
'</li>';
|
||||
$list.append(li);
|
||||
count++;
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
$list.html('<li class="empty">추천 영상이 없습니다.</li>');
|
||||
}
|
||||
}
|
||||
|
||||
// ── 추천영상 클릭 → 영상 전환 ──
|
||||
$modal.on('click', '#recommendedList .list', function (e) {
|
||||
e.preventDefault();
|
||||
var $a = $(this);
|
||||
// 현재 영상 진행상황 저장 후 전환
|
||||
stopTracking();
|
||||
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
|
||||
saveLearningProgress(Math.floor(lastSaveTime), 'N');
|
||||
}
|
||||
lastSaveTime = 0;
|
||||
openModal({
|
||||
contentId: $a.data('content-id'),
|
||||
videoUrl: $a.data('content-url') || '',
|
||||
title: $a.data('title') || $a.find('.title').text().trim(),
|
||||
description: $a.data('description') || '',
|
||||
categoryName: $a.data('category-name') || '리더십',
|
||||
subCategory: $a.data('sub-category') || '',
|
||||
watchTm: parseInt($a.data('watch-tm') || '0', 10),
|
||||
contentTm: parseInt($a.data('content-tm') || '0', 10)
|
||||
});
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// 댓글 CRUD
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
// ── 댓글 목록 로드 ──
|
||||
function loadComments(contentId) {
|
||||
var $commentList = $modal.find('.comment-list');
|
||||
if (!contentId) return;
|
||||
|
||||
$.getJSON(API_BASE + '/get_comments.php', { content_id: contentId })
|
||||
.done(function (data) {
|
||||
if (data.success && Array.isArray(data.data)) {
|
||||
renderComments(data.data);
|
||||
} else {
|
||||
$commentList.html('<li class="empty">등록된 댓글이 없습니다.</li>');
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
$commentList.html('<li class="empty">댓글을 불러올 수 없습니다.</li>');
|
||||
});
|
||||
}
|
||||
|
||||
// ── 댓글 렌더링 (온보딩 모달 동일 구조) ──
|
||||
function renderComments(comments) {
|
||||
var $commentList = $modal.find('.comment-list');
|
||||
if (!comments.length) {
|
||||
$commentList.html('<li class="empty">등록된 댓글이 없습니다.</li>');
|
||||
return;
|
||||
}
|
||||
|
||||
var userIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z"/></svg>';
|
||||
|
||||
var html = comments.map(function (c) {
|
||||
var safeComment = (c.comment || '')
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br>');
|
||||
var safeName = (c.member_name || '익명')
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
var buttons = c.is_author
|
||||
? '<div class="comment-actions">' +
|
||||
'<button type="button" class="btn-edit" data-id="' + c.id + '">수정</button>' +
|
||||
'<button type="button" class="btn-delete" data-id="' + c.id + '">삭제</button>' +
|
||||
'</div>'
|
||||
: '';
|
||||
|
||||
return '<li data-comment-id="' + c.id + '">' +
|
||||
'<div class="comment-header">' +
|
||||
'<span class="user-icon">' + userIconSvg + '</span>' +
|
||||
'<span class="comment-author">' + safeName + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="comment-body">' +
|
||||
'<div class="comment-content">' + safeComment + '</div>' +
|
||||
buttons +
|
||||
'</div>' +
|
||||
'<div class="comment-footer">' +
|
||||
'<span class="comment-date">' + (c.created_at || '') + '</span>' +
|
||||
'</div>' +
|
||||
'</li>';
|
||||
}).join('');
|
||||
|
||||
$commentList.html(html);
|
||||
}
|
||||
|
||||
// ── 댓글 수정 ──
|
||||
$modal.on('click', '.btn-edit', function () {
|
||||
var $li = $(this).closest('li');
|
||||
var content = $li.find('.comment-content').text();
|
||||
editingCommentId = $(this).data('id');
|
||||
|
||||
var $textarea = $modal.find('.comment-box textarea');
|
||||
$textarea.val(content).focus();
|
||||
$modal.find('.btn-cancel').prop('disabled', false);
|
||||
$modal.find('.btn-save').prop('disabled', false).text('수정');
|
||||
});
|
||||
|
||||
// ── 댓글 삭제 ──
|
||||
$modal.on('click', '.btn-delete', function () {
|
||||
if (!confirm('댓글을 삭제하시겠습니까?')) return;
|
||||
var commentId = $(this).data('id');
|
||||
|
||||
$.ajax({
|
||||
url: API_BASE + '/delete_comment.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ id: commentId }),
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
loadComments(currentContentId);
|
||||
} else {
|
||||
alert(data.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
},
|
||||
error: function () { alert('삭제 중 오류가 발생했습니다.'); }
|
||||
});
|
||||
});
|
||||
|
||||
// ── 댓글 작성/수정 저장 ──
|
||||
$modal.on('click', '.comment-box .btn-save', function () {
|
||||
var $textarea = $modal.find('.comment-box textarea');
|
||||
var comment = $textarea.val().trim();
|
||||
if (!comment || !currentContentId) return;
|
||||
|
||||
var payload = { content_id: currentContentId, comment: comment };
|
||||
if (editingCommentId) payload.id = editingCommentId;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASE + '/save_comment.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(payload),
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
resetCommentForm();
|
||||
loadComments(currentContentId);
|
||||
} else {
|
||||
alert(data.message || '저장에 실패했습니다.');
|
||||
}
|
||||
},
|
||||
error: function () { alert('저장 중 오류가 발생했습니다.'); }
|
||||
});
|
||||
});
|
||||
|
||||
// ── 댓글 취소 ──
|
||||
$modal.on('click', '.comment-box .btn-cancel', function () {
|
||||
resetCommentForm();
|
||||
});
|
||||
|
||||
// ── textarea 입력 → 버튼 활성화 ──
|
||||
$modal.on('input', '.comment-box textarea', function () {
|
||||
var hasText = $(this).val().trim().length > 0;
|
||||
$modal.find('.btn-cancel').prop('disabled', !hasText);
|
||||
$modal.find('.btn-save').prop('disabled', !hasText);
|
||||
});
|
||||
|
||||
function resetCommentForm() {
|
||||
$modal.find('.comment-box textarea').val('');
|
||||
$modal.find('.btn-cancel').prop('disabled', true);
|
||||
$modal.find('.btn-save').prop('disabled', true).text('등록');
|
||||
editingCommentId = null;
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,665 @@
|
||||
/**
|
||||
* =====================
|
||||
* 사용페이지 : 마이페이지
|
||||
* 작 성 자 : ERP기획팀 moon
|
||||
* 최초작성일자: 26.03.20.
|
||||
* =====================
|
||||
*/
|
||||
|
||||
(function () {
|
||||
|
||||
/**
|
||||
* 마이페이지 초기 진입 시 실행
|
||||
* - 학습활동 데이터 로드
|
||||
* - 콘텐츠 탭 클릭 재렌더링 이벤트 연결
|
||||
* - 최초 로드 완료 후 활성 탭 기준으로 리스트를 다시 렌더링한다
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// 연도 select 변경 이벤트 연결
|
||||
bindYearSelectChange();
|
||||
|
||||
// 학습활동 초기 데이터 조회
|
||||
loadMypageInit();
|
||||
|
||||
// 콘텐츠 탭 클릭 시 재렌더링 이벤트 연결
|
||||
bindContentTabReload();
|
||||
});
|
||||
|
||||
/**
|
||||
* 모든 리소스 로드 완료 후 실행
|
||||
* - 퍼블리싱용 mypage.js 초기화가 끝난 뒤
|
||||
* 현재 활성 탭 기준으로 콘텐츠 리스트를 다시 렌더링한다
|
||||
*/
|
||||
window.addEventListener("load", function () {
|
||||
setTimeout(function () {
|
||||
reloadActiveContentTab();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
//JS 1단계 (가장 먼저)
|
||||
/**
|
||||
* 마이페이지 초기 데이터 로드 함수
|
||||
* - 서버에서 학습활동 관련 데이터를 가져온다
|
||||
* - 연도 select 및 총 학습시간을 화면에 반영한다
|
||||
*/
|
||||
function loadMypageInit(year = '') {
|
||||
fetch('/edu/ajax/get_init_data_for_mypage.php?year=' + year)
|
||||
.then(res => res.json())
|
||||
.then(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);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//JS 2단계 (연도 select 채우기)
|
||||
function renderYearSelect(yearList, selectedYear) {
|
||||
const select = document.getElementById('mypage-year-select');
|
||||
if (!select) return;
|
||||
|
||||
select.innerHTML = '';
|
||||
|
||||
yearList.forEach(year => {
|
||||
const option = document.createElement('option');
|
||||
option.value = year;
|
||||
option.textContent = year + '년';
|
||||
|
||||
if (year == selectedYear) {
|
||||
option.selected = true;
|
||||
}
|
||||
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
//AJAX 응답의 my_total_minutes를 #mypage-total-minutes에 넣기
|
||||
/**
|
||||
* 총 학습시간(분)을 화면에 표시하는 함수
|
||||
* - 서버에서 받은 my_total_minutes 값을
|
||||
* #mypage-total-minutes 영역에 표시한다.
|
||||
* - 숫자는 천단위 콤마 포맷 적용
|
||||
*/
|
||||
function renderMyTotalMinutes(totalMinutes) {
|
||||
// 대상 DOM 가져오기
|
||||
const el = document.getElementById('mypage-total-minutes');
|
||||
if (!el) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어 (null, undefined 대비)
|
||||
const value = parseInt(totalMinutes, 10) || 0;
|
||||
|
||||
// 화면에 출력 (예: 1,200)
|
||||
el.textContent = value.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 모바일 총 학습시간(분)을 화면에 표시하는 함수
|
||||
* - 서버에서 받은 my_total_minutes 값을
|
||||
* #mobile-mypage-total-minutes 영역에 표시한다.
|
||||
* - 숫자는 천단위 콤마 포맷 적용
|
||||
*/
|
||||
function renderMobileMyTotalMinutes(totalMinutes) {
|
||||
// 대상 DOM 가져오기
|
||||
const el = document.getElementById('mobile-mypage-total-minutes');
|
||||
if (!el) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const value = parseInt(totalMinutes, 10) || 0;
|
||||
|
||||
// 화면에 출력
|
||||
el.textContent = value.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체평균 학습시간(분)을 화면에 표시하는 함수
|
||||
* - 서버에서 받은 avg_total_minutes 값을
|
||||
* #mypage-avg-minutes 영역에 표시한다.
|
||||
* - 숫자는 천단위 콤마 포맷 적용
|
||||
*/
|
||||
function renderAvgTotalMinutes(avgTotalMinutes) {
|
||||
// 대상 DOM 가져오기
|
||||
const el = document.getElementById('mypage-avg-minutes');
|
||||
if (!el) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const value = parseInt(avgTotalMinutes, 10) || 0;
|
||||
|
||||
// 화면에 출력
|
||||
el.textContent = value.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 총 학습시간 게이지를 화면에 반영하는 함수
|
||||
* - 서버에서 받은 my_total_minutes 값을 기준으로
|
||||
* #mypage-total-gauge의 width 값을 변경한다.
|
||||
* - 최대 기준은 40시간(2400분)으로 계산한다.
|
||||
* - 2400분을 초과해도 게이지는 100%를 넘지 않도록 제한한다.
|
||||
*/
|
||||
function renderMyTotalGauge(totalMinutes) {
|
||||
// 대상 DOM 가져오기
|
||||
const el = document.getElementById('mypage-total-gauge');
|
||||
if (!el) return;
|
||||
|
||||
// 총 학습시간(분) 숫자 변환
|
||||
const value = parseInt(totalMinutes, 10) || 0;
|
||||
|
||||
// 게이지 최대 기준값 (40시간 = 2400분)
|
||||
const maxMinutes = 2400;
|
||||
|
||||
// 퍼센트 계산
|
||||
let percent = Math.round((value / maxMinutes) * 100);
|
||||
|
||||
// 0 ~ 100 범위로 제한
|
||||
if (percent < 0) percent = 0;
|
||||
if (percent > 100) percent = 100;
|
||||
|
||||
// 게이지 너비 반영
|
||||
el.style.width = percent + '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* 연도 select 변경 이벤트를 연결하는 함수
|
||||
* - 사용자가 연도를 변경하면
|
||||
* 선택한 연도값으로 마이페이지 초기 데이터를 다시 조회한다.
|
||||
*/
|
||||
function bindYearSelectChange() {
|
||||
// 연도 select DOM 가져오기
|
||||
const select = document.getElementById('mypage-year-select');
|
||||
if (!select) return;
|
||||
|
||||
// 연도 변경 이벤트 연결
|
||||
select.addEventListener('change', function () {
|
||||
// 현재 선택된 연도 가져오기
|
||||
const selectedYear = this.value || '';
|
||||
|
||||
// 선택 연도 기준으로 데이터 다시 조회
|
||||
loadMypageInit(selectedYear);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 마이클래스(CA10001) 학습활동 데이터를 화면에 반영하는 함수
|
||||
* - activity_items 배열에서 마이클래스 항목만 찾아서
|
||||
* 해당 li[data-category="CA10001"] 영역의 수치를 갱신한다.
|
||||
* - 학습시간, 퍼센트, 게이지, 전체 콘텐츠 개수를 반영한다.
|
||||
*/
|
||||
function renderMyClassActivity(activityItems) {
|
||||
// activity_items가 배열이 아니면 종료
|
||||
if (!Array.isArray(activityItems)) return;
|
||||
|
||||
// 마이클래스 항목 찾기
|
||||
const item = activityItems.find(function (row) {
|
||||
return row.category_code === 'CA10001';
|
||||
});
|
||||
if (!item) return;
|
||||
|
||||
// 마이클래스 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10001"]');
|
||||
if (!li) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
||||
|
||||
// 1. 우측 상단 표시값 갱신
|
||||
// 예: 74분 (33%)
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분 (' + percent + '%)';
|
||||
}
|
||||
|
||||
// 2. 게이지 너비 갱신
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시 갱신
|
||||
// 예: 6개
|
||||
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;
|
||||
|
||||
// 해당 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10002"]');
|
||||
if (!li) return;
|
||||
|
||||
// 값 파싱
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
||||
|
||||
// 1. 학습시간 + 퍼센트 표시
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분 (' + percent + '%)';
|
||||
}
|
||||
|
||||
// 2. 게이지
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시
|
||||
const endEl = li.querySelector('.gauge-end');
|
||||
if (endEl) {
|
||||
endEl.textContent = totalCount.toLocaleString() + '개';
|
||||
}
|
||||
|
||||
// 4. 온보딩 안내문구 제어
|
||||
const noteEl = li.querySelector('.activity-note');
|
||||
const dateEl = li.querySelector('.onboarding-due-date');
|
||||
|
||||
if (noteEl && dateEl) {
|
||||
// 날짜 표시 (yy.mm.dd 형식)
|
||||
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;
|
||||
|
||||
// 해당 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10003"]');
|
||||
if (!li) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalCount = parseInt(item.master_total_cnt, 10) || 0;
|
||||
|
||||
// 1. 우측 상단 표시값 갱신
|
||||
// 예: 30분 (20%)
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분 (' + percent + '%)';
|
||||
}
|
||||
|
||||
// 2. 게이지 너비 갱신
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시 갱신
|
||||
// 예: 6개
|
||||
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;
|
||||
|
||||
// 해당 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10004"]');
|
||||
if (!li) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
||||
|
||||
// 1. 우측 상단 표시값 갱신
|
||||
// 예: 60분
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분';
|
||||
}
|
||||
|
||||
// 2. 게이지 너비 갱신
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시 갱신
|
||||
// 예: 240분
|
||||
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;
|
||||
|
||||
// 해당 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10005"]');
|
||||
if (!li) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
||||
|
||||
// 1. 우측 상단 표시값 갱신
|
||||
// 예: 43분
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분';
|
||||
}
|
||||
|
||||
// 2. 게이지 너비 갱신
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시 갱신
|
||||
// 예: 90분
|
||||
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;
|
||||
|
||||
// 해당 li 찾기
|
||||
const li = document.querySelector('.activity-list li[data-category="CA10006"]');
|
||||
if (!li) return;
|
||||
|
||||
// 숫자 변환 및 예외 방어
|
||||
const totalWatchTm = parseInt(item.total_watch_tm, 10) || 0;
|
||||
const percent = parseInt(item.percent, 10) || 0;
|
||||
const totalContentTm = parseInt(item.total_content_tm, 10) || 0;
|
||||
|
||||
// 1. 우측 상단 표시값 갱신
|
||||
// 예: 23분
|
||||
const valueEl = li.querySelector('.activity-value');
|
||||
if (valueEl) {
|
||||
valueEl.innerHTML = '<em>' + totalWatchTm.toLocaleString() + '</em>분';
|
||||
}
|
||||
|
||||
// 2. 게이지 너비 갱신
|
||||
const gaugeEl = li.querySelector('.gauge-fill');
|
||||
if (gaugeEl) {
|
||||
gaugeEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// 3. 분모 표시 갱신
|
||||
// 예: 80분
|
||||
const endEl = li.querySelector('.gauge-end');
|
||||
if (endEl) {
|
||||
endEl.textContent = totalContentTm.toLocaleString() + '분';
|
||||
}
|
||||
}//renderBizTrendActivity
|
||||
|
||||
|
||||
/**
|
||||
* 시청중인 콘텐츠 목록을 서버에서 조회하여 화면에 표시하는 함수
|
||||
* - watching 탭 기준으로 목록 HTML을 받아
|
||||
* #mypage-watching-list 영역에 삽입한다.
|
||||
* - mypage.js가 먼저 탭/패널 초기화를 수행한 뒤,
|
||||
* 이 함수가 실제 콘텐츠 목록을 최종 반영하는 역할을 한다.
|
||||
*/
|
||||
function loadWatchingContentList() {
|
||||
// 시청중 목록 영역 가져오기
|
||||
const listEl = document.getElementById('mypage-watching-list');
|
||||
if (!listEl) return;
|
||||
|
||||
// 서버에 시청중 탭 목록 요청
|
||||
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=watching&page=1')
|
||||
.then(function (res) {
|
||||
return res.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
// 응답 성공 여부 확인
|
||||
if (!data.success) return;
|
||||
|
||||
// 서버에서 내려준 HTML을 최종 반영
|
||||
listEl.innerHTML = data.html || '';
|
||||
|
||||
// 디버깅 확인용
|
||||
console.log('[watching] 최종 렌더링 완료:', listEl.querySelectorAll('.content-card').length);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error('시청중 콘텐츠 목록을 불러오지 못했습니다.', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 콘텐츠 탭 클릭 이벤트 감지 후
|
||||
* 해당 탭에 맞는 데이터를 다시 렌더링하는 함수
|
||||
*
|
||||
* - 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');
|
||||
|
||||
// 약간의 딜레이를 줘서 mypage.js 처리 이후 실행
|
||||
setTimeout(function () {
|
||||
|
||||
if (tab === 'watching') {
|
||||
loadWatchingContentList();
|
||||
} else if (tab === 'completed') {
|
||||
loadCompletedContentList();
|
||||
} else if (tab === 'saved') {
|
||||
loadSavedContentList();
|
||||
}
|
||||
|
||||
console.log('[탭 재렌더링]', tab);
|
||||
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 시청완료 콘텐츠 목록 로드
|
||||
*/
|
||||
function loadCompletedContentList() {
|
||||
|
||||
const listEl = document.querySelector('#panel-completed .content-grid');
|
||||
if (!listEl) return;
|
||||
|
||||
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=completed&page=1')
|
||||
.then(res => res.json())
|
||||
.then(function (data) {
|
||||
|
||||
if (!data.success) return;
|
||||
|
||||
listEl.innerHTML = data.html || '';
|
||||
|
||||
console.log('[completed] 렌더링 완료');
|
||||
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error('completed 로딩 실패', e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장한 콘텐츠 목록 로드
|
||||
*/
|
||||
function loadSavedContentList() {
|
||||
|
||||
const listEl = document.querySelector('#panel-saved .content-grid');
|
||||
if (!listEl) return;
|
||||
|
||||
fetch('/edu/ajax/get_video_list_for_mypage.php?tab=saved&page=1')
|
||||
.then(res => res.json())
|
||||
.then(function (data) {
|
||||
|
||||
if (!data.success) return;
|
||||
|
||||
listEl.innerHTML = data.html || '';
|
||||
|
||||
console.log('[saved] 렌더링 완료');
|
||||
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error('saved 로딩 실패', e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 활성화된 콘텐츠 탭을 기준으로
|
||||
* 해당 탭의 리스트를 다시 렌더링하는 함수
|
||||
*
|
||||
* - 최초 페이지 진입 시 사용
|
||||
* - mypage.js가 탭/패널 초기화를 끝낸 뒤 실행되어야 한다
|
||||
*/
|
||||
function reloadActiveContentTab() {
|
||||
// 현재 active 상태의 탭 버튼 찾기
|
||||
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);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user