1090 lines
50 KiB
PHP
1090 lines
50 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../bbs/auth.php';
|
|
edu_require_login();
|
|
require_once __DIR__ . '/../bbs/db_conn.php';
|
|
|
|
$mode = isset($_GET['mode']) ? trim((string) $_GET['mode']) : 'learning';
|
|
$allowedModes = ['learning', 'progress', 'complete', 'extended'];
|
|
if (!in_array($mode, $allowedModes, true)) {
|
|
$mode = 'learning';
|
|
}
|
|
|
|
$activeGoalId = isset($_GET['goal']) ? (int) $_GET['goal'] : 1;
|
|
$completedGoalId = isset($_GET['completed_goal']) ? (int) $_GET['completed_goal'] : 1;
|
|
$activeGoalId = max(1, min(6, $activeGoalId));
|
|
$completedGoalId = max(1, min(6, $completedGoalId));
|
|
|
|
if ($mode === 'extended' && $activeGoalId === $completedGoalId) {
|
|
$activeGoalId = $completedGoalId === 1 ? 2 : 1;
|
|
}
|
|
|
|
$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);
|
|
|
|
$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 '<script>';
|
|
echo 'console.log("[myclass_list.php] DEBUG: $activeGoalId=' . $activeGoalId . ', $mode=' . $mode . '");';
|
|
echo 'console.log("userActiveGoalRows", ' . $debugRows . ');';
|
|
echo 'console.log("goalCodeById", ' . $debugGoalCodeById . ');';
|
|
echo 'console.log("goalIdByCode", ' . $debugGoalIdByCode . ');';
|
|
echo 'console.log("activeGoalId", ' . $activeGoalId . ');';
|
|
echo 'console.log("completedDate", "' . $debugCompletedDate . '");';
|
|
echo '</script>';
|
|
|
|
// ====== DEBUG LOGGING (브라우저 콘솔) ======
|
|
echo '<script>console.log("[myclass_list.php] DEBUG: $activeGoalId=' . $activeGoalId . ', $mode=' . $mode . '");</script>';
|
|
|
|
// ====== 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 '<img src="/img/myclass/ico_medal_completed.svg" alt="완독">';
|
|
}
|
|
|
|
return '<img class="lottie-fallback" src="/img/myclass/' . $goal['gif'] . '" alt="" aria-hidden="true" />'
|
|
. '<span class="lottie-icon lottie-' . $goal['icon'] . '" data-lottie-url="/img/myclass/' . $goal['json'] . '"></span>';
|
|
}
|
|
|
|
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';
|
|
?>
|
|
<div class="<?php echo implode(' ', $classes); ?>" data-video-id="<?php echo htmlspecialchars((string)$videoKey, ENT_QUOTES, 'UTF-8'); ?>">
|
|
<div class="book-img-wrap" style="--book-img: url('<?php echo htmlspecialchars($coverDesktop, ENT_QUOTES, 'UTF-8'); ?>'); --book-img-m: url('<?php echo htmlspecialchars($coverMobile, ENT_QUOTES, 'UTF-8'); ?>')">
|
|
<span class="book-ico-pencil<?php echo $isCommentWritten ? ' book-ico-pencil-completed' : ''; ?>" aria-hidden="true" title="<?php echo $isCommentWritten ? '감상문 작성 완료' : '감상문 쓰기'; ?>"></span>
|
|
<picture class="book-img-picture">
|
|
<source media="(max-width: 1024px)" srcset="<?php echo htmlspecialchars($coverMobile, ENT_QUOTES, 'UTF-8'); ?>">
|
|
<img src="<?php echo htmlspecialchars($coverDesktop, ENT_QUOTES, 'UTF-8'); ?>" alt="추천 도서 이미지" class="book-img">
|
|
</picture>
|
|
</div>
|
|
<button type="button" class="book-info-btn" data-video-id="<?php echo htmlspecialchars((string)$videoKey, ENT_QUOTES, 'UTF-8'); ?>" aria-label="영상 상세 보기 - <?php echo $bookTitle; ?>">
|
|
<span class="book-info-wrap">
|
|
<div class="book-video-area">
|
|
<span class="book-video-thumb-wrap">
|
|
<img src="<?php echo htmlspecialchars($videoThumb, ENT_QUOTES, 'UTF-8'); ?>" alt="" class="book-video-thumb">
|
|
</span>
|
|
<label class="bookmark" for="<?php echo $bookmarkId; ?>" onclick="event.stopPropagation();"><input type="checkbox" id="<?php echo $bookmarkId; ?>" title="저장"></label>
|
|
</div>
|
|
<strong class="book-title"><?php echo $bookTitle; ?></strong>
|
|
<div class="book-desc">
|
|
<p class="book-desc-sub"><?php echo nl2br(htmlspecialchars($subText, ENT_QUOTES, 'UTF-8')); ?></p>
|
|
<p class="book-desc-main"><?php echo nl2br(htmlspecialchars($mainText, ENT_QUOTES, 'UTF-8')); ?></p>
|
|
</div>
|
|
<?php if ($isCompleted) : ?>
|
|
<div class="book-postit">
|
|
<strong class="book-postit-title">CHECK!</strong>
|
|
<ul class="book-postit-list">
|
|
<?php foreach (($book['postit'] ?? []) as $line) : ?>
|
|
<li><?php echo htmlspecialchars($line, ENT_QUOTES, 'UTF-8'); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
<!-- <span class="book-tag"><?php echo htmlspecialchars((string)($book['tag'] ?? 'default'), ENT_QUOTES, 'UTF-8'); ?></span> -->
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
function render_section($goal, $quarterCode, $books, $completedContentIds, $commentedContentIds, $sectionKey, $completedSection = false, $showReset = true) {
|
|
global $currentYear;
|
|
$showProgressClass = !$completedSection && !empty($completedContentIds);
|
|
$quarterNum = 1;
|
|
if (preg_match('/CA200Q(\d{2})/', $quarterCode, $m)) {
|
|
$quarterNum = (int)ltrim($m[1], '0');
|
|
}
|
|
// 분기 숫자가 1~4가 아니면 1로 보정
|
|
if ($quarterNum < 1 || $quarterNum > 4) {
|
|
$quarterNum = 1;
|
|
}
|
|
?>
|
|
<div class="scroll-section scroll-section-reveal" data-quarter="<?php echo $quarterNum; ?>">
|
|
<aside class="scroll-section-sidebar">
|
|
<p class="sidebar-quarter">
|
|
<small><span><?php echo substr($currentYear, -2); ?></span>년</small>
|
|
<strong><?php echo $quarterNum; ?></strong>분기
|
|
</p>
|
|
<div class="quarter-select-wrap" aria-label="분기 선택"></div>
|
|
<a href="#bookshelf-section-<?php echo $sectionKey; ?>" class="sidebar-goal-item is-active" data-index="<?php echo $quarterNum - 1; ?>">
|
|
<span class="sidebar-goal-icon" aria-hidden="true"><?php echo render_goal_icon($goal, $completedSection); ?></span>
|
|
<h3 class="sidebar-goal-title"><?php echo htmlspecialchars($goal['title'], ENT_QUOTES, 'UTF-8'); ?></h3>
|
|
<p class="sidebar-goal-desc"><?php echo htmlspecialchars($goal['desc'], ENT_QUOTES, 'UTF-8'); ?></p>
|
|
<?php if ($showReset) : ?>
|
|
<button class="btn-reset-goal" type="button" data-reset-url="/skin/myclass.php?mode=additional&completed_goal=<?php echo $goal['id']; ?>" onclick="event.preventDefault(); event.stopPropagation();">
|
|
<span class="ico-arrow-back" aria-hidden="true"></span> 목표 다시 선택
|
|
</button>
|
|
<?php endif; ?>
|
|
</a>
|
|
</aside>
|
|
|
|
<div class="scroll-section-content">
|
|
<section id="bookshelf-section-<?php echo $sectionKey; ?>" class="bookshelf-section" aria-label="<?php echo htmlspecialchars($goal['title'], ENT_QUOTES, 'UTF-8'); ?> 책장">
|
|
<div class="bookshelf">
|
|
<div class="books-list books-list--same-height js-video-cards-container" id="videoCardsContainer-<?php echo htmlspecialchars($sectionKey, ENT_QUOTES, 'UTF-8'); ?>" data-video-cards-container="true">
|
|
<?php foreach ($books as $book) {
|
|
render_book_item($book, $sectionKey, $completedContentIds, $commentedContentIds, $showProgressClass);
|
|
} ?>
|
|
</div>
|
|
<div class="shelf-plank">
|
|
<!-- <div class="shelf-tags">
|
|
<span><?php echo $quarterNum; ?>Q</span>
|
|
<ul>
|
|
<li>IT 테크</li>
|
|
<li>웰니스</li>
|
|
<li>마인드셋</li>
|
|
<li>리더십</li>
|
|
</ul>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<?php include(__DIR__ . '/_include/_head.php'); ?>
|
|
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
|
<style>
|
|
@media (min-width: 768px) {
|
|
.myclass-page.myclass-page-06 .myclass-content {
|
|
margin-top: 92px;
|
|
}
|
|
|
|
.myclass-page.myclass-page-06 .myclass-main {
|
|
padding-right: 76px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.myclass-page.myclass-page-06 .quarter-nav {
|
|
right: 14px;
|
|
padding-left: 16px;
|
|
}
|
|
|
|
.myclass-page.myclass-page-06 .bookshelf-section:not(.goal-slide-bookshelf):not(.books-list--no-hover) .books-item:hover {
|
|
transform: translateY(24px);
|
|
z-index: 5;
|
|
box-shadow: 0 -8px 24px rgba(255, 255, 255, 0.35), 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.myclass-page.myclass-page-06 .bookshelf-section:not(.goal-slide-bookshelf):not(.books-list--no-hover) .books-item:hover .book-video-area .book-video-thumb-wrap .book-video-thumb {
|
|
filter: grayscale(0%);
|
|
}
|
|
|
|
.myclass-page.myclass-page-06 .bookshelf-section:not(.goal-slide-bookshelf):not(.books-list--no-hover):has(.books-item:hover) .books-item:not(:hover) {
|
|
filter: brightness(0.6);
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap myclass myclass-page">
|
|
<?php include(__DIR__ . '/_include/_header.php'); ?>
|
|
|
|
<div class="container">
|
|
<div class="myclass-wrap myclass-layout">
|
|
<div class="myclass-top-bar top-bar-absolute">
|
|
<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>
|
|
</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 ($memberId !== '' && $sysCompCode !== '') {
|
|
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([$memberId, $sysCompCode, $currentQuarterCode]);
|
|
$activeGoalTitle = $stmtActiveGoal->fetchColumn();
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass_list.php] active goal title fetch error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
?>
|
|
<?php if ($mode === 'complete') : ?>
|
|
<div class="page-title">
|
|
<h3>축하합니다. <strong>[<?php echo htmlspecialchars($activeGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>, 빛나는 책장을 완성했어요.</h3>
|
|
<p><em>다음 책장</em>을 열어볼까요?</p>
|
|
<button type="button" class="btn-next-growth" data-next-url="/skin/myclass.php?mode=completed&completed_goal=<?php echo $activeGoal['id']; ?>">→ 다음 성장 이어가기</button>
|
|
</div>
|
|
<?php elseif ($mode === 'extended') : ?>
|
|
<div class="page-title page-title--goal">
|
|
<h3>두 번째 목표 <strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>로 새로운 책장이 열렸어요.</h3>
|
|
<p class="page-title-deadline">
|
|
<span class="deadline-highlight"><?php echo $daysDisplay; ?><small>(<?php echo $targetDateKr; ?>)</small></span>
|
|
<span class="deadline-body">까지 지금부터 또 하나의 지식을 쌓아보세요.</span>
|
|
</p>
|
|
</div>
|
|
<?php else : ?>
|
|
<div class="page-title page-title--goal">
|
|
<h3>
|
|
<?php if ($activeGoalTitle) : ?>
|
|
<strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoalTitle, ENT_QUOTES, 'UTF-8'); ?>]</strong>, 지금부터 하나씩 완독해보세요.
|
|
<?php else : ?>
|
|
<strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>, 지금부터 하나씩 완독해보세요.
|
|
<?php endif; ?>
|
|
</h3>
|
|
<p class="page-title-deadline">
|
|
<span class="deadline-highlight"><?php echo $daysDisplay; ?><small>(<?php echo $targetDateKr; ?>)</small></span>
|
|
<span class="deadline-body">나만의 지식을 채워갈 시간입니다.</span>
|
|
</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="page-intro-mobile">
|
|
<div class="page-title page-title--goal">
|
|
<?php if ($mode === 'complete') : ?>
|
|
<h3>나만의 빛나는 책장을 완성했어요.</h3>
|
|
<button type="button" class="btn-next-growth" data-next-url="/skin/myclass.php?mode=completed&completed_goal=<?php echo $activeGoal['id']; ?>">→ 다음 성장 이어가기</button>
|
|
<?php elseif ($mode === 'extended') : ?>
|
|
<p class="page-title-deadline">
|
|
<span class="deadline-highlight"><?php echo $daysDisplay; ?><small>(<?php echo $targetDateKr; ?>)</small></span>
|
|
<span class="deadline-body">까지 지금부터 또 하나의 지식을 쌓아보세요.</span>
|
|
</p>
|
|
<h3>두 번째 목표 <strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>로 새로운 책장이 열렸어요.</h3>
|
|
<?php else : ?>
|
|
<p class="page-title-deadline">
|
|
<span class="deadline-highlight"><?php echo $daysDisplay; ?><small>(<?php echo $targetDateKr; ?>)</small></span>
|
|
<span class="deadline-body">나만의 지식을 채워갈 시간입니다.</span>
|
|
</p>
|
|
<h3>
|
|
<?php if ($activeGoalTitle) : ?>
|
|
<strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoalTitle, ENT_QUOTES, 'UTF-8'); ?>]</strong>, 지금부터 하나씩 완독해보세요.
|
|
<?php else : ?>
|
|
<strong class="intro-goal-name">[<?php echo htmlspecialchars($activeGoal['title'], ENT_QUOTES, 'UTF-8'); ?>]</strong>, 지금부터 하나씩 완독해보세요.
|
|
<?php endif; ?>
|
|
</h3>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="quarter-nav" aria-label="분기 이동"></nav>
|
|
|
|
<div class="myclass-content">
|
|
<div class="myclass-main">
|
|
<div class="scroll-timeline" aria-hidden="true">
|
|
<div class="timeline-track">
|
|
<div class="timeline-progress-fill"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
$goalRowsByCode = myclass_list_fetch_goal_rows_by_codes(db_conn(), $userAllGoalCodes);
|
|
$renderedGoalSectionKeys = [];
|
|
|
|
foreach ($userAllGoalRows as $row) {
|
|
$goalCode = trim((string)($row['goal_code'] ?? ''));
|
|
$quarterCode = trim((string)($row['quarter'] ?? ''));
|
|
if ($goalCode === '' || $quarterCode === '' || !isset($goalRowsByCode[$goalCode])) {
|
|
continue;
|
|
}
|
|
|
|
$renderKey = $quarterCode . '|' . $goalCode;
|
|
if (isset($renderedGoalSectionKeys[$renderKey])) {
|
|
continue;
|
|
}
|
|
$renderedGoalSectionKeys[$renderKey] = true;
|
|
|
|
$goalRow = $goalRowsByCode[$goalCode];
|
|
$goalSlot = myclass_list_resolve_goal_slot($goalRow);
|
|
$baseGoal = $goals[$goalSlot - 1] ?? $goals[0];
|
|
$completedDate = trim((string)($row['completed_date'] ?? ''));
|
|
$isCompleted = (
|
|
$completedDate !== ''
|
|
&& $completedDate !== '0000-00-00'
|
|
&& $completedDate !== '0000-00-00 00:00:00'
|
|
);
|
|
|
|
$goal = [
|
|
'id' => $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);
|
|
}
|
|
?>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
<?php
|
|
$videoBooks = [];
|
|
$seenVideoIds = [];
|
|
foreach ($userAllGoalRows as $goalRow) {
|
|
$gCode = trim((string)($goalRow['goal_code'] ?? ''));
|
|
if ($gCode === '' || !isset($booksByGoalCode[$gCode])) {
|
|
continue;
|
|
}
|
|
foreach ($booksByGoalCode[$gCode] as $videoBook) {
|
|
$videoIdKey = trim((string)($videoBook['content_id'] ?? ''));
|
|
if ($videoIdKey === '') {
|
|
$videoIdKey = (string)($videoBook['id'] ?? '');
|
|
}
|
|
if ($videoIdKey === '' || isset($seenVideoIds[$videoIdKey])) {
|
|
continue;
|
|
}
|
|
$seenVideoIds[$videoIdKey] = true;
|
|
$videoBook['_goal_code'] = $gCode;
|
|
$videoBooks[] = $videoBook;
|
|
}
|
|
}
|
|
?>
|
|
window.MYCLASS_PAGE_VIDEOS = <?php echo json_encode(array_map(function ($book) use ($currentQuarterNum, $memberId, $sysCompCode, $completedContentIds, $commentedContentIds) {
|
|
$videoUrl = trim((string)($book['content_url'] ?? ''));
|
|
if ($videoUrl === '') {
|
|
$videoUrl = (string)($book['youtube'] ?? '');
|
|
}
|
|
|
|
$goalCode = trim((string)($book['_goal_code'] ?? ''));
|
|
$quarterLabel = $currentQuarterNum . '분기';
|
|
if ($goalCode !== '' && preg_match('/Q(\d{2})/', $goalCode, $m)) {
|
|
$quarterNum = (int)ltrim($m[1], '0');
|
|
if ($quarterNum >= 1 && $quarterNum <= 4) {
|
|
$quarterLabel = $quarterNum . '분기';
|
|
}
|
|
}
|
|
|
|
// DB에서 북마크 상태 조회
|
|
$bookmarkStatus = false;
|
|
$contentId = (string)($book['content_id'] ?? $book['id']);
|
|
$isCompleted = $contentId !== '' && in_array($contentId, $completedContentIds, true);
|
|
$isCommentWritten = $contentId !== '' && in_array($contentId, $commentedContentIds, true);
|
|
if ($memberId !== '' && $sysCompCode !== '' && $contentId !== '') {
|
|
try {
|
|
$stmtBookmark = db_conn()->prepare(
|
|
"SELECT is_active FROM edu_content_wishlist WHERE member_id = ? AND sys_comp_code = ? AND content_id = ? LIMIT 1"
|
|
);
|
|
$stmtBookmark->execute([$memberId, $sysCompCode, $contentId]);
|
|
$bookmarkRow = $stmtBookmark->fetch(PDO::FETCH_ASSOC);
|
|
if ($bookmarkRow && $bookmarkRow['is_active'] === '1') {
|
|
$bookmarkStatus = true;
|
|
}
|
|
} catch (Throwable $e) {
|
|
error_log('[myclass_list.php] bookmark fetch error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return [
|
|
'id' => $contentId,
|
|
'content_id' => $contentId,
|
|
'url' => $videoUrl,
|
|
'category' => '마이클래스',
|
|
'category_code' => 'CA10001',
|
|
'subcate' => $quarterLabel,
|
|
'title' => $book['title'],
|
|
'description' => (string)($book['description'] ?? ''),
|
|
'goal_code' => (string)($book['_goal_code'] ?? ''),
|
|
'type' => 'comment',
|
|
'bookmark' => $bookmarkStatus,
|
|
'completed' => $isCompleted,
|
|
'completed_at' => $isCompleted ? '1' : null,
|
|
'has_comment' => $isCommentWritten,
|
|
'postit' => array_values($book['postit'] ?? []),
|
|
'keywords' => [],
|
|
'gauge' => 0,
|
|
];
|
|
}, $videoBooks), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
|
window.MYCLASS_COMPLETED_CONTENT_IDS = <?php echo json_encode(array_values($completedContentIds), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
|
window.MYCLASS_COMMENTED_CONTENT_IDS = <?php echo json_encode(array_values($commentedContentIds), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
|
</script>
|
|
<script src="/js/main/Videomodalmanager.js" defer></script>
|
|
<script src="/js/myclass_list.js" defer></script>
|
|
</body>
|
|
</html>
|