| <!DOCTYPE html> |
| <html lang="th" class="h-full bg-[#0B0F19] text-[#F1F5F9]"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>ระบบติดตามและประเมินตัวชี้วัด สนอ. มรพส. (ปีงบประมาณ 2568)</title> |
| |
| |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Noto+Sans+Thai:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| |
| |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| colors: { |
| cyber: { |
| black: '#0B0F19', |
| navy: '#161F30', |
| charcoal: '#1F2937', |
| emerald: '#10B981', |
| mint: '#34D399', |
| slate: '#F1F5F9', |
| muted: '#64748B' |
| } |
| }, |
| fontFamily: { |
| sans: ['Noto Sans Thai', 'sans-serif'], |
| mono: ['JetBrains Mono', 'Fira Code', 'monospace'] |
| } |
| } |
| } |
| } |
| </script> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| |
| <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> |
| |
| <script src="https://unpkg.com/lucide@latest"></script> |
|
|
| <style> |
| |
| ::-webkit-scrollbar { |
| width: 8px; |
| height: 8px; |
| } |
| ::-webkit-scrollbar-track { |
| background: #0B0F19; |
| } |
| ::-webkit-scrollbar-thumb { |
| background: #1F2937; |
| border-radius: 4px; |
| } |
| ::-webkit-scrollbar-thumb:hover { |
| background: #10B981; |
| } |
| .glow-emerald { |
| box-shadow: 0 0 20px rgba(16, 185, 129, 0.15); |
| } |
| .text-glow { |
| text-shadow: 0 0 10px rgba(52, 211, 153, 0.3); |
| } |
| |
| .font-mono { |
| font-variant-numeric: tabular-nums; |
| } |
| </style> |
| </head> |
| <body class="h-full font-sans antialiased text-cyber-slate flex flex-col md:flex-row"> |
|
|
| |
| <div class="md:hidden w-full bg-cyber-navy border-b border-cyber-charcoal p-4 flex items-center justify-between sticky top-0 z-50"> |
| <div class="flex items-center space-x-3"> |
| <div class="p-2 bg-cyber-emerald/10 text-cyber-mint rounded-lg"> |
| <i data-lucide="shield-check" class="w-6 h-6"></i> |
| </div> |
| <div> |
| <h1 class="text-sm font-bold tracking-tight text-white">ระบบตัวชี้วัด สนอ. มรพส.</h1> |
| <p class="text-[10px] text-cyber-emerald">ปีงบประมาณ 2568</p> |
| </div> |
| </div> |
| <button id="mobileMenuBtn" class="p-2 rounded-lg text-cyber-slate hover:bg-cyber-charcoal focus:outline-none"> |
| <i data-lucide="menu" class="w-6 h-6"></i> |
| </button> |
| </div> |
|
|
| |
| <aside id="sidebar" class="hidden md:flex flex-col w-full md:w-80 lg:w-90 bg-cyber-navy border-r border-cyber-charcoal flex-shrink-0 h-screen overflow-y-auto sticky top-0 z-40 transition-all duration-300"> |
| |
| <div class="p-6 border-b border-cyber-charcoal flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="p-2.5 bg-cyber-emerald/15 text-cyber-mint rounded-xl border border-cyber-emerald/20 glow-emerald"> |
| <i data-lucide="trending-up" class="w-6 h-6"></i> |
| </div> |
| <div> |
| <h2 class="text-base font-bold text-white tracking-wide">สนอ. มรพส. 2568</h2> |
| <p class="text-xs text-cyber-mint font-mono font-semibold">KPI EVALUATION</p> |
| </div> |
| </div> |
| |
| <button id="collapseSidebarBtn" class="hidden md:flex p-1.5 rounded-lg text-cyber-muted hover:text-cyber-mint hover:bg-cyber-charcoal transition" title="ซ่อนแถบเมนู"> |
| <i data-lucide="chevron-left" class="w-5 h-5"></i> |
| </button> |
| </div> |
|
|
| |
| <div class="p-5 border-b border-cyber-charcoal bg-cyber-black/20"> |
| <div class="flex items-center justify-between mb-3"> |
| <span class="text-xs font-semibold text-cyber-muted tracking-wider uppercase">แหล่งข้อมูล</span> |
| <span id="dataStatusBadge" class="px-2 py-0.5 rounded-full text-[10px] font-medium bg-red-500/10 text-red-400 border border-red-500/20"> |
| ยังไม่มีข้อมูล |
| </span> |
| </div> |
| |
| |
| <div id="sidebarFileZone" class="border border-dashed border-cyber-charcoal rounded-lg p-3 hover:border-cyber-emerald/50 transition cursor-pointer flex flex-col items-center justify-center text-center bg-cyber-black/40"> |
| <input type="file" id="sidebarFileIn" accept=".xlsx,.xls" class="hidden"> |
| <i data-lucide="upload-cloud" class="w-6 h-6 text-cyber-muted mb-1"></i> |
| <span class="text-xs text-cyber-slate font-medium">เปลี่ยนไฟล์ Excel</span> |
| <span class="text-[9px] text-cyber-muted mt-0.5">ลากไฟล์มาที่นี่เพื่ออัปเดต</span> |
| </div> |
| <button id="loadDemoBtn" class="w-full mt-2.5 py-1.5 px-3 bg-cyber-emerald/10 hover:bg-cyber-emerald/20 text-cyber-mint border border-cyber-emerald/30 rounded-lg text-xs font-medium transition flex items-center justify-center space-x-1"> |
| <i data-lucide="database" class="w-3.5 h-3.5"></i> |
| <span>โหลดข้อมูลสาธิต (Demo)</span> |
| </button> |
| </div> |
|
|
| |
| <div class="p-6 flex-grow space-y-6"> |
| <div class="flex items-center justify-between"> |
| <h3 class="text-xs font-bold text-cyber-muted tracking-wider uppercase">ตัวกรองข้อมูลแบบมีปฏิสัมพันธ์</h3> |
| <button id="resetFiltersBtn" class="text-xs text-cyber-mint hover:text-white flex items-center space-x-1 font-semibold transition group"> |
| <i data-lucide="rotate-ccw" class="w-3.5 h-3.5 group-hover:rotate-180 transition-transform duration-500"></i> |
| <span>รีเซ็ต</span> |
| </button> |
| </div> |
|
|
| |
| <div class="space-y-2"> |
| <label for="filterStrategy" class="block text-xs font-semibold text-cyber-slate">ประเด็นยุทธศาสตร์</label> |
| <div class="relative"> |
| <select id="filterStrategy" class="w-full bg-cyber-black text-xs text-cyber-slate border border-cyber-charcoal rounded-lg py-2.5 pl-3 pr-8 focus:outline-none focus:border-cyber-emerald focus:ring-1 focus:ring-cyber-emerald appearance-none cursor-pointer"> |
| <option value="all">-- ทั้งหมด --</option> |
| </select> |
| <div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-cyber-muted"> |
| <i data-lucide="chevron-down" class="w-4 h-4"></i> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-2"> |
| <label for="filterDept" class="block text-xs font-semibold text-cyber-slate">กองงาน / หน่วยงานย่อย</label> |
| <div class="relative"> |
| <select id="filterDept" class="w-full bg-cyber-black text-xs text-cyber-slate border border-cyber-charcoal rounded-lg py-2.5 pl-3 pr-8 focus:outline-none focus:border-cyber-emerald focus:ring-1 focus:ring-cyber-emerald appearance-none cursor-pointer"> |
| <option value="all">-- ทั้งหมด --</option> |
| <option value="สนอ.">สนอ. (ภาพรวม)</option> |
| <option value="กก.">กก. (กองกลาง)</option> |
| <option value="กนผ.">กนผ. (กองนโยบายและแผน)</option> |
| <option value="กบค.">กบค. (กองบริหารงานบุคคล)</option> |
| <option value="กบศ.">กบศ. (กองบริการการศึกษา)</option> |
| <option value="กพน.">กพน. (กองพัฒนานักศึกษา)</option> |
| <option value="กค.">กค. (กองคลัง)</option> |
| </select> |
| <div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-cyber-muted"> |
| <i data-lucide="chevron-down" class="w-4 h-4"></i> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-2"> |
| <div class="flex justify-between items-center"> |
| <label class="block text-xs font-semibold text-cyber-slate">ตัวกรองคะแนนเป้าหมาย</label> |
| <span id="scoreThresholdVal" class="text-xs font-mono text-cyber-mint font-bold bg-cyber-emerald/10 px-2 py-0.5 rounded border border-cyber-emerald/20">≥ 1.0</span> |
| </div> |
| <input id="scoreThresholdSlider" type="range" min="1" max="5" step="0.5" value="1" class="w-full h-1 bg-cyber-charcoal rounded-lg appearance-none cursor-pointer accent-cyber-emerald"> |
| <div class="flex justify-between text-[10px] text-cyber-muted font-mono"> |
| <span>1.0</span> |
| <span>2.0</span> |
| <span>3.0</span> |
| <span>4.0</span> |
| <span>5.0</span> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="p-6 border-t border-cyber-charcoal mt-auto bg-cyber-black/10"> |
| <div class="flex items-center space-x-2 text-[11px] text-cyber-muted mb-1"> |
| <span class="w-1.5 h-1.5 rounded-full bg-cyber-emerald animate-pulse"></span> |
| <span>พอร์ทัลผู้บริหารระดับสูง (Executive Console)</span> |
| </div> |
| <div class="text-[10px] text-cyber-muted font-mono leading-relaxed"> |
| เวอร์ชันระบบ v1.2 (ปีงบประมาณ 2568)<br> |
| ประมวลผลภายในบราวเซอร์ 100% |
| </div> |
| </div> |
| </aside> |
|
|
| |
| <main class="flex-grow flex flex-col min-h-screen overflow-x-hidden"> |
|
|
| |
| <section id="uploadScreen" class="flex-grow flex flex-col items-center justify-center p-6 md:p-12 lg:p-16 max-w-5xl mx-auto w-full"> |
| <div class="text-center mb-10"> |
| <span class="px-3 py-1 bg-cyber-emerald/10 border border-cyber-emerald/30 text-cyber-mint rounded-full text-xs font-mono font-bold tracking-wider uppercase mb-3 inline-block"> |
| มหาวิทยาลัยราชภัฏพิบูลสงคราม |
| </span> |
| <h1 class="text-3xl md:text-4xl font-extrabold tracking-tight text-white mb-4 mt-2"> |
| ระบบติดตามและประเมินตัวชี้วัด <span class="text-transparent bg-clip-text bg-gradient-to-r from-cyber-emerald to-cyber-mint">สนอ. มรพส.</span> |
| </h1> |
| <p class="text-cyber-muted max-w-xl mx-auto text-sm md:text-base"> |
| แพลตฟอร์มวิเคราะห์และแสดงผลผลงานเชิงกลยุทธ์ สำหรับผู้บริหารระดับสูง สำนักงานอธิการบดี เพื่อตรวจจับและค้นหากลุ่มงานที่มีผลสัมฤทธิ์สูงสุด (Top Performer) ได้ทันที |
| </p> |
| </div> |
|
|
| |
| <div id="dropzone" class="w-full max-w-2xl border-2 border-dashed border-cyber-charcoal rounded-2xl p-10 md:p-14 hover:border-cyber-emerald bg-cyber-navy/40 transition duration-300 flex flex-col items-center justify-center text-center group cursor-pointer shadow-xl relative overflow-hidden"> |
| <div class="absolute inset-0 bg-gradient-to-b from-cyber-emerald/5 to-transparent pointer-events-none opacity-0 group-hover:opacity-100 transition duration-300"></div> |
| |
| <div class="p-4 bg-cyber-navy border border-cyber-charcoal rounded-2xl group-hover:scale-110 group-hover:border-cyber-emerald/40 transition duration-300 mb-6 relative"> |
| <i data-lucide="file-spreadsheet" class="w-12 h-12 text-cyber-mint"></i> |
| </div> |
| |
| <h3 class="text-lg font-bold text-white mb-2">ลากและวางไฟล์ผลการประเมินประเด็นยุทธศาสตร์ (.xlsx)</h3> |
| <p class="text-xs text-cyber-muted max-w-md mb-6 leading-relaxed"> |
| รองรับไฟล์ประเมินตัวชี้วัดของสำนักงานอธิการบดี (สนอ.) ปีงบประมาณ 2568 ทุกกองงาน ระบบจะทำการคลี่ตารางและล้างค่าสถิติซ้ำซ้อนโดยอัตโนมัติ |
| </p> |
| |
| <input type="file" id="mainFileIn" accept=".xlsx,.xls" class="hidden"> |
| <button class="px-6 py-2.5 bg-cyber-emerald hover:bg-cyber-emerald/85 text-cyber-black font-bold rounded-lg text-sm transition duration-150 flex items-center space-x-2 shadow-lg hover:shadow-cyber-emerald/20"> |
| <i data-lucide="search" class="w-4 h-4"></i> |
| <span>ค้นหาและเลือกไฟล์จากเครื่อง</span> |
| </button> |
| </div> |
|
|
| |
| <div class="mt-8 flex flex-col items-center text-center"> |
| <div class="text-xs text-cyber-muted max-w-md"> |
| <span class="text-cyber-mint font-semibold">หมายเหตุ:</span> ระบบพยายามตรวจเช็คฐานข้อมูลที่ <code class="font-mono bg-cyber-navy py-0.5 px-1.5 rounded text-cyber-slate">./datasource/ตัวชี้วัด สนอ.68.xlsx</code> เรียลไทม์ หากทำงานแบบออฟไลน์ คุณสามารถอัปโหลดไฟล์ตรงนี้ หรือคลิกปุ่มด้านล่างเพื่อประมวลผลด้วยข้อมูลจำลองทันที: |
| </div> |
| <button id="quickDemoBtn" class="mt-4 px-5 py-2 bg-cyber-navy border border-cyber-charcoal hover:border-cyber-emerald hover:text-cyber-mint text-cyber-slate font-medium text-xs rounded-lg transition duration-200 flex items-center space-x-2"> |
| <i data-lucide="zap" class="w-4 h-4 text-cyber-mint animate-bounce"></i> |
| <span>เริ่มต้นทดลองใช้งานด่วน (โหลดข้อมูลสาธิต)</span> |
| </button> |
| </div> |
| </section> |
|
|
| |
| <section id="dashboardContent" class="hidden flex-grow p-4 md:p-6 lg:p-8 space-y-6"> |
| |
| |
| <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between border-b border-cyber-charcoal pb-6 space-y-4 lg:space-y-0"> |
| <div class="flex items-start"> |
| |
| <button id="expandSidebarBtn" class="hidden p-2.5 bg-cyber-navy border border-cyber-charcoal rounded-xl text-cyber-mint hover:bg-cyber-charcoal transition mr-4 shadow-lg" title="แสดงแถบเมนู"> |
| <i data-lucide="menu" class="w-5 h-5"></i> |
| </button> |
| <div> |
| <div class="flex items-center space-x-2.5"> |
| <span id="activeFiltersBadge" class="hidden px-2 py-0.5 bg-yellow-500/15 border border-yellow-500/30 text-yellow-400 rounded-full text-[10px] font-mono"> |
| เปิดใช้งานตัวกรองอยู่ |
| </span> |
| <span id="dataSourceBadge" class="px-2 py-0.5 bg-cyber-emerald/10 border border-cyber-emerald/30 text-cyber-mint rounded-full text-[10px] font-mono"> |
| กำลังแสดงผลข้อมูลสาธิต |
| </span> |
| </div> |
| <h1 class="text-2xl md:text-3xl font-extrabold tracking-tight text-white mt-1"> |
| ระบบติดตามและประเมินตัวชี้วัด สนอ. มรพส. |
| </h1> |
| <p class="text-xs text-cyber-muted mt-1 leading-relaxed"> |
| แผงประเมินผลสำหรับผู้บริหารระดับสูง สำนักงานอธิการบดี มหาวิทยาลัยราชภัฏพิบูลสงคราม ประจำปีงบประมาณ 2568 |
| </p> |
| </div> |
| </div> |
|
|
| |
| <div class="flex flex-wrap items-center gap-2"> |
| <button id="exportRawExcelBtn" class="px-4 py-2 bg-cyber-navy border border-cyber-charcoal hover:border-cyber-emerald/50 text-cyber-slate hover:text-cyber-mint text-xs font-semibold rounded-lg transition flex items-center space-x-2"> |
| <i data-lucide="download" class="w-4 h-4"></i> |
| <span>ส่งออกข้อมูลคลี่ตาราง (.xlsx)</span> |
| </button> |
| <button id="changeFileTriggerBtn" class="px-4 py-2 bg-cyber-black hover:bg-cyber-charcoal border border-cyber-charcoal text-cyber-slate text-xs font-semibold rounded-lg transition flex items-center space-x-2"> |
| <i data-lucide="refresh-cw" class="w-4 h-4"></i> |
| <span>เปลี่ยนไฟล์ชุดข้อมูล</span> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5"> |
| |
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-6 relative overflow-hidden group hover:border-cyber-emerald/30 transition duration-300 flex flex-col justify-between"> |
| <div class="absolute -right-6 -bottom-6 text-cyber-emerald/5 group-hover:scale-110 transition-transform duration-500 pointer-events-none"> |
| <i data-lucide="award" class="w-36 h-36"></i> |
| </div> |
| <div class="flex justify-between items-start z-10"> |
| <div> |
| <p class="text-xs font-semibold text-cyber-muted tracking-wide uppercase">คะแนนเฉลี่ยภาพรวม สนอ.</p> |
| <span class="text-4xl md:text-5xl font-bold font-mono text-cyber-emerald tracking-tight block mt-2 text-glow" id="kpiAvgScore">-</span> |
| </div> |
| <span class="p-2 bg-cyber-emerald/10 text-cyber-mint rounded-lg"> |
| <i data-lucide="gauge" class="w-5 h-5"></i> |
| </span> |
| </div> |
| <div class="mt-4 pt-4 border-t border-cyber-charcoal/50 flex items-center justify-between text-xs text-cyber-muted z-10"> |
| <span>จากทุกตัวชี้วัดที่ประเมินแล้ว</span> |
| <span class="font-mono font-medium text-cyber-slate">เต็ม 5.00 คะแนน</span> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-6 relative overflow-hidden group hover:border-cyber-emerald/30 transition duration-300 flex flex-col justify-between"> |
| <div class="absolute -right-6 -bottom-6 text-cyber-mint/5 group-hover:scale-110 transition-transform duration-500 pointer-events-none"> |
| <i data-lucide="trophy" class="w-36 h-36"></i> |
| </div> |
| <div class="flex justify-between items-start z-10"> |
| <div> |
| <p class="text-xs font-semibold text-cyber-muted tracking-wide uppercase">กองงานดาวรุ่งยอดเยี่ยม (Top Performer)</p> |
| <span class="text-4xl md:text-5xl font-bold font-mono text-cyan-400 tracking-tight block mt-2 animate-pulse" id="kpiTopPerformer">-</span> |
| </div> |
| <span class="p-2 bg-cyan-500/10 text-cyan-400 rounded-lg"> |
| <i data-lucide="crown" class="w-5 h-5"></i> |
| </span> |
| </div> |
| <div class="mt-4 pt-4 border-t border-cyber-charcoal/50 flex items-center justify-between text-xs text-cyber-muted z-10"> |
| <span>หน่วยงานย่อยผลงานสูงสุดเฉลี่ย</span> |
| <span class="font-mono text-cyan-400 font-bold" id="kpiTopPerformerScore">-</span> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-6 relative overflow-hidden group hover:border-cyber-emerald/30 transition duration-300 flex flex-col justify-between"> |
| <div class="absolute -right-4 -bottom-4 text-purple-500/5 group-hover:scale-110 transition-transform duration-500 pointer-events-none"> |
| <i data-lucide="check-square" class="w-32 h-32"></i> |
| </div> |
| <div class="flex justify-between items-start z-10"> |
| <div> |
| <p class="text-xs font-semibold text-cyber-muted tracking-wide uppercase">ตัวชี้วัดที่เปิดใช้งานจริง</p> |
| <span class="text-4xl md:text-5xl font-bold font-mono text-purple-400 tracking-tight block mt-2" id="kpiActiveCount">-</span> |
| </div> |
| <span class="p-2 bg-purple-500/10 text-purple-400 rounded-lg"> |
| <i data-lucide="list-checks" class="w-5 h-5"></i> |
| </span> |
| </div> |
| <div class="mt-4 pt-4 border-t border-cyber-charcoal/50 flex items-center justify-between text-xs text-cyber-muted z-10"> |
| <span>คัดกรองแถวค่าเฉลี่ยสถิติออกแล้ว</span> |
| <span class="font-mono text-purple-400 font-semibold" id="kpiExcludedSummaryCount">0 แถว</span> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="grid grid-cols-1 lg:grid-cols-12 gap-6"> |
| |
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-5 lg:col-span-5 flex flex-col justify-between"> |
| <div class="flex items-center justify-between border-b border-cyber-charcoal/50 pb-3 mb-4"> |
| <div class="flex items-center space-x-2"> |
| <i data-lucide="bar-chart-3" class="w-4 h-4 text-cyber-emerald"></i> |
| <h3 class="text-sm font-bold text-white">อันดับคะแนนเฉลี่ยรายหน่วยงาน</h3> |
| </div> |
| <button onclick="downloadChartPng('rankingChart')" class="p-1.5 hover:bg-cyber-charcoal rounded text-cyber-muted hover:text-cyber-slate transition" title="ดาวน์โหลดแผนภาพเป็นรูปภาพ"> |
| <i data-lucide="image" class="w-4 h-4"></i> |
| </button> |
| </div> |
| <div class="relative flex-grow min-h-[300px] flex items-center justify-center"> |
| <canvas id="rankingChart"></canvas> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-5 lg:col-span-7 flex flex-col justify-between"> |
| <div class="flex items-center justify-between border-b border-cyber-charcoal/50 pb-3 mb-4"> |
| <div class="flex items-center space-x-2"> |
| <i data-lucide="radar" class="w-4 h-4 text-cyber-mint"></i> |
| <h3 class="text-sm font-bold text-white">เปรียบเทียบผลงานใน 5 มิติตามประเด็นยุทธศาสตร์</h3> |
| </div> |
| <button onclick="downloadChartPng('strategyChart')" class="p-1.5 hover:bg-cyber-charcoal rounded text-cyber-muted hover:text-cyber-slate transition" title="ดาวน์โหลดแผนภาพเป็นรูปภาพ"> |
| <i data-lucide="image" class="w-4 h-4"></i> |
| </button> |
| </div> |
| <div class="relative flex-grow min-h-[300px] flex items-center justify-center"> |
| <canvas id="strategyChart"></canvas> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="bg-cyber-navy border border-cyber-charcoal rounded-xl p-5"> |
| |
| |
| <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pb-5 mb-4 border-b border-cyber-charcoal/50"> |
| <div class="flex items-center space-x-2"> |
| <i data-lucide="table" class="w-4 h-4 text-cyber-emerald"></i> |
| <h3 class="text-sm font-bold text-white">รายละเอียดชุดคะแนนประเมินตัวชี้วัดรายข้อ</h3> |
| </div> |
| <div class="flex items-center space-x-2 max-w-md w-full sm:w-auto"> |
| <div class="relative w-full"> |
| <input id="tableSearchIn" type="text" placeholder="พิมพ์คำเพื่อค้นหาตัวชี้วัด..." class="w-full bg-cyber-black text-xs text-cyber-slate border border-cyber-charcoal rounded-lg pl-8 pr-3 py-2 focus:outline-none focus:border-cyber-emerald"> |
| <div class="absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none text-cyber-muted"> |
| <i data-lucide="search" class="w-3.5 h-3.5"></i> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="overflow-x-auto rounded-lg border border-cyber-charcoal max-h-[500px] overflow-y-auto"> |
| <table class="w-full text-left border-collapse"> |
| <thead> |
| <tr class="bg-cyber-black/70 border-b border-cyber-charcoal sticky top-0 z-10 backdrop-blur-md"> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase max-w-xs">ประเด็นยุทธศาสตร์</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase w-20">รหัส</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase min-w-[250px]">ชื่อตัวชี้วัด</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-mint tracking-wide uppercase text-right font-mono font-bold w-16">สนอ.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กก.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กนผ.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กบค.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กบศ.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กพน.</th> |
| <th class="p-3 text-[11px] font-bold text-cyber-slate tracking-wide uppercase text-right font-mono w-16">กค.</th> |
| </tr> |
| </thead> |
| <tbody id="indicatorsTableBody" class="divide-y divide-cyber-charcoal/40"> |
| |
| </tbody> |
| </table> |
| </div> |
|
|
| |
| <div class="mt-4 flex flex-col sm:flex-row items-center justify-between text-xs text-cyber-muted gap-2"> |
| <span id="tablePageStats">แสดงผล 0 จากทั้งหมด 0 แถว</span> |
| <span class="flex items-center space-x-1"> |
| <i data-lucide="help-circle" class="w-3.5 h-3.5 text-cyber-muted"></i> |
| <span>เลื่อนเมาส์ชี้เหนือนามย่อกองเพื่อดูชื่อหน่วยงานเต็ม</span> |
| </span> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| |
| <div class="bg-cyber-navy/50 border border-cyber-charcoal rounded-xl p-5 space-y-3"> |
| <h4 class="text-xs font-bold text-cyber-mint tracking-wider uppercase flex items-center space-x-1.5"> |
| <i data-lucide="bookmark-check" class="w-4 h-4"></i> |
| <span>คู่มือรหัสหน่วยงานย่อย (Department Index)</span> |
| </h4> |
| <div class="grid grid-cols-2 gap-y-1 gap-x-4 text-xs leading-relaxed text-cyber-muted"> |
| <div><strong class="text-cyber-slate font-mono">สนอ.</strong> : สำนักงานอธิการบดี (ภาพรวม)</div> |
| <div><strong class="text-cyber-slate font-mono">กก.</strong> : กองกลาง</div> |
| <div><strong class="text-cyber-slate font-mono">กนผ.</strong> : กองนโยบายและแผน</div> |
| <div><strong class="text-cyber-slate font-mono">กบค.</strong> : กองบริหารงานบุคคล</div> |
| <div><strong class="text-cyber-slate font-mono">กบศ.</strong> : กองบริการการศึกษา</div> |
| <div><strong class="text-cyber-slate font-mono">กพน.</strong> : กองพัฒนานักศึกษา</div> |
| <div><strong class="text-cyber-slate font-mono">กค.</strong> : กองคลัง</div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-cyber-navy/50 border border-cyber-charcoal rounded-xl p-5 space-y-2 flex flex-col justify-between"> |
| <h4 class="text-xs font-bold text-purple-400 tracking-wider uppercase flex items-center space-x-1.5"> |
| <i data-lucide="terminal" class="w-4 h-4"></i> |
| <span>ความสอดคล้องและการล้างข้อมูลในบราวเซอร์ (Data Cleaning Logs)</span> |
| </h4> |
| <p class="text-[11px] text-cyber-muted leading-relaxed"> |
| ระบบทำการตรวจจับมิติข้อมูลแบบ Cross-Tab แล้วคลี่โครงสร้างออกเป็นตารางเชิงสัมพันธ์ (Relational Rows) พร้อมทั้งตรวจจับข้อความและกรองเอา "คะแนนเฉลี่ยทุกประเด็นการประเมิน", "เฉลี่ยสะสม", และค่าร้อยละเฉพาะส่วนที่ซ้ำซ้อนออกทั้งหมดเพื่อรักษาความถูกต้องและปราศจากการคำนวณเบิ้ลสองเท่า (Double-counting Error) |
| </p> |
| <div class="text-[10px] text-cyber-mint font-mono mt-2 pt-2 border-t border-cyber-charcoal/40 flex justify-between"> |
| <span>สถานะระบบวิเคราะห์: ปกติ</span> |
| <span>รหัสคำนวณ: COMPLIANT-OK</span> |
| </div> |
| </div> |
| </div> |
|
|
| </section> |
|
|
| </main> |
|
|
| |
| <script> |
| |
| let rawIndicatorsData = []; |
| let cleanFilteredData = []; |
| let allUniqueStrategies = []; |
| |
| |
| let chartInstanceRanking = null; |
| let chartInstanceStrategy = null; |
| |
| |
| const DEPARTMENTS = ['สนอ.', 'กก.', 'กนผ.', 'กบค.', 'กบศ.', 'กพน.', 'กค.']; |
| |
| |
| const demoExcelRawMatrix = { |
| SheetNames: ["ตัวชี้วัดปี68"], |
| Sheets: { |
| "ตัวชี้วัดปี68": { |
| "!ref": "A1:H19", |
| "A1": { v: "ตัวชี้วัด" }, "B1": { v: "สนอ." }, "C1": { v: "กก." }, "D1": { v: "กนผ." }, "E1": { v: "กบค." }, "F1": { v: "กบศ." }, "G1": { v: "กพน." }, "H1": { v: "กค." }, |
| |
| |
| "A2": { v: "1 : พัฒนาระบบบริหารจัดการด้วยเทคโนโลยีและหลักธรรมาภิบาล" }, |
| "A3": { v: "1.1 ร้อยละความสำเร็จในการพัฒนาระบบประเมินผลออนไลน์" }, "B3": { v: 4.5 }, "C3": { v: 4.2 }, "D3": { v: 4.8 }, "E3": { v: 4.0 }, "F3": { v: 4.3 }, "G3": { v: 3.9 }, "H3": { v: 4.1 }, |
| "A4": { v: "1.2 ระดับความพึงพอใจต่อการดำเนินงานตามหลักธรรมาภิบาล" }, "B4": { v: 3.8 }, "C4": { v: 4.0 }, "D4": { v: 3.5 }, "E4": { v: 4.5 }, "F4": { v: 3.7 }, "G4": { v: null }, "H4": { v: 4.0 }, |
| "A5": { v: "คะแนนเฉลี่ย 1" }, "B5": { v: 4.15 }, "C5": { v: 4.1 }, "D5": { v: 4.15 }, "E5": { v: 4.25 }, "F5": { v: 4.0 }, "G5": { v: 3.9 }, "H5": { v: 4.05 }, |
| |
| |
| "A6": { v: "2 : ยกระดับและเพิ่มขีดความสามารถการบริการวิชาการแก่ชุมชนท้องถิ่น" }, |
| "A7": { v: "2.1 จำนวนโครงการวิจัยเพื่อพัฒนาท้องถิ่นที่สัมฤทธิ์ผลตามเป้าหมาย" }, "B7": { v: 4.2 }, "C7": { v: 4.0 }, "D7": { v: 4.5 }, "E7": { v: null }, "F7": { v: 4.6 }, "G7": { v: 4.7 }, "H7": { v: null }, |
| "A8": { v: "2.2 ระดับการนำองค์ความรู้ไปใช้ประโยชน์ในชุมชนเป้าหมาย" }, "B8": { v: 4.0 }, "C8": { v: 3.8 }, "D8": { v: 4.2 }, "E8": { v: 4.0 }, "F8": { v: 4.1 }, "G8": { v: 4.5 }, "H8": { v: 3.5 }, |
| "A9": { v: "คะแนนเฉลี่ย 2" }, "B9": { v: 4.1 }, "C9": { v: 3.9 }, "D9": { v: 4.35 }, "E9": { v: 4.0 }, "F9": { v: 4.35 }, "G9": { v: 4.6 }, "H9": { v: 3.5 }, |
| |
| |
| "A10": { v: "3 : พัฒนาคุณภาพการศึกษาและขีดความสามารถการเรียนรู้ของนักศึกษา" }, |
| "A11": { v: "3.1 ร้อยละของบัณฑิตที่มีสมรรถนะการทำงานในระดับพึงพอใจสูง" }, "B11": { v: 4.8 }, "C11": { v: null }, "D11": { v: 4.6 }, "E11": { v: 4.5 }, "F11": { v: 4.9 }, "G11": { v: 4.7 }, "H11": { v: 4.2 }, |
| "A12": { v: "3.2 คะแนนรวมเฉลี่ยผลสัมฤทธิ์ทางการเรียนและการพัฒนาทักษะอาชีพ" }, "B12": { v: 4.1 }, "C12": { v: 3.9 }, "D12": { v: 4.0 }, "E12": { v: 4.2 }, "F12": { v: 4.3 }, "G12": { v: 4.1 }, "H12": { v: 3.8 }, |
| "A13": { v: "คะแนนเฉลี่ย 3" }, "B13": { v: 4.45 }, "C13": { v: 3.9 }, "D13": { v: 4.3 }, "E13": { v: 4.35 }, "F13": { v: 4.6 }, "G13": { v: 4.4 }, "H13": { v: 4.0 }, |
| |
| |
| "A14": { v: "4 : เสริมสร้างวัฒนธรรมองค์กร ค่านิยม และอนุรักษ์ศิลปวัฒนธรรมท้องถิ่น" }, |
| "A15": { v: "4.1 ดัชนีความพึงพอใจของบุคลากรต่อการสืบสานทำนุบำรุงศิลปวัฒนธรรม" }, "B15": { v: 3.5 }, "C15": { v: 3.7 }, "D15": { v: 3.4 }, "E15": { v: 3.8 }, "F15": { v: 3.6 }, "G15": { v: 4.0 }, "H15": { v: 3.5 }, |
| "A16": { v: "คะแนนเฉลี่ย 4" }, "B16": { v: 3.5 }, "C16": { v: 3.7 }, "D16": { v: 3.4 }, "E16": { v: 3.8 }, "F16": { v: 3.6 }, "G16": { v: 4.0 }, "H16": { v: 3.5 }, |
| |
| |
| "A17": { v: "5 : เพิ่มประสิทธิภาพการบริหารสินทรัพย์และการสร้างรายได้เพิ่มขึ้น" }, |
| "A18": { v: "5.1 ร้อยละการเพิ่มขึ้นของรายได้จากสินทรัพย์และการบริการวิชาการ" }, "B18": { v: 4.2 }, "C18": { v: 3.9 }, "D18": { v: 4.1 }, "E18": { v: 3.8 }, "F18": { v: 4.0 }, "G18": { v: 4.3 }, "H18": { v: 4.5 }, |
| "A19": { v: "คะแนนเฉลี่ย 5" }, "B19": { v: 4.2 }, "C19": { v: 3.9 }, "D19": { v: 4.1 }, "E19": { v: 3.8 }, "F19": { v: 4.0 }, "G19": { v: 4.3 }, "H19": { v: 4.5 }, |
| } |
| } |
| }; |
| |
| |
| window.onload = function () { |
| lucide.createIcons(); |
| setupDragAndDrop(); |
| setupInteractiveFilters(); |
| setupSidebarToggle(); |
| tryAutoFetchDefaultExcel(); |
| }; |
| |
| |
| function setupSidebarToggle() { |
| const sidebar = document.getElementById('sidebar'); |
| const collapseSidebarBtn = document.getElementById('collapseSidebarBtn'); |
| const expandSidebarBtn = document.getElementById('expandSidebarBtn'); |
| const mobileMenuBtn = document.getElementById('mobileMenuBtn'); |
| |
| function hideSidebar() { |
| sidebar.classList.add('hidden'); |
| sidebar.classList.remove('md:flex'); |
| expandSidebarBtn.classList.remove('hidden'); |
| expandSidebarBtn.classList.add('flex'); |
| |
| |
| setTimeout(() => { |
| if (chartInstanceRanking) chartInstanceRanking.resize(); |
| if (chartInstanceStrategy) chartInstanceStrategy.resize(); |
| }, 100); |
| } |
| |
| function showSidebar() { |
| sidebar.classList.remove('hidden'); |
| sidebar.classList.add('md:flex'); |
| expandSidebarBtn.classList.add('hidden'); |
| expandSidebarBtn.classList.remove('flex'); |
| |
| |
| setTimeout(() => { |
| if (chartInstanceRanking) chartInstanceRanking.resize(); |
| if (chartInstanceStrategy) chartInstanceStrategy.resize(); |
| }, 100); |
| } |
| |
| collapseSidebarBtn.addEventListener('click', hideSidebar); |
| expandSidebarBtn.addEventListener('click', showSidebar); |
| |
| |
| mobileMenuBtn.addEventListener('click', () => { |
| if (sidebar.classList.contains('hidden')) { |
| sidebar.classList.remove('hidden'); |
| sidebar.classList.add('fixed', 'w-full', 'h-full'); |
| } else { |
| sidebar.classList.add('hidden'); |
| sidebar.classList.remove('fixed', 'w-full', 'h-full'); |
| } |
| }); |
| } |
| |
| |
| async function tryAutoFetchDefaultExcel() { |
| try { |
| const response = await fetch('./datasource/ตัวชี้วัด สนอ.68.xlsx'); |
| if (response.ok) { |
| const arrayBuffer = await response.arrayBuffer(); |
| const data = new Uint8Array(arrayBuffer); |
| const workbook = XLSX.read(data, { type: 'array' }); |
| processWorkbook(workbook, "ไฟล์อัตโนมัติ (ตัวชี้วัด สนอ.68.xlsx)"); |
| } else { |
| console.log("Could not auto-load default Excel file from datasource path. Awaiting manual upload or click to demo."); |
| } |
| } catch (error) { |
| console.log("Auto-fetch skipped or failed. Run on local web-server or upload manually. Details: ", error.message); |
| } |
| } |
| |
| |
| document.getElementById('loadDemoBtn').addEventListener('click', () => loadDemoDataset()); |
| document.getElementById('quickDemoBtn').addEventListener('click', () => loadDemoDataset()); |
| |
| function loadDemoDataset() { |
| processWorkbook(demoExcelRawMatrix, "ข้อมูลจำลองระบบประเมิน (Demo Mode)"); |
| } |
| |
| |
| function setupDragAndDrop() { |
| const dropzone = document.getElementById('dropzone'); |
| const mainFileIn = document.getElementById('mainFileIn'); |
| const sidebarFileIn = document.getElementById('sidebarFileIn'); |
| const sidebarFileZone = document.getElementById('sidebarFileZone'); |
| const changeFileTriggerBtn = document.getElementById('changeFileTriggerBtn'); |
| |
| |
| dropzone.addEventListener('click', () => mainFileIn.click()); |
| sidebarFileZone.addEventListener('click', () => sidebarFileIn.click()); |
| changeFileTriggerBtn.addEventListener('click', () => mainFileIn.click()); |
| |
| mainFileIn.addEventListener('change', handleFileInput); |
| sidebarFileIn.addEventListener('change', handleFileInput); |
| |
| |
| ['dragenter', 'dragover'].forEach(eventName => { |
| dropzone.addEventListener(eventName, (e) => { |
| e.preventDefault(); |
| dropzone.classList.add('border-cyber-emerald', 'bg-cyber-emerald/10'); |
| }, false); |
| }); |
| |
| ['dragleave', 'drop'].forEach(eventName => { |
| dropzone.addEventListener(eventName, (e) => { |
| e.preventDefault(); |
| dropzone.classList.remove('border-cyber-emerald', 'bg-cyber-emerald/10'); |
| }, false); |
| }); |
| |
| dropzone.addEventListener('drop', (e) => { |
| const dt = e.dataTransfer; |
| const files = dt.files; |
| if (files.length > 0) { |
| processUploadedFile(files[0]); |
| } |
| }); |
| } |
| |
| function handleFileInput(e) { |
| const files = e.target.files; |
| if (files.length > 0) { |
| processUploadedFile(files[0]); |
| } |
| } |
| |
| function processUploadedFile(file) { |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| const data = new Uint8Array(e.target.result); |
| try { |
| const workbook = XLSX.read(data, { type: 'array' }); |
| processWorkbook(workbook, file.name); |
| } catch (error) { |
| alert("เกิดข้อผิดพลาดในการเปิดและแปลงข้อมูลไฟล์ Excel นี้ กรุณาตรวจสอบรูปแบบไฟล์ของคุณ!"); |
| console.error(error); |
| } |
| }; |
| reader.readAsArrayBuffer(file); |
| } |
| |
| |
| function processWorkbook(workbook, sourceName) { |
| const sheetName = workbook.SheetNames[0]; |
| const worksheet = workbook.Sheets[sheetName]; |
| |
| |
| const rawRows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null }); |
| if (rawRows.length < 2) { |
| alert("ข้อมูลในตารางไม่ครบถ้วนหรือไม่ถูกต้องตามรูปแบบ"); |
| return; |
| } |
| |
| |
| const departmentColIndices = {}; |
| let headerFound = false; |
| |
| for (let r = 0; r < Math.min(rawRows.length, 3); r++) { |
| const currentRow = rawRows[r]; |
| if (!currentRow) continue; |
| for (let c = 0; c < currentRow.length; c++) { |
| const cellVal = currentRow[c] ? String(currentRow[c]).trim() : ''; |
| if (DEPARTMENTS.includes(cellVal)) { |
| departmentColIndices[cellVal] = c; |
| headerFound = true; |
| } |
| } |
| if (headerFound) break; |
| } |
| |
| |
| if (Object.keys(departmentColIndices).length === 0) { |
| DEPARTMENTS.forEach((dept, index) => { |
| departmentColIndices[dept] = index + 1; |
| }); |
| } |
| |
| const parsedIndicators = []; |
| let currentStrategyTheme = "ยังไม่ระบุประเด็นยุทธศาสตร์"; |
| let excludedAveragesCount = 0; |
| |
| |
| for (let r = 0; r < rawRows.length; r++) { |
| const row = rawRows[r]; |
| if (!row || row.length === 0) continue; |
| |
| const firstCell = row[0] ? String(row[0]).trim() : ''; |
| if (!firstCell) continue; |
| |
| |
| const strategyPattern = /^(\d+)\s*[:.]\s*(.*)$/; |
| if (strategyPattern.test(firstCell)) { |
| |
| if (!firstCell.includes("เฉลี่ย") && !firstCell.includes("ร้อยละ")) { |
| currentStrategyTheme = firstCell; |
| continue; |
| } |
| } |
| |
| |
| const testLower = firstCell.toLowerCase(); |
| if ( |
| testLower.includes("เฉลี่ย") || |
| testLower.includes("คะแนนเฉลี่ย") || |
| testLower.includes("เฉลี่ยทุกประเด็น") || |
| testLower.includes("ร้อยละ") && (testLower.includes("เฉลี่ย") || testLower.includes("ผลรวม") || testLower.includes("ภาพรวม")) |
| ) { |
| excludedAveragesCount++; |
| continue; |
| } |
| |
| |
| let kpiCode = "N/A"; |
| let kpiName = firstCell; |
| |
| |
| const codeMatch = firstCell.match(/^(\d+(\.\d+)+)\s*(.*)$/); |
| if (codeMatch) { |
| kpiCode = codeMatch[1]; |
| kpiName = codeMatch[3].trim(); |
| } |
| |
| |
| DEPARTMENTS.forEach(dept => { |
| const colIndex = departmentColIndices[dept]; |
| let scoreValue = null; |
| |
| if (colIndex !== undefined && colIndex < row.length) { |
| const rawCellScore = row[colIndex]; |
| if (rawCellScore !== null && rawCellScore !== undefined && rawCellScore !== '') { |
| const parsedNum = Number(rawCellScore); |
| if (!isNaN(parsedNum)) { |
| scoreValue = parsedNum; |
| } |
| } |
| } |
| |
| parsedIndicators.push({ |
| strategy: currentStrategyTheme, |
| kpi_code: kpiCode, |
| kpi_name: kpiName, |
| department: dept, |
| score: scoreValue |
| }); |
| }); |
| } |
| |
| |
| rawIndicatorsData = parsedIndicators; |
| |
| |
| const strategiesSet = new Set(rawIndicatorsData.map(item => item.strategy)); |
| allUniqueStrategies = Array.from(strategiesSet); |
| |
| |
| const filterStrategyEl = document.getElementById('filterStrategy'); |
| filterStrategyEl.innerHTML = '<option value="all">-- ทั้งหมด --</option>'; |
| allUniqueStrategies.forEach(strat => { |
| const opt = document.createElement('option'); |
| opt.value = strat; |
| opt.textContent = strat.length > 55 ? strat.substring(0, 55) + '...' : strat; |
| filterStrategyEl.appendChild(opt); |
| }); |
| |
| |
| document.getElementById('dataStatusBadge').textContent = 'โหลดเสร็จสิ้น'; |
| document.getElementById('dataStatusBadge').className = 'px-2 py-0.5 rounded-full text-[10px] font-medium bg-cyber-emerald/10 text-cyber-mint border border-cyber-emerald/20'; |
| document.getElementById('dataSourceBadge').textContent = `ฐานข้อมูล: ${sourceName}`; |
| document.getElementById('kpiExcludedSummaryCount').textContent = `${excludedAveragesCount} แถวสถิติ`; |
| |
| |
| document.getElementById('uploadScreen').classList.add('hidden'); |
| document.getElementById('dashboardContent').classList.remove('hidden'); |
| |
| |
| applyFilters(); |
| } |
| |
| |
| function setupInteractiveFilters() { |
| document.getElementById('filterStrategy').addEventListener('change', applyFilters); |
| document.getElementById('filterDept').addEventListener('change', applyFilters); |
| |
| const thresholdSlider = document.getElementById('scoreThresholdSlider'); |
| const thresholdVal = document.getElementById('scoreThresholdVal'); |
| |
| thresholdSlider.addEventListener('input', (e) => { |
| const val = parseFloat(e.target.value).toFixed(1); |
| thresholdVal.textContent = `≥ ${val}`; |
| applyFilters(); |
| }); |
| |
| document.getElementById('resetFiltersBtn').addEventListener('click', () => { |
| document.getElementById('filterStrategy').value = 'all'; |
| document.getElementById('filterDept').value = 'all'; |
| document.getElementById('scoreThresholdSlider').value = '1.0'; |
| document.getElementById('scoreThresholdVal').textContent = '≥ 1.0'; |
| document.getElementById('tableSearchIn').value = ''; |
| applyFilters(); |
| }); |
| |
| document.getElementById('tableSearchIn').addEventListener('input', applyFilters); |
| document.getElementById('exportRawExcelBtn').addEventListener('click', exportCleanedExcel); |
| } |
| |
| |
| function applyFilters() { |
| const selectedStrategy = document.getElementById('filterStrategy').value; |
| const selectedDept = document.getElementById('filterDept').value; |
| const thresholdVal = parseFloat(document.getElementById('scoreThresholdSlider').value); |
| const searchText = document.getElementById('tableSearchIn').value.trim().toLowerCase(); |
| |
| |
| const filtersActive = selectedStrategy !== 'all' || selectedDept !== 'all' || thresholdVal > 1.0 || searchText !== ''; |
| document.getElementById('activeFiltersBadge').classList.toggle('hidden', !filtersActive); |
| |
| |
| cleanFilteredData = rawIndicatorsData.filter(item => { |
| if (selectedStrategy !== 'all' && item.strategy !== selectedStrategy) return false; |
| if (selectedDept !== 'all' && item.department !== selectedDept) return false; |
| if (item.score !== null && item.score < thresholdVal) return false; |
| |
| if (searchText) { |
| const matchName = item.kpi_name.toLowerCase().includes(searchText); |
| const matchCode = item.kpi_code.toLowerCase().includes(searchText); |
| const matchStrategy = item.strategy.toLowerCase().includes(searchText); |
| if (!matchName && !matchCode && !matchStrategy) return false; |
| } |
| return true; |
| }); |
| |
| |
| renderExecutiveKPICards(); |
| |
| |
| renderRankingChart(); |
| renderStrategyRadarChart(); |
| |
| |
| renderDataTable(); |
| } |
| |
| |
| function renderExecutiveKPICards() { |
| |
| const overallScoreElements = cleanFilteredData.filter(item => item.department === 'สนอ.' && item.score !== null); |
| let avgScoreText = "ไม่มีข้อมูล"; |
| if (overallScoreElements.length > 0) { |
| const total = overallScoreElements.reduce((acc, curr) => acc + curr.score, 0); |
| avgScoreText = (total / overallScoreElements.length).toFixed(2); |
| } |
| document.getElementById('kpiAvgScore').textContent = avgScoreText; |
| |
| |
| |
| const deptAverages = {}; |
| DEPARTMENTS.filter(d => d !== 'สนอ.').forEach(dept => { |
| const deptScores = cleanFilteredData.filter(item => item.department === dept && item.score !== null); |
| if (deptScores.length > 0) { |
| const sum = deptScores.reduce((acc, c) => acc + c.score, 0); |
| deptAverages[dept] = sum / deptScores.length; |
| } |
| }); |
| |
| let topPerformer = "N/A"; |
| let topScore = 0; |
| Object.keys(deptAverages).forEach(dept => { |
| if (deptAverages[dept] > topScore) { |
| topScore = deptAverages[dept]; |
| topPerformer = dept; |
| } |
| }); |
| |
| |
| const fullNameMap = { |
| 'กก.': 'กก. (กองกลาง)', |
| 'กนผ.': 'กนผ. (นโยบายและแผน)', |
| 'กบค.': 'กบค. (บริหารงานบุคคล)', |
| 'กบศ.': 'กบศ. (บริการการศึกษา)', |
| 'กพน.': 'กพน. (พัฒนานักศึกษา)', |
| 'กค.': 'กค. (กองคลัง)' |
| }; |
| |
| document.getElementById('kpiTopPerformer').textContent = topPerformer; |
| document.getElementById('kpiTopPerformerScore').textContent = topScore > 0 ? `เฉลี่ย ${topScore.toFixed(2)} คะแนน` : '-'; |
| |
| |
| const uniqueKPIs = new Set(cleanFilteredData.map(item => item.kpi_code + '-' + item.kpi_name)); |
| document.getElementById('kpiActiveCount').textContent = uniqueKPIs.size; |
| } |
| |
| |
| function renderRankingChart() { |
| const ctx = document.getElementById('rankingChart').getContext('2d'); |
| |
| |
| const labels = DEPARTMENTS; |
| const values = DEPARTMENTS.map(dept => { |
| const deptData = cleanFilteredData.filter(item => item.department === dept && item.score !== null); |
| if (deptData.length === 0) return 0; |
| return parseFloat((deptData.reduce((acc, c) => acc + c.score, 0) / deptData.length).toFixed(2)); |
| }); |
| |
| if (chartInstanceRanking) { |
| chartInstanceRanking.destroy(); |
| } |
| |
| |
| const gradient = ctx.createLinearGradient(0, 0, 400, 0); |
| gradient.addColorStop(0, '#10B981'); |
| gradient.addColorStop(1, '#34D399'); |
| |
| chartInstanceRanking = new Chart(ctx, { |
| type: 'bar', |
| data: { |
| labels: labels, |
| datasets: [{ |
| label: 'คะแนนเฉลี่ยสะสม', |
| data: values, |
| backgroundColor: gradient, |
| borderRadius: 6, |
| borderWidth: 0, |
| barThickness: 16 |
| }] |
| }, |
| options: { |
| indexAxis: 'y', |
| responsive: true, |
| maintainAspectRatio: false, |
| plugins: { |
| legend: { display: false }, |
| tooltip: { |
| backgroundColor: '#161F30', |
| titleColor: '#F1F5F9', |
| bodyColor: '#34D399', |
| bodyFont: { family: 'JetBrains Mono', size: 12 }, |
| titleFont: { family: 'Noto Sans Thai', size: 12 }, |
| borderColor: '#1F2937', |
| borderWidth: 1 |
| } |
| }, |
| scales: { |
| x: { |
| min: 0, |
| max: 5, |
| grid: { color: 'rgba(16, 185, 129, 0.05)' }, |
| ticks: { color: '#64748B', font: { family: 'JetBrains Mono', size: 11 } } |
| }, |
| y: { |
| grid: { display: false }, |
| ticks: { color: '#F1F5F9', font: { family: 'Noto Sans Thai', size: 12, weight: 'bold' } } |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function renderStrategyRadarChart() { |
| const ctx = document.getElementById('strategyChart').getContext('2d'); |
| |
| |
| const shortStrategyLabels = allUniqueStrategies.map((s, idx) => `ยุทธศาสตร์ที่ ${idx + 1}`); |
| |
| |
| const datasets = []; |
| const colors = [ |
| '#34D399', |
| '#60A5FA', |
| '#F59E0B', |
| '#EC4899', |
| '#8B5CF6', |
| '#EF4444', |
| '#10B981' |
| ]; |
| |
| |
| const selectedDept = document.getElementById('filterDept').value; |
| let targetDeptRenderList = []; |
| |
| if (selectedDept !== 'all') { |
| targetDeptRenderList = [selectedDept]; |
| } else { |
| targetDeptRenderList = ['สนอ.', 'กก.', 'กนผ.', 'กบค.']; |
| } |
| |
| targetDeptRenderList.forEach((dept, idx) => { |
| const deptScoresByStrategy = allUniqueStrategies.map(strat => { |
| const subset = cleanFilteredData.filter(item => item.department === dept && item.strategy === strat && item.score !== null); |
| if (subset.length === 0) return 0; |
| return parseFloat((subset.reduce((acc, c) => acc + c.score, 0) / subset.length).toFixed(2)); |
| }); |
| |
| datasets.push({ |
| label: dept === 'สนอ.' ? 'สนอ. (ภาพรวม)' : `กองงาน ${dept}`, |
| data: deptScoresByStrategy, |
| borderColor: colors[idx % colors.length], |
| backgroundColor: colors[idx % colors.length] + '1A', |
| borderWidth: 2, |
| pointBackgroundColor: colors[idx % colors.length], |
| tension: 0.1 |
| }); |
| }); |
| |
| if (chartInstanceStrategy) { |
| chartInstanceStrategy.destroy(); |
| } |
| |
| |
| chartInstanceStrategy = new Chart(ctx, { |
| type: 'radar', |
| data: { |
| labels: shortStrategyLabels, |
| datasets: datasets |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| plugins: { |
| legend: { |
| position: 'bottom', |
| labels: { color: '#F1F5F9', font: { family: 'Noto Sans Thai', size: 11 } } |
| }, |
| tooltip: { |
| backgroundColor: '#161F30', |
| bodyFont: { family: 'JetBrains Mono', size: 12 }, |
| borderColor: '#1F2937', |
| borderWidth: 1 |
| } |
| }, |
| scales: { |
| r: { |
| grid: { color: 'rgba(16, 185, 129, 0.1)' }, |
| angleLines: { color: 'rgba(16, 185, 129, 0.1)' }, |
| ticks: { |
| display: false, |
| stepSize: 1 |
| }, |
| pointLabels: { |
| color: '#F1F5F9', |
| font: { family: 'Noto Sans Thai', size: 11 } |
| }, |
| suggestedMin: 1, |
| suggestedMax: 5 |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function renderDataTable() { |
| const tableBody = document.getElementById('indicatorsTableBody'); |
| tableBody.innerHTML = ''; |
| |
| |
| const groupedMap = new Map(); |
| cleanFilteredData.forEach(item => { |
| const key = `${item.kpi_code}-${item.kpi_name}`; |
| if (!groupedMap.has(key)) { |
| groupedMap.set(key, { |
| strategy: item.strategy, |
| code: item.kpi_code, |
| name: item.kpi_name, |
| scores: {} |
| }); |
| } |
| groupedMap.get(key).scores[item.department] = item.score; |
| }); |
| |
| const uniqueRows = Array.from(groupedMap.values()); |
| document.getElementById('tablePageStats').textContent = `แสดงผล ${uniqueRows.length} จากทั้งหมด ${uniqueRows.length} รายการ`; |
| |
| if (uniqueRows.length === 0) { |
| tableBody.innerHTML = ` |
| <tr> |
| <td colspan="10" class="text-center p-8 text-cyber-muted text-xs"> |
| <div class="flex flex-col items-center justify-center space-y-2"> |
| <i data-lucide="info" class="w-8 h-8 text-cyber-muted"></i> |
| <span>ไม่พบข้อมูลตัวชี้วัดที่สอดคล้องกับเงื่อนไขการกรองและการค้นหา</span> |
| </div> |
| </td> |
| </tr> |
| `; |
| return; |
| } |
| |
| |
| uniqueRows.forEach(row => { |
| const tr = document.createElement('tr'); |
| tr.className = 'hover:bg-cyber-black/40 transition duration-150'; |
| |
| |
| const shortStrategy = row.strategy.length > 40 ? row.strategy.substring(0, 40) + '...' : row.strategy; |
| |
| |
| let deptCellsHtml = ''; |
| DEPARTMENTS.forEach((dept, index) => { |
| const score = row.scores[dept]; |
| let displayScore = '-'; |
| let textClass = 'text-cyber-muted'; |
| |
| if (score !== undefined && score !== null) { |
| displayScore = score.toFixed(1); |
| if (score >= 4.5) { |
| textClass = 'text-cyber-mint font-bold text-glow'; |
| } else if (score >= 3.5) { |
| textClass = 'text-cyber-slate font-medium'; |
| } else { |
| textClass = 'text-amber-400 font-medium'; |
| } |
| } |
| |
| |
| if (dept === 'สนอ.') { |
| deptCellsHtml += `<td class="p-3 text-right font-mono font-bold text-sm ${textClass} border-r border-cyber-charcoal/40 bg-cyber-emerald/5">${displayScore}</td>`; |
| } else { |
| deptCellsHtml += `<td class="p-3 text-right font-mono text-xs ${textClass}">${displayScore}</td>`; |
| } |
| }); |
| |
| tr.innerHTML = ` |
| <td class="p-3 text-xs text-cyber-muted max-w-xs truncate" title="${row.strategy}">${shortStrategy}</td> |
| <td class="p-3 text-xs font-mono font-bold text-cyber-mint">${row.code}</td> |
| <td class="p-3 text-xs text-white font-medium max-w-md truncate" title="${row.name}">${row.name}</td> |
| ${deptCellsHtml} |
| `; |
| tableBody.appendChild(tr); |
| }); |
| } |
| |
| |
| function exportCleanedExcel() { |
| if (rawIndicatorsData.length === 0) return; |
| |
| const structuredExportData = cleanFilteredData.map(item => ({ |
| "ประเด็นยุทธศาสตร์": item.strategy, |
| "รหัสตัวชี้วัด": item.kpi_code, |
| "ชื่อตัวชี้วัดย่อย": item.kpi_name, |
| "กองงาน": item.department, |
| "คะแนนผลการประเมิน": item.score !== null ? item.score : "ยังไม่ได้ประเมิน" |
| })); |
| |
| const worksheet = XLSX.utils.json_to_sheet(structuredExportData); |
| const workbook = XLSX.utils.book_new(); |
| XLSX.utils.book_append_sheet(workbook, worksheet, "ข้อมูลตัวชี้วัดแบบคลี่แล้ว"); |
| |
| |
| const max_len = structuredExportData.reduce((w, r) => Math.max(w, String(r["ชื่อตัวชี้วัดย่อย"]).length), 10); |
| worksheet['!cols'] = [ { wch: 35 }, { wch: 12 }, { wch: max_len }, { wch: 12 }, { wch: 18 } ]; |
| |
| XLSX.writeFile(workbook, "ตัวชี้วัด_สนอ_มรพส_68_คลี่ตารางล้างข้อมูลแล้ว.xlsx"); |
| } |
| |
| |
| function downloadChartPng(chartId) { |
| let activeChart = null; |
| if (chartId === 'rankingChart') activeChart = chartInstanceRanking; |
| if (chartId === 'strategyChart') activeChart = chartInstanceStrategy; |
| |
| if (activeChart) { |
| const imgUrl = activeChart.toBase64Image(); |
| const a = document.createElement('a'); |
| a.href = imgUrl; |
| a.download = `${chartId}_สนอ_มรพส_68.png`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| } |
| } |
| </script> |
| </body> |
| </html> |