PDPA Notice: ระบบนี้ทำงานบน Client-Side Browser Storage โปรดหลีกเลี่ยงการบันทึกข้อมูลส่วนบุคคลที่อ่อนไหว เช่น เลขบัตรประชาชน หรือข้อมูลทางการเงิน
|

Dashboard แผนพัฒนาบุคลากรประจำปี

Annual Human Resource Development & Analytics System

งบประมาณรวมทั้งหมด

฿0

คำนวณตาม Filter ปัจจุบัน

บุคลากรที่เข้าร่วมอบรม

0 คน

จำนวนพนักงานในระบบ

จำนวนหลักสูตรทั้งหมด

0 หลักสูตร

รายการหลักสูตรที่ลงทะเบียน

อัตราความสำเร็จ (Success Rate)

0%

สัดส่วนหลักสูตรที่ 'เสร็จสิ้น'

สัดส่วนงบประมาณแยกตาม Skill

จำนวนผู้เข้าร่วมอบรมแยกตามแผนก

เลื่อนซ้าย-ขวาเพื่อดูข้อมูลตารางเพิ่มเติม
ปี ชื่อ-นามสกุล แผนก / ตำแหน่ง หลักสูตร ประเภท Skill งบประมาณ (บาท) สถานะ จัดการ
// Render Dynamic KPI Cards - Secure textContent implementation function renderKPIs() { const totalBudget = filteredItems.reduce((acc, curr) => acc + Number(curr.budget || 0), 0); const totalStaff = filteredItems.length; // Unique courses const uniqueCourses = new Set(filteredItems.map(i => i.course)).size; // Success Rate const completedCount = filteredItems.filter(i => i.status === 'เสร็จสิ้น').length; const successRate = totalStaff > 0 ? ((completedCount / totalStaff) * 100).toFixed(1) : 0; // Secure rendering using textContent (Preventing DOM-based XSS) document.getElementById('kpi-total-budget').textContent = `฿${totalBudget.toLocaleString()}`; document.getElementById('kpi-total-staff').textContent = `${totalStaff} คน`; document.getElementById('kpi-total-courses').textContent = `${uniqueCourses} หลักสูตร`; document.getElementById('kpi-success-rate').textContent = `${successRate}%`; } // Backup Data Management: Export JSON function exportBackupData() { if (hrItems.length === 0) { showToast("ไม่มีข้อมูลสำหรับสำรอง", "error"); return; } const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(hrItems, null, 2)); const downloadAnchor = document.createElement('a'); downloadAnchor.setAttribute("href", dataStr); downloadAnchor.setAttribute("download", `HRD_Backup_${new Date().toISOString().slice(0,10)}.json`); document.body.appendChild(downloadAnchor); downloadAnchor.click(); downloadAnchor.remove(); showToast("สำรองข้อมูลเป็นไฟล์ JSON สำเร็จ", "success"); } // Restore Data Management: Import JSON function importBackupData(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const importedData = JSON.parse(e.target.result); if (Array.isArray(importedData)) { hrItems = importedData; saveToLocalStorage(); applyFilters(); showToast("นำเข้าข้อมูลสำรองสำเร็จ", "success"); } else { showToast("รูปแบบไฟล์ JSON ไม่ถูกต้อง", "error"); } } catch (err) { showToast("เกิดข้อผิดพลาดในการอ่านไฟล์ JSON", "error"); } }; reader.readAsText(file); event.target.value = ''; // Reset input } // Initialize App on DOM Load window.addEventListener('DOMContentLoaded', () => { loadInitialData(); initCharts(); applyFilters(); }); // Load data from LocalStorage or Mock Data function loadInitialData() { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { hrItems = JSON.parse(stored); } else { hrItems = [...mockData]; saveToLocalStorage(); } } catch (e) { console.error("Failed to load local storage, falling back to mock data.", e); hrItems = [...mockData]; } } function saveToLocalStorage() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(hrItems)); } catch (e) { showToast("ไม่สามารถบันทึกข้อมูลลง LocalStorage ได้", "error"); } } // Apply Filters & Search Query function applyFilters() { const yearVal = document.getElementById('filter-year').value; const deptVal = document.getElementById('filter-department').value; const skillVal = document.getElementById('filter-skill').value; const searchVal = document.getElementById('search-input').value.trim().toLowerCase(); filteredItems = hrItems.filter(item => { const matchYear = yearVal === 'ALL' || item.year === yearVal; const matchDept = deptVal === 'ALL' || item.dept === deptVal; const matchSkill = skillVal === 'ALL' || item.skill === skillVal; const matchSearch = !searchVal || item.name.toLowerCase().includes(searchVal) || item.course.toLowerCase().includes(searchVal) || item.position.toLowerCase().includes(searchVal); return matchYear && matchDept && matchSkill && matchSearch; }); renderTable(); renderKPIs(); updateCharts(); } function resetFilters() { document.getElementById('filter-year').value = 'ALL'; document.getElementById('filter-department').value = 'ALL'; document.getElementById('filter-skill').value = 'ALL'; document.getElementById('search-input').value = ''; applyFilters(); } // Render Table Rows function renderTable() { const tbody = document.getElementById('table-body'); const emptyState = document.getElementById('empty-state'); tbody.innerHTML = ''; if (filteredItems.length === 0) { emptyState.classList.remove('hidden'); return; } emptyState.classList.add('hidden'); filteredItems.forEach(item => { const tr = document.createElement('tr'); tr.className = 'hover:bg-slate-50 transition border-b border-slate-100'; // Status Badge styling let statusBadge = ''; if (item.status === 'เสร็จสิ้น') { statusBadge = ` เสร็จสิ้น`; } else if (item.status === 'กำลังดำเนินการ') { statusBadge = ` กำลังดำเนินการ`; } else { statusBadge = ` ยังไม่เริ่ม`; } // Skill Badge let skillBadge = `${escapeHTML(item.skill)}`; tr.innerHTML = ` ${escapeHTML(item.year)} ${escapeHTML(item.name)}
${escapeHTML(item.dept)}
${escapeHTML(item.position)}
${escapeHTML(item.course)} ${skillBadge} ฿${Number(item.budget).toLocaleString()} ${statusBadge}
`; tbody.appendChild(tr); }); } // Render Dynamic KPI Cards function renderKPIs() { const totalBudget = filteredItems.reduce((acc, curr) => acc + Number(curr.budget || 0), 0); const totalStaff = filteredItems.length; // Unique courses const uniqueCourses = new Set(filteredItems.map(i => i.course)).size; // Success Rate const completedCount = filteredItems.filter(i => i.status === 'เสร็จสิ้น').length; const successRate = totalStaff > 0 ? ((completedCount / totalStaff) * 100).toFixed(1) : 0; document.getElementById('kpi-total-budget').innerText = `฿${totalBudget.toLocaleString()}`; document.getElementById('kpi-total-staff').innerText = `${totalStaff} คน`; document.getElementById('kpi-total-courses').innerText = `${uniqueCourses} หลักสูตร`; document.getElementById('kpi-success-rate').innerText = `${successRate}%`; } // Chart.js Initialization function initCharts() { const ctxA = document.getElementById('skillBudgetChart').getContext('2d'); skillChartInstance = new Chart(ctxA, { type: 'doughnut', data: { labels: ['Hard Skill', 'Soft Skill', 'Leadership'], datasets: [{ data: [0, 0, 0], backgroundColor: ['#0F172A', '#F97316', '#3B82F6'], borderWidth: 2, borderColor: '#FFFFFF' }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { font: { family: 'Sarabun' } } } } } }); const ctxB = document.getElementById('deptProgressChart').getContext('2d'); deptChartInstance = new Chart(ctxB, { type: 'bar', data: { labels: ['IT', 'HR', 'Sales', 'Finance', 'Operations'], datasets: [{ label: 'จำนวนคนอบรม', data: [0, 0, 0, 0, 0], backgroundColor: '#1E293B', borderRadius: 8 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { precision: 0, font: { family: 'Sarabun' } } }, x: { ticks: { font: { family: 'Sarabun' } } } }, plugins: { legend: { display: false } } } }); } // Update Charts with filtered data function updateCharts() { if (!skillChartInstance || !deptChartInstance) return; // Update Chart A: Skill Budget const skillBudgets = { 'Hard Skill': 0, 'Soft Skill': 0, 'Leadership': 0 }; filteredItems.forEach(i => { if (skillBudgets[i.skill] !== undefined) { skillBudgets[i.skill] += Number(i.budget || 0); } }); skillChartInstance.data.datasets[0].data = [ skillBudgets['Hard Skill'], skillBudgets['Soft Skill'], skillBudgets['Leadership'] ]; skillChartInstance.update(); // Update Chart B: Department Participants const depts = ['IT', 'HR', 'Sales', 'Finance', 'Operations']; const deptCounts = { 'IT': 0, 'HR': 0, 'Sales': 0, 'Finance': 0, 'Operations': 0 }; filteredItems.forEach(i => { if (deptCounts[i.dept] !== undefined) { deptCounts[i.dept] += 1; } }); deptChartInstance.data.datasets[0].data = depts.map(d => deptCounts[d]); deptChartInstance.update(); } // Modal Management function openAddModal() { document.getElementById('modal-title').innerText = 'เพิ่มแผนพัฒนาบุคลากร'; document.getElementById('form-id').value = ''; document.getElementById('hr-form').reset(); document.getElementById('modal-form').classList.remove('hidden'); } function openEditModal(id) { const item = hrItems.find(i => i.id === id); if (!item) return; document.getElementById('modal-title').innerText = 'แก้ไขแผนพัฒนาบุคลากร'; document.getElementById('form-id').value = item.id; document.getElementById('form-year').value = item.year; document.getElementById('form-department').value = item.dept; document.getElementById('form-name').value = item.name; document.getElementById('form-position').value = item.position; document.getElementById('form-course').value = item.course; document.getElementById('form-skill').value = item.skill; document.getElementById('form-budget').value = item.budget; document.getElementById('form-status').value = item.status; document.getElementById('modal-form').classList.remove('hidden'); } function closeModal() { document.getElementById('modal-form').classList.add('hidden'); } // Save Item (Create or Update) function saveItem(e) { e.preventDefault(); const id = document.getElementById('form-id').value; const newItem = { id: id || Date.now().toString(), year: document.getElementById('form-year').value, dept: document.getElementById('form-department').value, name: document.getElementById('form-name').value.trim(), position: document.getElementById('form-position').value.trim(), course: document.getElementById('form-course').value.trim(), skill: document.getElementById('form-skill').value, budget: Number(document.getElementById('form-budget').value), status: document.getElementById('form-status').value }; if (id) { // Update const idx = hrItems.findIndex(i => i.id === id); if (idx !== -1) hrItems[idx] = newItem; showToast("อัปเดตข้อมูลสำเร็จ", "success"); } else { // Create hrItems.unshift(newItem); showToast("บันทึกข้อมูลใหม่สำเร็จ", "success"); } saveToLocalStorage(); applyFilters(); closeModal(); } // Delete Confirm Dialog function confirmDelete(id) { pendingDeleteId = id; const item = hrItems.find(i => i.id === id); if (!item) return; document.getElementById('confirm-title').innerText = `ยืนยันการลบแผนพัฒนา`; document.getElementById('confirm-desc').innerText = `คุณแน่ใจหรือไม่ว่าต้องการลบหลักสูตร "${item.course}" ของคุณ ${item.name}?`; document.getElementById('btn-confirm-action').onclick = executeDelete; document.getElementById('modal-confirm').classList.remove('hidden'); } function executeDelete() { if (!pendingDeleteId) return; hrItems = hrItems.filter(i => i.id !== pendingDeleteId); saveToLocalStorage(); applyFilters(); closeConfirmModal(); showToast("ลบข้อมูลสำเร็จ", "success"); } function closeConfirmModal() { document.getElementById('modal-confirm').classList.add('hidden'); pendingDeleteId = null; } // Reset to Initial Mock Data function confirmResetMockData() { document.getElementById('confirm-title').innerText = `คืนค่าข้อมูลตัวอย่าง`; document.getElementById('confirm-desc').innerText = `คุณแน่ใจหรือไม่ว่าต้องการล้างข้อมูลปัจจุบันทั้งหมด และกลับไปใช้ข้อมูลตัวอย่างเริ่มต้น?`; document.getElementById('btn-confirm-action').onclick = () => { hrItems = [...mockData]; saveToLocalStorage(); applyFilters(); closeConfirmModal(); showToast("รีเซ็ตข้อมูลกลับสู่ค่าเริ่มต้นเรียบร้อย", "success"); }; document.getElementById('modal-confirm').classList.remove('hidden'); } // Data Export: Excel (SheetJS) function exportToExcel() { if (filteredItems.length === 0) { showToast("ไม่มีข้อมูลสำหรับส่งออก", "error"); return; } const exportData = filteredItems.map(item => ({ 'ปีงบประมาณ': item.year, 'ชื่อ-นามสกุล': item.name, 'แผนก': item.dept, 'ตำแหน่ง': item.position, 'ชื่อหลักสูตร': item.course, 'ประเภท Skill': item.skill, 'งบประมาณ (บาท)': item.budget, 'สถานะ': item.status })); const worksheet = XLSX.utils.json_to_sheet(exportData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "HRD_Plan"); XLSX.writeFile(workbook, `HRD_Development_Plan_${new Date().toISOString().slice(0,10)}.xlsx`); showToast("ส่งออกไฟล์ Excel สำเร็จ", "success"); } // Data Export: PDF (html2pdf) function exportToPDF() { const element = document.getElementById('export-area'); const opt = { margin: [0.3, 0.3, 0.3, 0.3], filename: `HRD_Dashboard_Report_${new Date().toISOString().slice(0,10)}.pdf`, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: 'in', format: 'a4', orientation: 'landscape' } }; showToast("กำลังสร้างเอกสาร PDF...", "info"); html2pdf().set(opt).from(element).save().then(() => { showToast("ส่งออกไฟล์ PDF สำเร็จ", "success"); }).catch(err => { console.error("PDF Export error:", err); showToast("เกิดข้อผิดพลาดในการสร้าง PDF", "error"); }); } // Custom Toast Notification System function showToast(message, type = 'info') { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); let bgClass = 'bg-slate-800 text-white'; let icon = 'fa-info-circle'; if (type === 'success') { bgClass = 'bg-emerald-600 text-white'; icon = 'fa-check-circle'; } else if (type === 'error') { bgClass = 'bg-rose-600 text-white'; icon = 'fa-exclamation-circle'; } toast.className = `pointer-events-auto flex items-center gap-3 p-4 rounded-xl shadow-lg ${bgClass} transition-all duration-300 transform translate-y-2 opacity-0 text-sm font-medium`; toast.innerHTML = ` ${escapeHTML(message)}`; container.appendChild(toast); // Animate in setTimeout(() => { toast.classList.remove('translate-y-2', 'opacity-0'); }, 10); // Remove after 3s setTimeout(() => { toast.classList.add('opacity-0', 'translate-y-2'); setTimeout(() => toast.remove(), 300); }, 3000); }