Files
lcbp3/docs/DMS v1.3.0 - Interactive Report.html
2025-11-17 22:09:01 +07:00

724 lines
48 KiB
HTML

<!DOCTYPE html>
<html lang="th" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DMS v1.3.0 - รายงานสรุปสถาปัตยกรรมระบบ</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;700&display=swap" rel="stylesheet">
<!-- Chosen Palette: Calm Harmony (Stone & Teal) -->
<!-- Application Structure Plan: A single-page application with a fixed top navigation (scroll-to-section). This structure is user-friendly, allowing exploration of the complex DMS specs thematically (Overview, Architecture, Modules, Features, Data Explorer) rather than forcing a linear read. The interactive "Data Explorer" is the key interaction, allowing users to dynamically query the database schema. This structure was chosen to synthesize all 4 source reports (Requirements, FullStack, Data Dictionary, SQL) into one cohesive and easily digestible dashboard for all stakeholders (devs, managers). -->
<!-- Visualization & Content Choices: 1. Architecture Diagram: (Report Info: Docker Stack from Req 2.1) -> (Goal: Organize) -> (Viz: HTML/Tailwind blocks) -> (Interaction: None) -> (Justification: No SVG/Mermaid rule. This is the clearest way to show the stack). 2. RBAC Chart: (Report Info: Roles/Permissions from Req 4.x, DD 2.x) -> (Goal: Compare) -> (Viz: Chart.js Horizontal Bar) -> (Interaction: Tooltip) -> (Justification: Clearly shows permission hierarchy). 3. Data Explorer: (Report Info: Data Dictionary/SQL) -> (Goal: Organize/Explore) -> (Viz: Dynamic HTML Table) -> (Interaction: Dropdown Select) -> (Justification: Makes the dense 44+ table schema interactive and searchable). -->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
<style>
body {
font-family: 'Sarabun', sans-serif;
background-color: #f5f5f4; /* stone-100 */
}
.chart-container {
position: relative;
width: 100%;
max-width: 600px; /* max-w-2xl */
margin-left: auto;
margin-right: auto;
height: 350px; /* h-~80 */
max-height: 40vh;
}
.chart-container-sm {
position: relative;
width: 100%;
max-width: 320px; /* max-w-xs */
margin-left: auto;
margin-right: auto;
height: 280px; /* h-72 */
max-height: 35vh;
}
@media (max-width: 640px) {
.chart-container {
height: 300px;
max-height: 50vh;
}
}
nav a {
transition: color 0.2s;
}
nav a:hover {
color: #0d9488; /* teal-600 */
}
.section-title {
font-size: 1.875rem; /* text-3xl */
font-weight: 700;
color: #1c1917; /* stone-900 */
margin-bottom: 1rem;
border-bottom: 2px solid #0d9488; /* teal-600 */
padding-bottom: 0.5rem;
}
.card {
background-color: #ffffff;
border-radius: 0.75rem; /* rounded-xl */
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); /* shadow-lg */
padding: 1.5rem; /* p-6 */
transition: all 0.3s;
display: flex;
flex-direction: column;
}
.card-content {
flex-grow: 1;
}
.card:hover {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); /* shadow-xl */
transform: translateY(-2px);
}
.stat-card {
background-color: #f5f5f4; /* stone-100 */
border: 1px solid #d6d3d1; /* stone-300 */
border-radius: 0.5rem; /* rounded-lg */
padding: 1rem;
text-align: center;
}
.stat-value {
font-size: 2.25rem; /* text-4xl */
font-weight: 700;
color: #0d9488; /* teal-600 */
}
.stat-label {
font-size: 0.875rem; /* text-sm */
color: #57534e; /* stone-600 */
margin-top: 0.25rem;
}
.schema-relation {
font-size: 0.75rem; /* text-xs */
font-family: monospace;
color: #0d9488; /* teal-600 */
}
.schema-type {
font-size: 0.875rem; /* text-sm */
font-family: monospace;
color: #a16207; /* yellow-700 */
}
</style>
</head>
<body class="text-stone-700">
<!-- Header & Navigation -->
<header class="bg-white/80 backdrop-blur-md shadow-sm sticky top-0 z-50">
<nav class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0">
<h1 class="text-xl font-bold text-stone-800">DMS v1.3.0 <span class="text-teal-600">Report</span></h1>
</div>
<div class="hidden md:flex md:space-x-6 lg:space-x-8">
<a href="#overview" class="nav-link font-medium text-stone-600">ภาพรวม</a>
<a href="#architecture" class="nav-link font-medium text-stone-600">สถาปัตยกรรม</a>
<a href="#modules" class="nav-link font-medium text-stone-600">โมดูลเอกสาร</a>
<a href="#features" class="nav-link font-medium text-stone-600">ฟีเจอร์หลัก</a>
<a href="#data-explorer" class="nav-link font-medium text-stone-600">โครงสร้างข้อมูล</a>
</div>
<div class="md:hidden">
<select id="mobile-nav" class="block w-full rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
<option value="#overview">ภาพรวม</option>
<option value="#architecture">สถาปัตยกรรม</option>
<option value="#modules">โมดูลเอกสาร</option>
<option value="#features">ฟีเจอร์หลัก</option>
<option value="#data-explorer">โครงสร้างข้อมูล</option>
</select>
</div>
</div>
</nav>
</header>
<!-- Main Content -->
<main class="container mx-auto p-4 sm:p-6 lg:p-8 pt-24">
<!-- 1. Overview Section -->
<section id="overview" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">ภาพรวมระบบ (Overview)</h2>
<p class="mb-8 text-lg text-stone-600">
นี่คือรายงานสรุปเชิงโต้ตอบสำหรับ **ระบบจัดการเอกสาร (DMS) v1.3.0** แอปพลิเคชันนี้ถูกออกแบบมาเพื่อวิเคราะห์ข้อกำหนดของระบบ, สถาปัตยกรรม, และโครงสร้างข้อมูล เพื่อให้ทีมพัฒนาและผู้มีส่วนได้ส่วนเสียสามารถทำความเข้าใจภาพรวมของโปรเจกต์ที่ซับซ้อนนี้ได้อย่างรวดเร็ว
</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="stat-card">
<div class="stat-value">6+</div>
<div class="stat-label">โมดูลเอกสารหลัก</div>
</div>
<div class="stat-card">
<div class="stat-value">44+</div>
<div class="stat-label">ตารางข้อมูล (Tables)</div>
</div>
<div class="stat-card">
<div class="stat-value">5+</div>
<div class="stat-label">วิวข้อมูล (Views)</div>
</div>
<div class="stat-card">
<div class="stat-value">7</div>
<div class="stat-label">Services (Docker)</div>
</div>
</div>
</section>
<!-- 2. Architecture Section -->
<section id="architecture" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">สถาปัตยกรรมและเทคโนโลยี (System Architecture)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS v1.3.0 ถูกออกแบบบนสถาปัตยกรรม Headless/API-First ที่ทันสมัย โดยทำงานทั้งหมดบน QNAP Server ผ่าน Container Station (Docker) เพื่อให้ง่ายต่อการจัดการและบำรุงรักษา
</p>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-6 text-center">แผนผังการเชื่อมต่อ (Docker Container Stack)</h3>
<div class="w-full bg-stone-50 p-6 rounded-lg border border-stone-200">
<!-- Nginx Reverse Proxy -->
<div class="relative p-4 border-2 border-teal-600 rounded-lg bg-teal-50/50">
<div class="absolute -top-3 left-4 bg-teal-600 text-white px-3 py-0.5 rounded-full text-sm font-medium">Nginx Proxy Manager</div>
<p class="text-center text-sm font-medium text-teal-800 pt-2">np-dms.work (HTTPS)</p>
<!-- Internal Network 'lcbp3' -->
<div class="mt-6 p-4 border-2 border-dashed border-stone-400 rounded-lg">
<div class="absolute -top-3 right-4 bg-stone-500 text-white px-3 py-0.5 rounded-full text-sm font-medium">Network: 'lcbp3'</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-4">
<!-- Frontend -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-blue-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-blue-700">Next.js (Frontend)</h4>
<p class="text-xs text-stone-600">ให้บริการ UI (React)</p>
</div>
</div>
<!-- Backend -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-red-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-red-700">NestJS (Backend)</h4>
<p class="text-xs text-stone-600">จัดการ API & Business Logic</p>
</div>
</div>
<!-- Other Services -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-purple-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-purple-700">N8N (Automation)</h4>
<p class="text-xs text-stone-600">จัดการ Workflow & Line Notify</p>
</div>
</div>
</div>
<!-- Arrow (Backend -> Other Services) -->
<div class="relative h-10 my-2">
<div class="absolute top-1/2 left-1/3 md:left-1/2 w-2/3 md:w-1/2 -translate-x-1/2 -translate-y-1/2 border-t-2 border-stone-500"></div>
<div class="absolute top-1/2 left-1/3 md:left-1/2 -translate-x-1/2 -translate-y-1/2 text-stone-500"></div>
</div>
<!-- Data Layer -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="w-full p-4 bg-white border border-orange-500 rounded-lg shadow-md text-center md:col-start-2">
<h4 class="font-bold text-orange-700">MariaDB (Database)</h4>
<p class="text-xs text-stone-600">เก็บข้อมูลทั้งหมด (SQL)</p>
</div>
<div class="w-full p-4 bg-white border border-green-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-green-700">Elasticsearch (Search)</h4>
<p class="text-xs text-stone-600">ให้บริการค้นหาขั้นสูง</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 3. Document Modules Section -->
<section id="modules" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">โมดูลเอกสารหลัก (Document Modules)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS แบ่งการจัดการเอกสารออกเป็นโมดูลย่อยที่ชัดเจน แต่ละโมดูลมี Workflow และตารางข้อมูลเฉพาะของตนเอง แต่ทั้งหมดเชื่อมโยงกับโครงสร้างเอกสารกลาง (Correspondence)
</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">1. Correspondence (เอกสารโต้ตอบ)</h3>
<p class="text-sm text-stone-600 mb-4">โมดูลหลักสำหรับเอกสารเข้า-ออกทั่วไป (จดหมาย, เมโม) เป็นตาราง "แม่" สำหรับเอกสารประเภทอื่นเกือบทั้งหมด</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`correspondences` (Master)</li>
<li>`correspondence_revisions` (Child)</li>
<li>`correspondence_recipients`</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">2. RFA (ขออนุมัติ)</h3>
<p class="text-sm text-stone-600 mb-4">โมดูลสำหรับเอกสารขออนุมัติ (Request for Approval) เช่น ขออนุมัติแบบ, วัสดุ, หรือเอกสาร ที่มี Workflow ชัดเจน (Draft, Submit, Review, Approved)</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`rfas` (Master)</li>
<li>`rfa_revisions` (Child)</li>
<li>`rfa_status_codes`</li>
<li>`rfa_items` (เชื่อม Shop Drawing)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">3. Drawing (แบบแปลน)</h3>
<p class="text-sm text-stone-600 mb-4">จัดการแบบแปลน 2 ประเภทหลัก: แบบตามสัญญา (Contract Drawing) และแบบสำหรับก่อสร้าง (Shop Drawing) ซึ่ง Shop Drawing จะถูกอ้างอิงใน RFA</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`contract_drawings`</li>
<li>`shop_drawings` (Master)</li>
<li>`shop_drawing_revisions` (Child)</li>
<li>`shop_drawing_revision_contract_refs`</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">4. Transmittal (เอกสารนำส่ง)</h3>
<p class="text-sm text-stone-600 mb-4">เอกสารนำส่ง (คล้ายใบปะหน้า) ที่ใช้สำหรับรวบรวมเอกสาร RFA หลายๆ ฉบับ เพื่อส่งให้ผู้รับในคราวเดียว</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`transmittals` (1:1 with Correspondence)</li>
<li>`transmittal_items` (เชื่อม RFA)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">5. Circulation (ใบเวียนภายใน)</h3>
<p class="text-sm text-stone-600 mb-4">ระบบใบเวียนภายในองค์กร (Internal) ใช้สำหรับส่งเอกสาร (ที่อ้างอิงจาก Correspondence) เพื่อให้ทีมตรวจสอบ, รับทราบ, หรือดำเนินการ</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`circulations` (Master)</li>
<li>`circulation_assignees` (Tasks)</li>
<li>`circulation_actions` (Logs)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">6. สัดส่วนตารางข้อมูล</h3>
<p class="text-sm text-stone-600 mb-4">แผนภูมิแสดงสัดส่วนตารางข้อมูลที่เกี่ยวข้องในแต่ละโมดูลหลัก (ไม่รวมตาราง Core และ RBAC)</p>
<div class="chart-container-sm">
<canvas id="moduleChart"></canvas>
</div>
</div>
</div>
</div>
</section>
<!-- 4. Core Features Section -->
<section id="features" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">ฟีเจอร์หลัก (Core Features)</h2>
<p class="mb-8 text-lg text-stone-600">
นอกเหนือจากโมดูลเอกสาร ระบบยังมีฟีเจอร์สนับสนุนที่สำคัญ ซึ่งทำงานข้ามโมดูลต่างๆ เพื่อให้ระบบทำงานได้อย่างสมบูรณ์
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การจัดการสิทธิ์ (RBAC)</h3>
<p class="text-sm text-stone-600 mb-4">
ระบบใช้ Role-Based Access Control (RBAC) ที่ละเอียดมาก ผู้ใช้ (Users) จะได้รับบทบาท (Roles) ซึ่งผูกกับสิทธิ์ (Permissions)
แผนภูมินี้แสดงตัวอย่างจำนวนสิทธิ์ (Permissions) ที่แต่ละบทบาทพื้นฐานอาจมี เพื่อให้เห็นภาพความซับซ้อนในการเข้าถึง
</p>
<div class="chart-container">
<canvas id="rbacChart"></canvas>
</div>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การสร้างเลขที่เอกสาร (Numbering)</h3>
<p class="text-sm text-stone-600 mb-4">
หนึ่งในส่วนที่สำคัญที่สุด คือการสร้างเลขที่เอกสาร (Running Number) เพื่อป้องกันเลขที่ซ้ำ (Race Condition) ระบบใช้ Stored Procedure (`sp_get_next_document_number`) ใน MariaDB ซึ่งใช้คำสั่ง `SELECT ... FOR UPDATE` เพื่อ "ล็อก" แถวของตัวนับก่อนที่จะอัปเดตค่า
</p>
<pre class="bg-stone-900 text-stone-100 text-sm rounded-lg p-4 overflow-x-auto">
<span class="text-blue-400">SELECT</span> last_number
<span class="text-blue-400">INTO</span> v_last_number
<span class="text-blue-400">FROM</span> document_number_counters
<span class="text-blue-400">WHERE</span> project_id = p_project_id
<span class="text-blue-400">AND</span> ...
<span class="text-red-400">FOR UPDATE</span>;
<span class="text-blue-400">IF</span> v_last_number IS NULL <span class="text-blue-400">THEN</span>
<span class="text-blue-400">INSERT INTO</span> ... (last_number) <span class="text-blue-400">VALUES</span> (1);
<span class="text-blue-400">SET</span> p_next_number = 1;
<span class="text-blue-400">ELSE</span>
<span class="text-blue-400">SET</span> p_next_number = v_last_number + 1;
<span class="text-blue-400">UPDATE</span> document_number_counters
<span class="text-blue-400">SET</span> last_number = p_next_number
<span class="text-blue-400">WHERE</span> ...;
<span class="text-blue-400">END IF</span>;</pre>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การค้นหา (Advanced Search)</h3>
<p class="text-sm text-stone-600 mb-4">
(Req 6.2) ระบบใช้ **Elasticsearch** เป็น Service แยกต่างหากสำหรับให้บริการค้นหาขั้นสูง
NestJS (Backend) จะทำการ Index ข้อมูลจาก Views (`v_current_correspondences`, `v_current_rfas` ฯลฯ) ไปยัง Elasticsearch
ทำให้ผู้ใช้สามารถค้นหาแบบ Full-text ข้ามโมดูลทั้งหมด (Correspondence, RFA, Drawing) ได้อย่างรวดเร็ว
</p>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การแจ้งเตือน (Notifications)</h3>
<p class="text-sm text-stone-600 mb-4">
(Req 6.7) ระบบใช้ **N8N (Automation)** เป็นศูนย์กลางการแจ้งเตือน
เมื่อมีเหตุการณ์สำคัญ (เช่น มีการสร้างใบเวียนใหม่) NestJS (Backend) จะยิง Webhook ไปที่ N8N
จากนั้น N8N จะทำหน้าที่ส่งการแจ้งเตือนไปยัง **Line Notify** หรือ Email ตาม Workflow ที่ตั้งค่าไว้
</p>
</div>
</div>
</section>
<!-- 5. Data Explorer Section -->
<section id="data-explorer" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">สำรวจโครงสร้างข้อมูล (Data Explorer)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS มีตารางข้อมูลมากกว่า 44 ตารางเพื่อรองรับฟีเจอร์ที่ซับซ้อน เลือกตารางจากรายการด้านล่างเพื่อดูโครงสร้าง, ประเภทข้อมูล, และความสัมพันธ์ (Foreign Keys)
</p>
<div class="card">
<div class="mb-4">
<label for="table-select" class="block text-sm font-medium text-stone-700 mb-1">เลือกตารางเพื่อดูรายละเอียด:</label>
<select id="table-select" class="block w-full md:w-1/2 lg:w-1/3 rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
<option value="users">users (ผู้ใช้)</option>
<option value="roles">roles (บทบาท)</option>
<option value="permissions">permissions (สิทธิ์)</option>
<option value="user_project_roles">user_project_roles (สิทธิ์ในโปรเจกต์)</option>
<option value="correspondences">correspondences (เอกสาร - Master)</option>
<option value="correspondence_revisions">correspondence_revisions (เอกสาร - Child)</option>
<option value="rfas">rfas (RFA - Master)</option>
<option value="rfa_revisions">rfa_revisions (RFA - Child)</option>
<option value="shop_drawings">shop_drawings (Shop Drawing - Master)</option>
<option value="shop_drawing_revisions">shop_drawing_revisions (Shop Drawing - Child)</option>
<option value="circulations">circulations (ใบเวียน - Master)</option>
<option value="circulation_assignees">circulation_assignees (ใบเวียน - Tasks)</option>
<option value="attachments">attachments (ไฟล์แนบ - Master)</option>
</select>
</div>
<div>
<h4 id="schema-title" class="text-2xl font-bold text-teal-700 mb-1"></h4>
<p id="schema-description" class="text-sm text-stone-600 mb-6"></p>
<div class="overflow-x-auto border border-stone-200 rounded-lg">
<table class="min-w-full divide-y divide-stone-200">
<thead class="bg-stone-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Column (Field)</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Type</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Description / Relation</th>
</tr>
</thead>
<tbody id="schema-body" class="bg-white divide-y divide-stone-200">
<!-- JS will populate this -->
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<footer class="text-center py-8 mt-12 border-t border-stone-200">
<p class="text-sm text-stone-500">Interactive Report generated for DMS v1.3.0</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- Chart.js Rendering ---
// 1. Module Chart (Donut)
const moduleCtx = document.getElementById('moduleChart');
if (moduleCtx) {
new Chart(moduleCtx, {
type: 'donut',
data: {
labels: ['Correspondence', 'RFA', 'Drawing', 'Circulation', 'Transmittal'],
datasets: [{
label: 'จำนวนตารางที่เกี่ยวข้อง',
data: [5, 5, 8, 5, 2],
backgroundColor: [
'rgb(20, 184, 166)', // teal-500
'rgb(15, 118, 110)', // teal-700
'rgb(107, 114, 128)', // stone-500
'rgb(55, 65, 81)', // stone-700
'rgb(245, 158, 11)' // amber-500
],
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
font: { family: 'Sarabun' }
}
}
}
}
});
}
// 2. RBAC Chart (Horizontal Bar)
const rbacCtx = document.getElementById('rbacChart');
if (rbacCtx) {
new Chart(rbacCtx, {
type: 'bar',
data: {
labels: ['Viewer (ผู้ดู)', 'Editor (ผู้แก้ไข)', 'Org Admin (แอดมินองค์กร)', 'Superadmin (ผู้ดูแลระบบ)'],
datasets: [{
label: 'จำนวนสิทธิ์ (ตัวอย่าง)',
data: [5, 12, 20, 30],
backgroundColor: 'rgba(13, 148, 136, 0.6)', // teal-600
borderColor: 'rgb(15, 118, 110)', // teal-700
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
beginAtZero: true,
grid: { display: false }
},
y: {
grid: { display: false }
}
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (context) => ` ${context.dataset.label}: ${context.raw} สิทธิ์`
}
}
}
}
});
}
// --- Data Explorer Logic ---
const databaseSchema = {
'users': {
description: 'ตารางเก็บข้อมูลผู้ใช้ในระบบ (Req 4.x)',
columns: [
{ name: 'user_id', type: 'INT (PK)', desc: 'ID หลักของผู้ใช้' },
{ name: 'username', type: 'VARCHAR(100)', desc: 'ชื่อเข้าระบบ (ต้อง Unique)' },
{ name: 'password_hash', type: 'VARCHAR(255)', desc: 'รหัสผ่านที่ถูก Hashed (Bcrypt)' },
{ name: 'email', type: 'VARCHAR(100)', desc: 'อีเมล' },
{ name: 'organization_id', type: 'INT', desc: 'องค์กรที่ผู้ใช้สังกัด', relation: 'organizations(id)' },
{ name: 'is_active', type: 'BOOLEAN', desc: 'สถานะการใช้งาน' },
{ name: 'is_superadmin', type: 'BOOLEAN', desc: 'สถานะผู้ดูแลสูงสุด' }
]
},
'roles': {
description: 'ตาราง Master เก็บ "บทบาท" ทั้งหมด (Req 4.3)',
columns: [
{ name: 'role_id', type: 'INT (PK)', desc: 'ID ของบทบาท' },
{ name: 'role_code', type: 'VARCHAR(50)', desc: 'รหัสบทบาท (เช่น EDITOR, VIEWER)' },
{ name: 'role_name', type: 'VARCHAR(100)', desc: 'ชื่อบทบาท (เช่น "ผู้แก้ไข")' },
{ name: 'is_system', type: 'BOOLEAN', desc: 'เป็นบทบาทของระบบ (ห้ามแอดมินลบ)' }
]
},
'permissions': {
description: 'ตาราง Master เก็บ "สิทธิ์" การกระทำทั้งหมดในระบบ (Req 4.3)',
columns: [
{ name: 'permission_id', type: 'INT (PK)', desc: 'ID ของสิทธิ์' },
{ name: 'permission_code', type: 'VARCHAR(100)', desc: 'รหัสสิทธิ์ (เช่น corr.create, rfa.view)' },
{ name: 'permission_group', type: 'VARCHAR(50)', desc: 'กลุ่มของสิทธิ์ (เช่น CORR, RFA, ADMIN)' }
]
},
'user_project_roles': {
description: 'ตารางเชื่อม M:N (Junction) ระหว่าง User, Role, และ Project',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของการผูกสิทธิ์' },
{ name: 'user_id', type: 'INT', desc: 'ผู้ใช้', relation: 'users(user_id)' },
{ name: 'role_id', type: 'INT', desc: 'บทบาท', relation: 'roles(role_id)' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' }
]
},
'correspondences': {
description: 'ตาราง "แม่" (Master) สำหรับเอกสารทั้งหมด (Req 3.2)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของเอกสาร' },
{ name: 'correspondence_number', type: 'VARCHAR(100)', desc: 'เลขที่เอกสาร (จาก Stored Procedure)' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
{ name: 'correspondence_type_id', type: 'INT', desc: 'ประเภทเอกสาร', relation: 'correspondence_types(id)' },
{ name: 'originator_id', type: 'INT', desc: 'องค์กรผู้ส่ง', relation: 'organizations(id)' },
{ name: 'recipient_id', type: 'INT', desc: 'องค์กรผู้รับ (หลัก)', relation: 'organizations(id)' },
{ name: 'deleted_at', type: 'DATETIME', desc: 'สำหรับ Soft Delete' }
]
},
'correspondence_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บประวัติการแก้ไขและเนื้อหาของเอกสาร (Req 3.2)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
{ name: 'revision_number', type: 'INT', desc: 'เลข Revision (เช่น 0, 1, 2)' },
{ name: 'is_current', type: 'BOOLEAN', desc: 'เป็น Revision ปัจจุบันหรือไม่' },
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง/หัวข้อ' },
{ name: 'document_date', type: 'DATE', desc: 'วันที่ในเอกสาร' },
{ name: 'issued_date', type: 'DATETIME', desc: 'วันที่ส่ง (Submit)' },
{ name: 'details', type: 'JSON', desc: 'ข้อมูลอื่นๆ (ถ้ามี)' }
]
},
'rfas': {
description: 'ตาราง "แม่" (Master) สำหรับเอกสาร RFA (Req 3.5)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ RFA' },
{ name: 'rfa_type_id', type: 'INT', desc: 'ประเภท RFA (DWG, MAT, DOC)', relation: 'rfa_types(id)' },
{ name: 'revision_number', type: 'INT', desc: 'Revision ล่าสุด (เช่น 0, 1)' }
]
},
'rfa_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บประวัติและสถานะของ RFA (Req 3.5)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ RFA Revision' },
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
{ name: 'rfa_id', type: 'INT', desc: 'ID RFA (Master)', relation: 'rfas(id)' },
{ name: 'rfa_status_code_id', type: 'INT', desc: 'สถานะ (เช่น DFT, FAP)', relation: 'rfa_status_codes(id)' },
{ name: 'rfa_approve_code_id', type: 'INT', desc: 'ผลการอนุมัติ (เช่น A, B, C)', relation: 'rfa_approve_codes(id)' },
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง' }
]
},
'shop_drawings': {
description: 'ตาราง "แม่" (Master) สำหรับ Shop Drawing (Req 3.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ Shop Drawing' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
{ name: 'drawing_number', type: 'VARCHAR(100)', desc: 'เลขที่แบบ (Unique)' },
{ name: 'title', type: 'VARCHAR(500)', desc: 'ชื่อแบบ' }
]
},
'shop_drawing_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บ Revision ของ Shop Drawing (Req 3.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
{ name: 'shop_drawing_id', type: 'INT', desc: 'ID Master', relation: 'shop_drawings(id)' },
{ name: 'revision_number', type: 'VARCHAR(10)', desc: 'เลข Revision (เช่น A, B, 0, 1)' },
{ name: 'revision_date', type: 'DATE', desc: 'วันที่ของ Revision' }
]
},
'circulations': {
description: 'ตาราง "แม่" (Master) สำหรับใบเวียนภายใน (Req 3.7)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของใบเวียน' },
{ name: 'correspondence_id', type: 'INT', desc: 'เอกสารที่อ้างอิง', relation: 'correspondences(id)' },
{ name: 'organization_id', type: 'INT', desc: 'องค์กรเจ้าของใบเวียน', relation: 'organizations(id)' },
{ name: 'circulation_no', type: 'VARCHAR(100)', desc: 'เลขที่ใบเวียน' },
{ name: 'circulation_subject', type: 'VARCHAR(500)', desc: 'เรื่อง' },
{ name: 'circulation_status_code', type: 'VARCHAR(20)', desc: 'สถานะ (OPEN, COMPLETED)', relation: 'circulation_status_codes(code)' }
]
},
'circulation_assignees': {
description: 'ตาราง "Task" สำหรับผู้ที่ต้องดำเนินการในใบเวียน (Req 3.7.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Task' },
{ name: 'circulation_id', type: 'INT', desc: 'ใบเวียน', relation: 'circulations(id)' },
{ name: 'user_id', type: 'INT', desc: 'ผู้ได้รับมอบหมาย', relation: 'users(user_id)' },
{ name: 'assignee_type', type: 'ENUM', desc: 'ประเภท (MAIN, ACTION, INFO)' },
{ name: 'deadline', type: 'DATE', desc: 'วันที่กำหนดเสร็จ' },
{ name: 'is_completed', type: 'BOOLEAN', desc: 'สถานะว่าเสร็จสิ้นหรือยัง' }
]
},
'attachments': {
description: 'ตาราง "แม่" (Master) สำหรับไฟล์แนบทั้งหมด (Req 3.9)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของไฟล์' },
{ name: 'original_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์เดิม' },
{ name: 'stored_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์ที่เก็บ (UUID)' },
{ name: 'file_path', type: 'VARCHAR(500)', desc: 'Path ที่เก็บไฟล์บน Server' },
{ name: 'mime_type', type: 'VARCHAR(100)', desc: 'ประเภทไฟล์' },
{ name: 'file_size', type: 'INT', desc: 'ขนาดไฟล์ (bytes)' },
{ name: 'uploaded_by_user_id', type: 'INT', desc: 'ผู้อัปโหลด', relation: 'users(user_id)' }
]
}
};
const tableSelect = document.getElementById('table-select');
const schemaTitle = document.getElementById('schema-title');
const schemaDescription = document.getElementById('schema-description');
const schemaBody = document.getElementById('schema-body');
function updateSchemaView(tableName) {
const tableData = databaseSchema[tableName];
if (!tableData) return;
schemaTitle.textContent = tableName;
schemaDescription.textContent = tableData.description;
let html = '';
tableData.columns.forEach(col => {
const relationHtml = col.relation
? `<span class="schema-relation block">FK &rarr; ${col.relation}</span>`
: '';
html += `
<tr class="hover:bg-stone-50">
<td class="px-6 py-4 whitespace-nowrap">
<strong class="text-sm font-medium text-stone-900">${col.name}</strong>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="schema-type">${col.type}</span>
</td>
<td class="px-6 py-4">
<span class="text-sm text-stone-700">${col.desc}</span>
${relationHtml}
</td>
</tr>
`;
});
schemaBody.innerHTML = html;
}
tableSelect.addEventListener('change', (e) => updateSchemaView(e.target.value));
// Initial load
updateSchemaView(tableSelect.value);
// --- Navigation Logic ---
// 1. Mobile Navigation
const mobileNav = document.getElementById('mobile-nav');
mobileNav.addEventListener('change', (e) => {
const targetId = e.target.value;
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
});
// 2. Desktop Smooth Scroll
const desktopLinks = document.querySelectorAll('a.nav-link');
desktopLinks.forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
});
});
});
</script>
</body>
</html>