Files
survey/js/admin.js
T
2026-09-12 14:34:09 +07:00

1268 lines
60 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* TPU Survey - Admin Dashboard Logic (Redesigned)
* Card-based UI, search, filters, charts, animated counters
*/
let currentView = 'dashboard';
let surveysList = [];
let usersList = [];
let currentSurveyFilter = 'all';
// ============================================================
// Initialization
// ============================================================
async function initAdmin() {
const loggedIn = await Auth.check();
if (!loggedIn || !Auth.isAdmin()) {
window.location.href = 'index.html';
return;
}
// Set user info in sidebar
const name = Auth.user.full_name || 'ผู้ดูแลระบบ';
document.getElementById('welcomeName').textContent = name;
document.getElementById('userName').textContent = name;
document.getElementById('userAvatar').textContent = name.charAt(0).toUpperCase();
// Set current date
setWelcomeDate();
showView('dashboard');
}
function setWelcomeDate() {
const el = document.getElementById('welcomeDate');
if (!el) return;
const now = new Date();
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
el.textContent = '📅 ' + now.toLocaleDateString('th-TH', options);
}
// ============================================================
// Navigation
// ============================================================
function showView(view) {
currentView = view;
document.querySelectorAll('.view-section').forEach(s => s.classList.add('hidden'));
document.getElementById(`view-${view}`)?.classList.remove('hidden');
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.querySelector(`.nav-item[data-view="${view}"]`)?.classList.add('active');
const titles = {
dashboard: 'ภาพรวมระบบ',
users: 'จัดการผู้ใช้',
surveys: 'จัดการแบบสอบถาม',
profile: 'ข้อมูลส่วนตัว'
};
document.getElementById('pageTitle').textContent = titles[view] || '';
if (view === 'dashboard') loadDashboardStats();
else if (view === 'users') loadUsers();
else if (view === 'surveys') loadSurveys();
else if (view === 'profile') loadProfile();
}
// ============================================================
// Animated Counter
// ============================================================
function animateCounter(el, target) {
const duration = 800;
const start = parseInt(el.textContent) || 0;
const diff = target - start;
if (diff === 0) { el.textContent = target; return; }
const startTime = performance.now();
function step(currentTime) {
const progress = Math.min((currentTime - startTime) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
el.textContent = Math.round(start + diff * eased);
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
// ============================================================
// Dashboard Stats
// ============================================================
async function loadDashboardStats() {
try {
const [usersData, surveysData] = await Promise.all([
API.get('users.php?action=list'),
API.get('surveys.php?action=list')
]);
usersList = usersData.users || [];
surveysList = surveysData.surveys || [];
const totalUsers = usersList.length;
const totalSurveys = surveysList.length;
const activeSurveys = surveysList.filter(s => s.status === 'active').length;
const draftSurveys = surveysList.filter(s => s.status === 'draft').length;
const closedSurveys = surveysList.filter(s => s.status === 'closed').length;
const totalResponses = surveysList.reduce((sum, s) => sum + (parseInt(s.response_count) || 0), 0);
// Animate stat counters
animateCounter(document.getElementById('statUsers'), totalUsers);
animateCounter(document.getElementById('statSurveys'), totalSurveys);
animateCounter(document.getElementById('statActive'), activeSurveys);
animateCounter(document.getElementById('statResponses'), totalResponses);
// Render status chart
renderStatusChart(activeSurveys, draftSurveys, closedSurveys, totalSurveys);
// Recent surveys list
renderRecentSurveys(surveysList.slice(0, 5));
} catch (err) {
Toast.error(err.message);
}
}
function renderStatusChart(active, draft, closed, total) {
const max = total || 1;
const activeEl = document.getElementById('chartActive');
const draftEl = document.getElementById('chartDraft');
const closedEl = document.getElementById('chartClosed');
if (activeEl) {
setTimeout(() => { activeEl.style.width = Math.max((active / max) * 100, active > 0 ? 8 : 0) + '%'; }, 100);
}
if (draftEl) {
setTimeout(() => { draftEl.style.width = Math.max((draft / max) * 100, draft > 0 ? 8 : 0) + '%'; }, 200);
}
if (closedEl) {
setTimeout(() => { closedEl.style.width = Math.max((closed / max) * 100, closed > 0 ? 8 : 0) + '%'; }, 300);
}
const activeCount = document.getElementById('chartActiveCount');
const draftCount = document.getElementById('chartDraftCount');
const closedCount = document.getElementById('chartClosedCount');
if (activeCount) activeCount.textContent = `${active} (${total ? Math.round(active / total * 100) : 0}%)`;
if (draftCount) draftCount.textContent = `${draft} (${total ? Math.round(draft / total * 100) : 0}%)`;
if (closedCount) closedCount.textContent = `${closed} (${total ? Math.round(closed / total * 100) : 0}%)`;
}
function renderRecentSurveys(surveys) {
const list = document.getElementById('recentSurveysList');
if (!list) return;
if (surveys.length === 0) {
list.innerHTML = `
<li class="activity-item" style="justify-content:center;padding:40px;">
<span class="text-muted">ยังไม่มีแบบสอบถาม</span>
</li>`;
return;
}
const statusIcons = { active: '🟢', draft: '📝', closed: '🔴' };
const statusLabels = { active: 'เปิดใช้', draft: 'ร่าง', closed: 'ปิด' };
list.innerHTML = surveys.map(s => `
<li class="activity-item">
<div class="activity-icon">${statusIcons[s.status] || '📄'}</div>
<div class="activity-text">
<strong>${escapeHtml(s.title)}</strong>
<span>สร้างโดย ${escapeHtml(s.creator_name || '-')} · ${statusLabels[s.status] || s.status} · ${s.response_count || 0} คำตอบ</span>
</div>
</li>
`).join('');
}
// ============================================================
// User Management — Card View
// ============================================================
async function loadUsers() {
try {
const data = await API.get('users.php?action=list');
usersList = data.users || [];
renderUserCards();
} catch (err) {
Toast.error(err.message);
}
}
function renderUserCards(filteredList) {
const container = document.getElementById('userCardsContainer');
const summary = document.getElementById('userSummary');
if (!container) return;
const users = filteredList || usersList;
if (users.length === 0) {
container.innerHTML = `
<div class="card-tapi p-8 text-center bg-white rounded-xl border border-slate-200">
<i class="fas fa-users-slash text-4xl text-blue-300 mb-3 block"></i>
<p class="text-gray-600 font-bold">ไม่พบผู้ใช้ ลองค้นหาด้วยคำอื่น หรือเพิ่มผู้ใช้ใหม่</p>
</div>`;
if (summary) summary.textContent = '';
return;
}
const roleMap = {
admin: { label: '👑 ผู้ดูแลระบบ', bg: 'bg-blue-100 text-blue-800 border-blue-300' },
teacher: { label: '🎓 อาจารย์ / เจ้าของโครงการ', bg: 'bg-amber-100 text-amber-800 border-amber-300' }
};
container.innerHTML = users.map(u => {
const roleInfo = roleMap[u.role] || { label: u.role, bg: 'bg-slate-100 text-slate-800 border-slate-300' };
const isCurrentUser = u.id == Auth.user.id;
return `
<div class="card-tapi p-3.5 rounded-xl bg-white flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 transition-all hover:shadow-md border border-slate-200">
<div class="flex items-center gap-3 flex-1 min-w-0">
<div class="w-10 h-10 rounded-full flex items-center justify-center font-bold text-sm text-white flex-shrink-0 ${u.role === 'admin' ? 'bg-blue-700' : 'bg-amber-500'}">
${(u.full_name || 'U').charAt(0).toUpperCase()}
</div>
<div class="space-y-0.5 flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<span class="px-2 py-0.5 text-[11px] font-extrabold rounded border whitespace-nowrap ${roleInfo.bg}">${roleInfo.label}</span>
<h4 class="font-bold text-xs sm:text-sm text-blue-950 truncate">${escapeHtml(u.full_name)}</h4>
</div>
<div class="flex items-center gap-3 text-[11px] text-gray-500 font-semibold flex-wrap">
<span><i class="fas fa-id-card text-slate-500 mr-1"></i> รหัส: ${escapeHtml(u.personnel_code || u.username || '-')}</span>
${u.email ? `<span><i class="fas fa-envelope text-blue-600 mr-1"></i> ${escapeHtml(u.email)}</span>` : ''}
${u.subject_area ? `<span><i class="fas fa-graduation-cap text-amber-600 mr-1"></i> ${escapeHtml(u.subject_area)}</span>` : ''}
</div>
</div>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0 self-end sm:self-auto">
<button onclick="editUser(${u.id})" class="bg-amber-400 hover:bg-amber-500 text-gray-900 border border-slate-800 px-3 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="แก้ไขข้อมูลผู้ใช้">
✏️ แก้ไข
</button>
${!isCurrentUser ? `
<button onclick="deleteUser(${u.id}, '${escapeHtml(u.full_name)}')" class="bg-rose-600 hover:bg-rose-700 text-white border border-slate-800 px-3 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="ลบผู้ใช้">
🗑️ ลบ
</button>
` : '<span class="text-xs text-slate-400 font-semibold px-2 py-1 bg-slate-100 rounded">บัญชีคุณ</span>'}
</div>
</div>
`;
}).join('');
// Summary
const admins = users.filter(u => u.role === 'admin').length;
const teachers = users.filter(u => u.role === 'teacher').length;
if (summary) {
summary.textContent = `รวม ${users.length} ผู้ใช้ — 👑 ผู้ดูแล ${admins} คน · 🎓 อาจารย์ ${teachers} คน`;
}
}
function filterUsers() {
const query = document.getElementById('userSearchInput')?.value?.toLowerCase() || '';
if (!query) {
renderUserCards();
return;
}
const filtered = usersList.filter(u =>
(u.full_name || '').toLowerCase().includes(query) ||
(u.personnel_code || '').toLowerCase().includes(query) ||
(u.username || '').toLowerCase().includes(query)
);
renderUserCards(filtered);
}
function showAddUserModal() {
const html = `
<form id="userForm">
<div class="form-group">
<label class="form-label">รหัสบุคลากร *</label>
<input type="text" class="form-input" name="personnel_code" required placeholder="เช่น 12345">
<small class="text-muted text-xs mt-8" style="display:block;">ใช้เป็นชื่อผู้ใช้และรหัสผ่านสำหรับเข้าสู่ระบบ</small>
</div>
<div class="form-group">
<label class="form-label">ชื่อ-นามสกุล *</label>
<input type="text" class="form-input" name="full_name" required placeholder="เช่น นายสมชาย ใจดี">
</div>
<div class="form-group">
<label class="form-label">อีเมล</label>
<input type="email" class="form-input" name="email" placeholder="เช่น somchai@example.com">
</div>
<div class="form-group">
<label class="form-label">บทบาท</label>
<select class="form-input" name="role">
<option value="teacher">อาจารย์ / เจ้าของโครงการ</option>
<option value="admin">ผู้ดูแลระบบ</option>
</select>
</div>
</form>
`;
const footer = `
<button class="btn btn-secondary" onclick="Modal.hide('addUserModal');document.getElementById('addUserModal').remove();">ยกเลิก</button>
<button class="btn btn-primary" onclick="submitAddUser()">บันทึก</button>
`;
Modal.create('เพิ่มผู้ใช้ใหม่', html, footer);
document.querySelector('.modal-overlay:last-child').id = 'addUserModal';
}
async function submitAddUser() {
const form = document.getElementById('userForm');
const fd = new FormData(form);
const data = Object.fromEntries(fd);
if (!data.personnel_code || !data.full_name) {
Toast.warning('กรุณากรอกรหัสบุคลากรและชื่อ-นามสกุล');
return;
}
// ใช้รหัสบุคลากรเป็น username และ password
data.username = data.personnel_code;
data.password = data.personnel_code;
try {
await API.post('users.php?action=create', data);
Toast.success('สร้างผู้ใช้สำเร็จ (รหัสผ่าน = รหัสบุคลากร)');
Modal.hide('addUserModal');
document.getElementById('addUserModal').remove();
loadUsers();
} catch (err) {
Toast.error(err.message);
}
}
async function editUser(id) {
try {
const data = await API.get(`users.php?action=get&id=${id}`);
const u = data.user;
const html = `
<form id="editUserForm">
<input type="hidden" name="id" value="${u.id}">
<div class="form-group">
<label class="form-label">ชื่อ-นามสกุล</label>
<input type="text" class="form-input" name="full_name" value="${escapeHtml(u.full_name)}">
</div>
<div class="form-group">
<label class="form-label">อีเมล</label>
<input type="email" class="form-input" name="email" value="${escapeHtml(u.email || '')}" placeholder="เช่น somchai@example.com">
</div>
<div class="form-group">
<label class="form-label">บทบาท</label>
<select class="form-input" name="role">
<option value="teacher" ${u.role==='teacher'?'selected':''}>อาจารย์</option>
<option value="admin" ${u.role==='admin'?'selected':''}>ผู้ดูแลระบบ</option>
</select>
</div>
<div class="form-group">
<label class="form-label">รหัสบุคลากร</label>
<input type="text" class="form-input" name="personnel_code" value="${escapeHtml(u.personnel_code || '')}">
</div>
<div class="form-group">
<label class="form-label">สังกัด/สาขาวิชา</label>
<input type="text" class="form-input" name="subject_area" value="${escapeHtml(u.subject_area || '')}">
</div>
<div class="form-group">
<label class="form-label">รหัสสาขา</label>
<input type="text" class="form-input" name="branch_code" value="${escapeHtml(u.branch_code || '')}">
</div>
<div class="form-group">
<label class="form-label">รหัสผ่านใหม่ (เว้นว่างถ้าไม่เปลี่ยน)</label>
<input type="password" class="form-input" name="password" placeholder="ไม่เปลี่ยนรหัสผ่าน">
</div>
</form>
`;
const footer = `
<button class="btn btn-secondary" onclick="Modal.hide('editUserModal');document.getElementById('editUserModal').remove();">ยกเลิก</button>
<button class="btn btn-primary" onclick="submitEditUser()">บันทึก</button>
`;
Modal.create('แก้ไขผู้ใช้', html, footer);
document.querySelector('.modal-overlay:last-child').id = 'editUserModal';
} catch (err) {
Toast.error(err.message);
}
}
async function submitEditUser() {
const form = document.getElementById('editUserForm');
const fd = new FormData(form);
const data = Object.fromEntries(fd);
try {
await API.post('users.php?action=update', data);
Toast.success('อัปเดตผู้ใช้สำเร็จ');
Modal.hide('editUserModal');
document.getElementById('editUserModal').remove();
loadUsers();
} catch (err) {
Toast.error(err.message);
}
}
async function deleteUser(id, name) {
if (!confirm(`ต้องการลบผู้ใช้ "${name}" ใช่หรือไม่?`)) return;
try {
await API.post('users.php?action=delete', { id });
Toast.success('ลบผู้ใช้สำเร็จ');
loadUsers();
} catch (err) {
Toast.error(err.message);
}
}
// ============================================================
// Survey Management — Card View with Filter
// ============================================================
async function loadSurveys() {
try {
const data = await API.get('surveys.php?action=list');
surveysList = data.surveys || [];
updateSurveyFilterCounts();
renderSurveyCards();
} catch (err) {
Toast.error(err.message);
}
}
function updateSurveyFilterCounts() {
const all = surveysList.length;
const active = surveysList.filter(s => s.status === 'active').length;
const draft = surveysList.filter(s => s.status === 'draft').length;
const closed = surveysList.filter(s => s.status === 'closed').length;
const countAll = document.getElementById('countAll');
const countActive = document.getElementById('countActive');
const countDraft = document.getElementById('countDraft');
const countClosed = document.getElementById('countClosed');
if (countAll) countAll.textContent = all;
if (countActive) countActive.textContent = active;
if (countDraft) countDraft.textContent = draft;
if (countClosed) countClosed.textContent = closed;
}
function getFilteredSurveys() {
let list = surveysList;
if (currentSurveyFilter !== 'all') {
list = list.filter(s => s.status === currentSurveyFilter);
}
const query = document.getElementById('surveySearchInput')?.value?.toLowerCase() || '';
if (query) {
list = list.filter(s =>
(s.title || '').toLowerCase().includes(query) ||
(s.creator_name || '').toLowerCase().includes(query)
);
}
return list;
}
function renderSurveyCards() {
const container = document.getElementById('surveyCardsContainer');
const summary = document.getElementById('surveySummary');
if (!container) return;
const surveys = getFilteredSurveys();
if (surveys.length === 0) {
container.innerHTML = `
<div class="card-tapi p-8 text-center bg-white rounded-xl border border-slate-200" style="grid-column: 1/-1;">
<i class="fas fa-clipboard-list text-4xl text-blue-300 mb-3 block"></i>
<p class="text-gray-600 font-bold">ไม่พบแบบสอบถาม ลองค้นหาด้วยคำอื่น หรือสร้างแบบสอบถามใหม่</p>
</div>`;
if (summary) summary.textContent = '';
return;
}
const statusMap = {
active: { label: 'เปิดใช้งาน', bg: 'bg-emerald-100 text-emerald-800 border-emerald-300' },
draft: { label: 'ฉบับร่าง', bg: 'bg-amber-100 text-amber-800 border-amber-300' },
closed: { label: 'ปิดแล้ว', bg: 'bg-rose-100 text-rose-800 border-rose-300' }
};
container.innerHTML = surveys.map(s => {
const st = statusMap[s.status] || { label: s.status || 'เปิดใช้งาน', bg: 'bg-emerald-100 text-emerald-800 border-emerald-300' };
return `
<div class="card-tapi p-3.5 rounded-xl bg-white flex flex-col lg:flex-row justify-between items-start lg:items-center gap-3 transition-all hover:shadow-md border border-slate-200">
<div class="flex flex-col sm:flex-row sm:items-center gap-2.5 flex-1 min-w-0">
<span class="px-2 py-0.5 text-[11px] font-extrabold rounded border whitespace-nowrap self-start sm:self-auto ${st.bg}">${st.label}</span>
<div class="space-y-0.5 flex-1 min-w-0">
<h4 class="font-bold text-xs sm:text-sm text-blue-950 truncate" title="${escapeHtml(s.title)}">
${escapeHtml(s.title)}
</h4>
<div class="flex items-center gap-3 text-[11px] text-gray-500 font-semibold flex-wrap">
<span><i class="fas fa-user text-slate-500 mr-1"></i> ${escapeHtml(s.creator_name || '-')}</span>
<span><i class="fas fa-comments text-blue-600 mr-1"></i> ${s.response_count || 0} คำตอบ</span>
<span><i class="fas fa-calendar-alt text-amber-600 mr-1"></i> ${formatDate(s.created_at)}</span>
</div>
</div>
</div>
<div class="flex flex-wrap lg:flex-nowrap gap-1 items-center flex-shrink-0 w-full lg:w-auto overflow-x-auto pb-1 lg:pb-0">
<select class="bg-slate-50 border border-slate-300 text-slate-800 px-1.5 py-1 rounded text-xs font-bold shadow-sm transition-all focus:outline-none" onchange="changeStatus(${s.id}, this.value)" title="เปลี่ยนสถานะ">
<option value="draft" ${s.status==='draft'?'selected':''}>📝 ร่าง</option>
<option value="active" ${s.status==='active'?'selected':''}>🟢 เปิดใช้งาน</option>
<option value="closed" ${s.status==='closed'?'selected':''}>🔴 ปิดแล้ว</option>
</select>
<button onclick="editSurvey(${s.id})" class="bg-amber-400 hover:bg-amber-500 text-gray-900 border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="แก้ไขแบบสอบถาม">
✏️ แก้ไข
</button>
<button onclick="duplicateSurvey(${s.id})" class="bg-blue-600 hover:bg-blue-700 text-white border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="คัดลอกแบบสอบถาม">
📑 คัดลอก
</button>
<button onclick="generateLink(${s.id})" class="bg-blue-900 hover:bg-blue-950 text-white border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="ลิงก์และ QR Code">
🔗 ลิงก์
</button>
<button onclick="showImportResponsesModal(${s.id}, '${escapeHtml(s.title)}')" class="bg-emerald-600 hover:bg-emerald-700 text-white border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="นำเข้าคำตอบจาก CSV">
📥 นำเข้าคำตอบ
</button>
<button onclick="viewStats(${s.id})" class="bg-indigo-600 hover:bg-indigo-700 text-white border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="ผลสถิติ">
📊 สถิติ
</button>
<button onclick="deleteSurvey(${s.id}, '${escapeHtml(s.title)}')" class="bg-rose-600 hover:bg-rose-700 text-white border border-slate-800 px-2 py-1 rounded text-xs font-bold flex items-center gap-1 shadow-sm transition-all whitespace-nowrap active:scale-95" title="ลบแบบสอบถาม">
🗑️ ลบ
</button>
</div>
</div>
`;
}).join('');
if (summary) {
summary.textContent = `แสดง ${surveys.length} จาก ${surveysList.length} แบบสอบถาม`;
}
}
function filterSurveys(filter, btn) {
currentSurveyFilter = filter;
document.querySelectorAll('#surveyFilterTabs .filter-tab').forEach(t => t.classList.remove('active'));
if (btn) btn.classList.add('active');
renderSurveyCards();
}
function filterSurveysBySearch() {
renderSurveyCards();
}
function showCreateSurveyModal() {
const html = `
<form id="surveyForm">
<div class="form-group">
<label class="form-label">ชื่อแบบสอบถาม *</label>
<input type="text" class="form-input" name="title" required placeholder="เช่น แบบประเมินความพึงพอใจโครงการ...">
</div>
<div class="form-group">
<label class="form-label">รายละเอียด</label>
<textarea class="form-input" name="description" placeholder="คำอธิบายเพิ่มเติม (ไม่จำเป็น)"></textarea>
</div>
<hr style="border-color:var(--border-color);margin:20px 0;">
<p class="text-sm text-muted mb-16">ระบบจะสร้างแบบสอบถามพร้อม 3 ส่วนเริ่มต้น คุณสามารถแก้ไขคำถามได้ภายหลัง</p>
</form>
`;
const footer = `
<button class="btn btn-secondary" onclick="Modal.hide('createSurveyModal');document.getElementById('createSurveyModal').remove();">ยกเลิก</button>
<button class="btn btn-primary" onclick="submitCreateSurvey()">สร้างแบบสอบถาม (ใช้ 3 ส่วนมาตรฐาน)</button>
`;
Modal.create('สร้างแบบสอบถามใหม่', html, footer);
document.querySelector('.modal-overlay:last-child').id = 'createSurveyModal';
}
async function submitCreateSurvey() {
const form = document.getElementById('surveyForm');
const fd = new FormData(form);
const data = Object.fromEntries(fd);
if (!data.title) { Toast.warning('กรุณากรอกชื่อแบบสอบถาม'); return; }
// Pre-fill 3 sections structure
data.sections = [
{
section_number: 1,
title: 'ส่วนที่ ข้อมูลทั่วไป',
category: 'general',
questions: [
{ question_text: 'เพศ', question_type: 'radio', options: ['ชาย', 'หญิง'], is_required: 1 },
{ question_text: 'ช่วงอายุ', question_type: 'dropdown', options: ['18-25 ปี', '26-35 ปี', '36-45 ปี', '46-55 ปี', '56 ปีขึ้นไป'], is_required: 1 }
]
},
{
section_number: 2,
title: 'ส่วนที่ การประเมินความพึงพอใจ',
category: 'rating',
questions: [
{ question_text: 'ด้านเนื้อหาของการจัดโครงการ', question_type: 'radio', is_required: 1 },
{ question_text: 'ด้านวิทยากร / ผู้บรรยาย', question_type: 'radio', is_required: 1 }
]
},
{
section_number: 3,
title: 'ส่วนที่ ข้อเสนอแนะอื่นๆ',
category: 'suggestions',
questions: [
{ question_text: 'ข้อเสนอแนะเพิ่มเติม', question_type: 'text', is_required: 0 }
]
}
];
try {
const result = await API.post('surveys.php?action=create', data);
Toast.success('สร้างแบบสอบถามสำเร็จ');
Modal.hide('createSurveyModal');
document.getElementById('createSurveyModal').remove();
loadSurveys();
setTimeout(() => editSurvey(result.id), 500);
} catch (err) {
Toast.error(err.message);
}
}
function editSurvey(id) {
window.open(`edit.html?id=${id}`, '_blank');
}
function buildQuestionEditor(si, qi, q = {}) {
const qType = q.question_type || 'radio';
const qText = q.question_text || '';
const qOptions = Array.isArray(q.options) ? q.options.join('\n') : '';
const showOptions = ['radio', 'checkbox', 'dropdown'].includes(qType);
const qDescription = q.description || '';
return `
<div class="question-builder">
<div class="qb-header">
<span class="qb-handle">⋮⋮</span>
<span class="text-sm fw-600 q-number-label">คำถาม</span>
<div class="qb-actions">
<button type="button" class="btn btn-ghost btn-sm" onclick="addQuestion(${si}, this.closest('.question-builder'))" title="แทรกคำถามใต้ข้อนี้">➕ แทรก</button>
<button type="button" class="btn btn-ghost btn-sm text-danger" onclick="removeQuestion(this, ${si})" title="ลบคำถาม">🗑️ ลบ</button>
</div>
</div>
<div class="form-group">
<label class="form-label">คำถาม / ข้อความ</label>
<input type="text" class="form-input q-text-input" value="${escapeHtml(qText)}" placeholder="พิมพ์คำถาม...">
</div>
<div class="form-group">
<label class="form-label">รายละเอียดเพิ่มเติม (คำใบ้/คำอธิบาย - ไม่บังคับ)</label>
<input type="text" class="form-input q-desc-input" value="${escapeHtml(qDescription)}" placeholder="เช่น กรุณาระบุเป็นภาษาไทย">
</div>
<div class="form-group">
<label class="form-label">ประเภทคำถาม</label>
<select class="form-input q-type-select" onchange="toggleOptions(this)">
<option value="radio" ${qType==='radio'?'selected':''}>📊 ระดับ 1-5 (Radio)</option>
<option value="text" ${qType==='text'?'selected':''}>📝 ข้อความ (Text)</option>
<option value="dropdown" ${qType==='dropdown'?'selected':''}>📋 ตัวเลือก (Dropdown)</option>
<option value="checkbox" ${qType==='checkbox'?'selected':''}>☑️ หลายตัวเลือก (Checkbox)</option>
</select>
</div>
<div class="form-group q-options-group" style="${showOptions ? '' : 'display:none;'}">
<label class="form-label">ตัวเลือก (1 รายการต่อบรรทัด)</label>
<textarea class="form-input q-options-input" placeholder="ตัวเลือกที่ 1\nตัวเลือกที่ 2" rows="3">${escapeHtml(qOptions)}</textarea>
<small class="text-muted">เว้นว่างไว้หากต้องการใช้ระดับ 1-5 (สำหรับ Radio)</small>
</div>
</div>
`;
}
function addSection() {
const container = document.getElementById('additionalSections');
if (!container) return;
const si = document.querySelectorAll('.card[data-section]').length;
const html = `
<div class="card mb-24" data-section="${si}">
<div class="card-header d-flex justify-between items-center">
<input type="text" class="form-input" style="font-weight:700;font-size:16px;border:none;padding:4px 0;background:transparent;"
value="ส่วนที่ ข้อมูลเพิ่มเติม" data-field="section-title-${si}">
<button type="button" class="btn btn-ghost btn-sm text-danger" onclick="this.closest('.card').remove()">🗑️ ลบส่วนนี้</button>
</div>
<div class="card-body">
<div id="questions-${si}"></div>
<button type="button" class="btn btn-secondary btn-sm mt-16" onclick="addQuestion(${si})">+ เพิ่มคำถาม</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
addQuestion(si);
}
function toggleOptions(select) {
const group = select.closest('.question-builder').querySelector('.q-options-group');
if (['radio', 'checkbox', 'dropdown'].includes(select.value)) {
group.style.display = 'block';
} else {
group.style.display = 'none';
}
}
function addQuestion(sectionIdx, afterEl = null) {
const container = document.getElementById(`questions-${sectionIdx}`);
if (!container) return;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = buildQuestionEditor(sectionIdx, 0);
const newQ = tempDiv.firstElementChild;
if (afterEl) {
afterEl.after(newQ);
} else {
container.appendChild(newQ);
}
reindexQuestions(sectionIdx);
}
function removeQuestion(btn, sectionIdx) {
if (!confirm('ยืนยันลบคำถามนี้?')) return;
btn.closest('.question-builder').remove();
reindexQuestions(sectionIdx);
}
function reindexQuestions(sectionIdx) {
const container = document.getElementById(`questions-${sectionIdx}`);
if (!container) return;
container.querySelectorAll('.question-builder').forEach((qEl, idx) => {
const label = qEl.querySelector('.q-number-label');
if (label) label.textContent = `คำถาม`;
});
}
async function submitEditSurvey() {
const form = document.getElementById('editSurveyForm');
const id = form.querySelector('[name="id"]').value;
const title = form.querySelector('[name="title"]').value;
const description = form.querySelector('[name="description"]').value;
const sections = [];
form.querySelectorAll('.card[data-section]').forEach((sectionEl, si) => {
const sTitle = sectionEl.querySelector(`[data-field="section-title-${si}"]`)?.value || `ส่วนที่ ${si+1}`;
const sCategory = sectionEl.querySelector(`[data-field="section-category-${si}"]`)?.value || `rating`;
const questions = [];
sectionEl.querySelectorAll('.question-builder').forEach((qEl, qi) => {
const qTextInput = qEl.querySelector('.q-text-input');
const qDescInput = qEl.querySelector('.q-desc-input');
const qTypeSelect = qEl.querySelector('.q-type-select');
const qOptionsArea = qEl.querySelector('.q-options-input');
const qText = qTextInput ? qTextInput.value : '';
const qDescription = qDescInput ? qDescInput.value : '';
const qType = qTypeSelect ? qTypeSelect.value : 'radio';
const qOptionsRaw = qOptionsArea ? qOptionsArea.value : '';
const options = qOptionsRaw.split('\n').map(o => o.trim()).filter(o => o !== '');
if (qText.trim()) {
questions.push({
question_text: qText.trim(),
description: qDescription.trim() || null,
question_type: qType,
order_num: qi,
is_required: 1,
options: options.length > 0 ? options : null
});
}
});
sections.push({
section_number: si + 1,
title: sTitle,
category: sCategory,
questions
});
});
try {
await API.post('surveys.php?action=update', { id: parseInt(id), title, description, sections });
Toast.success('บันทึกแบบสอบถามสำเร็จ');
Modal.hide('editSurveyModal');
document.getElementById('editSurveyModal').remove();
loadSurveys();
} catch (err) {
Toast.error(err.message);
}
}
async function duplicateSurvey(id) {
if (!confirm('ยืนยันสร้างสำเนาแบบสอบถามนี้?')) return;
try {
await API.post('surveys.php?action=duplicate', { id });
Toast.success('คัดลอกสำเร็จ');
loadSurveys();
} catch (err) {
Toast.error(err.message);
}
}
async function changeStatus(id, status) {
try {
await API.post('surveys.php?action=updateStatus', { id, status });
Toast.success('อัปเดตสถานะสำเร็จ');
// Refresh the card view
const survey = surveysList.find(s => s.id == id);
if (survey) survey.status = status;
updateSurveyFilterCounts();
renderSurveyCards();
} catch (err) {
Toast.error(err.message);
loadSurveys();
}
}
async function generateLink(id) {
try {
const data = await API.post('surveys.php?action=generateLink', { id });
const basePath = window.location.pathname.includes('/survey') ? '/survey' : '';
const fullUrl = data.full_url || `${window.location.origin}${basePath}/respond.php?link=${data.unique_link}`;
// Find survey title
const survey = surveysList.find(s => s.id == id);
const surveyTitle = survey ? survey.title : 'แบบสอบถาม';
const html = `
<div class="qr-link-container">
<div class="qr-link-section">
<div class="qr-link-label">🔗 ลิงก์แบบสอบถาม</div>
<div class="link-display">
<span class="flex-1" id="surveyLinkText">${fullUrl}</span>
<button class="copy-btn" onclick="copyLink()">📋 คัดลอก</button>
</div>
</div>
<div class="qr-code-section">
<div class="qr-link-label">📱 QR Code สำหรับตอบแบบสอบถาม</div>
<div class="qr-code-wrapper" id="qrCodeWrapper">
<div id="qrCodeContainer"></div>
</div>
<p class="qr-survey-title" id="qrSurveyTitle">${escapeHtml(surveyTitle)}</p>
<div class="qr-actions">
<button class="btn btn-primary btn-sm" onclick="downloadQR()">📥 ดาวน์โหลด QR</button>
<button class="btn btn-secondary btn-sm" onclick="printQR()">🖨️ พิมพ์ QR</button>
</div>
</div>
<p class="mt-16 text-sm text-muted" style="text-align:center;">⚡ แบบสอบถามถูกเปิดใช้งานอัตโนมัติเมื่อสร้างลิงก์</p>
</div>
`;
Modal.create('🔗 ลิงก์และ QR Code', html, '', 'modal-lg');
// Generate QR Code using QRCodeJS
setTimeout(() => {
const container = document.getElementById('qrCodeContainer');
if (container && typeof QRCode !== 'undefined') {
new QRCode(container, {
text: fullUrl,
width: 280,
height: 280,
colorDark: '#1e3a8a',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
}
}, 100);
} catch (err) {
Toast.error(err.message);
}
}
function copyLink() {
const text = document.getElementById('surveyLinkText')?.textContent;
if (text) {
navigator.clipboard.writeText(text).then(() => {
Toast.success('คัดลอกลิงก์แล้ว');
}).catch(() => {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
Toast.success('คัดลอกลิงก์แล้ว');
});
}
}
function getQRImage() {
const container = document.getElementById('qrCodeContainer');
if (!container) return null;
// QRCodeJS generates both canvas and img — prefer canvas for export
return container.querySelector('canvas') || container.querySelector('img');
}
function downloadQR() {
const qrEl = getQRImage();
const title = document.getElementById('qrSurveyTitle')?.textContent || 'survey';
if (!qrEl) return;
// Get data URL from canvas or img
let qrDataUrl;
if (qrEl.tagName === 'CANVAS') {
qrDataUrl = qrEl.toDataURL('image/png');
} else {
qrDataUrl = qrEl.src;
}
// Create a new canvas with title and styling
const exportCanvas = document.createElement('canvas');
const qrSize = 280;
const padding = 40;
const titleHeight = 60;
const footerHeight = 40;
exportCanvas.width = qrSize + padding * 2;
exportCanvas.height = qrSize + padding * 2 + titleHeight + footerHeight;
const ctx = exportCanvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
// Title
ctx.fillStyle = '#1a1a2e';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
const maxTitleWidth = exportCanvas.width - 40;
let displayTitle = title;
if (ctx.measureText(displayTitle).width > maxTitleWidth) {
while (ctx.measureText(displayTitle + '...').width > maxTitleWidth && displayTitle.length > 0) {
displayTitle = displayTitle.slice(0, -1);
}
displayTitle += '...';
}
ctx.fillText(displayTitle, exportCanvas.width / 2, padding + 20);
// Draw QR
const img = new Image();
img.onload = function() {
ctx.drawImage(img, padding, padding + titleHeight, qrSize, qrSize);
// Footer text
ctx.fillStyle = '#6b7280';
ctx.font = '12px sans-serif';
ctx.fillText('สแกนเพื่อตอบแบบสอบถาม — TPU Survey', exportCanvas.width / 2, exportCanvas.height - 16);
// Download
const link = document.createElement('a');
const safeName = title.replace(/[^a-zA-Z0-9ก-๙]/g, '_').substring(0, 50);
link.download = `QR_${safeName}.png`;
link.href = exportCanvas.toDataURL('image/png');
link.click();
Toast.success('ดาวน์โหลด QR Code สำเร็จ');
};
img.src = qrDataUrl;
}
function printQR() {
const qrEl = getQRImage();
const title = document.getElementById('qrSurveyTitle')?.textContent || 'แบบสอบถาม';
const linkText = document.getElementById('surveyLinkText')?.textContent || '';
if (!qrEl) return;
let qrDataUrl;
if (qrEl.tagName === 'CANVAS') {
qrDataUrl = qrEl.toDataURL('image/png');
} else {
qrDataUrl = qrEl.src;
}
const printWindow = window.open('', '_blank');
printWindow.document.write(`
<!DOCTYPE html><html><head><title>พิมพ์ QR Code</title>
<style>
body { font-family: 'Sarabun', sans-serif; text-align: center; padding: 40px; }
h2 { color: #1a1a2e; margin-bottom: 8px; }
.subtitle { color: #6b7280; font-size: 14px; margin-bottom: 24px; }
img { border: 2px solid #e5e7eb; border-radius: 12px; padding: 8px; }
.link { color: #6b7280; font-size: 11px; word-break: break-all; margin-top: 16px; }
.footer { margin-top: 32px; color: #9ca3af; font-size: 12px; }
</style></head>
<body>
<h2>${title}</h2>
<p class="subtitle">สแกน QR Code เพื่อตอบแบบสอบถาม</p>
<img src="${qrDataUrl}" width="300" height="300">
<p class="link">${linkText}</p>
<p class="footer">TPU Survey — มหาวิทยาลัยตาปี</p>
<script>window.onload = function() { window.print(); }<\/script>
</body></html>
`);
printWindow.document.close();
}
async function deleteSurvey(id, title) {
if (!confirm(`ต้องการลบแบบสอบถาม "${title}" ใช่หรือไม่?\nข้อมูลคำตอบทั้งหมดจะถูกลบด้วย`)) return;
try {
await API.post('surveys.php?action=delete', { id });
Toast.success('ลบแบบสอบถามสำเร็จ');
loadSurveys();
} catch (err) {
Toast.error(err.message);
}
}
// ============================================================
// Statistics View
// ============================================================
function viewStats(surveyId) {
window.open(`stats.html?survey_id=${surveyId}`, '_blank');
}
function exportReport(type, surveyId) {
window.open(`api/export.php?action=${type}&survey_id=${surveyId}`, '_blank');
}
// ============================================================
// Google Forms Import Modals & Functions
// ============================================================
function showImportGoogleFormModal() {
const html = `
<div style="margin-bottom:16px;">
<div style="display:flex;gap:12px;margin-bottom:16px;border-bottom:1px solid #e5e7eb;padding-bottom:10px;">
<button id="tabUrlBtn" type="button" class="btn btn-sm btn-primary" onclick="switchImportTab('url')">🌐 วาง URL / Link</button>
<button id="tabJsonBtn" type="button" class="btn btn-sm btn-secondary" onclick="switchImportTab('json')">📄 อัปโหลดไฟล์ JSON</button>
</div>
<!-- Tab 1: URL -->
<div id="importTabUrl">
<div class="form-group">
<label class="form-label">Google Form URL / Link *</label>
<input type="url" class="form-input" id="googleFormUrl" placeholder="https://docs.google.com/forms/d/e/1FAIpQLS.../viewform">
<small class="text-muted mt-8" style="display:block;">วางลิงก์แบบสอบถาม Google Forms ที่เปิดสาธารณะ (Public)</small>
</div>
</div>
<!-- Tab 2: JSON -->
<div id="importTabJson" style="display:none;">
<div class="form-group">
<label class="form-label">เลือกไฟล์ JSON โครงสร้างฟอร์ม *</label>
<input type="file" class="form-input" id="googleFormJsonFile" accept=".json">
</div>
<div class="form-group">
<label class="form-label">หรือวางข้อมูล JSON ตรงนี้</label>
<textarea class="form-input" id="googleFormJsonText" rows="4" placeholder='{"title": "แบบสอบถาม...", "sections": [...]}'></textarea>
</div>
</div>
</div>
`;
const footer = `
<button class="btn btn-secondary" onclick="Modal.hide('importGFormModal');document.getElementById('importGFormModal')?.remove();">ยกเลิก</button>
<button class="btn btn-primary" id="btnSubmitImportGForm" onclick="submitImportGoogleForm()">📥 นำเข้าแบบสอบถาม</button>
`;
const modalId = Modal.create('📥 นำเข้าแบบสอบถามจาก Google Forms', html, footer);
const modalEl = document.getElementById(modalId);
if (modalEl) modalEl.id = 'importGFormModal';
}
function switchImportTab(tab) {
const urlTab = document.getElementById('importTabUrl');
const jsonTab = document.getElementById('importTabJson');
const urlBtn = document.getElementById('tabUrlBtn');
const jsonBtn = document.getElementById('tabJsonBtn');
if (tab === 'url') {
if (urlTab) urlTab.style.display = 'block';
if (jsonTab) jsonTab.style.display = 'none';
if (urlBtn) urlBtn.className = 'btn btn-sm btn-primary';
if (jsonBtn) jsonBtn.className = 'btn btn-sm btn-secondary';
} else {
if (urlTab) urlTab.style.display = 'none';
if (jsonTab) jsonTab.style.display = 'block';
if (urlBtn) urlBtn.className = 'btn btn-sm btn-secondary';
if (jsonBtn) jsonBtn.className = 'btn btn-sm btn-primary';
}
}
async function submitImportGoogleForm() {
const urlTab = document.getElementById('importTabUrl');
const btn = document.getElementById('btnSubmitImportGForm');
if (btn) {
btn.disabled = true;
btn.textContent = '⏳ กำลังนำเข้าข้อมูล...';
}
try {
if (urlTab && urlTab.style.display !== 'none') {
const url = document.getElementById('googleFormUrl')?.value?.trim();
if (!url) {
Toast.warning('กรุณาระบุ Google Form URL');
if (btn) { btn.disabled = false; btn.textContent = '📥 นำเข้าแบบสอบถาม'; }
return;
}
const res = await API.post('import_google_form.php?action=importFromUrl', { url });
Toast.success(res.message || 'นำเข้าแบบสอบถามสำเร็จ!');
Modal.hide('importGFormModal');
document.getElementById('importGFormModal')?.remove();
if (typeof loadSurveys === 'function') loadSurveys();
if (typeof loadTeacherSurveys === 'function') loadTeacherSurveys();
} else {
const fileInput = document.getElementById('googleFormJsonFile');
const jsonText = document.getElementById('googleFormJsonText')?.value?.trim();
let surveyData = null;
if (fileInput && fileInput.files.length > 0) {
const text = await fileInput.files[0].text();
surveyData = JSON.parse(text);
} else if (jsonText) {
surveyData = JSON.parse(jsonText);
}
if (!surveyData) {
Toast.warning('กรุณาเลือกไฟล์ JSON หรือระบุข้อความ JSON');
if (btn) { btn.disabled = false; btn.textContent = '📥 นำเข้าแบบสอบถาม'; }
return;
}
const res = await API.post('import_google_form.php?action=importFromJson', { survey_data: surveyData });
Toast.success(res.message || 'นำเข้าแบบสอบถามสำเร็จ!');
Modal.hide('importGFormModal');
document.getElementById('importGFormModal')?.remove();
if (typeof loadSurveys === 'function') loadSurveys();
if (typeof loadTeacherSurveys === 'function') loadTeacherSurveys();
}
} catch (err) {
Toast.error(err.message || 'เกิดข้อผิดพลาดในการนำเข้าแบบสอบถาม');
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = '📥 นำเข้าแบบสอบถาม';
}
}
}
function showImportResponsesModal(surveyId, surveyTitle) {
const html = `
<form id="importRespForm">
<input type="hidden" name="survey_id" value="${surveyId}">
<div class="form-group">
<label class="form-label">แบบสอบถาม:</label>
<div style="font-weight:600;font-size:15px;color:var(--navy-600);">${escapeHtml(surveyTitle)}</div>
</div>
<div class="form-group">
<label class="form-label">เลือกไฟล์ CSV ข้อมูลคำตอบจาก Google Forms *</label>
<input type="file" class="form-input" name="csv_file" accept=".csv" required>
<small class="text-muted mt-8" style="display:block;">ไฟล์ .csv ที่ Export มาจาก Google Sheets / Google Forms (แถวแรกเป็นชื่อคำถาม)</small>
</div>
</form>
`;
const footer = `
<button class="btn btn-secondary" onclick="Modal.hide('importRespModal');document.getElementById('importRespModal')?.remove();">ยกเลิก</button>
<button class="btn btn-primary" id="btnSubmitImportResp" onclick="submitImportResponses(${surveyId})">📥 นำเข้าคำตอบลงระบบ</button>
`;
const modalId = Modal.create('📥 นำเข้าคำตอบจาก Google Forms (CSV)', html, footer);
const modalEl = document.getElementById(modalId);
if (modalEl) modalEl.id = 'importRespModal';
}
async function submitImportResponses(surveyId) {
const form = document.getElementById('importRespForm');
const fileInput = form?.querySelector('input[name="csv_file"]');
if (!fileInput || fileInput.files.length === 0) {
Toast.warning('กรุณาเลือกไฟล์ CSV คำตอบ');
return;
}
const btn = document.getElementById('btnSubmitImportResp');
if (btn) {
btn.disabled = true;
btn.textContent = '⏳ กำลังนำเข้าคำตอบ...';
}
const fd = new FormData(form);
try {
const res = await API.upload('import_responses.php', fd);
Toast.success(res.message || 'นำเข้าคำตอบสำเร็จ!');
Modal.hide('importRespModal');
document.getElementById('importRespModal')?.remove();
if (typeof loadSurveys === 'function') loadSurveys();
if (typeof loadTeacherSurveys === 'function') loadTeacherSurveys();
} catch (err) {
Toast.error(err.message || 'เกิดข้อผิดพลาดในการนำเข้าคำตอบ');
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = '📥 นำเข้าคำตอบลงระบบ';
}
}
}
// ============================================================
// Profile Management — Matching teacher.js
// ============================================================
async function loadProfile() {
try {
const data = await API.get('profile.php?action=get');
const u = data.user;
// Fill the form
if (document.getElementById('profileCode')) document.getElementById('profileCode').value = u.personnel_code || u.username || '-';
if (document.getElementById('profileName')) document.getElementById('profileName').value = u.full_name || '';
if (document.getElementById('profileEmail')) document.getElementById('profileEmail').value = u.email || '';
if (document.getElementById('profileSubject')) document.getElementById('profileSubject').value = u.subject_area || '';
if (document.getElementById('profileBranch')) document.getElementById('profileBranch').value = u.branch_code || '';
if (document.getElementById('profilePassword')) document.getElementById('profilePassword').value = '';
if (document.getElementById('profilePasswordConfirm')) document.getElementById('profilePasswordConfirm').value = '';
// Fill the sidebar card
if (document.getElementById('profileDisplayName')) document.getElementById('profileDisplayName').textContent = u.full_name || '-';
if (document.getElementById('profileDisplayRole')) document.getElementById('profileDisplayRole').textContent = u.role === 'admin' ? 'ผู้ดูแลระบบ' : 'อาจารย์ / เจ้าของโครงการ';
if (document.getElementById('profileAvatar')) document.getElementById('profileAvatar').textContent = (u.full_name || 'A').charAt(0).toUpperCase();
if (document.getElementById('profilePersonnelCode')) document.getElementById('profilePersonnelCode').textContent = u.personnel_code || u.username || '-';
const badge = document.getElementById('profileBadge');
if (badge) {
badge.textContent = u.role === 'admin' ? '👑 ผู้ดูแลระบบ' : '🎓 อาจารย์';
badge.className = `user-card-badge ${u.role}`;
}
} catch (err) {
Toast.error(err.message);
}
}
async function saveProfile() {
const fullName = document.getElementById('profileName')?.value.trim();
const email = document.getElementById('profileEmail')?.value.trim();
const subjectArea = document.getElementById('profileSubject')?.value.trim();
const branchCode = document.getElementById('profileBranch')?.value.trim();
const password = document.getElementById('profilePassword')?.value;
const passwordConfirm = document.getElementById('profilePasswordConfirm')?.value;
if (!fullName) {
Toast.warning('กรุณากรอกชื่อ-นามสกุล');
return;
}
if (password && password.length < 4) {
Toast.warning('รหัสผ่านต้องมีอย่างน้อย 4 ตัวอักษร');
return;
}
if (password && password !== passwordConfirm) {
Toast.warning('รหัสผ่านไม่ตรงกัน');
return;
}
const payload = {
full_name: fullName,
email: email,
subject_area: subjectArea,
branch_code: branchCode
};
if (password) {
payload.password = password;
}
try {
await API.post('profile.php?action=update', payload);
Toast.success('บันทึกข้อมูลส่วนตัวสำเร็จ');
// Update sidebar display
const nameEl = document.getElementById('userName');
const avatarEl = document.getElementById('userAvatar');
if (nameEl) nameEl.textContent = fullName;
if (avatarEl) avatarEl.textContent = fullName.charAt(0).toUpperCase();
// Reload profile card
loadProfile();
} catch (err) {
Toast.error(err.message);
}
}
// Global Window bindings
window.showImportGoogleFormModal = showImportGoogleFormModal;
window.switchImportTab = switchImportTab;
window.submitImportGoogleForm = submitImportGoogleForm;
window.showImportResponsesModal = showImportResponsesModal;
window.submitImportResponses = submitImportResponses;
window.loadProfile = loadProfile;
window.saveProfile = saveProfile;
// Init
document.addEventListener('DOMContentLoaded', initAdmin);