Initial commit - TPU Survey System with Google Forms & Certificate Generator
This commit is contained in:
+804
@@ -0,0 +1,804 @@
|
||||
/**
|
||||
* TPU Survey - Teacher Dashboard Logic
|
||||
*/
|
||||
|
||||
let teacherSurveys = [];
|
||||
|
||||
async function initTeacher() {
|
||||
const loggedIn = await Auth.check();
|
||||
if (!loggedIn || !Auth.isTeacher()) {
|
||||
if (Auth.isAdmin()) {
|
||||
window.location.href = 'admin.html';
|
||||
return;
|
||||
}
|
||||
window.location.href = 'index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('userName').textContent = Auth.user.full_name;
|
||||
document.getElementById('userRole').textContent = 'อาจารย์ / เจ้าของโครงการ';
|
||||
document.getElementById('userAvatar').textContent = Auth.user.full_name.charAt(0);
|
||||
|
||||
showTeacherView('mySurveys');
|
||||
}
|
||||
|
||||
function showTeacherView(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 = { mySurveys: 'แบบสอบถามของฉัน', profile: 'ข้อมูลส่วนตัว' };
|
||||
document.getElementById('pageTitle').textContent = titles[view] || '';
|
||||
|
||||
if (view === 'mySurveys') loadTeacherSurveys();
|
||||
else if (view === 'profile') loadProfile();
|
||||
}
|
||||
|
||||
async function loadTeacherSurveys() {
|
||||
try {
|
||||
const data = await API.get('surveys.php?action=list');
|
||||
teacherSurveys = data.surveys || [];
|
||||
renderTeacherSurveys();
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTeacherSurveys() {
|
||||
const container = document.getElementById('teacherSurveysList') || document.getElementById('teacherSurveysBody');
|
||||
if (!container) return;
|
||||
|
||||
if (teacherSurveys.length === 0) {
|
||||
const emptyHtml = `
|
||||
<div class="card-tapi p-8 text-center bg-white rounded-lg border border-slate-200">
|
||||
<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 (container.tagName === 'TBODY') {
|
||||
container.innerHTML = `<tr><td colspan="5" class="text-center text-muted" style="padding:40px">ยังไม่มีแบบสอบถาม กดปุ่ม "สร้างแบบสอบถาม" เพื่อเริ่มต้น</td></tr>`;
|
||||
} else {
|
||||
container.innerHTML = emptyHtml;
|
||||
}
|
||||
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 = teacherSurveys.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-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">
|
||||
<button onclick="teacherEditSurvey(${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="teacherDuplicateSurvey(${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="teacherGenerateLink(${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="teacherViewStats(${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="teacherDeleteSurvey(${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('');
|
||||
}
|
||||
|
||||
async function teacherDeleteSurvey(id, title) {
|
||||
if (!confirm(`คุณต้องการลบแบบสอบถาม "${title}" ใช่หรือไม่?`)) return;
|
||||
try {
|
||||
await API.get(`surveys.php?action=delete&id=${id}`);
|
||||
Toast.success('ลบแบบสอบถามเรียบร้อยแล้ว');
|
||||
loadTeacherSurveys();
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function showTeacherCreateModal() {
|
||||
const html = `
|
||||
<form id="teacherSurveyForm">
|
||||
<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>
|
||||
</form>
|
||||
`;
|
||||
const footer = `
|
||||
<button class="btn btn-secondary" onclick="Modal.hide('tCreateModal');document.getElementById('tCreateModal').remove();">ยกเลิก</button>
|
||||
<button class="btn btn-primary" onclick="submitTeacherCreate()">สร้างแบบสอบถาม (ใช้ 3 ส่วนมาตรฐาน)</button>
|
||||
`;
|
||||
Modal.create('สร้างแบบสอบถามใหม่', html, footer);
|
||||
document.querySelector('.modal-overlay:last-child').id = 'tCreateModal';
|
||||
}
|
||||
|
||||
async function submitTeacherCreate() {
|
||||
const form = document.getElementById('teacherSurveyForm');
|
||||
const fd = new FormData(form);
|
||||
const data = Object.fromEntries(fd);
|
||||
|
||||
if (!data.title) { Toast.warning('กรุณากรอกชื่อ'); return; }
|
||||
|
||||
// Pre-fill 3 sections
|
||||
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('tCreateModal');
|
||||
document.getElementById('tCreateModal').remove();
|
||||
loadTeacherSurveys();
|
||||
setTimeout(() => teacherEditSurvey(result.id), 500);
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function teacherEditSurvey(id) {
|
||||
window.open(`edit.html?id=${id}`, '_blank');
|
||||
}
|
||||
|
||||
function buildTeacherQEditor(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="addTeacherQ(${si}, this.closest('.question-builder'))" title="แทรกคำถามใต้ข้อนี้">➕ แทรก</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm text-danger" onclick="removeTeacherQ(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="toggleTeacherOptions(this)">
|
||||
<option value="radio" ${qType==='radio'?'selected':''}>📊 ระดับ 1-5</option>
|
||||
<option value="text" ${qType==='text'?'selected':''}>📝 ข้อความ</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 addTeacherSection() {
|
||||
const container = document.getElementById('additionalTeacherSections');
|
||||
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="t-questions-${si}"></div>
|
||||
<button type="button" class="btn btn-secondary btn-sm mt-16" onclick="addTeacherQ(${si})">+ เพิ่มคำถาม</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
// Add one initial question to the new section
|
||||
addTeacherQ(si);
|
||||
}
|
||||
|
||||
function toggleTeacherOptions(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 addTeacherQ(si, afterEl = null) {
|
||||
const container = document.getElementById(`t-questions-${si}`);
|
||||
if (!container) return;
|
||||
|
||||
// Create temporary div to parse HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = buildTeacherQEditor(si, 0);
|
||||
const newQ = tempDiv.firstElementChild;
|
||||
|
||||
if (afterEl) {
|
||||
afterEl.after(newQ);
|
||||
} else {
|
||||
container.appendChild(newQ);
|
||||
}
|
||||
|
||||
reindexTeacherQuestions(si);
|
||||
}
|
||||
|
||||
function removeTeacherQ(btn, si) {
|
||||
if (!confirm('ยืนยันลบคำถามนี้?')) return;
|
||||
btn.closest('.question-builder').remove();
|
||||
reindexTeacherQuestions(si);
|
||||
}
|
||||
|
||||
function reindexTeacherQuestions(si) {
|
||||
const container = document.getElementById(`t-questions-${si}`);
|
||||
if (!container) return;
|
||||
container.querySelectorAll('.question-builder').forEach((qEl, idx) => {
|
||||
const label = qEl.querySelector('.q-number-label');
|
||||
if (label) label.textContent = `คำถาม`;
|
||||
});
|
||||
}
|
||||
|
||||
async function submitTeacherEdit() {
|
||||
const form = document.getElementById('tEditForm');
|
||||
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 sTitleInput = sectionEl.querySelector('input[data-field^="section-title"]');
|
||||
const sCategorySelect = sectionEl.querySelector('select[data-field^="section-category"]');
|
||||
const sTitle = sTitleInput ? sTitleInput.value : `ส่วนที่ ${si+1}`;
|
||||
const sCategory = sCategorySelect ? sCategorySelect.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('tEditModal');
|
||||
document.getElementById('tEditModal').remove();
|
||||
loadTeacherSurveys();
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function teacherDuplicateSurvey(id) {
|
||||
if (!confirm('ยืนยันสร้างสำเนาแบบสอบถามนี้?')) return;
|
||||
try {
|
||||
await API.post('surveys.php?action=duplicate', { id });
|
||||
Toast.success('คัดลอกสำเร็จ');
|
||||
loadTeacherSurveys();
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function teacherGenerateLink(id) {
|
||||
try {
|
||||
const data = await API.post('surveys.php?action=generateLink', { id });
|
||||
const fullUrl = data.full_url || `${window.location.origin}/survey/respond.php?link=${data.unique_link}`;
|
||||
|
||||
// Find survey title
|
||||
const survey = teacherSurveys.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="copyTeacherLink()">📋 คัดลอก</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="teacherDownloadQR()">📥 ดาวน์โหลด QR</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="teacherPrintQR()">🖨️ พิมพ์ 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 copyTeacherLink() {
|
||||
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 getTeacherQRImage() {
|
||||
const container = document.getElementById('qrCodeContainer');
|
||||
if (!container) return null;
|
||||
return container.querySelector('canvas') || container.querySelector('img');
|
||||
}
|
||||
|
||||
function teacherDownloadQR() {
|
||||
const qrEl = getTeacherQRImage();
|
||||
const title = document.getElementById('qrSurveyTitle')?.textContent || 'survey';
|
||||
if (!qrEl) return;
|
||||
|
||||
let qrDataUrl;
|
||||
if (qrEl.tagName === 'CANVAS') {
|
||||
qrDataUrl = qrEl.toDataURL('image/png');
|
||||
} else {
|
||||
qrDataUrl = qrEl.src;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
const img = new Image();
|
||||
img.onload = function() {
|
||||
ctx.drawImage(img, padding, padding + titleHeight, qrSize, qrSize);
|
||||
ctx.fillStyle = '#6b7280';
|
||||
ctx.font = '12px sans-serif';
|
||||
ctx.fillText('สแกนเพื่อตอบแบบสอบถาม — TPU Survey', exportCanvas.width / 2, exportCanvas.height - 16);
|
||||
|
||||
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 teacherPrintQR() {
|
||||
const qrEl = getTeacherQRImage();
|
||||
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();
|
||||
}
|
||||
|
||||
function teacherViewStats(surveyId) {
|
||||
window.open(`stats.html?survey_id=${surveyId}`, '_blank');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Profile Management
|
||||
// ============================================================
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const data = await API.get('profile.php?action=get');
|
||||
const u = data.user;
|
||||
|
||||
// Fill the form
|
||||
document.getElementById('profileCode').value = u.personnel_code || u.username || '-';
|
||||
document.getElementById('profileName').value = u.full_name || '';
|
||||
document.getElementById('profileEmail').value = u.email || '';
|
||||
document.getElementById('profileSubject').value = u.subject_area || '';
|
||||
document.getElementById('profileBranch').value = u.branch_code || '';
|
||||
document.getElementById('profilePassword').value = '';
|
||||
document.getElementById('profilePasswordConfirm').value = '';
|
||||
|
||||
// Fill the sidebar card
|
||||
document.getElementById('profileDisplayName').textContent = u.full_name || '-';
|
||||
document.getElementById('profileDisplayRole').textContent = u.role === 'admin' ? 'ผู้ดูแลระบบ' : 'อาจารย์ / เจ้าของโครงการ';
|
||||
document.getElementById('profileAvatar').textContent = (u.full_name || 'T').charAt(0).toUpperCase();
|
||||
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
|
||||
document.getElementById('userName').textContent = fullName;
|
||||
document.getElementById('userAvatar').textContent = fullName.charAt(0).toUpperCase();
|
||||
|
||||
// Reload profile card
|
||||
loadProfile();
|
||||
} catch (err) {
|
||||
Toast.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 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 = '📥 นำเข้าคำตอบลงระบบ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global Window bindings
|
||||
window.showImportGoogleFormModal = showImportGoogleFormModal;
|
||||
window.switchImportTab = switchImportTab;
|
||||
window.submitImportGoogleForm = submitImportGoogleForm;
|
||||
window.showImportResponsesModal = showImportResponsesModal;
|
||||
window.submitImportResponses = submitImportResponses;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initTeacher);
|
||||
Reference in New Issue
Block a user