format('Y'); $currentMonth = (int) $today->format('m'); $currentQuarterNum = (int) ceil($currentMonth / 3); $currentQuarterCode = sprintf('CA200Q%02d', $currentQuarterNum); $memberId = (string)($_SESSION['member_id'] ?? ''); $sysCompCode = (string)($_SESSION['sys_comp_code'] ?? ''); $userActiveGoalRows = []; $userActiveGoalCodes = []; $userAllGoalRows = []; $userAllGoalCodes = []; try { if ($memberId !== '' && $sysCompCode !== '') { $stmtUserGoals = db_conn()->prepare( "SELECT u.goal_code, u.completed_date, u.updated_at, u.created_at, u.is_active FROM edu_user_learning_goals u WHERE u.member_id = ? AND u.sys_comp_code = ? AND u.quarter = ? AND ( u.is_active = '1' OR ( u.completed_date IS NOT NULL AND u.completed_date != '0000-00-00' AND u.completed_date != '0000-00-00 00:00:00' ) ) ORDER BY CASE WHEN u.completed_date IS NULL OR u.completed_date = '0000-00-00' OR u.completed_date = '0000-00-00 00:00:00' THEN 0 ELSE 1 END ASC, u.updated_at DESC, u.created_at DESC" ); $stmtUserGoals->execute([$memberId, $sysCompCode, $currentQuarterCode]); $userActiveGoalRows = $stmtUserGoals->fetchAll(PDO::FETCH_ASSOC); $userActiveGoalCodes = array_values(array_unique(array_filter(array_map(function ($row) { return trim((string)($row['goal_code'] ?? '')); }, $userActiveGoalRows)))); $stmtAllUserGoals = db_conn()->prepare( "SELECT u.goal_code, u.quarter, u.completed_date, u.updated_at, u.created_at, u.is_active FROM edu_user_learning_goals u WHERE u.member_id = ? AND u.sys_comp_code = ? AND ( u.is_active = '1' OR ( u.completed_date IS NOT NULL AND u.completed_date != '0000-00-00' AND u.completed_date != '0000-00-00 00:00:00' ) ) ORDER BY u.quarter DESC, CASE WHEN u.completed_date IS NULL OR u.completed_date = '0000-00-00' OR u.completed_date = '0000-00-00 00:00:00' THEN 0 ELSE 1 END ASC, u.created_at ASC, u.updated_at ASC" ); $stmtAllUserGoals->execute([$memberId, $sysCompCode]); $userAllGoalRows = $stmtAllUserGoals->fetchAll(PDO::FETCH_ASSOC); usort($userAllGoalRows, function ($left, $right) { $extractQuarterNum = function ($quarterCode) { $quarterCode = trim((string)$quarterCode); if (preg_match('/CA200Q(\d{2})/', $quarterCode, $match)) { return (int)ltrim($match[1], '0'); } return 0; }; $isCompletedRow = function ($row) { $completedDate = trim((string)($row['completed_date'] ?? '')); return $completedDate !== '' && $completedDate !== '0000-00-00' && $completedDate !== '0000-00-00 00:00:00'; }; $leftQuarter = $extractQuarterNum($left['quarter'] ?? ''); $rightQuarter = $extractQuarterNum($right['quarter'] ?? ''); if ($leftQuarter !== $rightQuarter) { return $rightQuarter <=> $leftQuarter; } $leftCompleted = $isCompletedRow($left); $rightCompleted = $isCompletedRow($right); if ($leftCompleted !== $rightCompleted) { return $leftCompleted <=> $rightCompleted; } $leftCreatedAt = trim((string)($left['created_at'] ?? '')); $rightCreatedAt = trim((string)($right['created_at'] ?? '')); if ($leftCreatedAt !== $rightCreatedAt) { return strcmp($leftCreatedAt, $rightCreatedAt); } $leftUpdatedAt = trim((string)($left['updated_at'] ?? '')); $rightUpdatedAt = trim((string)($right['updated_at'] ?? '')); return strcmp($leftUpdatedAt, $rightUpdatedAt); }); $userAllGoalCodes = array_values(array_unique(array_filter(array_map(function ($row) { return trim((string)($row['goal_code'] ?? '')); }, $userAllGoalRows)))); } } catch (Throwable $e) { error_log('[myclass_list.php] active user goal fetch error: ' . $e->getMessage()); } // 기본 목표 배열 (icon/gif/json은 고정, title/desc는 DB로 덮어씀) $goals = [ ['id' => 1, 'title' => '자기이해와 강점 찾기', 'desc' => '나의 성향, 가치관, 강점 등을 탐색하고 이해하며 자기인식을 높이는 과정', 'icon' => '01', 'gif' => 'ico_study_01.gif', 'json' => 'ico_study_01.json'], ['id' => 2, 'title' => '효과적인 의사소통 배우기', 'desc' => '상황에 맞는 말하기와 경청을 통해 타인과 원활하게 소통하는 방법을 익히는 과정', 'icon' => '02', 'gif' => 'ico_study_02.gif', 'json' => 'ico_study_02.json'], ['id' => 3, 'title' => '감정 조절과 자기관리', 'desc' => '다양한 감정을 인식하고 조절하여 안정적인 삶을 유지하는 자기관리 훈련 과정', 'icon' => '03', 'gif' => 'ico_study_03.gif', 'json' => 'ico_study_03.json'], ['id' => 4, 'title' => '비판적 사고와 문제 해결', 'desc' => '다양한 관점에서 생각하고 문제 상황을 논리적으로 해결하는 능력을 기르는 과정', 'icon' => '04', 'gif' => 'ico_study_04.gif', 'json' => 'ico_study_04.json'], ['id' => 5, 'title' => '협업과 팀워크 기르기', 'desc' => '타인과 협력하며 공동의 목표를 위해 함께 노력하는 태도와 기술을 기르는 과정', 'icon' => '05', 'gif' => 'ico_study_05.gif', 'json' => 'ico_study_05.json'], ['id' => 6, 'title' => '커리어 탐색과 역량 개발', 'desc' => '직무와 산업의 흐름을 이해하고, 자신의 커리어 방향성과 필요한 역량을 점검·계획하는 과정', 'icon' => '06', 'gif' => 'ico_study_06.gif', 'json' => 'ico_study_06.json'], ]; $quarterEndDates = [ 'CA200Q01' => ['end' => '03-31', 'kr' => '3월 31일', 'num' => 1], 'CA200Q02' => ['end' => '06-30', 'kr' => '6월 30일', 'num' => 2], 'CA200Q03' => ['end' => '09-30', 'kr' => '9월 30일', 'num' => 3], 'CA200Q04' => ['end' => '12-31', 'kr' => '12월 31일', 'num' => 4], ]; $goalCodeById = []; $goalQuarterById = []; function myclass_list_fetch_quarter_goal_rows(PDO $conn, $currentYear, $currentQuarterCode) { $orderSql = "ORDER BY CASE WHEN sort_order IS NULL OR sort_order = 0 THEN 999999 ELSE sort_order END ASC, goal_no ASC, goal_code ASC"; $stmt = $conn->prepare( "SELECT goal_code, title AS goal_title, remarks AS goal_remarks, quarter, goal_no, sort_order, base_year FROM edu_learning_goals WHERE is_active = '1' AND base_year = ? AND quarter = ? {$orderSql}" ); $stmt->execute([(string)$currentYear, (string)$currentQuarterCode]); $goalRows = $stmt->fetchAll(PDO::FETCH_ASSOC); if (count($goalRows) < 6) { $stmt = $conn->prepare( "SELECT goal_code, title AS goal_title, remarks AS goal_remarks, quarter, goal_no, sort_order, base_year FROM edu_learning_goals WHERE is_active = '1' AND quarter = ? ORDER BY base_year DESC, CASE WHEN sort_order IS NULL OR sort_order = 0 THEN 999999 ELSE sort_order END ASC, goal_no ASC, goal_code ASC LIMIT 6" ); $stmt->execute([(string)$currentQuarterCode]); $goalRows = $stmt->fetchAll(PDO::FETCH_ASSOC); } return $goalRows; } function myclass_list_fetch_goal_rows_by_codes(PDO $conn, array $goalCodes) { $goalCodes = array_values(array_unique(array_filter(array_map(function ($code) { return trim((string)$code); }, $goalCodes)))); if (empty($goalCodes)) { return []; } $placeholders = implode(',', array_fill(0, count($goalCodes), '?')); $stmt = $conn->prepare( "SELECT goal_code, title AS goal_title, remarks AS goal_remarks, quarter, goal_no, sort_order, base_year FROM edu_learning_goals WHERE is_active = '1' AND goal_code IN ($placeholders)" ); $stmt->execute($goalCodes); $rowsByCode = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $goalCode = trim((string)($row['goal_code'] ?? '')); if ($goalCode !== '') { $rowsByCode[$goalCode] = $row; } } return $rowsByCode; } function myclass_list_resolve_goal_slot(array $goalRow) { $sortOrder = (int)($goalRow['sort_order'] ?? 0); if ($sortOrder >= 1 && $sortOrder <= 6) { return $sortOrder; } $goalNo = (int)($goalRow['goal_no'] ?? 0); if ($goalNo >= 1 && $goalNo <= 6) { return $goalNo; } return 1; } // DB에서 현재 분기 목표 6개를 slot(1~6) 기준으로 고정 매핑한다. try { $goalRows = myclass_list_fetch_quarter_goal_rows(db_conn(), (string)$currentYear, (string)$currentQuarterCode); $goalSlot = 1; foreach ($goalRows as $goalRow) { if ($goalSlot > 6) { break; } $goalCode = trim((string)($goalRow['goal_code'] ?? '')); if ($goalCode === '') { continue; } $goalId = $goalSlot; $goalCodeById[$goalId] = $goalCode; $goalQuarterById[$goalId] = trim((string)($goalRow['quarter'] ?? '')) !== '' ? trim((string)($goalRow['quarter'] ?? '')) : $currentQuarterCode; $title = trim((string)($goalRow['goal_title'] ?? '')); $desc = trim((string)($goalRow['goal_remarks'] ?? '')); if ($title !== '') { $goals[$goalId - 1]['title'] = $title; } if ($desc !== '') { $goals[$goalId - 1]['desc'] = $desc; } $goalSlot++; } } catch (Throwable $e) { error_log('[myclass_list.php] quarter goal fetch error: ' . $e->getMessage()); } $books = [ ['id' => 1, 'youtube' => 'KE_MeQZgnPM', 'title' => '조직도가 리셋된다!', 'sub' => '위계 중심 조직은\n더 이상 통하지 않습니다.', 'main' => '더 유연하게\n일할 방법', 'tag' => 'IT 테크', 'img' => 'img_book_01', 'postit' => ['일의 도구를 갖추고 효율을 높인다.', '반복 업무는 자동화로 줄이다.', '협업은 체계적인 방식으로!']], ['id' => 2, 'youtube' => 'a2l1uZfsRi0', 'title' => '혈당 스파이크', 'sub' => '식후 졸림은 의지 문제가\n아니라 혈당의 문제!', 'main' => '혈당을 안정시켜줄\n실생활 관리법', 'tag' => '웰니스', 'img' => 'img_book_02', 'postit' => ['식후 피로의 원인을 혈당에서 찾는다.', '탄수화물 섭취 점검', '생활습관으로 혈당 안정']], ['id' => 3, 'youtube' => 'IeF8r0ycgVg', 'title' => '제대로 쉬는 방법', 'sub' => '아무리 자도\n피곤한가요?', 'main' => '진짜 회복되는 쉼이\n무엇인지 알게됩니다.', 'tag' => '마인드셋', 'img' => 'img_book_03', 'postit' => ['쉬어도 피곤한 이유 이해', '회복을 위한 진짜 쉼 실천', '회복을 방해하는 습관 점검']], ['id' => 4, 'youtube' => 'KMZXMI0QPoA', 'title' => "'AI 도파민'의 바다에 빠진 이유", 'sub' => 'AI는 선택이 아닌\n생존의 도구', 'main' => 'AI는 도구가 아닌\n업무 파트너', 'tag' => 'IT 테크', 'img' => 'img_book_04', 'postit' => ['AI를 생존 도구가 아닌 성장 도구로 본다.', '반복 업무를 AI와 분담', 'AI를 도구가 아닌 협업 파트너로']], ['id' => 5, 'youtube' => 'CRKwszz6l2M', 'title' => '살찌고 망가진몸 되살리는 방법', 'sub' => '몸을 망쳤다면?\n다이어트 아닌 리셋이 답!', 'main' => '건강한 몸 되찾기', 'tag' => '웰니스', 'img' => 'img_book_05', 'postit' => ['단순 감량보다 건강 회복이 우선', '다이어트보다 나은 루틴을 만들기', '몸을 되살리는 작은 습관 실천']], ['id' => 6, 'youtube' => 'Gf5WoZ3BmgI', 'title' => '자기 관점의 힘', 'sub' => '당신의 삶을 바꾸는\n가장 강력한 무기?', 'main' => '자신만의 관점으로\n경쟁력을 키우는 방법', 'tag' => '리더십', 'img' => 'img_book_06', 'postit' => ['관점이 삶에 미치는 영향을 자각한다.', '나만의 강점과 가능성 정의', '더 넓은 시야로 삶을 바라본다.']], ]; // ====== 강제 완료 모드 전환: activeGoal의 completed_date가 있으면 $mode = 'complete' ====== // if (!empty($userActiveGoalRows) && !empty($goalCodeById)) { // $goalIdByCode = array_flip($goalCodeById); // foreach ($userActiveGoalRows as $row) { // $goalCode = trim((string)($row['goal_code'] ?? '')); // if ($goalCode === '' || !isset($goalIdByCode[$goalCode])) { // continue; // } // $goalId = (int)$goalIdByCode[$goalCode]; // if ($goalId === $activeGoalId) { // $completedDate = trim((string)($row['completed_date'] ?? '')); // if ($completedDate !== '' && $completedDate !== '0000-00-00') { // $mode = 'complete'; // } // } // } // } if (!empty($userActiveGoalRows) && !empty($goalCodeById)) { $goalIdByCode = array_flip($goalCodeById); $incompleteGoalIds = []; $completedGoalIds = []; $seenIncompleteGoalIds = []; $seenCompletedGoalIds = []; foreach ($userActiveGoalRows as $row) { $goalCode = trim((string)($row['goal_code'] ?? '')); if ($goalCode === '' || !isset($goalIdByCode[$goalCode])) { continue; } $goalId = (int)$goalIdByCode[$goalCode]; $completedDate = trim((string)($row['completed_date'] ?? '')); $isCompleted = ( $completedDate !== '' && $completedDate !== '0000-00-00' && $completedDate !== '0000-00-00 00:00:00' ); if ($isCompleted) { if (!isset($seenCompletedGoalIds[$goalId])) { $completedGoalIds[] = $goalId; $seenCompletedGoalIds[$goalId] = true; } } else { if (!isset($seenIncompleteGoalIds[$goalId])) { $incompleteGoalIds[] = $goalId; $seenIncompleteGoalIds[$goalId] = true; } } } if (!empty($incompleteGoalIds)) { $activeGoalId = (int)$incompleteGoalIds[0]; } elseif (!empty($completedGoalIds)) { $activeGoalId = (int)$completedGoalIds[0]; } if (!empty($completedGoalIds)) { $completedGoalId = (int)$completedGoalIds[0]; } else { $completedGoalId = $activeGoalId; } if (!empty($incompleteGoalIds) && !empty($completedGoalIds) && in_array($mode, ['learning', 'progress'], true)) { $mode = 'extended'; } } function 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 ''; } $booksByGoalId = []; $booksByGoalCode = []; $allContentIds = []; // ====== 최종 완료 상태 체크: activeGoal의 completed_date가 있으면 mode를 'complete'로 확정 ====== if (!empty($userActiveGoalRows) && !empty($goalCodeById)) { $goalIdByCode = array_flip($goalCodeById); foreach ($userActiveGoalRows as $row) { $goalCode = trim((string)($row['goal_code'] ?? '')); if ($goalCode === '' || !isset($goalIdByCode[$goalCode])) { continue; } $goalId = (int)$goalIdByCode[$goalCode]; if ($goalId === $activeGoalId) { $completedDate = trim((string)($row['completed_date'] ?? '')); if ($completedDate !== '' && $completedDate !== '0000-00-00') { $mode = 'complete'; } } } } try { $goalCodes = !empty($userAllGoalCodes) ? $userAllGoalCodes : array_values(array_unique(array_filter($goalCodeById))); if (count($goalCodes) > 0) { $goalRowsByCodeForBooks = myclass_list_fetch_goal_rows_by_codes(db_conn(), $goalCodes); $placeholders = implode(',', array_fill(0, count($goalCodes), '?')); $quarterCodes = array_values(array_unique(array_filter(array_map(function ($row) { return trim((string)($row['quarter'] ?? '')); }, $userAllGoalRows)))); if (empty($quarterCodes)) { $quarterCodes = [$currentQuarterCode]; } $quarterPlaceholders = implode(',', array_fill(0, count($quarterCodes), '?')); $stmtBooks = db_conn()->prepare( "SELECT content_id, goal_code, category_group, sort_order, title, description, description1, description2, content_url, thumbnail_url FROM edu_contents WHERE is_active = '1' AND category_code = 'CA10001' AND category_group IN ($quarterPlaceholders) AND goal_code IN ($placeholders) ORDER BY goal_code ASC, sort_order ASC, content_id ASC" ); $stmtBooks->execute(array_merge($quarterCodes, $goalCodes)); $bookRows = $stmtBooks->fetchAll(PDO::FETCH_ASSOC); // 1. 모든 content_id 수집 foreach ($bookRows as $bookRow) { $cid = trim((string)($bookRow['content_id'] ?? '')); if ($cid !== '') $allContentIds[] = $cid; } $memoMap = []; if (!empty($allContentIds)) { $in = implode(',', array_fill(0, count($allContentIds), '?')); $stmtMemo = db_conn()->prepare( "SELECT content_id, seq, title FROM edu_content_memos WHERE content_id IN ($in) AND (is_active = '1' OR is_active IS NULL) ORDER BY content_id ASC, seq ASC" ); $stmtMemo->execute($allContentIds); $memoRows = $stmtMemo->fetchAll(PDO::FETCH_ASSOC); foreach ($memoRows as $m) { $cid = (string)$m['content_id']; if (!isset($memoMap[$cid])) $memoMap[$cid] = []; $memoTitle = trim((string)($m['title'] ?? '')); if ($memoTitle !== '') $memoMap[$cid][] = $memoTitle; } } foreach ($bookRows as $bookRow) { $goalCode = (string)($bookRow['goal_code'] ?? ''); if ($goalCode === '' || !isset($goalRowsByCodeForBooks[$goalCode])) { continue; } $goalSlot = myclass_list_resolve_goal_slot($goalRowsByCodeForBooks[$goalCode]); $contentUrl = trim((string)($bookRow['content_url'] ?? '')); $videoId = extract_video_id($contentUrl); $thumbnailUrl = trim((string)($bookRow['thumbnail_url'] ?? '')); $sortOrder = (int)($bookRow['sort_order'] ?? 0); if ($sortOrder < 1 || $sortOrder > 6) { continue; } $templateBook = $books[$sortOrder - 1] ?? null; if ($templateBook === null) { continue; } if (!isset($booksByGoalId[$goalSlot])) { $booksByGoalId[$goalSlot] = $books; } if (!isset($booksByGoalCode[$goalCode])) { $booksByGoalCode[$goalCode] = $books; } $cid = (string)($bookRow['content_id'] ?? ''); $postitArr = isset($memoMap[$cid]) ? $memoMap[$cid] : (array)($templateBook['postit'] ?? []); $bookPayload = [ 'id' => (int)($templateBook['id'] ?? $sortOrder), 'content_id' => $cid, 'order' => $sortOrder, 'youtube' => $videoId !== '' ? $videoId : (string)($templateBook['youtube'] ?? ''), 'content_url' => $contentUrl, 'title' => trim((string)($bookRow['title'] ?? (string)($templateBook['title'] ?? ''))), 'description' => trim((string)($bookRow['description'] ?? '')), 'sub' => trim((string)($bookRow['description1'] ?? (string)($templateBook['sub'] ?? ''))), 'main' => trim((string)($bookRow['description2'] ?? (string)($templateBook['main'] ?? ''))), 'tag' => (string)($templateBook['tag'] ?? ''), 'img' => (string)($templateBook['img'] ?? 'img_book_01'), 'thumbnail' => $thumbnailUrl, 'postit' => $postitArr, ]; $booksByGoalId[$goalSlot][$sortOrder - 1] = $bookPayload; $booksByGoalCode[$goalCode][$sortOrder - 1] = $bookPayload; } foreach ($booksByGoalId as $goalId => $goalBooks) { if (is_array($goalBooks)) { ksort($goalBooks); $booksByGoalId[$goalId] = array_values($goalBooks); } } foreach ($booksByGoalCode as $goalCode => $goalBooks) { if (is_array($goalBooks)) { ksort($goalBooks); $booksByGoalCode[$goalCode] = array_values($goalBooks); } } } } catch (Throwable $e) { error_log('[myclass_list.php] edu_contents fetch error: ' . $e->getMessage()); } function get_goal_books($goalId, $booksByGoalId, $fallbackBooks) { if (isset($booksByGoalId[$goalId]) && count($booksByGoalId[$goalId]) > 0) { return $booksByGoalId[$goalId]; } return $fallbackBooks; } function get_goal_books_by_code($goalCode, $goalSlot, $booksByGoalCode, $fallbackBooks) { if ($goalCode !== '' && isset($booksByGoalCode[$goalCode]) && count($booksByGoalCode[$goalCode]) > 0) { return $booksByGoalCode[$goalCode]; } $fallbackGoalId = max(1, min(6, (int)$goalSlot)); return get_goal_books($fallbackGoalId, [], $fallbackBooks); } $activeGoal = $goals[$activeGoalId - 1]; $completedGoal = $goals[$completedGoalId - 1]; $activeGoalQuarter = $goalQuarterById[$activeGoal['id']] ?? $currentQuarterCode; $quarterInfo = $quarterEndDates[$activeGoalQuarter] ?? ['end' => '12-31', 'kr' => '12월 31일', 'num' => 4]; $todayDateOnly = (clone $today)->setTime(0, 0, 0); $targetDateStr = $currentYear . '-' . $quarterInfo['end']; $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; $targetDateKr = $quarterInfo['kr']; // ====== DEBUG LOGGING (브라우저 콘솔) ====== $debugRows = json_encode($userActiveGoalRows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $debugGoalCodeById = json_encode($goalCodeById, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $debugGoalIdByCode = json_encode(isset($goalIdByCode) ? $goalIdByCode : [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $debugCompletedDate = ''; foreach ($userActiveGoalRows as $row) { $goalCode = trim((string)($row['goal_code'] ?? '')); if (isset($goalIdByCode[$goalCode]) && $goalIdByCode[$goalCode] == $activeGoalId) { $debugCompletedDate = trim((string)($row['completed_date'] ?? '')); break; } } echo ''; // ====== DEBUG LOGGING (브라우저 콘솔) ====== echo ''; // ====== DEBUG LOGGING ====== error_log('[myclass_list.php] DEBUG: $activeGoalId=' . $activeGoalId . ', $mode=' . $mode); // ========== DB에서 시청 완료된 영상의 content_id 목록 조회 ========== // edu_learning_histories.completed_at 이 설정된 영상 = 다 본 영상 → 포스트잇 표시 $completedContentIds = []; try { $allContentIds = []; foreach ($booksByGoalCode as $goalBooks) { foreach ($goalBooks as $book) { $cid = trim((string)($book['content_id'] ?? '')); if ($cid !== '') { $allContentIds[] = $cid; } } } $allContentIds = array_values(array_unique($allContentIds)); if (!empty($allContentIds) && $memberId !== '' && $sysCompCode !== '') { $placeholders = implode(',', array_fill(0, count($allContentIds), '?')); $stmtCompleted = db_conn()->prepare( "SELECT content_id FROM edu_learning_histories WHERE member_id = ? AND sys_comp_code = ? AND content_id IN ($placeholders) AND completed_at IS NOT NULL AND completed_at != '0000-00-00 00:00:00'" ); $stmtCompleted->execute(array_merge([$memberId, $sysCompCode], $allContentIds)); $completedContentIds = array_values(array_unique(array_filter(array_map(function ($value) { return trim((string)$value); }, array_column($stmtCompleted->fetchAll(PDO::FETCH_ASSOC), 'content_id')), function ($value) { return $value !== ''; }))); } } catch (Throwable $e) { error_log('[myclass_list.php] completed histories fetch error: ' . $e->getMessage()); } // ========== DB에서 한줄 소감문 작성된 영상의 content_id 목록 조회 ========== $commentedContentIds = []; try { if (!empty($allContentIds) && $memberId !== '' && $sysCompCode !== '') { $placeholders = implode(',', array_fill(0, count($allContentIds), '?')); $stmtCommented = db_conn()->prepare( "SELECT content_id FROM edu_learning_histories WHERE member_id = ? AND sys_comp_code = ? AND content_id IN ($placeholders) AND comment IS NOT NULL AND TRIM(comment) != ''" ); $stmtCommented->execute(array_merge([$memberId, $sysCompCode], $allContentIds)); $commentedContentIds = array_values(array_unique(array_filter(array_map(function ($value) { return trim((string)$value); }, array_column($stmtCommented->fetchAll(PDO::FETCH_ASSOC), 'content_id')), function ($value) { return $value !== ''; }))); } } catch (Throwable $e) { error_log('[myclass_list.php] commented histories fetch error: ' . $e->getMessage()); } function render_goal_icon($goal, $completed = false) { if ($completed) { return '완독'; } return '' . ''; } function render_book_item($book, $sectionKey, $completedContentIds, $commentedContentIds, $showProgressClass) { $contentId = trim((string)($book['content_id'] ?? '')); $isCompleted = $contentId !== '' && in_array($contentId, $completedContentIds, true); $isCommentWritten = $contentId !== '' && in_array($contentId, $commentedContentIds, true); $classes = ['books-item']; if ($isCompleted) { $classes[] = 'books-item-completed'; } elseif ($showProgressClass) { $classes[] = 'books-item-progress'; } $videoKey = trim((string)($book['content_id'] ?? '')); if ($videoKey === '') { $videoKey = (string)($book['id'] ?? ''); } $bookId = $videoKey; $bookTitle = htmlspecialchars((string)($book['title'] ?? ''), ENT_QUOTES, 'UTF-8'); $bookmarkId = 'like_book_' . $sectionKey . '_' . preg_replace('/[^A-Za-z0-9_-]/', '_', (string)$bookId); $subText = str_replace('\\n', "\n", (string)($book['sub'] ?? '')); $mainText = str_replace('\\n', "\n", (string)($book['main'] ?? '')); $bookImg = (string)($book['img'] ?? 'img_book_01'); $bookThumbnail = trim((string)($book['thumbnail'] ?? '')); $bookYoutube = (string)($book['youtube'] ?? ''); $coverDesktop = $bookThumbnail !== '' ? $bookThumbnail : '/img/myclass/' . $bookImg . '.png'; $coverMobile = $bookThumbnail !== '' ? $bookThumbnail : '/img/myclass/' . $bookImg . '_m.png'; $videoThumb = $bookThumbnail !== '' ? $bookThumbnail : 'https://img.youtube.com/vi/' . $bookYoutube . '/sddefault.jpg'; ?>
추천 도서 이미지
4) { $quarterNum = 1; } ?>
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([$memberId, $sysCompCode, $currentQuarterCode]); $activeGoalTitle = $stmtActiveGoal->fetchColumn(); } catch (Throwable $e) { error_log('[myclass_list.php] active goal title fetch error: ' . $e->getMessage()); } } ?>

축하합니다. [], 빛나는 책장을 완성했어요.

다음 책장을 열어볼까요?

두 번째 목표 []로 새로운 책장이 열렸어요.

() 까지 지금부터 또 하나의 지식을 쌓아보세요.

[], 지금부터 하나씩 완독해보세요. [], 지금부터 하나씩 완독해보세요.

() 나만의 지식을 채워갈 시간입니다.

나만의 빛나는 책장을 완성했어요.

() 까지 지금부터 또 하나의 지식을 쌓아보세요.

두 번째 목표 []로 새로운 책장이 열렸어요.

() 나만의 지식을 채워갈 시간입니다.

[], 지금부터 하나씩 완독해보세요. [], 지금부터 하나씩 완독해보세요.

$goalSlot, 'title' => trim((string)($goalRow['goal_title'] ?? '')) !== '' ? trim((string)($goalRow['goal_title'] ?? '')) : (string)($baseGoal['title'] ?? ''), 'desc' => trim((string)($goalRow['goal_remarks'] ?? '')) !== '' ? trim((string)($goalRow['goal_remarks'] ?? '')) : (string)($baseGoal['desc'] ?? ''), 'icon' => (string)($baseGoal['icon'] ?? '01'), 'gif' => (string)($baseGoal['gif'] ?? 'ico_study_01.gif'), 'json' => (string)($baseGoal['json'] ?? 'ico_study_01.json'), ]; $booksArr = get_goal_books_by_code($goalCode, $goalSlot, $booksByGoalCode, $books); $sectionKey = ($isCompleted ? 'completed_' : 'active_') . preg_replace('/[^A-Za-z0-9_]/', '_', $quarterCode . '_' . $goalCode); $showReset = !$isCompleted && $quarterCode === $currentQuarterCode; render_section($goal, $quarterCode, $booksArr, $completedContentIds, $commentedContentIds, $sectionKey, $isCompleted, $showReset); } ?>