Files
edu/admin/skin/settings.php
T

1384 lines
59 KiB
PHP

<?php
require_once __DIR__ . '/../../bbs/auth.php';
edu_require_login();
include_once 'header.php';
require_once __DIR__ . '/../../bbs/db_conn.php';
// PDO 연결 생성
$pdo = db_conn();
// 로그인 사용자 소속법인 코드
$loginBelongComp = edu_user_field('belong_comp', '');
$sys_comp_code = $_SESSION['sys_comp_code'] ?? '';
// 법인 리스트 조회
$compStmt = $pdo->prepare("CALL proc_get_code2_list('CO100')");
$compStmt->execute();
$companies = $compStmt->fetchAll(PDO::FETCH_ASSOC);
$compStmt->closeCursor();
// 권한 리스트 조회
$authStmt = $pdo->prepare("CALL proc_get_code_list('LE100')");
$authStmt->execute();
$authLevels = $authStmt->fetchAll(PDO::FETCH_ASSOC);
$authStmt->closeCursor();
// 키워드 리스트 조회
$kwStmt = $pdo->prepare("CALL proc_get_code_list('KW100')");
$kwStmt->execute();
$keywords = $kwStmt->fetchAll(PDO::FETCH_ASSOC);
$kwStmt->closeCursor();
// 제안상태 리스트 조회
$offerStmt = $pdo->prepare("CALL proc_get_code_list('OF100')");
$offerStmt->execute();
$offerStatuses = $offerStmt->fetchAll(PDO::FETCH_ASSOC);
$offerStmt->closeCursor();
// 알림 내용 조회
$notifStmt = $pdo->prepare("SELECT code, desc01 FROM edu_codes WHERE group_code = 'AL100' AND code IN ('100', '200', '900')");
$notifStmt->execute();
$notifications = $notifStmt->fetchAll(PDO::FETCH_KEY_PAIR);
$notifStmt->closeCursor();
?>
<main class="max-w-[1600px] mx-auto p-8">
<header class="mb-8">
<h2 class="text-3xl font-bold text-gray-800 tracking-tight">설정</h2>
</header>
<div class="flex flex-col lg:flex-row gap-8">
<!-- 사이드 탭 메뉴 -->
<aside class="w-full lg:w-64 flex-shrink-0">
<nav class="bg-white border border-gray-200 rounded-2xl p-2 space-y-1 shadow-sm sticky top-24">
<button id="btn-auth"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-user-shield w-5 text-center"></i>권한관리
</button>
<button id="btn-msg"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-bell w-5 text-center"></i>알림관리
</button>
<button id="btn-period"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-calendar-check w-5 text-center"></i>법정의무교육 기간설정
</button>
<button id="btn-code"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-code w-5 text-center"></i>코드관리
</button>
<button id="btn-keyword"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-hashtag w-5 text-center"></i>키워드설정
</button>
<button id="btn-offer"
class="w-full flex items-center gap-3 p-3.5 text-sm font-bold text-gray-500 rounded-xl transition hover:bg-gray-300">
<i class="fa-solid fa-lightbulb w-5 text-center"></i>컨텐츠 제안관리
</button>
</aside>
<!-- 탭 콘텐츠 영역 -->
<section class="flex-1 min-h-[600px]">
<!-- 권한관리 탭 -->
<div id="tab-auth" class="tab-content bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/30">
<h3 class="font-bold text-gray-800">권한관리</h3>
</div>
<!-- 검색 조건 -->
<div class="p-6 border-b border-gray-100 bg-gray-50/30">
<form id="search-form" class="grid grid-cols-2 md:grid-cols-6 gap-2">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">소속법인</label>
<select name="belong_comp" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
<option value="">전체</option>
<?php foreach ($companies as $c): ?>
<option value="<?= htmlspecialchars($c['code'], ENT_QUOTES, 'UTF-8'); ?>">
<?= htmlspecialchars($c['name'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">근무법인</label>
<select name="working_comp" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
<option value="">전체</option>
<?php foreach ($companies as $c): ?>
<option value="<?= htmlspecialchars($c['code'], ENT_QUOTES, 'UTF-8'); ?>"
<?= ($c['code'] === $sys_comp_code) ? 'selected' : ''; ?>>
<?= htmlspecialchars($c['name'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">사번</label>
<input name="member_id" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">이름</label>
<input name="name" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">부서</label>
<input name="dept_name" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">권한</label>
<select name="auth_level" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
<option value="">전체</option>
<?php foreach ($authLevels as $a): ?>
<option value="<?= htmlspecialchars($a['code'], ENT_QUOTES, 'UTF-8'); ?>">
<?= htmlspecialchars($a['name'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endforeach; ?>
</select>
</div>
</form>
<div class="flex justify-end mt-4">
<button id="search-btn" class="px-4 py-2 bg-gray-800 text-white rounded-lg font-bold text-sm">검색</button>
</div>
</div>
<!-- 테이블 -->
<table class="w-full text-sm text-left">
<thead class="bg-gray-50/50 text-gray-500 font-bold">
<tr>
<th class="p-4">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">부서</th>
<th class="p-4">권한</th>
<th class="p-4 text-center">관리</th>
</tr>
</thead>
<tbody id="user-table-body" class="divide-y divide-gray-50">
<!-- JS로 로드 -->
</tbody>
</table>
</div>
<!-- 법정의무교육 기간설정 탭 -->
<div id="tab-period"
class="tab-content hidden bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-100 bg-gray-50/30">
<h3 class="font-bold text-gray-800">법정의무교육 기간설정</h3>
</div>
<div class="p-6">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<p class="text-sm text-blue-700">법정의무교육 기간을 설정합니다. 설정된 기간 동안 해당 교육과정이 활성화됩니다.</p>
</div>
<form class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">기준년도</label>
<input id="base_year" type="text" placeholder="예: 2026"
class="w-full border border-gray-300 rounded-lg p-3 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">시작일</label>
<input id="start_date" type="date"
class="w-full border border-gray-300 rounded-lg p-3 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">종료일</label>
<input id="end_date" type="date"
class="w-full border border-gray-300 rounded-lg p-3 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none">
</div>
</form>
<div class="flex justify-end mb-6">
<button id="save-period-btn"
class="px-6 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-sm flex items-center shadow-md hover:bg-[#0d3a2f] transition">
<i class="fa-solid fa-save mr-2"></i>저장
</button>
</div>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
<p class="text-sm font-bold text-gray-700 mb-2">현재 설정된 기간</p>
<p id="current_period" class="text-lg text-gray-800 font-mono">현재 설정된 기간: </p>
</div>
</div>
</div>
<!-- 알람관리 탭 -->
<div id="tab-msg" class="tab-content hidden space-y-6">
<!-- 미수료자 알람 -->
<div class="bg-white border border-gray-200 rounded-2xl p-6 shadow-sm">
<h3 class="font-bold text-gray-800 mb-2">미수료자 알람</h3>
<div class="mb-4">
<label class="block text-xs font-bold text-gray-700 mb-1">알람종료일</label>
<input type="date" id="end_date_100"
class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm">
</div>
<p class="text-xs text-gray-400 mb-4">사용 가능한 변수: <code class="bg-gray-100 px-1 py-0.5 rounded">{과정명}</code>,
<code class="bg-gray-100 px-1 py-0.5 rounded">{종료일}</code></p>
<textarea id="content_100"
class="w-full border border-gray-300 rounded-xl p-4 bg-gray-50 text-sm focus:ring-2 focus:ring-teal-500 outline-none resize-none"
rows="4"><?= htmlspecialchars($notifications['100'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<div class="flex justify-end gap-2 mt-4">
<button onclick="saveNotification('100')"
class="px-6 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-[#0d3a2f] transition">
<i class="fa-solid fa-save mr-2"></i>저장
</button>
<button onclick="sendNotification('100')"
class="px-6 py-2 bg-blue-600 text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-blue-700 transition">
<i class="fa-solid fa-paper-plane mr-2"></i>알람발송
</button>
</div>
</div>
<!-- 법정의무교육 안내 -->
<div class="bg-white border border-gray-200 rounded-2xl p-6 shadow-sm">
<h3 class="font-bold text-gray-800 mb-2">법정의무교육 안내</h3>
<div class="mb-4">
<label class="block text-xs font-bold text-gray-700 mb-1">알람종료일</label>
<input type="date" id="end_date_200"
class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm">
</div>
<p class="text-xs text-gray-400 mb-4">사용 가능한 변수: <code class="bg-gray-100 px-1 py-0.5 rounded">{학습자명}</code>,
<code class="bg-gray-100 px-1 py-0.5 rounded">{시작일}</code>, <code
class="bg-gray-100 px-1 py-0.5 rounded">{종료일}</code></p>
<textarea id="content_200"
class="w-full border border-gray-300 rounded-xl p-4 bg-gray-50 text-sm focus:ring-2 focus:ring-teal-500 outline-none resize-none"
rows="4"><?= htmlspecialchars($notifications['200'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<div class="flex justify-end gap-2 mt-4">
<button onclick="saveNotification('200')"
class="px-6 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-[#0d3a2f] transition">
<i class="fa-solid fa-save mr-2"></i>저장
</button>
<button onclick="sendNotification('200')"
class="px-6 py-2 bg-blue-600 text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-blue-700 transition">
<i class="fa-solid fa-paper-plane mr-2"></i>알람발송
</button>
</div>
</div>
<!-- 전체 공지 알람 -->
<div class="bg-white border border-gray-200 rounded-2xl p-6 shadow-sm">
<h3 class="font-bold text-gray-800 mb-2">전체 공지 알람</h3>
<div class="mb-4">
<label class="block text-xs font-bold text-gray-700 mb-1">알람종료일</label>
<input type="date" id="end_date_900"
class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm">
</div>
<p class="text-xs text-gray-400 mb-4">사용 가능한 변수: <code class="bg-gray-100 px-1 py-0.5 rounded">{학습자명}</code>
</p>
<textarea id="content_900"
class="w-full border border-gray-300 rounded-xl p-4 bg-gray-50 text-sm focus:ring-2 focus:ring-teal-500 outline-none resize-none"
rows="4"><?= htmlspecialchars($notifications['900'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<div class="flex justify-end gap-2 mt-4">
<button onclick="saveNotification('900')"
class="px-6 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-[#0d3a2f] transition">
<i class="fa-solid fa-save mr-2"></i>저장
</button>
<button onclick="sendNotification('900')"
class="px-6 py-2 bg-blue-600 text-white rounded-lg font-bold text-xs flex items-center shadow-md hover:bg-blue-700 transition">
<i class="fa-solid fa-paper-plane mr-2"></i>알람발송
</button>
</div>
</div>
</div>
<!-- 코드관리 탭 -->
<div id="tab-code"
class="tab-content hidden bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-100 bg-gray-50/30">
<h3 class="font-bold text-gray-800 mb-4">코드관리</h3>
</div>
<!-- 코드마스터 등록 -->
<div class="p-6 border-b border-gray-100">
<h4 class="font-bold text-gray-700 mb-4">코드 List</h4>
<!-- 삭제된 그룹 저장 폼 -->
<!-- 그룹 리스트 테이블 (5행 표시+스크롤) -->
<div class="max-h-[240px] overflow-auto border rounded-lg relative">
<table id="group-table" class="w-full text-sm text-left">
<thead class="sticky top-0 bg-gray-50 text-gray-500 font-bold z-10">
<tr>
<th class="p-2">메인코드</th>
<th class="p-2">코드명</th>
<th class="p-2">사용여부</th>
<th class="p-2">비고</th>
<th class="p-2">설명1</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50" id="group-table-body">
<!-- JS 로드 -->
</tbody>
</table>
</div>
</div>
<div class="p-6">
<h4 class="font-bold text-gray-700 mb-4 flex justify-between items-center">
코드상세 등록
<button id="new-code-btn"
class="px-3 py-1 bg-green-600 text-white rounded text-xs hover:bg-green-700 transition">신규</button>
</h4>
<!-- 코드 상세 리스트 테이블 -->
<div class="mt-6 p-4 bg-white border border-gray-200 rounded-lg overflow-x-auto">
<table id="code-table" class="w-full text-sm text-left">
<thead class="sticky top-0 bg-gray-50 text-gray-500 font-bold z-20">
<tr>
<th class="p-2">서브코드</th>
<th class="p-2">코드명</th>
<th class="p-2">사용여부</th>
<th class="p-2">설명1</th>
<th class="p-2 text-center">관리</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50" id="code-table-body">
<!-- JS 로드 -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 키워드설정 탭 -->
<div id="tab-keyword"
class="tab-content hidden bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/30">
<h3 class="font-bold text-gray-800">키워드설정</h3>
</div>
<form id="keyword-form" onsubmit="saveKeywords(event)">
<input type="hidden" name="keywords" id="kw_selected">
<div class="p-8">
<div class="text-sm text-gray-600 mb-4">최대 2개의 키워드만 선택 가능합니다.</div>
<div class="flex flex-wrap justify-center gap-3 relative z-10" id="keyword-container-modal">
<?php foreach ($keywords as $kw): ?>
<button type="button"
class="kw-btn flex items-center justify-between gap-2 px-5 py-2.5 rounded-full border border-gray-200 bg-white text-gray-400 text-sm font-bold hover:border-orange-300 hover:text-orange-500 transition shadow-sm data-[active=true]:bg-orange-600 data-[active=true]:border-orange-700 data-[active=true]:text-white data-[active=true]:shadow-md"
data-code="<?= htmlspecialchars($kw['code'], ENT_QUOTES, 'UTF-8'); ?>">
#<?= htmlspecialchars($kw['name'], ENT_QUOTES, 'UTF-8'); ?>
<span class="kw-icon"><i class="fa-solid fa-plus text-xs"></i></span>
</button>
<?php endforeach; ?>
</div>
</div>
<div class="p-4 bg-gray-50 border-t border-gray-100 flex justify-end gap-2">
<button type="submit" class="px-6 py-2 bg-orange-600 text-white rounded-lg font-bold shadow-lg">저장</button>
</div>
</form>
</div>
<!-- 컨텐츠 제안관리 탭 -->
<div id="tab-offer"
class="tab-content hidden bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/30">
<h3 class="font-bold text-gray-800">컨텐츠 제안관리</h3>
</div>
<!-- 검색 조건 -->
<div class="p-6 border-b border-gray-100 bg-gray-50/30">
<form id="offer-search-form" action="" method="get" class="grid grid-cols-2 md:grid-cols-5 gap-2">
<button type="submit" style="display:none;" aria-hidden="true"></button>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안일자 (시작)</label>
<input name="offer_date_fr" type="date"
class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안일자 (종료)</label>
<input name="offer_date_to" type="date"
class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안자</label>
<input name="member_id" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안이유</label>
<input name="reason" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안상태</label>
<select name="status_code" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
<option value="">전체</option>
<?php foreach ($offerStatuses as $os): ?>
<option value="<?= htmlspecialchars($os['code'], ENT_QUOTES, 'UTF-8'); ?>">
<?= htmlspecialchars($os['name'], ENT_QUOTES, 'UTF-8'); ?></option>
<?php endforeach; ?>
</select>
</div>
</form>
<div class="flex justify-end mt-4">
<button id="offer-search-btn"
class="px-4 py-2 bg-gray-800 text-white rounded-lg font-bold text-sm">검색</button>
</div>
</div>
<!-- 그리드 G1 -->
<div class="p-6">
<table class="w-full text-sm text-left">
<thead class="bg-gray-50/50 text-gray-500 font-bold">
<tr>
<th class="p-4 w-16 text-center">NO</th>
<th class="p-4 w-32">제안ID</th>
<th class="p-4 w-32">제안일자</th>
<th class="p-4 w-16 text-center">URL</th>
<th class="p-4">추천이유</th>
<th class="p-4 w-28">제안상태</th>
<th class="p-4">반려사유</th>
<th class="p-4 w-28">제안자</th>
</tr>
</thead>
<tbody id="offer-table-body" class="divide-y divide-gray-50">
<!-- JS로 로드 -->
</tbody>
</table>
</div>
<!-- 프리폼 제거됨 (모달로 변경) -->
</div>
</section>
</div>
</main>
<script>
console.log('settings.php script start');
let selectedGroup = '';
let authLevels = [];
try {
authLevels = <?php echo json_encode($authLevels, JSON_UNESCAPED_UNICODE); ?> || [];
} catch (e) {
console.error('authLevels parse error', e);
authLevels = [];
}
let offerStatuses = [];
try {
offerStatuses = <?php echo json_encode($offerStatuses, JSON_UNESCAPED_UNICODE); ?> || [];
} catch (e) {
console.error('offerStatuses parse error', e);
offerStatuses = [];
}
function showTab(tabId) {
console.log('showTab 호출:', tabId);
try {
// 안전 검사
const tab = document.getElementById(tabId);
if (!tab) {
console.error('showTab: 탭 요소를 찾을 수 없습니다:', tabId);
return;
}
// 모든 탭 콘텐츠 숨기기
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
// 선택된 탭 보이기
tab.classList.remove('hidden');
// 사이드 버튼 활성화 상태 변경
document.querySelectorAll('aside nav button').forEach(btn => {
btn.classList.remove('text-white', 'bg-[#114b3d]');
btn.classList.add('text-gray-500');
});
const btnId = 'btn-' + tabId.split('-')[1];
const activeBtn = document.getElementById(btnId);
if (activeBtn) {
activeBtn.classList.add('text-white', 'bg-[#114b3d]');
activeBtn.classList.remove('text-gray-500');
}
// 키워드 탭 선택 시 키워드 로드
if (tabId === 'tab-keyword') {
loadSelectedKeywords();
}
// 컨텐츠 제안관리 탭 선택 시 자동 조회
if (tabId === 'tab-offer') {
searchOffers();
}
} catch (e) {
console.error('showTab 에러', e);
}
}
// 법정의무교육 기간 저장
async function savePeriod() {
console.log('savePeriod 함수 실행');
const baseYear = document.getElementById('base_year').value;
const startDate = document.getElementById('start_date').value;
const endDate = document.getElementById('end_date').value;
if (!baseYear || !startDate || !endDate) {
alert('모든 필드를 입력하세요.');
return;
}
try {
const resp = await fetch('../bbs/save_period.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ base_year: baseYear, start_date: startDate, end_date: endDate })
});
const data = await resp.json();
if (data.success) {
alert('저장되었습니다.');
loadCurrentPeriod();
// 헤더의 법정의무교육 기간 업데이트
updateHeaderPeriod(startDate, endDate);
} else {
alert('저장 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('저장에 실패했습니다.');
}
}
// 전역에 노출
window.savePeriod = savePeriod;
// 헤더의 법정의무교육 기간 업데이트
function updateHeaderPeriod(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
const startStr = start.getFullYear() + '.' + String(start.getMonth() + 1).padStart(2, '0') + '.' + String(start.getDate()).padStart(2, '0');
const endStr = end.getFullYear() + '.' + String(end.getMonth() + 1).padStart(2, '0') + '.' + String(end.getDate()).padStart(2, '0');
const periodText = startStr + ' ~ ' + endStr;
// 헤더에서 법정의무교육 기간 부분 찾기
const headerElements = document.querySelectorAll('nav span.font-bold');
headerElements.forEach(el => {
if (el.textContent.includes('~')) {
el.textContent = periodText;
}
});
}
// 코드 모달 열기(신규 or 수정)
function openModal(data = null) {
console.log('openModal 호출, data=', data);
const groupField = document.getElementById('modal-group');
const codeField = document.getElementById('modal-code');
const titleField = document.getElementById('modal-title');
const nameField = document.getElementById('modal-code-name');
const activeField = document.getElementById('modal-active');
const descField = document.getElementById('modal-desc01');
if (data) {
// 수정 모드
titleField.textContent = '코드 상세 수정';
groupField.value = data.group;
codeField.value = data.code;
codeField.readOnly = true;
codeField.className = 'w-full border border-gray-300 rounded-lg p-2 bg-gray-100 text-sm outline-none'; // readonly 스타일
nameField.value = data.name || '';
activeField.checked = data.active;
descField.value = data.desc || '';
} else {
// 신규 모드
if (!selectedGroup) {
alert('먼저 메인코드를 선택하세요.');
return;
}
titleField.textContent = '코드 상세 등록';
groupField.value = selectedGroup;
codeField.value = '';
codeField.readOnly = false;
codeField.className = 'w-full border border-gray-300 rounded-lg p-2 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none';
nameField.value = '';
activeField.checked = true;
descField.value = '';
}
document.getElementById('code-modal').classList.remove('hidden');
}
function closeModal() {
document.getElementById('code-modal').classList.add('hidden');
}
async function saveModalCode() {
const group = document.getElementById('modal-group').value;
const code = document.getElementById('modal-code').value.trim();
const name = document.getElementById('modal-code-name').value.trim();
const active = document.getElementById('modal-active').checked ? '1' : '0';
const desc = document.getElementById('modal-desc01').value.trim();
if (!code) {
alert('서브코드를 입력하세요.');
return;
}
const formData = new FormData();
formData.append('group_code', group);
formData.append('code', code);
formData.append('code_name', name);
formData.append('is_active', active);
formData.append('desc01', desc);
formData.append('base_code', group + code);
try {
const resp = await fetch('../bbs/save_code.php', {
method: 'POST',
body: formData
});
const data = await resp.json();
if (data.success) {
alert('저장되었습니다.');
loadCodes(group);
closeModal();
} else {
alert('저장 실패');
}
} catch (err) {
alert('저장에 실패했습니다.');
}
}
// 현재 설정된 기간 로드
async function loadCurrentPeriod() {
const baseYear = document.getElementById('base_year').value;
if (!baseYear) return;
try {
const resp = await fetch(`../bbs/get_period.php?base_year=${baseYear}`);
const data = await resp.json();
const periodText = document.getElementById('current_period');
const startInput = document.getElementById('start_date');
const endInput = document.getElementById('end_date');
if (data.start_date && data.end_date) {
periodText.textContent = `현재 설정된 기간: ${data.start_date} ~ ${data.end_date}`;
if (startInput) startInput.value = data.start_date;
if (endInput) endInput.value = data.end_date;
} else {
periodText.textContent = '현재 설정된 기간: ';
if (startInput) startInput.value = '';
if (endInput) endInput.value = '';
}
} catch (err) {
console.error('기간 로드 실패', err);
}
}
// 기준년도 변경 시 기간 로드
// 코드그룹 저장
async function saveCodeGroup() {
const form = document.getElementById('code-group-form');
const formData = new FormData(form);
try {
const resp = await fetch('../bbs/save_code_group.php', {
method: 'POST',
body: formData
});
const data = await resp.json();
if (data.success) {
alert('저장되었습니다.');
loadCodeGroups();
} else {
alert('저장 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('저장에 실패했습니다.');
}
}
// 코드그룹 목록 로드
async function loadCodeGroups() {
try {
const resp = await fetch('../bbs/get_code_groups.php');
const data = await resp.json();
const tbody = document.getElementById('group-table-body');
tbody.innerHTML = '';
if (data.groups && data.groups.length > 0) {
data.groups.forEach(g => {
const activeText = g.is_active === '1' ? 'Y' : (g.is_active === '0' ? 'N' : g.is_active);
const tr = document.createElement('tr');
tr.className = 'hover:bg-teal-50/30 transition';
tr.innerHTML = `
<td class="p-2">${escapeHtml(g.group_code)}</td>
<td class="p-2">${escapeHtml(g.group_name)}</td>
<td class="p-2">${activeText}</td>
<td class="p-2">${escapeHtml(g.comment || '-')}</td>
<td class="p-2">${escapeHtml(g.desc01 || '-')}</td>
`;
tbody.appendChild(tr);
// 클릭 시 그룹 선택
tr.addEventListener('click', () => {
document.querySelectorAll('#group-table tbody tr').forEach(r => r.classList.remove('bg-teal-100'));
tr.classList.add('bg-teal-100');
selectedGroup = g.group_code;
loadCodes(selectedGroup);
});
});
} else {
tbody.innerHTML = '<tr><td colspan="5" class="p-4 text-center text-gray-400">등록된 코드그룹이 없습니다</td></tr>';
}
} catch (err) {
console.error('코드그룹 로드 실패', err);
}
}
// 코드 상세 목록 로드
async function loadCodes(groupCode = '') {
const tbody = document.getElementById('code-table-body');
if (!groupCode) {
tbody.innerHTML = '<tr><td colspan="5" class="p-4 text-center text-gray-400">메인코드를 선택하세요</td></tr>';
return;
}
try {
const params = `?group_code=${encodeURIComponent(groupCode)}`;
const resp = await fetch(`../bbs/get_codes.php${params}`);
const data = await resp.json();
tbody.innerHTML = '';
if (data.codes && data.codes.length > 0) {
data.codes.forEach(c => {
const activeText = c.is_active === '1' ? 'Y' : (c.is_active === '0' ? 'N' : c.is_active);
const tr = document.createElement('tr');
tr.className = 'hover:bg-teal-50/30 transition cursor-pointer';
tr.innerHTML = `
<td class="p-2">${escapeHtml(c.code)}</td>
<td class="p-2">${escapeHtml(c.code_name || '-')}</td>
<td class="p-2">${activeText}</td>
<td class="p-2">${escapeHtml(c.desc01 || '-')}</td>
<td class="p-2 text-center flex justify-center gap-1">
<button onclick='event.stopPropagation(); editCodeRow(${JSON.stringify(c)})' class="px-2 py-1 bg-[#114b3d] text-white rounded text-xs hover:bg-[#0d3a2f] transition">수정</button>
<button onclick="event.stopPropagation(); deleteCode('${c.group_code}','${c.code}')" class="px-2 py-1 bg-red-600 text-white rounded text-xs hover:bg-red-700 transition">삭제</button>
</td>
`;
tbody.appendChild(tr);
// row 클릭으로 수정 모달
tr.addEventListener('click', () => {
editCodeRow(c);
});
});
} else {
tbody.innerHTML = '<tr><td colspan="5" class="p-4 text-center text-gray-400">검색된 코드가 없습니다</td></tr>';
}
} catch (err) {
console.error('코드 로드 실패', err);
}
}
// 행 데이터 구조를 openModal에 맞게 변환
function editCodeRow(c) {
openModal({
group: c.group_code,
code: c.code,
name: c.code_name,
active: c.is_active === '1' || c.is_active === 'Y',
desc: c.desc01
});
}
async function deleteCode(groupCode, code) {
console.log('deleteCode called with groupCode:', groupCode, 'code:', code);
if (!confirm('정말 삭제하시겠습니까?')) return;
if (!groupCode || !code) {
console.error('deleteCode 호출 시 파라미터 누락', groupCode, code);
alert('삭제할 코드 정보가 부족합니다.');
return;
}
try {
const resp = await fetch('../bbs/delete_code.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ group_code: groupCode, code: code })
});
const data = await resp.json();
if (data.success) {
loadCodes(groupCode);
} else {
alert('삭제 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
console.error('deleteCode 에러', err);
alert('삭제에 실패했습니다. 콘솔을 확인하세요.');
}
}
// 제안 검색
async function searchOffers() {
// 검색 시 제안 모달 닫기
closeOfferModal();
const form = document.getElementById('offer-search-form');
const formData = new FormData(form);
const params = new URLSearchParams(formData);
const url = `../bbs/get_offers.php?${params}`;
console.log('searchOffers 호출, URL=', url);
try {
const resp = await fetch(url);
if (!resp.ok) {
alert('서버 응답 오류: ' + resp.status);
return;
}
const text = await resp.text();
let data;
try {
data = JSON.parse(text);
} catch (parseErr) {
console.error('searchOffers JSON 파싱 실패', parseErr, '응답 본문:', text);
alert('서버에서 유효한 JSON을 받지 못했습니다. 콘솔을 확인하세요.');
return;
}
if (data.success === false) {
alert('검색 실패: ' + (data.message || data.error || '알 수 없는 오류'));
return;
}
const tbody = document.getElementById('offer-table-body');
tbody.innerHTML = '';
if (data.offers && data.offers.length > 0) {
data.offers.forEach((offer, index) => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-teal-50/30 transition cursor-pointer';
tr.innerHTML = `
<td class="p-4 text-center whitespace-nowrap">${index + 1}</td>
<td class="p-4 font-mono text-xs whitespace-nowrap">${escapeHtml(offer.offer_id)}</td>
<td class="p-4 text-gray-500 whitespace-nowrap">${escapeHtml(offer.offer_date || '-')}</td>
<td class="p-4 text-center whitespace-nowrap">
<a href="${escapeHtml(offer.reference_url)}" target="_blank" title="${escapeHtml(offer.reference_url)}" class="text-blue-600 hover:text-blue-800 transition">
<i class="fa-solid fa-link"></i>
</a>
</td>
<td class="p-4 whitespace-nowrap overflow-hidden text-ellipsis max-w-[200px]" title="${escapeHtml(offer.reason)}">${escapeHtml(offer.reason)}</td>
<td class="p-4 whitespace-nowrap">${escapeHtml(offer.status_name || offer.status_code)}</td>
<td class="p-4 whitespace-nowrap overflow-hidden text-ellipsis max-w-[200px]" title="${escapeHtml(offer.reason_return || '')}">${escapeHtml(offer.reason_return || '')}</td>
<td class="p-4 whitespace-nowrap">${escapeHtml(offer.name || offer.member_id)}</td>
`;
tbody.appendChild(tr);
// 행 클릭 시 상세 표시
tr.addEventListener('click', () => {
selectOffer(offer);
});
});
} else {
tbody.innerHTML = '<tr><td colspan="7" class="p-6 text-center text-gray-400">검색된 제안이 없습니다</td></tr>';
}
} catch (err) {
console.error('searchOffers 에러', err);
alert('검색에 실패했습니다. 콘솔을 확인하세요.');
}
}
// 제안 선택 (모달 표시)
function selectOffer(offer) {
document.getElementById('detail-offer-id').value = offer.offer_id;
document.getElementById('detail-offer-date').value = offer.offer_date || '-';
document.getElementById('detail-proposer').value = (offer.name || offer.member_id) + ' (' + offer.member_id + ')';
document.getElementById('detail-url').value = offer.reference_url;
document.getElementById('detail-reason').value = offer.reason;
document.getElementById('detail-status').value = offer.status_code;
document.getElementById('detail-reason-return').value = offer.reason_return || '';
toggleReasonReturn();
document.getElementById('offer-modal').classList.remove('hidden');
}
// 제안 모달 닫기
function closeOfferModal() {
const modal = document.getElementById('offer-modal');
if (modal) {
modal.classList.add('hidden');
// 필드 초기화
document.getElementById('detail-offer-id').value = '';
document.getElementById('detail-offer-date').value = '';
document.getElementById('detail-proposer').value = '';
document.getElementById('detail-url').value = '';
document.getElementById('detail-reason').value = '';
document.getElementById('detail-status').value = '';
document.getElementById('detail-reason-return').value = '';
}
}
// 반려사유 활성화/비활성화
function toggleReasonReturn() {
const status = document.getElementById('detail-status').value;
const reasonReturn = document.getElementById('detail-reason-return');
reasonReturn.disabled = status !== 'OF10004';
reasonReturn.classList.toggle('bg-gray-100', status !== 'OF10004');
reasonReturn.classList.toggle('bg-white', status === 'OF10004');
}
// 제안 저장
async function saveOffer() {
const offerId = document.getElementById('detail-offer-id').value;
const status = document.getElementById('detail-status').value;
const reasonReturn = document.getElementById('detail-reason-return').value;
if (!offerId) {
alert('제안을 선택하세요.');
return;
}
try {
const resp = await fetch('../bbs/update_offer.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ offer_id: offerId, status_code: status, reason_return: reasonReturn })
});
const data = await resp.json();
if (data.success) {
alert('저장되었습니다.');
closeOfferModal(); // 모달 닫기
searchOffers(); // 목록 새로고침
} else {
alert('저장 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('저장에 실패했습니다.');
}
}
async function searchUsers() {
const form = document.getElementById('search-form');
const formData = new FormData(form);
const params = new URLSearchParams(formData);
const url = `../bbs/get_users.php?${params}`;
console.log('searchUsers 호출, URL=', url);
try {
const resp = await fetch(url);
if (!resp.ok) {
alert('서버 응답 오류: ' + resp.status);
return;
}
const data = await resp.json();
if (data.success === false) {
// 백엔드에서 실패 응답
alert('검색 실패: ' + (data.message || data.error || '알 수 없는 오류'));
return;
}
const tbody = document.getElementById('user-table-body');
tbody.innerHTML = '';
if (data.users && data.users.length > 0) {
data.users.forEach((user, index) => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-teal-50/30 transition';
tr.innerHTML = `
<td class="p-4">${index + 1}</td>
<td class="p-4">${escapeHtml(user.belong_comp_name || user.belong_comp || '-')}</td>
<td class="p-4">${escapeHtml(user.working_comp_name || user.working_comp || '-')}</td>
<td class="p-4 font-mono text-xs">${escapeHtml(user.member_id)}</td>
<td class="p-4 font-bold">${escapeHtml(user.name)}</td>
<td class="p-4">${escapeHtml(user.rank_name || '-')}</td>
<td class="p-4">${escapeHtml(user.dept_name || '-')}</td>
<td class="p-4">${escapeHtml(user.auth_level_name || user.auth_level || '-')}</td>
<td class="p-4 text-center">
<button onclick='editAuth(this, ${JSON.stringify(user.member_id)}, ${JSON.stringify(user.auth_level)})' class="px-3 py-1 bg-teal-800 text-white rounded text-xs hover:bg-teal-900 transition">수정</button>
</td>
`;
tbody.appendChild(tr);
});
} else {
tbody.innerHTML = '<tr><td colspan="9" class="p-6 text-center text-gray-400">검색된 사용자가 없습니다</td></tr>';
}
} catch (err) {
console.error('searchUsers 에러', err);
alert('검색에 실패했습니다. 콘솔을 확인하세요.');
}
}
// escapeHtml 함수
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 권한 수정
function editAuth(button, memberId, currentAuth) {
const row = button.closest('tr');
const authCell = row.cells[7]; // 권한 셀 (0-based index: 0=NO,1=소속,2=근무,3=사번,4=이름,5=직위,6=부서,7=권한,8=관리)
const originalHtml = authCell.innerHTML;
const select = document.createElement('select');
select.className = 'border border-gray-200 rounded p-1 text-sm';
authLevels.forEach(comp => {
const option = document.createElement('option');
option.value = comp.code;
option.textContent = comp.name;
if (comp.code === currentAuth) option.selected = true;
select.appendChild(option);
});
authCell.innerHTML = '';
authCell.appendChild(select);
// 버튼 변경
const actionCell = row.cells[8];
actionCell.innerHTML = `
<button onclick="saveAuth('${memberId}', this)" class="px-2 py-1 bg-green-600 text-white rounded text-xs mr-1">저장</button>
<button onclick="cancelEdit(this, '${originalHtml}')" class="px-2 py-1 bg-gray-500 text-white rounded text-xs">취소</button>
`;
}
// 권한 저장
async function saveAuth(memberId, button) {
const row = button.closest('tr');
const select = row.cells[7].querySelector('select');
const newAuth = select.value;
try {
const resp = await fetch('../bbs/update_user_auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ member_id: memberId, auth_level: newAuth })
});
const data = await resp.json();
if (data.success) {
// 성공 시 셀 업데이트
const authName = authLevels.find(c => c.code === newAuth)?.name || newAuth;
row.cells[7].innerHTML = escapeHtml(authName);
row.cells[8].innerHTML = '<button onclick="editAuth(this, \'' + memberId + '\', \'' + newAuth + '\')" class="px-3 py-1 bg-teal-800 text-white rounded text-xs hover:bg-teal-900 transition">수정</button>';
alert('권한이 수정되었습니다.');
} else {
alert('수정 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('수정에 실패했습니다.');
}
}
// 수정 취소
function cancelEdit(button, originalHtml) {
const row = button.closest('tr');
row.cells[7].innerHTML = originalHtml;
row.cells[8].innerHTML = '<button onclick="editAuth(this, \'' + row.cells[3].textContent.trim() + '\', \'' + row.cells[7].textContent.trim() + '\')" class="px-3 py-1 bg-teal-800 text-white rounded text-xs hover:bg-teal-900 transition">수정</button>';
}
// 페이지 로드 시 초기 검색
document.addEventListener('DOMContentLoaded', () => {
console.log('settings.js DOMContentLoaded');
// 기본 탭 활성화
showTab('tab-auth');
// 기준년도 기본값 설정
const baseYearInput = document.getElementById('base_year');
if (baseYearInput && !baseYearInput.value) {
baseYearInput.value = new Date().getFullYear();
}
loadCurrentPeriod();
searchUsers(); // 초기 로드
loadCodeGroups();
loadCodes();
// 검색 form 제출 방지 (엔터 시 리로드 방지)
const searchForm = document.getElementById('search-form');
if (searchForm) {
searchForm.addEventListener('submit', e => {
e.preventDefault();
searchUsers();
});
}
// 기준년도 변경 시 기간 로드
if (baseYearInput) {
baseYearInput.addEventListener('change', loadCurrentPeriod);
}
// 신규 코드 버튼
const newCodeBtn = document.getElementById('new-code-btn');
if (newCodeBtn) {
newCodeBtn.addEventListener('click', () => openModal());
}
// 사이드 탭 버튼 이벤트 리스너 직접 바인드
const tabButtons = [
{ id: 'btn-auth', tab: 'tab-auth' },
{ id: 'btn-msg', tab: 'tab-msg' },
{ id: 'btn-period', tab: 'tab-period' },
{ id: 'btn-code', tab: 'tab-code' },
{ id: 'btn-keyword', tab: 'tab-keyword' },
{ id: 'btn-offer', tab: 'tab-offer' }
];
tabButtons.forEach(item => {
const btn = document.getElementById(item.id);
if (btn) {
btn.addEventListener('click', () => showTab(item.tab));
}
});
// 검색 버튼 이벤트
const searchBtn = document.getElementById('search-btn');
if (searchBtn) {
searchBtn.addEventListener('click', searchUsers);
}
// 법정의무교육 기간 저장 버튼 이벤트
const savePeriodBtn = document.getElementById('save-period-btn');
if (savePeriodBtn) {
savePeriodBtn.addEventListener('click', savePeriod);
}
// 제안 검색 버튼 이벤트
const offerSearchBtn = document.getElementById('offer-search-btn');
if (offerSearchBtn) {
offerSearchBtn.addEventListener('click', searchOffers);
}
// 제안 검색 폼 엔터키 이벤트
const offerSearchForm = document.getElementById('offer-search-form');
if (offerSearchForm) {
offerSearchForm.addEventListener('submit', (e) => {
e.preventDefault();
console.log('폼 submit 이벤트 발생');
searchOffers();
});
offerSearchForm.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
console.log('폼 keydown Enter 이벤트 발생');
searchOffers();
}
});
}
// 제안 저장 버튼 이벤트
const offerSaveBtn = document.getElementById('offer-save-btn');
if (offerSaveBtn) {
offerSaveBtn.addEventListener('click', saveOffer);
}
// 제안 상태 변경 시 반려사유 토글
const detailStatus = document.getElementById('detail-status');
if (detailStatus) {
detailStatus.addEventListener('change', toggleReasonReturn);
}
// 키워드 버튼 이벤트
const kwBtns = document.querySelectorAll('.kw-btn');
kwBtns.forEach(btn => {
btn.addEventListener('click', () => {
const isActive = btn.dataset.active === 'true';
const activeCount = document.querySelectorAll('.kw-btn[data-active="true"]').length;
if (!isActive && activeCount >= 2) {
alert('최대 2개의 키워드만 선택 가능합니다.');
return;
}
btn.dataset.active = !isActive;
const iconWrap = btn.querySelector('.kw-icon');
if (iconWrap) {
iconWrap.innerHTML = !isActive
? '<i class="fa-solid fa-minus text-xs"></i>'
: '<i class="fa-solid fa-plus text-xs"></i>';
}
updateKeywordInput();
});
});
});
// 선택된 키워드 로드
async function loadSelectedKeywords() {
const kwBtns = document.querySelectorAll('.kw-btn');
kwBtns.forEach(btn => {
btn.dataset.active = 'false';
const iconWrap = btn.querySelector('.kw-icon');
if (iconWrap) iconWrap.innerHTML = '<i class="fa-solid fa-plus text-xs"></i>';
});
try {
const res = await fetch(`../bbs/code_list.php?type=recommend_keywords`);
const data = await res.json();
const kws = data.items || [];
kws.forEach(k => {
const b = Array.from(kwBtns).find(btn => btn.dataset.code === k);
if (b) {
b.dataset.active = 'true';
const iconWrap = b.querySelector('.kw-icon');
if (iconWrap) iconWrap.innerHTML = '<i class="fa-solid fa-minus text-xs"></i>';
}
});
updateKeywordInput();
} catch (e) {
console.error('Failed to load keywords', e);
}
}
// 키워드 입력 업데이트
function updateKeywordInput() {
const activeKws = Array.from(document.querySelectorAll('.kw-btn'))
.filter(btn => btn.dataset.active === 'true')
.map(btn => btn.dataset.code);
const kwInput = document.getElementById('kw_selected');
if (kwInput) {
kwInput.value = activeKws.join(',');
}
}
// 키워드 저장
async function saveKeywords(e) {
e.preventDefault();
const form = document.getElementById('keyword-form');
const formData = new FormData(form);
try {
const res = await fetch('../bbs/recommend_keyword_save.php', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
alert('키워드가 저장되었습니다.');
} else {
alert('오류가 발생했습니다: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('저장 중 오류가 발생했습니다.');
}
}
// 알림 저장
async function saveNotification(code) {
const content = document.getElementById(`content_${code}`).value;
try {
const res = await fetch('../bbs/notification_save.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `code=${encodeURIComponent(code)}&content=${encodeURIComponent(content)}`
});
const data = await res.json();
if (data.success) {
alert('저장되었습니다.');
} else {
alert('저장 실패: ' + (data.message || '알 수 없는 오류'));
}
} catch (err) {
alert('저장 중 오류가 발생했습니다.');
}
}
// 알림 발송
async function sendNotification(code) {
const endDate = document.getElementById(`end_date_${code}`).value;
if (!endDate) {
alert('알람종료일을 입력해주세요.');
return;
}
try {
// 대상자 수 조회
const countRes = await fetch('../bbs/notification_send.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `code=${encodeURIComponent(code)}&end_date=${encodeURIComponent(endDate)}&action=count`
});
const countData = await countRes.json();
if (!countData.success) {
alert('대상자 조회 실패: ' + (countData.message || '알 수 없는 오류'));
return;
}
const targetCount = countData.count || 0;
if (targetCount === 0) {
alert('발송 대상이 없습니다.');
return;
}
if (!confirm(`${targetCount}명에게 알림을 발송하시겠습니까?`)) return;
// 실제 발송
const sendRes = await fetch('../bbs/notification_send.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `code=${encodeURIComponent(code)}&end_date=${encodeURIComponent(endDate)}&action=send`
});
const sendData = await sendRes.json();
if (sendData.success) {
alert('알림이 발송되었습니다.');
} else {
alert('발송 실패: ' + (sendData.message || '알 수 없는 오류'));
}
} catch (err) {
alert('발송 중 오류가 발생했습니다.');
}
}
</script>
<!-- 컨텐츠 제안 상세 모달 -->
<div id="offer-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-xl shadow-lg w-full max-w-2xl p-6 relative">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-gray-800">제안 상세 정보</h3>
<button onclick="closeOfferModal()" class="text-gray-400 hover:text-gray-600">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<form id="offer-modal-form" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안ID</label>
<input id="detail-offer-id" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm" readonly>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안일자</label>
<input id="detail-offer-date" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm" readonly>
</div>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안자</label>
<input id="detail-proposer" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm" readonly>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">URL</label>
<div class="flex gap-2">
<input id="detail-url" type="text" class="flex-1 border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm" readonly>
<button type="button" onclick="window.open(document.getElementById('detail-url').value, '_blank')" class="px-3 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition">
<i class="fa-solid fa-external-link text-sm"></i>
</button>
</div>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">추천이유</label>
<textarea id="detail-reason" class="w-full border border-gray-300 rounded-lg p-2 bg-gray-50 text-sm" rows="3" readonly></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">제안상태</label>
<select id="detail-status" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm">
<?php foreach ($offerStatuses as $os): ?>
<option value="<?= htmlspecialchars($os['code'], ENT_QUOTES, 'UTF-8'); ?>">
<?= htmlspecialchars($os['name'], ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">반려사유</label>
<input id="detail-reason-return" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm" disabled>
</div>
</div>
<div class="flex justify-end space-x-2 pt-4">
<button type="button" onclick="closeOfferModal()" class="px-4 py-2 bg-gray-500 text-white rounded-lg font-bold text-sm">취소</button>
<button type="button" id="offer-save-btn" class="px-4 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-sm">저장</button>
</div>
</form>
</div>
</div>
<!-- 코드 상세 등록/수정 모달 -->
<div id="code-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-xl shadow-lg w-full max-w-md p-6 relative">
<div class="flex justify-between items-center mb-4">
<h3 id="modal-title" class="text-lg font-bold text-gray-800">코드 상세 정보</h3>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<form id="code-modal-form" class="space-y-4" onsubmit="event.preventDefault(); saveModalCode();">
<!-- 메인코드는 hidden으로 처리 (사용자 요청) -->
<input id="modal-group" type="hidden">
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">서브코드</label>
<input id="modal-code" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none" placeholder="예: 100">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">코드명</label>
<input id="modal-code-name" type="text" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none">
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-2">사용여부</label>
<div class="flex items-center gap-2">
<input id="modal-active" type="checkbox" class="w-4 h-4 text-teal-600 border-gray-300 rounded focus:ring-teal-500" checked>
<span class="text-sm text-gray-600">사용함</span>
</div>
</div>
<div>
<label class="block text-xs font-bold text-gray-700 mb-1">설명1</label>
<textarea id="modal-desc01" class="w-full border border-gray-300 rounded-lg p-2 bg-white text-sm focus:ring-2 focus:ring-teal-500 outline-none" rows="3"></textarea>
</div>
<div class="flex justify-end space-x-2 pt-4">
<button type="button" onclick="closeModal()" class="px-4 py-2 bg-gray-500 text-white rounded-lg font-bold text-sm">취소</button>
<button type="submit" class="px-4 py-2 bg-[#114b3d] text-white rounded-lg font-bold text-sm shadow-md hover:bg-[#0d3a2f] transition">저장</button>
</div>
</form>
</div>
</div>
</body>
</html>