Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../bbs/auth.php";
|
||||
edu_require_login();
|
||||
|
||||
require_once __DIR__ . "/../bbs/db_conn.php";
|
||||
$pdo = db_conn();
|
||||
|
||||
$memberId = (string)($_SESSION['member_id'] ?? '');
|
||||
$sysCompCode = (string)($_SESSION['sys_comp_code'] ?? '');
|
||||
|
||||
if ($memberId === '' || $sysCompCode === '') {
|
||||
http_response_code(401);
|
||||
exit('로그인 정보가 유효하지 않습니다. 다시 로그인해 주세요.');
|
||||
}
|
||||
|
||||
// 사용자 정보 및 join_date 조회
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT name, rank_name, join_date, sys_comp_code
|
||||
FROM edu_users
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([$memberId, $sysCompCode]);
|
||||
$userRow = $stmtUser->fetch() ?: [];
|
||||
|
||||
$userName = $userRow['name'] ?? '사용자';
|
||||
$userRank = $userRow['rank_name'] ?? '';
|
||||
$sysCompCode = (string)($userRow['sys_comp_code'] ?? $sysCompCode);
|
||||
|
||||
// join_date 기준 14일 마감 계산
|
||||
$today = new DateTime(date('Y-m-d'));
|
||||
try {
|
||||
$joinBase = !empty($userRow['join_date'])
|
||||
? new DateTime(date('Y-m-d', strtotime($userRow['join_date'])))
|
||||
: clone $today;
|
||||
} catch (Exception $e) {
|
||||
$joinBase = clone $today;
|
||||
}
|
||||
|
||||
$deadline = (clone $joinBase)->modify('+14 days');
|
||||
$dDay = ($today <= $deadline) ? (int)$today->diff($deadline)->days : 0;
|
||||
$deadlineStr = $deadline->format('n월 j일');
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<?php include(__DIR__ . "/_include/_head.php") ?>
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css" />
|
||||
|
||||
<!-- <style>
|
||||
/* puzzle-onboarding.js 미구현 동안 스크롤 임시 허용 */
|
||||
html, body { overflow: auto !important; height: auto !important; }
|
||||
.wrap.onboarding { position: static !important; overflow: visible !important; }
|
||||
.wrap.onboarding .container { position: static !important; overflow: visible !important; }
|
||||
</style> -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap onboarding">
|
||||
<?php include(__DIR__ . "/_include/_header.php") ?>
|
||||
<!-- container -->
|
||||
<div class="container">
|
||||
<div class="puzzle-wrap">
|
||||
<div class="page-title">
|
||||
<h3>
|
||||
<span><em><?= htmlspecialchars(trim($userName . ' ' . $userRank), ENT_QUOTES, 'UTF-8') ?></em>님,</span> <?= $dDay>0?'입사를 환영합니다.':''?>
|
||||
</h3>
|
||||
<p>
|
||||
<em><?= $dDay>0?'D-'.$dDay:'' ?><small><?= $dDay>0?'('.htmlspecialchars($deadlineStr, ENT_QUOTES, 'UTF-8').')':'' ?></small><?= $dDay>0?'까지':''?> 필수 콘텐츠</em> 시청을
|
||||
완료해주세요.
|
||||
</p>
|
||||
</div>
|
||||
<div class="puzzle-area">
|
||||
<div class="puzzle-board" id="puzzleBoard">
|
||||
<!-- SVG가 여기에 동적으로 삽입됩니다 -->
|
||||
|
||||
<p class="img-box family">
|
||||
<img src="/img/onboarding/img_obj_01.png" loading="lazy" />
|
||||
</p>
|
||||
|
||||
<p class="img-box hanmac">
|
||||
<img src="/img/onboarding/img_obj_02.png" loading="lazy" />
|
||||
</p>
|
||||
|
||||
<p class="img-box value">
|
||||
<img src="/img/onboarding/img_obj_03.png" loading="lazy" />
|
||||
</p>
|
||||
|
||||
<p class="img-box company">
|
||||
<img src="/img/onboarding/img_obj_04.png" loading="lazy" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- // container -->
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 1. 온보딩 콘텐츠 조회 (category_group 기준)
|
||||
$onboardingGroups = [
|
||||
'CA200O01', 'CA200O02', 'CA200O03', 'CA200O04', 'CA200O05',
|
||||
'CA200O06', 'CA200O07', 'CA200O08', 'CA200O09', 'CA200O10',
|
||||
];
|
||||
$placeholders = implode(',', array_fill(0, count($onboardingGroups), '?'));
|
||||
|
||||
// edu_content_histories 테이블 존재 여부에 따라 쿼리 분기
|
||||
$useContentHistories = true;
|
||||
try {
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'edu_content_histories'");
|
||||
if ($checkTable->rowCount() === 0) {
|
||||
$useContentHistories = false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$useContentHistories = false;
|
||||
}
|
||||
|
||||
if ($useContentHistories) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
c.*,
|
||||
COALESCE(lh.watch_tm, 0) AS watch_tm,
|
||||
COALESCE(lh.content_tm, 0) AS content_tm,
|
||||
lh.completed_at AS lh_completed_at,
|
||||
CASE
|
||||
WHEN ch.content_id IS NULL THEN 'none'
|
||||
WHEN ch.completed_at IS NOT NULL THEN 'completed'
|
||||
ELSE 'in_progress'
|
||||
END AS learning_status
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
LEFT JOIN edu_content_histories ch
|
||||
ON ch.content_id = c.content_id
|
||||
AND ch.member_id = ?
|
||||
AND ch.sys_comp_code = ?
|
||||
WHERE c.category_group IN ($placeholders)
|
||||
ORDER BY FIELD(c.category_group, 'CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10'), COALESCE(NULLIF(c.sort_order, 0), 9999), c.content_id
|
||||
");
|
||||
$stmt->execute(array_merge([$memberId, $sysCompCode, $memberId, $sysCompCode], $onboardingGroups));
|
||||
$rows = $stmt->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
// edu_content_histories JOIN 실패 시 fallback
|
||||
$useContentHistories = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$useContentHistories) {
|
||||
// fallback: edu_learning_histories만 사용
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
c.*,
|
||||
COALESCE(lh.watch_tm, 0) AS watch_tm,
|
||||
COALESCE(lh.content_tm, 0) AS content_tm,
|
||||
lh.completed_at AS lh_completed_at,
|
||||
CASE
|
||||
WHEN lh.content_id IS NULL THEN 'none'
|
||||
WHEN lh.completed_at IS NOT NULL THEN 'completed'
|
||||
ELSE 'in_progress'
|
||||
END AS learning_status
|
||||
FROM edu_contents c
|
||||
LEFT JOIN edu_learning_histories lh
|
||||
ON lh.content_id = c.content_id
|
||||
AND lh.member_id = ?
|
||||
AND lh.sys_comp_code = ?
|
||||
WHERE c.category_group IN ($placeholders)
|
||||
ORDER BY FIELD(c.category_group, 'CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10'), COALESCE(NULLIF(c.sort_order, 0), 9999), c.content_id
|
||||
");
|
||||
$stmt->execute(array_merge([$memberId, $sysCompCode], $onboardingGroups));
|
||||
$rows = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// 2. category_group -> 퍼즐 피스/영역 매핑
|
||||
// area: family(소개), hanmac(DX), value(가치), company(회사생활)
|
||||
$categoryMap = [
|
||||
'CA200O01' => ['pieceId' => 1, 'group' => 3, 'area' => 'value'],
|
||||
'CA200O02' => ['pieceId' => 2, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O03' => ['pieceId' => 3, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O04' => ['pieceId' => 4, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O05' => ['pieceId' => 5, 'group' => 2, 'area' => 'hanmac'],
|
||||
'CA200O06' => ['pieceId' => 6, 'group' => 3, 'area' => 'value'],
|
||||
'CA200O07' => ['pieceId' => 7, 'group' => 4, 'area' => 'company'],
|
||||
'CA200O08' => ['pieceId' => 8, 'group' => 4, 'area' => 'company'],
|
||||
'CA200O09' => ['pieceId' => 9, 'group' => 1, 'area' => 'family'],
|
||||
'CA200O10' => ['pieceId' => 10, 'group' => 1, 'area' => 'family'],
|
||||
];
|
||||
|
||||
// ✅ 온보딩 챕터 제목을 edu_codes 테이블에서 동적으로 로드
|
||||
// 관리자가 edu_codes에서 code_name을 수정하면 자동으로 퍼즐 제목이 변경됨
|
||||
$chapterNameMap = [];
|
||||
try {
|
||||
$stmtCodes = $pdo->prepare("
|
||||
SELECT base_code, code_name
|
||||
FROM edu_codes
|
||||
WHERE group_code = 'CA200'
|
||||
AND base_code IN ('CA200O01','CA200O02','CA200O03','CA200O04','CA200O05','CA200O06','CA200O07','CA200O08','CA200O09','CA200O10')
|
||||
ORDER BY base_code
|
||||
");
|
||||
$stmtCodes->execute();
|
||||
$codeRows = $stmtCodes->fetchAll();
|
||||
|
||||
foreach ($codeRows as $codeRow) {
|
||||
$baseCode = (string)($codeRow['base_code'] ?? '');
|
||||
$codeName = trim((string)($codeRow['code_name'] ?? ''));
|
||||
if ($baseCode && $codeName) {
|
||||
$chapterNameMap[$baseCode] = $codeName;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// 학습 부분은 진행되어야 하므로 조용히 실패 처리
|
||||
error_log('[onboarding.php] edu_codes 조회 실패: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 3. 같은 category_group은 같은 모달(챕터)로 묶고 lessons에 1/2/3차시 누적
|
||||
$chapterMap = [];
|
||||
foreach ($rows as $idx => $row) {
|
||||
$categoryGroup = $row['category_group'] ?? '';
|
||||
$mapped = $categoryMap[$categoryGroup] ?? null;
|
||||
|
||||
// description 컬럼명 편차 대응
|
||||
$description = trim((string)($row['description'] ?? $row['description1'] ?? $row['description_1'] ?? $row['content_desc'] ?? $row['content_desc1'] ?? $row['content_description'] ?? $row['content_description1'] ?? ''));
|
||||
$description2 = trim((string)($row['description2'] ?? $row['description_2'] ?? $row['content_desc2'] ?? $row['content_description2'] ?? ''));
|
||||
|
||||
// 미정의 category_group은 뒤 순번으로 안전 처리
|
||||
$pid = $mapped['pieceId'] ?? ($idx + 1);
|
||||
$group = $mapped['group'] ?? 1;
|
||||
$area = $mapped['area'] ?? 'family';
|
||||
|
||||
if (!isset($chapterMap[$categoryGroup])) {
|
||||
$chapterMap[$categoryGroup] = [
|
||||
'id' => 0, // 아래에서 순차 부여
|
||||
// ✅ 챕터 제목은 고정된 chapterNameMap에서 가져옴 (콘텐츠 title이 아님)
|
||||
'name' => $chapterNameMap[$categoryGroup] ?? str_replace('[온보딩] ', '', (string)$row['title']),
|
||||
'pieceId' => $pid,
|
||||
'type' => 'youtube',
|
||||
'group' => $group,
|
||||
'category_group' => $categoryGroup,
|
||||
'area' => $area,
|
||||
'lessons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$lessonNo = (int)($row['sort_order'] ?? 0);
|
||||
if ($lessonNo <= 0) {
|
||||
$lessonNo = count($chapterMap[$categoryGroup]['lessons']) + 1;
|
||||
}
|
||||
|
||||
// learning_status: none(미진행), in_progress(학습중), completed(학습완료)
|
||||
$learningStatus = $row['learning_status'] ?? 'none';
|
||||
|
||||
$chapterMap[$categoryGroup]['lessons'][] = [
|
||||
'content_id' => $row['content_id'],
|
||||
// 북마크/댓글은 edu_contents.content_id와 동일한 문자열 키를 사용한다.
|
||||
'bookmark_content_id' => $row['content_id'],
|
||||
'comment_content_id' => $row['content_id'],
|
||||
'title' => $row['title'],
|
||||
'label' => str_replace('[온보딩] ', '', $row['title']),
|
||||
'description' => $description,
|
||||
'description2' => $description2,
|
||||
'url' => $row['content_url'],
|
||||
'completed' => ($learningStatus === 'completed'),
|
||||
'learning_status' => $learningStatus,
|
||||
'watch_tm' => (int)$row['watch_tm'],
|
||||
'content_tm' => (int)$row['content_tm'],
|
||||
];
|
||||
}
|
||||
|
||||
// 4. 북마크 상태 로드 (is_active: 1=표시, 0=미표시)
|
||||
$bookmarkIds = [];
|
||||
foreach ($chapterMap as $chapterItem) {
|
||||
foreach (($chapterItem['lessons'] ?? []) as $lessonItem) {
|
||||
$bookmarkId = trim((string)($lessonItem['bookmark_content_id'] ?? ''));
|
||||
if ($bookmarkId !== '') {
|
||||
$bookmarkIds[] = $bookmarkId;
|
||||
}
|
||||
}
|
||||
}
|
||||
$bookmarkIds = array_values(array_unique($bookmarkIds));
|
||||
|
||||
$wishlistMap = [];
|
||||
if (!empty($bookmarkIds)) {
|
||||
$wishlistPlaceholders = implode(',', array_fill(0, count($bookmarkIds), '?'));
|
||||
$stmtWishlist = $pdo->prepare(
|
||||
"SELECT content_id, is_active
|
||||
FROM edu_content_wishlist
|
||||
WHERE member_id = ?
|
||||
AND sys_comp_code = ?
|
||||
AND content_id IN ($wishlistPlaceholders)"
|
||||
);
|
||||
$stmtWishlist->execute(array_merge([$memberId, $sysCompCode], $bookmarkIds));
|
||||
$wishlistRows = $stmtWishlist->fetchAll();
|
||||
|
||||
foreach ($wishlistRows as $wishlistRow) {
|
||||
$wishlistMap[(string)$wishlistRow['content_id']] = ((string)($wishlistRow['is_active'] ?? '0') === '1');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($chapterMap as &$chapterItem) {
|
||||
foreach ($chapterItem['lessons'] as &$lessonItem) {
|
||||
$bookmarkId = trim((string)($lessonItem['bookmark_content_id'] ?? ''));
|
||||
$lessonItem['is_bookmarked'] = ($bookmarkId !== '' && !empty($wishlistMap[$bookmarkId]));
|
||||
}
|
||||
unset($lessonItem);
|
||||
}
|
||||
unset($chapterItem);
|
||||
|
||||
// category_group 정의 순서대로 챕터 정렬 + id 재부여
|
||||
$puzzleData = [];
|
||||
foreach ($onboardingGroups as $code) {
|
||||
if (!isset($chapterMap[$code])) {
|
||||
continue;
|
||||
}
|
||||
$chapterMap[$code]['id'] = count($puzzleData) + 1;
|
||||
$puzzleData[] = $chapterMap[$code];
|
||||
}
|
||||
$puzzleJson = json_encode($puzzleData, JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
|
||||
<script>
|
||||
// 1. CONFIG 설정
|
||||
window.puzzleConfig = {
|
||||
COMPLETION_MODE: "COMPLETED",
|
||||
CURRENT_MEMBER_ID: <?= json_encode($memberId, JSON_UNESCAPED_UNICODE) ?>,
|
||||
};
|
||||
|
||||
// 2. 챕터 기반 데이터 설정 (DB에서 동적 로드)
|
||||
window.puzzleChapterData = <?= $puzzleJson ?>;
|
||||
</script>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/js/puzzle-onboarding.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user