1259 lines
60 KiB
JavaScript
1259 lines
60 KiB
JavaScript
/**
|
||
* 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" onclick="copyLink(this.querySelector('.copy-btn'))" style="cursor:pointer;" title="คลิกเพื่อคัดลอกลิงก์">
|
||
<span class="flex-1 select-all" id="surveyLinkText">${fullUrl}</span>
|
||
<button class="copy-btn" onclick="event.stopPropagation(); copyLink(this)">📋 คัดลอก</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(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(`
|
||
<!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);
|