860 lines
37 KiB
PHP
860 lines
37 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../bbs/auth.php';
|
|
edu_require_login();
|
|
|
|
$mode = isset($_GET['mode']) ? trim((string) $_GET['mode']) : 'initial';
|
|
$completedGoalId = isset($_GET['completed_goal']) ? (int) $_GET['completed_goal'] : 1;
|
|
$debugRecsRaw = isset($_GET['debug_recs']) ? strtolower(trim((string)$_GET['debug_recs'])) : '';
|
|
$debugRecs = in_array($debugRecsRaw, ['1', 'true', 'yes', 'on', 'y', 'debug'], true)
|
|
|| (isset($_SERVER['QUERY_STRING']) && strpos((string)$_SERVER['QUERY_STRING'], 'debug_recs') !== false);
|
|
$debugRecsPayload = [];
|
|
if ($debugRecs && !headers_sent()) {
|
|
header('X-Debug-Recs: on');
|
|
}
|
|
// ========== DB 연동 초기화 ==========
|
|
|
|
require_once __DIR__ . '/../bbs/db_conn.php';
|
|
|
|
// 세션 정보로부터 사용자 ID 및 정보 추출
|
|
$memberId = $_SESSION['member_id'] ?? null;
|
|
$userName = $_SESSION['member_name'] ?? $_SESSION['user_name'] ?? '사용자';
|
|
$userPosition = $_SESSION['member_rank'] ?? $_SESSION['user_position'] ?? '';
|
|
$userCompany = $_SESSION['sys_comp_code'] ?? '';
|
|
|
|
// ========== 년도/분기/남은일수 계산 ==========
|
|
$today = new DateTime('now', new DateTimeZone('Asia/Seoul'));
|
|
$currentYear = (int) $today->format('Y');
|
|
$currentMonth = (int) $today->format('m');
|
|
$currentQuarterNum = (int) ceil($currentMonth / 3);
|
|
$currentQuarterCode = sprintf('CA200Q%02d', $currentQuarterNum);
|
|
$modalQuarterLabel = substr((string)$currentYear, 2) . '년 ' . $currentQuarterNum . '분기)';
|
|
|
|
// DB에서 추가 정보 보충 (member_id + sys_comp_code 복합키 기준)
|
|
if (isset($memberId) && $memberId !== null && $memberId !== '' && $userCompany !== '' && ($userPosition === '' || $userName === '' || $userName === '사용자')) {
|
|
try {
|
|
$stmt = db_conn()->prepare(
|
|
'SELECT name, rank_name, sys_comp_code FROM edu_users WHERE member_id = ? AND sys_comp_code = ? LIMIT 1'
|
|
);
|
|
$stmt->execute([(string)$memberId, (string)$userCompany]);
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
if ($row) {
|
|
if ($userName === '' || $userName === '사용자') {
|
|
$userName = $row['name'] ?? '사용자';
|
|
}
|
|
if ($userPosition === '') {
|
|
$userPosition = $row['rank_name'] ?? '';
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] DB fetch error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// ========== 목표 설정 여부 확인 → 이미 설정한 사용자는 myclass_list.php로 바로 이동 ==========
|
|
// mode=initial(기본 진입)일 때만 체크. completed/additional 모드는 목표 완료 플로우이므로 통과.
|
|
if ($mode === 'initial' && isset($memberId) && !$debugRecs) {
|
|
try {
|
|
$sysCompCodeCheck = (string)($userCompany ?? '');
|
|
$hasGoal = false;
|
|
|
|
if ($memberId !== null && $memberId !== '' && $sysCompCodeCheck !== '') {
|
|
$stmtGoalCheck = db_conn()->prepare(
|
|
"SELECT COUNT(*) FROM edu_user_learning_goals
|
|
WHERE member_id = ?
|
|
AND sys_comp_code = ?
|
|
AND quarter = ?
|
|
AND is_active = '1'
|
|
LIMIT 1"
|
|
);
|
|
$stmtGoalCheck->execute([(string)$memberId, $sysCompCodeCheck, $currentQuarterCode]);
|
|
$hasGoal = (int)$stmtGoalCheck->fetchColumn() > 0;
|
|
}
|
|
|
|
if ($hasGoal) {
|
|
header('Location: /skin/myclass_list.php');
|
|
exit;
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] goal check error: ' . $e->getMessage());
|
|
// 오류 시 그냥 myclass.php 렌더링 계속
|
|
}
|
|
}
|
|
|
|
// 분기 마지막 날짜
|
|
$quarterEndDates = [
|
|
1 => '03-31', 2 => '06-30', 3 => '09-30', 4 => '12-31'
|
|
];
|
|
$todayDateOnly = (clone $today)->setTime(0, 0, 0);
|
|
$targetDateStr = $currentYear . '-' . $quarterEndDates[$currentQuarterNum];
|
|
$targetDate = new DateTime($targetDateStr, new DateTimeZone('Asia/Seoul'));
|
|
$targetDate->setTime(0, 0, 0);
|
|
$daysInterval = $todayDateOnly->diff($targetDate);
|
|
$daysRemaining = max(0, (int) $daysInterval->format('%a'));
|
|
$daysDisplay = 'D-' . $daysRemaining;
|
|
|
|
// 분기 마지막 날짜 (한글)
|
|
$endDateParts = explode('-', $quarterEndDates[$currentQuarterNum]);
|
|
$endMonth = (int) $endDateParts[0];
|
|
$endDay = (int) $endDateParts[1];
|
|
$targetDateKr = $endMonth . '월 ' . $endDay . '일';
|
|
|
|
$completedGoalId = max(1, min(6, $completedGoalId));
|
|
|
|
$defaultGoals = [
|
|
['id' => 1, 'title' => '자기이해와 강점 찾기', 'desc' => '나의 성향, 가치관, 강점 등을 탐색하고 이해하며 자기인식을 높이는 과정', 'icon' => '01', 'gif' => 'ico_study_01.png', 'json' => 'ico_study_01.json'],
|
|
['id' => 2, 'title' => '효과적인\n의사소통 배우기', 'desc' => '상황에 맞는 말하기와 경청을 통해 타인과 원활하게 소통하는 방법을 익히는 과정', 'icon' => '02', 'gif' => 'ico_study_02.png', 'json' => 'ico_study_02.json'],
|
|
['id' => 3, 'title' => '감정 조절과 자기관리', 'desc' => '다양한 감정을 인식하고 조절하여 안정적인 삶을 유지하는 자기관리 훈련 과정', 'icon' => '03', 'gif' => 'ico_study_03.png', 'json' => 'ico_study_03.json'],
|
|
['id' => 4, 'title' => '비판적 사고와 문제 해결', 'desc' => '다양한 관점에서 생각하고 문제 상황을 논리적으로 해결하는 능력을 기르는 과정', 'icon' => '04', 'gif' => 'ico_study_04.png', 'json' => 'ico_study_04.json'],
|
|
['id' => 5, 'title' => '협업과 팀워크 기르기', 'desc' => '타인과 협력하며 공동의 목표를 위해 함께 노력하는 태도와 기술을 기르는 과정', 'icon' => '05', 'gif' => 'ico_study_05.png', 'json' => 'ico_study_05.json'],
|
|
['id' => 6, 'title' => '커리어 탐색과 역량 개발', 'desc' => '직무와 산업의 흐름을 이해하고, 자신의 커리어 방향성과 필요한 역량을 점검·계획하는 과정', 'icon' => '06', 'gif' => 'ico_study_06.png', 'json' => 'ico_study_06.json'],
|
|
];
|
|
|
|
$defaultModalBooks = [
|
|
['id' => 1, 'youtube' => 'KE_MeQZgnPM', 'title' => '조직도가 리셋된다!', 'sub' => '위계 중심 조직은\n더 이상 통하지 않습니다.', 'main' => '더 유연하게\n일할 방법', 'tag' => 'IT 테크', 'img' => 'img_book_01'],
|
|
['id' => 2, 'youtube' => 'a2l1uZfsRi0', 'title' => '혈당 스파이크', 'sub' => '식후 졸림은 의지 문제가\n아니라 혈당의 문제!', 'main' => '혈당을 안정시켜줄\n실생활 관리법', 'tag' => '웰니스', 'img' => 'img_book_02'],
|
|
['id' => 3, 'youtube' => 'IeF8r0ycgVg', 'title' => '제대로 쉬는 방법', 'sub' => '아무리 자도\n피곤한가요?', 'main' => '진짜 회복되는 쉼이\n무엇인지 알게됩니다.', 'tag' => '마인드셋', 'img' => 'img_book_03'],
|
|
['id' => 4, 'youtube' => 'KMZXMI0QPoA', 'title' => "'AI 도파민'의 바다에 빠진 이유", 'sub' => 'AI는 선택이 아닌\n생존의 도구', 'main' => 'AI는 도구가 아닌\n업무 파트너', 'tag' => 'IT 테크', 'img' => 'img_book_04'],
|
|
['id' => 5, 'youtube' => 'CRKwszz6l2M', 'title' => '살찌고 망가진몸 되살리는 방법', 'sub' => '몸을 망쳤다면?\n다이어트 아닌 리셋이 답!', 'main' => '건강한 몸 되찾기', 'tag' => '웰니스', 'img' => 'img_book_05'],
|
|
['id' => 6, 'youtube' => 'Gf5WoZ3BmgI', 'title' => '자기 관점의 힘', 'sub' => '당신의 삶을 바꾸는\n가장 강력한 무기?', 'main' => '자신만의 관점으로\n경쟁력을 키우는 방법', 'tag' => '리더십', 'img' => 'img_book_06'],
|
|
];
|
|
|
|
$goals = $defaultGoals;
|
|
$goalCodeById = [];
|
|
|
|
$defaultRecommendedItems = [
|
|
['title' => '업무에 익숙해졌지만 성장 정체를 느끼는 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"],
|
|
['title' => '문제 해결을 요구받기 시작한 주니어, 중급 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"],
|
|
['title' => '팀 또는 프로젝트 단위로 사고의 확장이 필요한 구성원', 'description' => "창의적 사고를 '아이디어가 아니라 업무를 바라보는 방식으로 재정렬 할 수 있어요!"],
|
|
];
|
|
|
|
$goalRecommendations = [];
|
|
for ($i = 1; $i <= 6; $i++) {
|
|
$goalRecommendations[$i] = $defaultRecommendedItems;
|
|
}
|
|
|
|
$goalBooksById = [];
|
|
for ($i = 1; $i <= 6; $i++) {
|
|
$goalBooksById[$i] = [];
|
|
}
|
|
|
|
function myclass_extract_video_id($value) {
|
|
$value = trim((string)$value);
|
|
if ($value === '') {
|
|
return '';
|
|
}
|
|
|
|
if (preg_match('~(?:v=|\.be/)([A-Za-z0-9_-]{11})~', $value, $m)) {
|
|
return $m[1];
|
|
}
|
|
|
|
if (preg_match('/^[A-Za-z0-9_-]{11}$/', $value)) {
|
|
return $value;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// 목표 카드 6개: edu_learning_goals.title / edu_learning_goals.remarks 연동
|
|
try {
|
|
$stmtGoals = db_conn()->prepare(
|
|
"SELECT goal_code,
|
|
title AS goal_title,
|
|
remarks AS goal_remarks
|
|
FROM edu_learning_goals
|
|
WHERE is_active = '1'
|
|
AND base_year = ?
|
|
AND quarter = ?
|
|
ORDER BY goal_code ASC"
|
|
);
|
|
$stmtGoals->execute([(string)$currentYear, $currentQuarterCode]);
|
|
$goalRows = $stmtGoals->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 현재 연도/분기 데이터가 부족하면 동일 분기의 최신 연도로 fallback
|
|
if (count($goalRows) < 6) {
|
|
$stmtGoals = db_conn()->query(
|
|
"SELECT goal_code,
|
|
title AS goal_title,
|
|
remarks AS goal_remarks
|
|
FROM edu_learning_goals
|
|
WHERE is_active = '1'
|
|
AND quarter = " . db_conn()->quote($currentQuarterCode) . "
|
|
ORDER BY base_year DESC, goal_code ASC
|
|
LIMIT 6"
|
|
);
|
|
$goalRows = $stmtGoals->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
$goalSlot = 1;
|
|
foreach ($goalRows as $goalRow) {
|
|
if ($goalSlot > 6) {
|
|
break;
|
|
}
|
|
|
|
$goalCode = (string)($goalRow['goal_code'] ?? '');
|
|
$title = trim((string)($goalRow['goal_title'] ?? ''));
|
|
$desc = trim((string)($goalRow['goal_remarks'] ?? ''));
|
|
|
|
if ($goalCode === '') {
|
|
continue;
|
|
}
|
|
|
|
$goalId = $goalSlot;
|
|
|
|
$goalCodeById[$goalId] = $goalCode;
|
|
|
|
if ($title !== '') {
|
|
$goals[$goalId - 1]['title'] = $title;
|
|
}
|
|
if ($desc !== '') {
|
|
$goals[$goalId - 1]['desc'] = $desc;
|
|
}
|
|
|
|
$goalSlot++;
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] edu_learning_goals fetch error: ' . $e->getMessage());
|
|
}
|
|
|
|
// 목표별 추천 3개: goal_code + seq 기준으로 연동 (rec-text는 title2 우선 사용)
|
|
try {
|
|
$goalCodes = array_values(array_unique(array_filter($goalCodeById)));
|
|
if (count($goalCodes) > 0) {
|
|
$normalizeGoalCode = static function ($value) {
|
|
return strtoupper(trim((string)$value));
|
|
};
|
|
|
|
$goalIdByCode = [];
|
|
foreach ($goalCodeById as $goalId => $goalCodeRaw) {
|
|
$normalizedCode = $normalizeGoalCode($goalCodeRaw);
|
|
if ($normalizedCode === '') {
|
|
continue;
|
|
}
|
|
$goalIdByCode[$normalizedCode] = (int)$goalId;
|
|
}
|
|
|
|
$normalizedGoalCodes = [];
|
|
foreach ($goalCodes as $goalCodeRaw) {
|
|
$normalizedCode = $normalizeGoalCode($goalCodeRaw);
|
|
if ($normalizedCode !== '') {
|
|
$normalizedGoalCodes[] = $normalizedCode;
|
|
}
|
|
}
|
|
$normalizedGoalCodes = array_values(array_unique($normalizedGoalCodes));
|
|
if (count($normalizedGoalCodes) === 0) {
|
|
throw new RuntimeException('goal codes are empty after normalization');
|
|
}
|
|
|
|
$goalSuffixes = [];
|
|
foreach (array_keys($goalIdByCode) as $normalizedCode) {
|
|
if (preg_match('/-(\d{3})$/', $normalizedCode, $m)) {
|
|
$goalSuffixes[] = '-' . $m[1];
|
|
}
|
|
}
|
|
$goalSuffixes = array_values(array_unique($goalSuffixes));
|
|
|
|
$placeholders = implode(',', array_fill(0, count($normalizedGoalCodes), '?'));
|
|
$suffixPlaceholders = count($goalSuffixes) > 0
|
|
? implode(',', array_fill(0, count($goalSuffixes), '?'))
|
|
: '';
|
|
|
|
$pdo = db_conn();
|
|
|
|
$hasColumn = static function (PDO $conn, $tableName, $columnName) {
|
|
$stmt = $conn->prepare(
|
|
"SELECT COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = ?
|
|
AND column_name = ?"
|
|
);
|
|
$stmt->execute([(string)$tableName, (string)$columnName]);
|
|
return ((int)$stmt->fetchColumn()) > 0;
|
|
};
|
|
|
|
$hasTable = static function (PDO $conn, $tableName) {
|
|
$stmt = $conn->prepare(
|
|
"SELECT COUNT(*)
|
|
FROM information_schema.tables
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = ?"
|
|
);
|
|
$stmt->execute([(string)$tableName]);
|
|
return ((int)$stmt->fetchColumn()) > 0;
|
|
};
|
|
|
|
// 운영 환경별 오탈자 대응: edu_recommended_goals / edu_recommended_gaols
|
|
// 둘 다 존재할 수 있으므로, 실제 현재 목표코드와 매칭되는 row 수가 많은 테이블을 선택
|
|
$availableRecTables = [];
|
|
foreach (['edu_recommended_goals', 'edu_recommended_gaols'] as $candTable) {
|
|
if ($hasTable($pdo, $candTable)) {
|
|
$availableRecTables[] = $candTable;
|
|
}
|
|
}
|
|
if (count($availableRecTables) === 0) {
|
|
throw new RuntimeException('recommended goals table not found');
|
|
}
|
|
|
|
$tableScores = [];
|
|
$recTable = $availableRecTables[0];
|
|
$bestScore = -1;
|
|
|
|
foreach ($availableRecTables as $candTable) {
|
|
$candHasQuarter = $hasColumn($pdo, $candTable, 'quarter');
|
|
$candHasBaseYear = $hasColumn($pdo, $candTable, 'base_year');
|
|
$candHasIsActive = $hasColumn($pdo, $candTable, 'is_active');
|
|
|
|
$candCodeCondition = "UPPER(TRIM(goal_code)) IN ($placeholders)";
|
|
if ($suffixPlaceholders !== '') {
|
|
$candCodeCondition .= " OR RIGHT(UPPER(TRIM(goal_code)), 4) IN ($suffixPlaceholders)";
|
|
}
|
|
|
|
$candWhere = [];
|
|
if ($candHasIsActive) {
|
|
$candWhere[] = "is_active = '1'";
|
|
}
|
|
$candWhere[] = "({$candCodeCondition})";
|
|
if ($candHasQuarter) {
|
|
$candWhere[] = 'quarter = ?';
|
|
}
|
|
if ($candHasBaseYear) {
|
|
$candWhere[] = 'base_year = ?';
|
|
}
|
|
|
|
$candSql = "SELECT COUNT(*) FROM {$candTable} WHERE " . implode(' AND ', $candWhere);
|
|
$candParams = $normalizedGoalCodes;
|
|
if (count($goalSuffixes) > 0) {
|
|
$candParams = array_merge($candParams, $goalSuffixes);
|
|
}
|
|
if ($candHasQuarter) {
|
|
$candParams[] = $currentQuarterCode;
|
|
}
|
|
if ($candHasBaseYear) {
|
|
$candParams[] = (string)$currentYear;
|
|
}
|
|
|
|
$stmtScore = $pdo->prepare($candSql);
|
|
$stmtScore->execute($candParams);
|
|
$score = (int)$stmtScore->fetchColumn();
|
|
$tableScores[$candTable] = $score;
|
|
|
|
if ($score > $bestScore) {
|
|
$bestScore = $score;
|
|
$recTable = $candTable;
|
|
}
|
|
}
|
|
|
|
$hasTitle2Col = $hasColumn($pdo, $recTable, 'title2');
|
|
$hasQuarterCol = $hasColumn($pdo, $recTable, 'quarter');
|
|
$hasBaseYearCol = $hasColumn($pdo, $recTable, 'base_year');
|
|
$hasIsActiveCol = $hasColumn($pdo, $recTable, 'is_active');
|
|
$hasSeqCol = $hasColumn($pdo, $recTable, 'seq');
|
|
$hasSortOrderCol = $hasColumn($pdo, $recTable, 'sort_order');
|
|
$hasDescCol = $hasColumn($pdo, $recTable, 'description');
|
|
|
|
$title2Expr = $hasTitle2Col ? 'rg.title2' : "''";
|
|
$recDescExpr = $hasDescCol ? 'rg.description' : "''";
|
|
$seqExpr = $hasSeqCol ? 'rg.seq' : ($hasSortOrderCol ? 'rg.sort_order' : '0');
|
|
$joinSeqCondition = '';
|
|
if ($hasSeqCol) {
|
|
$joinSeqCondition = ' AND c.sort_order = rg.seq';
|
|
} elseif ($hasSortOrderCol) {
|
|
$joinSeqCondition = ' AND c.sort_order = rg.sort_order';
|
|
}
|
|
|
|
$codeCondition = "UPPER(TRIM(rg.goal_code)) IN ($placeholders)";
|
|
if ($suffixPlaceholders !== '') {
|
|
$codeCondition .= " OR RIGHT(UPPER(TRIM(rg.goal_code)), 4) IN ($suffixPlaceholders)";
|
|
}
|
|
|
|
$whereConditions = [];
|
|
if ($hasIsActiveCol) {
|
|
$whereConditions[] = "rg.is_active = '1'";
|
|
}
|
|
$whereConditions[] = "({$codeCondition})";
|
|
if ($hasQuarterCol) {
|
|
$whereConditions[] = 'rg.quarter = ?';
|
|
}
|
|
if ($hasBaseYearCol) {
|
|
$whereConditions[] = 'rg.base_year = ?';
|
|
}
|
|
|
|
$whereSql = implode("\n AND ", $whereConditions);
|
|
|
|
$stmtRec = $pdo->prepare(
|
|
"SELECT rg.goal_code,
|
|
{$seqExpr} AS seq,
|
|
rg.title AS rec_title,
|
|
{$recDescExpr} AS rec_description,
|
|
{$title2Expr} AS rec_title2,
|
|
c.title AS content_title,
|
|
c.description AS content_description
|
|
FROM {$recTable} rg
|
|
LEFT JOIN edu_contents c
|
|
ON c.goal_code = rg.goal_code
|
|
{$joinSeqCondition}
|
|
AND c.is_active = '1'
|
|
WHERE {$whereSql}
|
|
ORDER BY rg.goal_code ASC, seq ASC"
|
|
);
|
|
|
|
$stmtRecParams = $normalizedGoalCodes;
|
|
if (count($goalSuffixes) > 0) {
|
|
$stmtRecParams = array_merge($stmtRecParams, $goalSuffixes);
|
|
}
|
|
if ($hasQuarterCol) {
|
|
$stmtRecParams[] = $currentQuarterCode;
|
|
}
|
|
if ($hasBaseYearCol) {
|
|
$stmtRecParams[] = (string)$currentYear;
|
|
}
|
|
|
|
$stmtRec->execute($stmtRecParams);
|
|
$recRows = $stmtRec->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
if ($debugRecs) {
|
|
$debugRecsPayload['table'] = $recTable;
|
|
$debugRecsPayload['tableScores'] = $tableScores;
|
|
$debugRecsPayload['schema'] = [
|
|
'hasTitle2Col' => $hasTitle2Col,
|
|
'hasQuarterCol' => $hasQuarterCol,
|
|
'hasBaseYearCol' => $hasBaseYearCol,
|
|
'hasIsActiveCol' => $hasIsActiveCol,
|
|
'hasSeqCol' => $hasSeqCol,
|
|
'hasSortOrderCol' => $hasSortOrderCol,
|
|
'hasDescCol' => $hasDescCol,
|
|
];
|
|
$debugRecsPayload['normalizedGoalCodes'] = $normalizedGoalCodes;
|
|
$debugRecsPayload['stmtRecParams'] = $stmtRecParams;
|
|
$debugRecsPayload['recRows'] = $recRows;
|
|
error_log('[myclass.php][debug_recs] table=' . $recTable . ' normalizedGoalCodes=' . json_encode($normalizedGoalCodes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
error_log('[myclass.php][debug_recs] recRows=' . json_encode($recRows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
$fallbackSeqByGoalId = [];
|
|
|
|
foreach ($recRows as $recRow) {
|
|
$goalCode = $normalizeGoalCode($recRow['goal_code'] ?? '');
|
|
$seq = (int)($recRow['seq'] ?? 0);
|
|
$recTitle = trim((string)($recRow['rec_title'] ?? ''));
|
|
$recTitle2 = trim((string)($recRow['rec_title2'] ?? ''));
|
|
$recDesc = trim((string)($recRow['rec_description'] ?? ''));
|
|
$contentTitle = trim((string)($recRow['content_title'] ?? ''));
|
|
$goalId = 0;
|
|
|
|
if ($goalCode !== '' && isset($goalIdByCode[$goalCode])) {
|
|
$goalId = (int)$goalIdByCode[$goalCode];
|
|
} elseif (preg_match('/-(\d{3})$/', $goalCode, $m)) {
|
|
$suffixGoalId = (int)$m[1];
|
|
if ($suffixGoalId >= 1 && $suffixGoalId <= 6) {
|
|
$goalId = $suffixGoalId;
|
|
}
|
|
}
|
|
|
|
if ($goalId < 1 || $goalId > 6) {
|
|
continue;
|
|
}
|
|
|
|
if ($seq < 1 || $seq > 3) {
|
|
$fallbackSeqByGoalId[$goalId] = (int)($fallbackSeqByGoalId[$goalId] ?? 0) + 1;
|
|
$seq = $fallbackSeqByGoalId[$goalId];
|
|
if ($seq < 1 || $seq > 3) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ($recTitle === '') {
|
|
$recTitle = $contentTitle;
|
|
}
|
|
if ($recTitle2 !== '') {
|
|
$recDesc = $recTitle2;
|
|
}
|
|
|
|
if ($recTitle !== '') {
|
|
$goalRecommendations[$goalId][$seq - 1]['title'] = $recTitle;
|
|
}
|
|
if ($recDesc !== '') {
|
|
$goalRecommendations[$goalId][$seq - 1]['description'] = $recDesc;
|
|
}
|
|
|
|
if ($debugRecs) {
|
|
error_log('[myclass.php][debug_recs] mapped goalCode=' . $goalCode . ' seq=' . $seq . ' title=' . $recTitle . ' title2=' . $recTitle2 . ' desc=' . $recDesc);
|
|
}
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
if ($debugRecs) {
|
|
$debugRecsPayload['recsError'] = $e->getMessage();
|
|
}
|
|
error_log('[myclass.php] edu_recommended_goals fetch error: ' . $e->getMessage());
|
|
}
|
|
|
|
// 목표별 영상 리스트: edu_contents.title/description1/description2/content_url/thumbnail 연동
|
|
try {
|
|
$goalCodes = array_values(array_unique(array_filter($goalCodeById)));
|
|
if (count($goalCodes) > 0) {
|
|
$goalIdByCode = array_flip($goalCodeById);
|
|
$placeholders = implode(',', array_fill(0, count($goalCodes), '?'));
|
|
|
|
$stmtContents = db_conn()->prepare(
|
|
"SELECT content_id,
|
|
goal_code,
|
|
sort_order,
|
|
title,
|
|
description,
|
|
description1,
|
|
description2,
|
|
content_url,
|
|
thumbnail_url
|
|
FROM edu_contents
|
|
WHERE is_active = '1'
|
|
AND category_code = 'CA10001'
|
|
AND category_group = ?
|
|
AND goal_code IN ($placeholders)
|
|
ORDER BY goal_code ASC, sort_order ASC, content_id ASC"
|
|
);
|
|
$stmtContents->execute(array_merge([$currentQuarterCode], $goalCodes));
|
|
$contentRows = $stmtContents->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($contentRows as $contentRow) {
|
|
$goalCode = (string)($contentRow['goal_code'] ?? '');
|
|
if (!isset($goalIdByCode[$goalCode])) {
|
|
continue;
|
|
}
|
|
|
|
$goalId = (int)$goalIdByCode[$goalCode];
|
|
if (!isset($goalBooksById[$goalId])) {
|
|
$goalBooksById[$goalId] = [];
|
|
}
|
|
|
|
$contentUrl = trim((string)($contentRow['content_url'] ?? ''));
|
|
$thumbnailUrl = trim((string)($contentRow['thumbnail_url'] ?? ''));
|
|
$sortOrder = (int)($contentRow['sort_order'] ?? 0);
|
|
if ($sortOrder < 1 || $sortOrder > 6) {
|
|
continue;
|
|
}
|
|
|
|
$templateBook = $defaultModalBooks[$sortOrder - 1] ?? null;
|
|
if ($templateBook === null) {
|
|
continue;
|
|
}
|
|
|
|
if (count($goalBooksById[$goalId]) === 0) {
|
|
$goalBooksById[$goalId] = $defaultModalBooks;
|
|
}
|
|
|
|
$description1 = trim((string)($contentRow['description1'] ?? ''));
|
|
$description2 = trim((string)($contentRow['description2'] ?? ''));
|
|
$description = trim((string)($contentRow['description'] ?? ''));
|
|
if ($description1 === '') {
|
|
$description1 = $description;
|
|
}
|
|
|
|
$goalBooksById[$goalId][$sortOrder - 1] = [
|
|
'id' => (int)($templateBook['id'] ?? $sortOrder),
|
|
'order' => $sortOrder,
|
|
'youtube' => ($tmpVideoId = myclass_extract_video_id($contentUrl)) !== '' ? $tmpVideoId : (string)($templateBook['youtube'] ?? ''),
|
|
'content_url' => $contentUrl,
|
|
'title' => trim((string)($contentRow['title'] ?? (string)($templateBook['title'] ?? ''))),
|
|
'sub' => $description1 !== '' ? $description1 : (string)($templateBook['sub'] ?? ''),
|
|
'main' => $description2 !== '' ? $description2 : (string)($templateBook['main'] ?? ''),
|
|
'tag' => (string)($templateBook['tag'] ?? ''),
|
|
'img' => (string)($templateBook['img'] ?? 'img_book_01'),
|
|
'thumbnail' => $thumbnailUrl,
|
|
];
|
|
}
|
|
|
|
foreach ($goalBooksById as $goalId => $goalBooks) {
|
|
if (count($goalBooks) > 0) {
|
|
ksort($goalBooks);
|
|
$goalBooksById[$goalId] = array_values($goalBooks);
|
|
} else {
|
|
$goalBooksById[$goalId] = $defaultModalBooks;
|
|
}
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] edu_contents fetch error: ' . $e->getMessage());
|
|
}
|
|
|
|
foreach ($goalBooksById as $goalId => $goalBooks) {
|
|
if (count($goalBooks) === 0) {
|
|
$goalBooksById[$goalId] = $defaultModalBooks;
|
|
}
|
|
}
|
|
|
|
$modalBooks = $goalBooksById[1] ?? $defaultModalBooks;
|
|
$modalRecItems = $goalRecommendations[1] ?? $defaultRecommendedItems;
|
|
|
|
if ($debugRecs) {
|
|
$debugRecsPayload['goalCodeById'] = $goalCodeById;
|
|
$debugRecsPayload['goalRecommendations1'] = $goalRecommendations[1] ?? [];
|
|
$debugRecsPayload['modalRecItems'] = $modalRecItems;
|
|
error_log('[myclass.php][debug_recs] goalCodeById=' . json_encode($goalCodeById, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
error_log('[myclass.php][debug_recs] goalRecommendations[1]=' . json_encode($goalRecommendations[1] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
error_log('[myclass.php][debug_recs] modalRecItems=' . json_encode($modalRecItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
// 실제 완료된 목표 목록 조회 (DB 기준)
|
|
$completedGoalIds = [];
|
|
if (isset($memberId) && $userCompany !== '') {
|
|
try {
|
|
$stmtCompleted = db_conn()->prepare(
|
|
"SELECT g.goal_code, g.title, g.remarks, u.completed_date, u.goal_code AS user_goal_code
|
|
FROM edu_user_learning_goals u
|
|
JOIN edu_learning_goals g ON u.goal_code = g.goal_code AND g.is_active = '1'
|
|
WHERE u.member_id = ?
|
|
AND u.sys_comp_code = ?
|
|
AND u.is_active = '1'
|
|
AND g.quarter = ?
|
|
AND u.completed_date IS NOT NULL"
|
|
);
|
|
$stmtCompleted->execute([(string)$memberId, (string)$userCompany, $currentQuarterCode]);
|
|
$rowsCompleted = $stmtCompleted->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($rowsCompleted as $row) {
|
|
// goalCodeById: [1=>goal_code1, 2=>goal_code2, ...]
|
|
$goalCode = (string)($row['user_goal_code'] ?? $row['goal_code'] ?? '');
|
|
$goalId = array_search($goalCode, $goalCodeById, true);
|
|
if ($goalId !== false) {
|
|
$completedGoalIds[] = (int)$goalId;
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] fetch completed goals error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// completed/additional 화면은 DB completed_date(트리거 결과)가 확인될 때만 허용
|
|
$isTriggeredCompletion = in_array($completedGoalId, $completedGoalIds, true);
|
|
if (in_array($mode, ['completed', 'additional'], true) && !$isTriggeredCompletion) {
|
|
$mode = 'initial';
|
|
}
|
|
|
|
$completedGoal = $goals[$completedGoalId - 1];
|
|
$isCompletionFlow = in_array($mode, ['completed', 'additional'], true);
|
|
?>
|
|
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<?php include(__DIR__ . '/_include/_head.php'); ?>
|
|
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
|
|
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="wrap myclass">
|
|
<?php include(__DIR__ . '/_include/_header.php'); ?>
|
|
|
|
<div class="container">
|
|
<div class="myclass-wrap">
|
|
<div class="myclass-top-bar">
|
|
<div class="top-bar-left">
|
|
<!-- <div class="user-rank-area">
|
|
<div class="user-rank-area-inner">
|
|
<div class="user-profile">
|
|
<img src="/img/insight/profile.png" alt="프로필 이미지" />
|
|
</div>
|
|
<span class="rank-text">홍길동님 현재 <em>128</em>등 <span class="rank-change">(지난 주 대비 4↑)</span></span>
|
|
<span class="rank-text">홍길동님 현재 <em>1250</em>등</span>
|
|
</div>
|
|
<div class="rank-toggle-wrap">
|
|
<button class="btn-rank-toggle" type="button" aria-label="순위 상세 보기" aria-expanded="false" aria-controls="rankListPopup">
|
|
<span class="ico-trophy" aria-hidden="true">
|
|
<img class="lottie-fallback" src="/img/myclass/ico_trophy.gif" alt="" aria-hidden="true" />
|
|
<span class="lottie-icon lottie-trophy" data-lottie-url="/img/myclass/ico_trophy.json"></span>
|
|
</span>
|
|
<span class="ico-chevron-down" aria-hidden="true"></span>
|
|
</button>
|
|
<div class="rank-list-popup" id="rankListPopup" role="region" aria-label="순위 목록" hidden>
|
|
<ul class="rank-list">
|
|
<li class="rank-item rank-1"><span class="rank-medal rank-gold" aria-hidden="true"><img src="/img/myclass/ico_medal_gold.png" alt="금메달 아이콘" /></span><span class="rank-name">박길동 <small>(바론컨설턴트)</small></span></li>
|
|
<li class="rank-item rank-2"><span class="rank-medal rank-silver" aria-hidden="true"><img src="/img/myclass/ico_medal_silver.png" alt="은메달 아이콘" /></span><span class="rank-name">홍길동 <small>(한맥기술)</small></span></li>
|
|
<li class="rank-item rank-3"><span class="rank-medal rank-bronze" aria-hidden="true"><img src="/img/myclass/ico_medal_bronze.png" alt="동메달 아이콘" /></span><span class="rank-name">김철수 <small>(삼안)</small></span></li>
|
|
<li class="rank-item rank-4"><span class="rank-num">4.</span><span class="rank-name">박철수 <small>(바른컨설턴트)</small></span></li>
|
|
<li class="rank-item rank-5"><span class="rank-num">5.</span><span class="rank-name">김영희 <small>(한맥기술)</small></span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div> -->
|
|
</div>
|
|
|
|
|
|
<div class="page-intro" id="pageIntro">
|
|
<?php
|
|
// DB에서 현재 유저의 활성 목표 title 가져오기 (최신 분기 기준)
|
|
$activeGoalTitle = null;
|
|
if (isset($memberId) && $userCompany !== '') {
|
|
try {
|
|
$stmtActiveGoal = db_conn()->prepare(
|
|
"SELECT g.title
|
|
FROM edu_user_learning_goals u
|
|
JOIN edu_learning_goals g ON u.goal_code = g.goal_code AND g.is_active = '1'
|
|
WHERE u.member_id = ?
|
|
AND u.sys_comp_code = ?
|
|
AND u.is_active = '1'
|
|
AND g.quarter = ?
|
|
ORDER BY u.completed_date IS NULL DESC, u.completed_date ASC, u.goal_code ASC
|
|
LIMIT 1"
|
|
);
|
|
$stmtActiveGoal->execute([(string)$memberId, (string)$userCompany, $currentQuarterCode]);
|
|
$activeGoalTitle = $stmtActiveGoal->fetchColumn();
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass.php] active goal title fetch error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
?>
|
|
<?php if ($mode === 'completed') : ?>
|
|
<div class="page-title">
|
|
<h3><?php echo $currentYear; ?>년도 <?php echo $currentQuarterNum; ?>분기의 첫번째 목표를 완성했어요!</h3>
|
|
<p><em>다음 목표를 선택</em>해, 나만의 책장을 더 풍성하게 채워보세요.</p>
|
|
</div>
|
|
<?php elseif ($mode === 'additional') : ?>
|
|
<div class="page-title">
|
|
<h3><em>축하합니다.</em> <strong>[<?php echo htmlspecialchars($completedGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>, 빛나는 책장을 완성했어요.</h3>
|
|
<p><em>다음 책장</em>을 열어볼까요?</p>
|
|
</div>
|
|
<?php else : ?>
|
|
<div class="page-title page-title--goal" data-state="initial">
|
|
<h3>
|
|
<em><?php echo htmlspecialchars($userName, ENT_QUOTES, 'UTF-8') . ' ' . htmlspecialchars($userPosition, ENT_QUOTES, 'UTF-8'); ?></em>님,
|
|
<strong><?php echo $currentYear; ?>년도 <?php echo $currentQuarterNum; ?>분기 학습 목표</strong>를 골라볼까요?
|
|
</h3>
|
|
<p class="page-title-deadline">
|
|
<span class="deadline-highlight"><?php echo $daysDisplay; ?><small>(<?php echo $targetDateKr; ?>)</small></span>
|
|
<span class="deadline-body">
|
|
<?php if ($activeGoalTitle) : ?>
|
|
현재 <strong>선택한 목표</strong>: <strong style="color:#f5b800;"><?php echo htmlspecialchars($activeGoalTitle, ENT_QUOTES, 'UTF-8'); ?></strong>
|
|
<?php else : ?>
|
|
<strong>선택한 목표</strong>와 함께 배움을 채워가세요.
|
|
<?php endif; ?>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="page-intro-mobile">
|
|
<div class="page-title page-title--goal">
|
|
<?php if ($isCompletionFlow) : ?>
|
|
<h3><strong>다음 목표</strong>를 선택해 책장을 채워보세요.</h3>
|
|
<?php else : ?>
|
|
<h3><strong>선택한 목표</strong>와 함께 배움을 채워가세요.</h3>
|
|
<?php endif; ?>
|
|
<p class="page-title-deadline">D-64</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="myclass-inner">
|
|
<div class="sub-title">
|
|
<h4><?php echo substr($currentYear, 2); ?>년 <strong><?php echo $currentQuarterNum; ?></strong>분기)</h4>
|
|
</div>
|
|
|
|
<section class="bookshelf goal" id="bookshelf" aria-label="학습 목표 선택">
|
|
<div class="shelf-legs">
|
|
<span class="shelf-leg shelf-leg-left"></span>
|
|
<span class="shelf-leg shelf-leg-center"></span>
|
|
<span class="shelf-leg shelf-leg-center"></span>
|
|
<span class="shelf-leg shelf-leg-right"></span>
|
|
</div>
|
|
|
|
<?php for ($row = 0; $row < 2; $row++) : ?>
|
|
<div class="shelf-row">
|
|
<ul class="goal-list">
|
|
<?php for ($col = 0; $col < 3; $col++) : ?>
|
|
<?php
|
|
$idx = $row * 3 + $col;
|
|
$goal = $goals[$idx];
|
|
$goalId = $goal['id'];
|
|
$isCompletedDB = in_array($goalId, $completedGoalIds, true);
|
|
?>
|
|
<li class="goal-item<?php echo ($isCompletedDB ? ' goal-item-completed-db' : ''); ?>" data-goal-id="<?php echo $goalId; ?>">
|
|
<button class="goal-card" type="button" aria-label="<?php echo str_replace("\n", ' ', $goal['title']); ?> 목표 선택"
|
|
<?php if ($isCompletedDB) : ?>
|
|
disabled tabindex="-1" aria-disabled="true"
|
|
<?php endif; ?>
|
|
>
|
|
<?php if ($isCompletedDB) : ?>
|
|
<div class="goal-completed-preview" aria-hidden="true">
|
|
<div class="goal-preview-header">
|
|
<span class="goal-preview-badge">
|
|
<img src="/img/myclass/ico_medal_completed.svg" alt="완득" />
|
|
<span class="badge-text">완득</span>
|
|
</span>
|
|
<strong class="goal-preview-title"><?php echo str_replace("\n", '', $goal['title']); ?></strong>
|
|
</div>
|
|
</div>
|
|
<?php else : ?>
|
|
<div class="card-default-area">
|
|
<div class="card-title-wrap">
|
|
<div class="card-icon-wrap">
|
|
<span class="card-icon" aria-hidden="true">
|
|
<img class="lottie-fallback" src="/img/myclass/<?php echo $goal['gif']; ?>" alt="" aria-hidden="true" />
|
|
<span class="lottie-icon lottie-<?php echo $goal['icon']; ?>" data-lottie-url="/img/myclass/<?php echo $goal['json']; ?>"></span>
|
|
</span>
|
|
</div>
|
|
<strong class="card-title"><?php echo nl2br(htmlspecialchars($goal['title'], ENT_QUOTES, 'UTF-8')); ?></strong>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-desc"><?php echo htmlspecialchars($goal['desc'], ENT_QUOTES, 'UTF-8'); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</button>
|
|
</li>
|
|
<?php endfor; ?>
|
|
</ul>
|
|
<div class="shelf-board"></div>
|
|
<?php if ($row === 1) : ?><div class="shelf-board shelf-board-mobile"></div><?php endif; ?>
|
|
</div>
|
|
<?php endfor; ?>
|
|
</section>
|
|
|
|
<?php if ($isCompletionFlow) : ?>
|
|
<!--
|
|
<div class="myclass-extra-actions" style="text-align:center; margin-bottom:28px;">
|
|
<button type="button" class="btn-next-growth" id="btnSelectGoal">추가 목표 고르기</button>
|
|
<a href="/skin/myclass_list.php" class="btn-next-growth" style="margin-left:8px;">학습하러 가기</a>
|
|
</div>
|
|
-->
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include(__DIR__ . '/_modal/goal-layer.php'); ?>
|
|
</div>
|
|
|
|
<script>
|
|
window.MYCLASS_GOALS = <?php
|
|
echo json_encode(array_map(function ($g) {
|
|
return [
|
|
'id' => (int)$g['id'],
|
|
'title' => str_replace("\n", ' ', (string)$g['title']),
|
|
'desc' => (string)$g['desc'],
|
|
];
|
|
}, $goals), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>;
|
|
window.MYCLASS_GOAL_RECS = <?php
|
|
echo json_encode($goalRecommendations, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>;
|
|
window.MYCLASS_GOAL_BOOKS = <?php
|
|
echo json_encode($goalBooksById, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>;
|
|
window.MYCLASS_GOAL_CODE_MAP = <?php
|
|
echo json_encode($goalCodeById, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>;
|
|
window.MYCLASS_QUARTER_CODE = <?php
|
|
echo json_encode($currentQuarterCode, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>;
|
|
<?php if ($debugRecs) : ?>
|
|
window.DEBUG_RECS_PAYLOAD = <?php echo json_encode($debugRecsPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
|
console.error('[debug_recs] ACTIVE');
|
|
console.error('[debug_recs] MYCLASS_GOAL_CODE_MAP', window.MYCLASS_GOAL_CODE_MAP);
|
|
console.error('[debug_recs] MYCLASS_GOAL_RECS', window.MYCLASS_GOAL_RECS);
|
|
console.error('[debug_recs] MODAL_REC_ITEMS_FROM_PHP', <?php echo json_encode($modalRecItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>);
|
|
console.error('[debug_recs] PAYLOAD', window.DEBUG_RECS_PAYLOAD);
|
|
<?php endif; ?>
|
|
</script>
|
|
<script src="/js/myclass.js"></script>
|
|
</body>
|
|
</html>
|