492 lines
23 KiB
PHP
492 lines
23 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../../bbs/auth.php';
|
|
edu_require_login();
|
|
|
|
include_once 'header.php';
|
|
require_once __DIR__ . '/../../bbs/db_conn.php';
|
|
|
|
$search_comp = $_GET['comp'] ?? '';
|
|
// 만약 사용자가 처음 페이지에 들어왔거나(GET값이 없음), '전체'를 누른 게 아니라면 초기값 설정
|
|
// 소속회사(comp)도 동일한 메커니즘 적용
|
|
$search_comp = $_GET['comp'] ?? '';
|
|
if (empty($search_comp) && !isset($_GET['comp'])) {
|
|
$search_comp = $sys_comp_code;
|
|
}
|
|
$search_year = $_GET['year'] ?? date('Y');
|
|
$search_dept = $_GET['dept'] ?? '';
|
|
$search_name = $_GET['name'] ?? '';
|
|
$search_comp_status = $_GET['comp_status'] ?? '';
|
|
|
|
$message_name = '';
|
|
|
|
|
|
$all_user_qty = 0;
|
|
$completed_qty = 0;
|
|
$incomplete_qty = 0;
|
|
$corp_list = [];
|
|
$rows = [];
|
|
|
|
try {
|
|
$pdo = db_conn();
|
|
|
|
// 1. 프로시저 호입 후 nextRowset으로 부분적 result set을 완전 소진
|
|
try {
|
|
$stmt_corp = $pdo->query("CALL proc_get_code2_list('CO100')");
|
|
$corp_list = $stmt_corp->fetchAll(PDO::FETCH_ASSOC);
|
|
while ($stmt_corp->nextRowset()) {
|
|
}
|
|
unset($stmt_corp);
|
|
} catch (Exception $eProc) {
|
|
$corp_list = [];
|
|
}
|
|
|
|
// 권한에 따른 법인 목록 제어
|
|
// LE10001: 전체권한 → 전체 법인 표시 (corp_list 그대로)
|
|
// LE10002: 법인권한 → 본인 법인($sys_comp_code)만 표시, 검색값도 고정
|
|
// 그 외: 법인 목록 없음
|
|
// 법인 초기값은 로그인한 사용자의 법인으로 설정
|
|
$message_name = $pdo->prepare("SELECT DESC01 FROM edu_codes WHERE base_code = 'AL100100'");
|
|
$message_name->execute();
|
|
$message_name = $message_name->fetchColumn();
|
|
|
|
|
|
if ($auth_level === 'LE10002') {
|
|
// 본인 법인만 필터링
|
|
$corp_list = array_filter($corp_list, fn($c) => $c['code'] === $sys_comp_code);
|
|
$corp_list = array_values($corp_list);
|
|
// 검색 법인도 강제 고정
|
|
$search_comp = $sys_comp_code;
|
|
} elseif ($auth_level !== 'LE10001') {
|
|
// 그 외 권한: 법인 목록 비움
|
|
$corp_list = [];
|
|
}
|
|
|
|
// 2. 전체 대상자 수 로그인한 법인과 소속회사가 같은 기준으로 계산, 퇴사자 제외
|
|
$stmt_all = $pdo->prepare("SELECT COUNT(DISTINCT member_id)
|
|
FROM edu_users
|
|
WHERE (end_date IS NULL OR (end_date > '1000-01-01' AND YEAR(end_date) >= ?))
|
|
AND (? = '' OR belong_comp = ?)
|
|
and sys_comp_code = belong_comp");
|
|
$stmt_all->execute([$search_year, $search_comp, $search_comp]);
|
|
$all_user_qty = (int) $stmt_all->fetchColumn();
|
|
|
|
// 3. 미수료 인원
|
|
$stmt_incomp = $pdo->prepare("SELECT COUNT(DISTINCT u.member_id)
|
|
FROM edu_users u
|
|
WHERE (u.end_date IS NULL OR (u.end_date > '1000-01-01' AND YEAR(u.end_date) >= ?))
|
|
AND (? = '' OR u.belong_comp = ?)
|
|
AND sys_comp_code = belong_comp
|
|
AND fn_get_progress_rate(u.sys_comp_code,?,u.member_id,'CA10003','') != 100");
|
|
|
|
$stmt_incomp->execute([$search_year, $search_comp, $search_comp, $search_year]);
|
|
$incomplete_qty = (int) $stmt_incomp->fetchColumn();
|
|
|
|
$completed_qty = max(0, $all_user_qty - $incomplete_qty);
|
|
|
|
// 4. G1 메인 쿼리 (? 위치 파라미터 사용으로 재사용 문제 없음)
|
|
$sql_inner = "SELECT a.sys_comp_code, a.belong_comp
|
|
, (SELECT code_name FROM edu_codes c WHERE c.group_code = 'CO100' AND c.code = a.belong_comp LIMIT 1) AS comp_name
|
|
, a.name, a.member_id
|
|
, a.dept_name
|
|
, IFNULL(b.formatted_tm, '00시간 00분') AS all_tm
|
|
, CASE WHEN a.member_id IN (
|
|
SELECT u2.member_id FROM edu_users u2 WHERE NOT EXISTS (
|
|
SELECT 1 FROM edu_contents c2 WHERE c2.category_code = 'CA10003' AND c2.base_year = ? AND c2.is_active = '1'
|
|
AND NOT EXISTS (SELECT 1 FROM edu_learning_histories h2 WHERE h2.content_id = c2.content_id AND h2.member_id = u2.member_id AND h2.sys_comp_code = u2.sys_comp_code AND h2.completed_at IS NOT NULL AND h2.completed_at != '')
|
|
)
|
|
) THEN '수료' ELSE '미수료' END AS completion_status
|
|
, fn_get_progress_rate(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS progress_rate -- 진행율
|
|
, fn_get_completion_date(a.sys_comp_code, ?, a.member_id, 'CA10003', '') AS completion_date -- 학습완료일
|
|
FROM edu_users a
|
|
LEFT JOIN (
|
|
SELECT
|
|
t.sys_comp_code,
|
|
t.member_id,
|
|
CONCAT(
|
|
LPAD(FLOOR(SUM(t.calc_tm)/3600), 2, '0'), '시간 ',
|
|
LPAD(FLOOR((SUM(t.calc_tm)%3600)/60), 2, '0'), '분'
|
|
) AS formatted_tm
|
|
FROM (
|
|
SELECT
|
|
z.sys_comp_code,
|
|
x.member_id,
|
|
CASE
|
|
WHEN x.completed_at IS NOT NULL AND x.completed_at <> '' THEN x.content_tm
|
|
ELSE x.watch_tm
|
|
END AS calc_tm
|
|
FROM edu_learning_histories x
|
|
JOIN edu_contents y ON x.content_id = y.content_id
|
|
JOIN edu_users z ON x.sys_comp_code = z.working_comp AND x.member_id = z.member_id
|
|
WHERE y.category_code = 'CA10003'
|
|
AND YEAR(x.first_viewed_at) = ?
|
|
) t
|
|
GROUP BY t.sys_comp_code, t.member_id
|
|
) b ON a.member_id = b.member_id AND a.sys_comp_code = b.sys_comp_code
|
|
WHERE (a.end_date IS NULL OR a.end_date = '' OR (a.end_date > '1000-01-01' AND YEAR(a.end_date) >= ?))
|
|
and a.sys_comp_code = a.belong_comp
|
|
AND (? = '' OR a.belong_comp = ?)
|
|
AND a.dept_name LIKE CONCAT('%', ?, '%')
|
|
AND a.name LIKE CONCAT('%', ?, '%')";
|
|
|
|
if ($search_comp_status === 'Y') {
|
|
$sql = "SELECT * FROM ($sql_inner) t WHERE completion_status = '수료'";
|
|
} elseif ($search_comp_status === 'N') {
|
|
$sql = "SELECT * FROM ($sql_inner) t WHERE completion_status = '미수료'";
|
|
} else {
|
|
$sql = "SELECT * FROM ($sql_inner) t";
|
|
}
|
|
|
|
$stmt_g1 = $pdo->prepare($sql);
|
|
// ? 순서: 1=completion_status(base_year), 2=progress_rate(year), 3=completion_date(year), 4=LEFT JOIN(first_viewed_at), 5=WHERE(end_date), 6=belong_comp 체크, 7=belong_comp 필터, 8=dept_name, 9=name
|
|
$stmt_g1->execute([$search_year, $search_year, $search_year, $search_year, $search_year, $search_comp, $search_comp, $search_dept, $search_name]);
|
|
$rows = $stmt_g1->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
} catch (Exception $e) {
|
|
$db_error = $e->getMessage();
|
|
}
|
|
|
|
?>
|
|
|
|
<main class="max-w-[1600px] mx-auto p-6">
|
|
|
|
|
|
<header class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
|
<h2 class="text-2xl font-bold text-gray-800 italic">법정의무교육</h2>
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
<button onclick="alert('준비중입니다.')"
|
|
class="px-4 py-2 bg-white border border-gray-200 rounded-md text-sm font-medium hover:bg-gray-50 flex items-center shadow-sm">
|
|
<i class="fa-solid fa-file-excel mr-2"></i>교육결과보고서
|
|
</button>
|
|
<button onclick="sendIncompleteNotification()"
|
|
class="px-4 py-2 bg-red-50 text-red-600 border border-red-100 rounded-md text-sm font-bold flex items-center hover:bg-red-100 shadow-sm transition">
|
|
<i class="fa-solid fa-bell mr-2"></i>미수료자 알림 (<?php echo $incomplete_qty; ?>명)
|
|
</button>
|
|
<button onclick="downloadExcel()"
|
|
class="px-4 py-2 bg-gray-100 text-gray-600 rounded-md text-sm font-medium hover:bg-gray-200 flex items-center transition">
|
|
<i class="fa-solid fa-download mr-2"></i>다운로드
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
|
|
<p class="text-xs font-bold text-gray-400 mb-1">전체 대상자</p>
|
|
<p class="text-3xl font-bold text-gray-800"><?php echo $all_user_qty; ?></p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
|
|
<p class="text-xs font-bold text-gray-400 mb-1">수료 완료</p>
|
|
<p class="text-3xl font-bold text-teal-600"><?php echo $completed_qty; ?></p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
|
|
<p class="text-xs font-bold text-gray-400 mb-1">미수료</p>
|
|
<p class="text-3xl font-bold text-red-500"><?php echo $incomplete_qty; ?>명</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 검색 조건 폼 -->
|
|
<form id="searchForm" method="GET" action="legal_edu.php"
|
|
class="bg-white p-5 rounded-xl border border-gray-200 shadow-sm mb-6 flex flex-wrap md:flex-row gap-4 items-end">
|
|
<div class="flex-1 min-w-[120px]">
|
|
<label for="comp" class="block text-xs font-bold text-gray-500 mb-2">법인 선택</label>
|
|
<?php if ($auth_level === 'LE10002'): ?>
|
|
<?php
|
|
// LE10002: 본인 법인명 표시 (변경 불가)
|
|
$fixed_corp_name = !empty($corp_list) ? htmlspecialchars($corp_list[0]['name']) : htmlspecialchars($sys_comp_code);
|
|
?>
|
|
<!-- 실제 전송값은 hidden으로, UI는 고정 텍스트로 표시 -->
|
|
<input type="hidden" name="comp" value="<?= htmlspecialchars($sys_comp_code) ?>">
|
|
<input type="text" value="<?= $fixed_corp_name ?>" readonly
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-100 text-gray-600 cursor-not-allowed">
|
|
<?php else: ?>
|
|
<select id="comp" name="comp" onchange="this.form.submit()"
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-50">
|
|
<option value="">전체</option>
|
|
<?php foreach ($corp_list as $corp): ?>
|
|
<option value="<?= htmlspecialchars($corp['code']) ?>" <?= $search_comp === $corp['code'] ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($corp['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="flex-1 min-w-[100px]">
|
|
<label for="year" class="block text-xs font-bold text-gray-500 mb-2">기준년도</label>
|
|
<input type="text" id="year" name="year" value="<?= htmlspecialchars($search_year) ?>" placeholder="YYYY"
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-50"
|
|
onkeydown="if(event.key==='Enter') this.form.submit();">
|
|
</div>
|
|
<div class="flex-1 min-w-[120px]">
|
|
<label for="dept" class="block text-xs font-bold text-gray-500 mb-2">부서</label>
|
|
<input type="text" id="dept" name="dept" value="<?= htmlspecialchars($search_dept) ?>"
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-50"
|
|
onkeydown="if(event.key==='Enter') this.form.submit();">
|
|
</div>
|
|
<div class="flex-1 min-w-[100px]">
|
|
<label for="name" class="block text-xs font-bold text-gray-500 mb-2">성명</label>
|
|
<input type="text" id="name" name="name" value="<?= htmlspecialchars($search_name) ?>"
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-50"
|
|
onkeydown="if(event.key==='Enter') this.form.submit();">
|
|
</div>
|
|
<div class="flex-1 min-w-[120px]">
|
|
<label for="comp_status" class="block text-xs font-bold text-gray-500 mb-2">이수여부</label>
|
|
<select id="comp_status" name="comp_status" onchange="this.form.submit()"
|
|
class="w-full border-gray-200 rounded-lg text-sm p-2.5 bg-gray-50">
|
|
<option value="">전체</option>
|
|
<option value="Y" <?= $search_comp_status === 'Y' ? 'selected' : '' ?>>이수</option>
|
|
<option value="N" <?= $search_comp_status === 'N' ? 'selected' : '' ?>>미이수</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
|
|
<section class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden mb-12">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm text-left">
|
|
<thead class="bg-gray-50 border-b border-gray-200 text-gray-500 font-bold italic">
|
|
<tr>
|
|
<th class="p-4 w-16 text-center">NO</th>
|
|
<th class="p-4">소속법인</th>
|
|
<th class="p-4">성명</th>
|
|
<th class="p-4">사번</th>
|
|
<th class="p-4">부서</th>
|
|
<th class="p-4">학습시간</th>
|
|
<th class="p-4 w-48">진도율</th>
|
|
<th class="p-4">교육이수일</th>
|
|
<th class="p-4 text-center">수료구분</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-100">
|
|
<?php if (count($rows) === 0): ?>
|
|
<tr>
|
|
<td colspan="9" class="p-8 text-center text-gray-400">데이터가 없습니다.</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php $idx = 1;
|
|
foreach ($rows as $row): ?>
|
|
<tr class="hover:bg-gray-50 transition cursor-pointer"
|
|
onclick="openDetailModal('<?= htmlspecialchars($row['sys_comp_code']) ?>', '<?= htmlspecialchars($row['member_id']) ?>', '<?= htmlspecialchars($row['name']) ?>', '<?= htmlspecialchars($row['dept_name']) ?>')">
|
|
<td class="p-4 text-center text-gray-500"><?= $idx++ ?></td>
|
|
<td class="p-4 text-gray-500"><?= htmlspecialchars($row['comp_name'] ?? '') ?></td>
|
|
<td class="p-4 font-bold text-gray-800"><?= htmlspecialchars($row['name'] ?? '') ?></td>
|
|
<td class="p-4 text-gray-500"><?= htmlspecialchars($row['member_id'] ?? '') ?></td>
|
|
<td class="p-4 text-gray-500"><?= htmlspecialchars($row['dept_name'] ?? '') ?></td>
|
|
<td class="p-4 text-gray-500"><?= htmlspecialchars($row['all_tm'] ?? '') ?></td>
|
|
<td class="p-4">
|
|
<div class="flex items-center space-x-2">
|
|
<?php
|
|
$progress = $row['progress_rate'] ?? '0%';
|
|
$progress_value = is_numeric($progress) ? (int) $progress : (int) preg_replace('/[^0-9]/', '', $progress);
|
|
$progress_value = min(100, max(0, $progress_value));
|
|
?>
|
|
<div class="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden">
|
|
<div class="bg-teal-500 h-full" style="width: <?= $progress_value ?>%;"></div>
|
|
</div>
|
|
<span class="text-[10px] font-bold text-teal-600"><?= $progress_value ?>%</span>
|
|
</div>
|
|
</td>
|
|
<td class="p-4 text-gray-600"><?= htmlspecialchars($row['completion_date'] ?? '-') ?></td>
|
|
<td class="p-4 text-center">
|
|
<?php if ($row['completion_status'] === '수료'): ?>
|
|
<span
|
|
class="px-3 py-1 bg-green-50 text-green-600 border border-green-100 rounded-full text-[11px] font-bold"><i
|
|
class="fa-solid fa-check-circle mr-1"></i>수료</span>
|
|
<?php else: ?>
|
|
<span class="px-3 py-1 bg-red-50 text-red-600 border border-red-100 rounded-full text-[11px] font-bold"><i
|
|
class="fa-solid fa-circle-xmark mr-1"></i>미수료</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- 상세 데이터 (G2) 모달 팝업 -->
|
|
<div id="detail-modal" class="fixed inset-0 bg-black/60 flex items-center justify-center z-[100] hidden p-4">
|
|
<div class="bg-white w-full max-w-4xl rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh]">
|
|
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50 shrink-0">
|
|
<h3 class="text-xl font-bold text-gray-800">법정의무교육 이수 상세</h3>
|
|
<button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600"><i
|
|
class="fa-solid fa-xmark text-xl"></i></button>
|
|
</div>
|
|
<div class="p-4 bg-white border-b border-gray-100 flex gap-6 text-sm font-bold text-gray-600 shrink-0">
|
|
<div>성명: <span id="modal-name" class="text-gray-900"></span></div>
|
|
<div>사번: <span id="modal-member-id" class="text-gray-900"></span></div>
|
|
<div>부서명: <span id="modal-dept" class="text-gray-900"></span></div>
|
|
</div>
|
|
<div class="flex-1 overflow-auto p-4 bg-gray-50/50">
|
|
<table class="w-full text-left border-collapse bg-white border border-gray-200">
|
|
<thead class="bg-gray-50 border-b border-gray-200 text-gray-500 font-bold">
|
|
<tr>
|
|
<th class="p-3 text-center w-12">NO</th>
|
|
<th class="p-3">교육과정명</th>
|
|
<th class="p-3 w-32">학습시간</th>
|
|
<th class="p-3 w-32">진도율</th>
|
|
<th class="p-3 w-32">학습완료일</th>
|
|
<th class="p-3 w-28 text-center">수료구분</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="detail-grid-body" class="divide-y divide-gray-100 text-sm">
|
|
<!-- AJAX JS INJECTION -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="p-4 bg-gray-50 border-t border-gray-100 flex justify-end shrink-0">
|
|
<button onclick="closeDetailModal()"
|
|
class="px-6 py-2 bg-gray-500 text-white rounded-lg font-bold shadow-lg">닫기</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function downloadExcel() {
|
|
const form = document.getElementById('searchForm');
|
|
const urlParams = new URLSearchParams(new FormData(form)).toString();
|
|
window.location.href = '../bbs/legal_edu_excel.php?' + urlParams;
|
|
}
|
|
|
|
function openDetailModal(sys_comp_code, member_id, name, dept) {
|
|
document.getElementById('modal-name').textContent = name;
|
|
document.getElementById('modal-member-id').textContent = member_id;
|
|
document.getElementById('modal-dept').textContent = dept;
|
|
document.getElementById('detail-modal').classList.remove('hidden');
|
|
|
|
const gridBody = document.getElementById('detail-grid-body');
|
|
gridBody.innerHTML = '<tr><td colspan="6" class="p-6 text-center text-gray-500">로딩 중...</td></tr>';
|
|
|
|
const year = document.getElementById('year').value;
|
|
|
|
const requestUrl = `../bbs/get_legal_edu_detail.php?sys_comp_code=${encodeURIComponent(sys_comp_code)}&member_id=${encodeURIComponent(member_id)}&year=${encodeURIComponent(year)}`;
|
|
console.log('[G2] requestUrl', requestUrl, { sys_comp_code, member_id, year });
|
|
|
|
fetch(requestUrl)
|
|
.then(res => res.text())
|
|
.then(text => {
|
|
console.log('[G2] raw response', text);
|
|
let data;
|
|
try {
|
|
data = JSON.parse(text);
|
|
} catch (e) {
|
|
console.error('[G2] JSON parse error', e, text);
|
|
gridBody.innerHTML = '<tr><td colspan="6" class="p-6 text-center text-red-500">JSON 파싱 오류 발생했습니다.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
gridBody.innerHTML = '';
|
|
if (!data.success || !data.items || data.items.length === 0) {
|
|
gridBody.innerHTML = '<tr><td colspan="6" class="p-6 text-center text-gray-400">학습 내역이 없습니다.</td></tr>';
|
|
return;
|
|
}
|
|
data.items.forEach((it, idx) => {
|
|
const tr = document.createElement('tr');
|
|
tr.className = 'hover:bg-gray-50 transition';
|
|
const stHtml = it.comp_status === '수료'
|
|
? `<span class="px-3 py-1 bg-green-50 text-green-600 border border-green-100 rounded-full text-[11px] font-bold"><i class="fa-solid fa-check-circle mr-1"></i>수료</span>`
|
|
: `<span class="px-3 py-1 bg-red-50 text-red-600 border border-red-100 rounded-full text-[11px] font-bold"><i class="fa-solid fa-circle-xmark mr-1"></i>미수료</span>`;
|
|
|
|
// progress_rate에서 숫자만 추출
|
|
let progressValue = 0;
|
|
if (it.progress_rate) {
|
|
const match = it.progress_rate.toString().match(/\d+/);
|
|
progressValue = match ? parseInt(match[0]) : 0;
|
|
}
|
|
progressValue = Math.min(100, Math.max(0, progressValue));
|
|
|
|
tr.innerHTML = `
|
|
<td class="p-3 text-center text-gray-400">${idx + 1}</td>
|
|
<td class="p-3 font-bold text-gray-800">${escapeHtml(it.title)}</td>
|
|
<td class="p-3 text-gray-600">${escapeHtml(it.learn_time)}</td>
|
|
<td class="p-3">
|
|
<div class="flex items-center space-x-2">
|
|
<div class="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden">
|
|
<div class="bg-teal-500 h-full" style="width: ${progressValue}%;"></div>
|
|
</div>
|
|
<span class="text-[10px] font-bold text-teal-600">${progressValue}%</span>
|
|
</div>
|
|
</td>
|
|
<td class="p-3 text-gray-600">${escapeHtml(it.completion_date) || '-'}</td>
|
|
<td class="p-3 text-center">${stHtml}</td>
|
|
`;
|
|
gridBody.appendChild(tr);
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error('데이터 로드 오류:', err);
|
|
gridBody.innerHTML = '<tr><td colspan="6" class="p-6 text-center text-red-500">데이터를 불러오는 중 오류가 발생했습니다.</td></tr>';
|
|
});
|
|
}
|
|
|
|
function closeDetailModal() {
|
|
document.getElementById('detail-modal').classList.add('hidden');
|
|
}
|
|
|
|
function sendIncompleteNotification() {
|
|
const incompleteCount = <?php echo $incomplete_qty; ?>;
|
|
if (incompleteCount === 0) {
|
|
alert('미수료자가 없습니다.');
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`<?php echo $message_name; ?> \n"위의 메시지로발송됩니다."\n미수료자 ${incompleteCount}명에게 알림을 발송하시겠습니까?`)) {
|
|
return;
|
|
}
|
|
|
|
// 현재 날짜 + 14일 계산
|
|
const today = new Date();
|
|
const endDate = new Date(today);
|
|
endDate.setDate(today.getDate() + 14);
|
|
const endDateStr = endDate.toISOString().split('T')[0];
|
|
|
|
// 알림 발송 요청
|
|
fetch('../bbs/notification_send.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
code: '100',
|
|
end_date: endDateStr,
|
|
action: 'send'
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`미수료자 알림이 성공적으로 발송되었습니다.\n발송 대상: ${data.sent_count || incompleteCount}명`);
|
|
} else {
|
|
alert(`알림 발송에 실패했습니다: ${data.message || '알 수 없는 오류'}`);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('알림 발송 오류:', err);
|
|
alert('알림 발송 중 오류가 발생했습니다.');
|
|
});
|
|
}
|
|
|
|
function downloadExcel() {
|
|
const params = new URLSearchParams({
|
|
comp: document.getElementById('comp').value || '',
|
|
year: document.getElementById('year').value || '',
|
|
dept: document.getElementById('dept').value || '',
|
|
name: document.getElementById('name').value || '',
|
|
comp_status: document.getElementById('comp_status').value || ''
|
|
});
|
|
window.location.href = `../bbs/legal_edu_excel.php?${params.toString()}`;
|
|
}
|
|
|
|
function escapeHtml(unsafe) {
|
|
return (unsafe || '').toString()
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|