Initial commit: 교육 프로젝트 배포

This commit is contained in:
송대일
2026-07-01 18:32:42 +09:00
commit be6dccd120
1483 changed files with 5082202 additions and 0 deletions
+484
View File
@@ -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, '&quot;') + '" ' +
'data-title="' + title.replace(/"/g, '&quot;') + '" ' +
'data-description="' + desc.replace(/"/g, '&quot;') + '" ' +
'data-category-name="' + catName.replace(/"/g, '&quot;') + '" ' +
'data-sub-category="' + subCat.replace(/"/g, '&quot;') + '" ' +
'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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
var safeName = (c.member_name || '익명')
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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