/**
* 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 = `
ยังไม่มีแบบสอบถาม
`;
return;
}
const statusIcons = { active: '🟢', draft: '📝', closed: '🔴' };
const statusLabels = { active: 'เปิดใช้', draft: 'ร่าง', closed: 'ปิด' };
list.innerHTML = surveys.map(s => `
${statusIcons[s.status] || '📄'}
${escapeHtml(s.title)}
สร้างโดย ${escapeHtml(s.creator_name || '-')} · ${statusLabels[s.status] || s.status} · ${s.response_count || 0} คำตอบ
`).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 = `
ไม่พบผู้ใช้ ลองค้นหาด้วยคำอื่น หรือเพิ่มผู้ใช้ใหม่
`;
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 `
${(u.full_name || 'U').charAt(0).toUpperCase()}
${escapeHtml(u.full_name)}
รหัส: ${escapeHtml(u.personnel_code || u.username || '-')}
${u.email ? ` ${escapeHtml(u.email)} ` : ''}
${u.subject_area ? ` ${escapeHtml(u.subject_area)} ` : ''}
แก้ไข
${!isCurrentUser ? `
ลบ
` : 'บัญชีคุณ '}
`;
}).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 = `
`;
const footer = `
ยกเลิก
บันทึก
`;
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 = `
`;
const footer = `
ยกเลิก
บันทึก
`;
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 = `
ไม่พบแบบสอบถาม ลองค้นหาด้วยคำอื่น หรือสร้างแบบสอบถามใหม่
`;
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 `
${escapeHtml(s.title)}
${escapeHtml(s.creator_name || '-')}
${s.response_count || 0} คำตอบ
${formatDate(s.created_at)}
📝 ร่าง
🟢 เปิดใช้งาน
🔴 ปิดแล้ว
แก้ไข
คัดลอก
ลิงก์
นำเข้า
สถิติ
ลบ
`;
}).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 = `
`;
const footer = `
ยกเลิก
สร้างแบบสอบถาม (ใช้ 3 ส่วนมาตรฐาน)
`;
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 `
`;
}
function addSection() {
const container = document.getElementById('additionalSections');
if (!container) return;
const si = document.querySelectorAll('.card[data-section]').length;
const html = `
`;
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 = `
🔗 ลิงก์แบบสอบถาม
${fullUrl}
📋 คัดลอก
📱 QR Code สำหรับตอบแบบสอบถาม
${escapeHtml(surveyTitle)}
📥 ดาวน์โหลด QR
🖨️ พิมพ์ QR
⚡ แบบสอบถามถูกเปิดใช้งานอัตโนมัติเมื่อสร้างลิงก์
`;
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(btnEl) {
const text = document.getElementById('surveyLinkText')?.textContent?.trim();
if (text) {
const btn = btnEl || document.querySelector('.link-display .copy-btn');
copyTextToClipboard(text, btn);
}
}
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(`
พิมพ์ QR Code
${title}
สแกน QR Code เพื่อตอบแบบสอบถาม
${linkText}