ข้ามไปที่เนื้อหา

04. Lab Module - Page-by-Page Features Documentation

เอกสารรายละเอียดคุณสมบัติของแต่ละหน้า
HIS Prototype - Laboratory Management System


📋 Table of Contents

  1. index.html - หน้าแรก/เมนูหลัก Lab Module
  2. doctor-order.html - สั่งตรวจแล็บ (Doctor Order Entry)
  3. order-queue.html - จัดการคิว Order แล็บ
  4. specimen-collection.html - เก็บสิ่งส่งตรวจ
  5. result-entry.html - ลงผลแล็บ (Result Entry)
  6. result-approval.html - อนุมัติผลแล็บ (Result Approval)
  7. Utility Pages - หน้าเสริม (Debug/Clear Storage)

1. index.html - หน้าแรก/เมนูหลัก

📌 ภาพรวม

หน้า Landing Page ของระบบงานชันสูตร ทำหน้าที่เป็น Navigation Hub เชื่อมโยงไปยังหน้าต่างๆ ในระบบ Lab

🎯 วัตถุประสงค์

  • ให้ผู้ใช้เข้าถึงหน้าต่างๆ ในระบบ Lab ได้สะดวก
  • แสดงภาพรวมความสามารถของระบบ Lab Module
  • จัดกลุ่มเมนูตามบทบาทผู้ใช้งาน (Doctor, Phlebotomist, Technician, Supervisor)

🧩 UI Components

1. Hero Section

┌─────────────────────────────────────────────────────────────┐
│  🔬 ระบบงานชันสูตร - Laboratory Management System          │
│  จัดการคำสั่งตรวจแล็บ ตัวอย่างสิ่งส่งตรวจ และผลแล็บ       │
└─────────────────────────────────────────────────────────────┘

คุณสมบัติ: - Background Gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%) - สีตัวอักษร: สีขาว (White) - แสดงชื่อระบบและคำอธิบายสั้นๆ

2. Doctor Workflow Section

หัวข้อ: 👨‍⚕️ ระบบสำหรับแพทย์ (Doctor Workflow)

เมนู: 1. สั่งตรวจแล็บ (Lab Order) - ไอคอน: fas fa-file-medical - สี Badge: Success Green - ลิงก์: doctor-order.html - คำอธิบาย: สั่งตรวจแล็บสำหรับผู้ป่วย เลือกรายการตรวจ และพิมพ์ใบนำส่ง

  1. ดูผลแล็บ (View Results)
  2. ไอคอน: fas fa-flask
  3. สี Badge: Info Blue
  4. ลิงก์: # (Coming Soon)
  5. คำอธิบาย: ตรวจสอบผลแล็บ แสดง Critical Results และประวัติการตรวจ

3. Lab Staff Section

หัวข้อ: 🔬 ระบบสำหรับเจ้าหน้าที่แล็บ (Lab Staff Workflow)

เมนู:

3.1 จัดการคิว Order (Order Queue) - ไอคอน: fas fa-list-check - สี Badge: Warning Orange - ลิงก์: order-queue.html - คำอธิบาย: ตรวจสอบคิว Order แล็บ ยืนยันการรับ Order และจัดการสถานะ

3.2 เก็บสิ่งส่งตรวจ (Specimen Collection) - ไอคอน: fas fa-vial - สี Badge: Primary Blue - ลิงก์: specimen-collection.html - คำอธิบาย: บันทึกการเก็บตัวอย่าง ตรวจสอบคุณภาพ และพิมพ์ Label

3.3 ลงผลแล็บ (Result Entry) - ไอคอน: fas fa-keyboard - สี Badge: Info Cyan - ลิงก์: result-entry.html - คำอธิบาย: บันทึกผลแล็บ Delta Check และ Critical Alert

3.4 อนุมัติผลแล็บ (Result Approval) - ไอคอน: fas fa-check-double - สี Badge: Success Green - ลิงก์: result-approval.html - คำอธิบาย: ตรวจสอบและอนุมัติผลแล็บก่อนรายงาน

4. Utility Tools Section

หัวข้อ: 🛠️ เครื่องมือเสริม (Utility Tools)

เมนู:

4.1 ตรวจสอบข้อมูล (Debug Lab Data) - ไอคอน: fas fa-bug - สี Badge: Secondary Gray - ลิงก์: debug-lab-data.html - คำอธิบาย: ตรวจสอบข้อมูลใน LocalStorage

4.2 ล้างข้อมูล (Clear Storage) - ไอคอน: fas fa-trash-can - สี Badge: Danger Red - ลิงก์: clear-storage.html - คำอธิบาย: ล้างข้อมูล LocalStorage และ Reset ระบบ


🎨 Design System

Color Scheme

--primary-color: #667eea (Purple-Blue)
--success-color: #28a745 (Green)
--warning-color: #ffc107 (Orange)
--danger-color: #dc3545 (Red)
--info-color: #17a2b8 (Cyan)
--secondary-color: #6c757d (Gray)

Card Layout

┌─────────────────────────────────────────┐
│  🔬 [Icon]                      [Badge] │
│  **Card Title**                         │
│  Description text here...               │
│                                         │
│  [View Details →]                       │
└─────────────────────────────────────────┘

Card Properties: - Border: 1px solid #ddd - Border Radius: 8px - Padding: 1.5rem - Shadow: 0 2px 4px rgba(0,0,0,0.1) - Hover Effect: Lift + Shadow increase - Transition: all 0.3s ease

Grid System

  • Layout: CSS Grid
  • Columns: repeat(auto-fill, minmax(280px, 1fr))
  • Gap: 1.5rem
  • Responsive: Auto-adjust columns based on screen width

🔧 Technical Specifications

Dependencies

<!-- CSS -->
<link rel="stylesheet" href="../../assets/css/main.css">
<link rel="stylesheet" href="../../assets/css/components.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

File Size

  • Total Lines: ~656 lines
  • Primarily HTML + CSS (No JavaScript logic)
  • Static navigation page

Browser Compatibility

  • Modern browsers (Chrome, Firefox, Edge, Safari)
  • Responsive design (Mobile, Tablet, Desktop)
  • No polyfills required

📱 Responsive Behavior

Desktop (> 1200px)

  • 3-4 cards per row
  • Full-width hero section
  • Spacious padding

Tablet (768px - 1200px)

  • 2-3 cards per row
  • Medium padding
  • Readable font sizes

Mobile (< 768px)

  • 1 card per row (stacked)
  • Compact hero section
  • Touch-friendly button sizes (min 44x44px)

graph LR
    A[index.html] --> B[doctor-order.html]
    A --> C[order-queue.html]
    A --> D[specimen-collection.html]
    A --> E[result-entry.html]
    A --> F[result-approval.html]
    A --> G[debug-lab-data.html]
    A --> H[clear-storage.html]

    style A fill:#667eea,color:#fff
    style B fill:#28a745,color:#fff
    style C fill:#ffc107,color:#000
    style D fill:#007bff,color:#fff
    style E fill:#17a2b8,color:#fff
    style F fill:#28a745,color:#fff
    style G fill:#6c757d,color:#fff
    style H fill:#dc3545,color:#fff

💡 Best Practices

For UX/UI Team:

  1. Visual Hierarchy: ใช้ Section Headers แยกกลุ่มเมนูชัดเจน
  2. Color Coding: สีของ Badge บ่งบอกประเภทการใช้งาน (Success = Create, Info = View, Warning = Manage)
  3. Icon Consistency: ใช้ FontAwesome เพื่อความสม่ำเสมอ
  4. White Space: เว้นระยะระหว่าง Section เพื่อลดความรู้สึกแออัด

For Developers:

  1. No JavaScript Required: Static HTML page ไม่ต้อง Load Services
  2. Fast Load Time: Minimal dependencies (CSS + FontAwesome เท่านั้น)
  3. Easy Maintenance: แก้ไขเมนูได้ง่ายโดยเพิ่ม/ลบ Card HTML

🐛 Known Issues

  • Coming Soon Links: เมนู "ดูผลแล็บ" ยังไม่มีหน้าจริง (ใช้ href="#")
  • No Active State: ไม่มีการไฮไลต์ Current Page (เพราะเป็นหน้าเมนูหลัก)

📊 Usage Statistics (Mock Data)

  • Most Clicked: doctor-order.html (Doctor workflow entry point)
  • Second: specimen-collection.html (Daily task for phlebotomists)
  • Least Used: Utility tools (Debug/Clear - Admin only)

2. doctor-order.html - สั่งตรวจแล็บ

📌 ภาพรวม

หน้าสำหรับแพทย์สั่งตรวจแล็บให้ผู้ป่วย เป็นจุดเริ่มต้นของ Lab Workflow ทั้งหมด มีฟีเจอร์ครบถ้วนตั้งแต่ค้นหาผู้ป่วย เลือกรายการตรวจ คำนวณค่าใช้จ่าย จนถึงพิมพ์ใบนำส่ง

🎯 วัตถุประสงค์

  • ให้แพทย์สั่งตรวจแล็บได้สะดวกและรวดเร็ว
  • แสดงข้อมูลค่าใช้จ่ายโปร่งใส (ราคาเต็ม, สิทธิ์เบิก, ผู้ป่วยจ่าย)
  • รองรับการสั่ง STAT Order (ด่วนพิเศษ)
  • สร้าง Order Number อัตโนมัติ และพิมพ์ใบนำส่งแล็บ

📊 File Statistics

  • Total Lines: 1,445 lines
  • Components: 4 major sections
  • Dependencies: Patient.js, LabItem.js, LabOrder.js, PatientService, LabOrderService
  • Data Files: lab-items.json, lab-panels.json, lab-pricing.json, patients.json

🧩 UI Components & Workflow

UI Layout:

┌────────────────────────────────────────────────────────────┐
│ 🔍 ค้นหาผู้ป่วย                                           │
├────────────────────────────────────────────────────────────┤
│  เลข HN: [_________________________]  [🔍 ค้นหา]          │
│                                                            │
│  💡 ข้อมูลทดสอบ - HN ที่มีในระบบ:                        │
│  [HN000001] [HN000002] [HN000123] [HN000456] ...          │
└────────────────────────────────────────────────────────────┘

คุณสมบัติ: 1. HN Input Field - Placeholder: "ระบุเลข HN ผู้ป่วย (เช่น HN000001)" - Validation: Required field - Auto-uppercase: แปลง "hn000001" → "HN000001" อัตโนมัติ

  1. Quick Select Buttons
  2. แสดง HN 6 คนแรกจาก Mock Data
  3. คลิก → Auto-fill HN input → Trigger search
  4. สี: Gray (#6c757d) เพื่อให้ต่างจาก Primary Action
  5. Format: "HN000001 - ชื่อ" (แสดง First Name only)

  6. Search Logic (searchPatient())

    // 1. Validate HN input
    // 2. Call PatientService.getPatientByHN(hn)
    // 3. If found → Display Patient Info + Show Lab Selection
    // 4. If not found → Alert "ไม่พบผู้ป่วย HN: XXX"
    

Error Handling: - Empty HN → "กรุณาระบุเลข HN" - Invalid HN → "ไม่พบผู้ป่วย HN: XXX" - Network Error → "เกิดข้อผิดพลาดในการค้นหา"


Section 2: ข้อมูลผู้ป่วย (Patient Information)

Visibility: display: none initially, แสดงหลัง Search สำเร็จ

UI Layout:

┌────────────────────────────────────────────────────────────┐
│ 👤 ข้อมูลผู้ป่วย                                          │
├────────────────────────────────────────────────────────────┤
│  HN: HN000123              ชื่อ-สกุล: นายสมชาย ใจดี       │
│  อายุ: 30 ปี               เพศ: ชาย                      │
│  วันเกิด: 15/05/2538       กรุ๊ปเลือด: O+                 │
│  สิทธิการรักษา: บัตรทอง (UC)                              │
│  แพ้ยา: Penicillin, Aspirin                               │
│  โรคประจำตัว: Diabetes Type 2                             │
└────────────────────────────────────────────────────────────┘

Data Display: - Title: แปลงจาก 'mr' → 'นาย', 'mrs' → 'นาง' (via Patient.getTitleName()) - Age: คำนวณจาก Date of Birth (via Patient.getAge()) - Insurance Type: แสดงเป็นภาษาไทย (uc → 'บัตรทอง', social → 'ประกันสังคม') - Allergies: แสดงเป็น Comma-separated list, สีแดงถ้ามี - Chronic Diseases: แสดงเป็น List, สีส้มถ้ามี

Styling: - Background: Info Light Blue (#cfe2ff) - Border: Info Border (#0dcaf0) - Grid: 2 columns on Desktop, 1 column on Mobile


Section 3: เลือกรายการตรวจ (Lab Item Selection)

Visibility: แสดงหลัง Patient Info loaded

UI Structure:

┌────────────────────────────────────────────────────────────┐
│ 🧪 เลือกรายการตรวจแล็บ                                    │
├────────────────────────────────────────────────────────────┤
│  [🎯 Panel Tests] [📋 Individual Tests]                   │
│                                                            │
│  ┌─── Panel View (Tab 1) ───────────────────────────────┐ │
│  │  ┌────────────────┐  ┌────────────────┐              │ │
│  │  │ CBC (PANEL)    │  │ LFT (PANEL)    │              │ │
│  │  │ นับเม็ดเลือด   │  │ ตับอักเสบ      │              │ │
│  │  │ 🔴 EDTA        │  │ 🟢 Plain       │              │ │
│  │  │ ⏱ 2 ชม.       │  │ ⏱ 4 ชม.        │              │ │
│  │  │ 📋 8 รายการ   │  │ 📋 5 รายการ    │              │ │
│  │  │ [Regular] [⚡STAT] │                              │ │
│  │  └────────────────┘  └────────────────┘              │ │
│  └──────────────────────────────────────────────────────┘ │
│                                                            │
│  ┌─── Individual View (Tab 2) ──────────────────────────┐ │
│  │  Category Tabs:                                       │ │
│  │  [ทั้งหมด] [Hematology] [Chemistry] [Immunology]...  │ │
│  │                                                        │ │
│  │  🔍 Search: [_________________________________]        │ │
│  │                                                        │ │
│  │  ┌────────────────┐  ┌────────────────┐              │ │
│  │  │ CBC            │  │ FBS            │              │ │
│  │  │ นับเม็ดเลือด   │  │ น้ำตาลในเลือด  │              │ │
│  │  │ 🔴 EDTA        │  │ 🟢 Fluoride    │              │ │
│  │  │ ⏱ 120 นาที    │  │ ⏱ 60 นาที     │              │ │
│  │  │ [Regular] [⚡STAT] │ [Regular]     │              │ │
│  │  └────────────────┘  └────────────────┘              │ │
│  └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘

3.1 Panel Tests (Tab 1)

Panel Card Components: - Panel Name: ชื่อ Panel (เช่น "CBC", "LFT") + Badge "PANEL" - Thai Name: ชื่อภาษาไทย (เช่น "นับเม็ดเลือด", "ตับอักเสบ") - Code: รหัส Panel (เช่น "PANEL001") - Specimen Badge: สีหลอด + ชนิดตัวอย่าง - 🔴 Red (EDTA) - CBC, Hematology - 🟢 Green (Plain/Gel) - Chemistry - 🟡 Yellow (SST) - Immunology - 🔵 Blue (Sodium Citrate) - Coagulation - 🟣 Purple (Fluoride) - Glucose - Turnaround Time: แสดงเป็นชั่วโมงหรือวัน - Item Count: จำนวนรายการในแต่ละ Panel (เช่น "8 รายการ") - Priority Buttons: - Regular (เขียว) - ตรวจปกติ - ⚡ STAT (แดง) - ด่วนพิเศษ (ถ้า isStatAvailable: true)

Interaction: - Click Card Area → Show Panel Details (รายการภายใน Panel) - Click [Regular] → Add ทั้ง Panel เป็น Regular priority - Click [⚡ STAT] → Add ทั้ง Panel เป็น STAT priority (พร้อม Alert "STAT Order ต้องระบุเหตุผล")

3.2 Individual Tests (Tab 2)

Category Filter: - ทั้งหมด (ALL): แสดงรายการทั้งหมด - Hematology: เลือด (CBC, ESR, Blood Film) - Chemistry: เคมี (FBS, LFT, RFT, Lipid Profile) - Immunology: ภูมิคุ้มกัน (Hepatitis Markers, Thyroid Function) - Microbiology: จุลชีววิทยา (Culture & Sensitivity) - Coagulation: การแข็งตัวของเลือด (PT, APTT, INR) - Urinalysis: ตรวจปัสสาวะ (UA, Urine C&S) - Serology: เซรุ่มวิทยา (VDRL, HIV, HBsAg)

Search Bar: - Real-time search โดย filterLabItems(query) - ค้นจาก: Item Name (EN), Item Name (TH), Item Code - Case-insensitive - แสดง "ไม่พบรายการตรวจที่ตรงกับเงื่อนไข" ถ้าค้นไม่เจอ

Lab Item Card Components: - Item Name (EN): ชื่อรายการภาษาอังกฤษ - Item Name (TH): ชื่อภาษาไทย - Item Code: รหัสรายการ (เช่น "CBC001") - Specimen Badge: สีหลอด + ชนิดตัวอย่าง - STAT Badge: แสดง "⚡ STAT" ถ้ารองรับ - Outsource Badge: แสดง "🔗 ส่งตรวจนอก" + ชื่อแล็บ (ถ้า isOutsourced: true) - Turnaround Time: แสดงเป็นชั่วโมง/วัน - Fasting Warning: "⚠️ ต้องงดอาหาร" (เช่น FBS, Lipid Profile) - Priority Buttons: Regular / STAT

Selection State: - Unselected: White background, Gray border - Selected Regular: Light Green background (#d4edda) - Selected STAT: Light Red background (#f8d7da) - Active Button: Bold + Primary Color

Interaction: - Click [Regular] → toggleLabItem(itemId, 'regular') - If already selected → Remove from cart - If not selected → Add to cart + Fetch pricing - Click [⚡ STAT] → toggleLabItem(itemId, 'stat') - Same logic as Regular, but set priority: 'stat'


Section 4: สรุปรายการสั่งตรวจ (Order Summary)

UI Layout:

┌────────────────────────────────────────────────────────────┐
│ 📋 สรุปรายการสั่งตรวจ                                      │
├────────────────────────────────────────────────────────────┤
│  Diagnosis (ICD-10): [____________________________]        │
│  Clinical Note:     [____________________________]         │
│                      (จำเป็นสำหรับ STAT Order)             │
│                                                            │
│  ┌─── Order Table ────────────────────────────────────┐   │
│  │ ลำดับ │ รายการ      │ Priority │ ราคา │ เบิก │ จ่าย│   │
│  ├───────┼────────────┼──────────┼──────┼──────┼─────┤   │
│  │  1    │ CBC        │ STAT 🔴  │ 120  │ 100  │ 20  │   │
│  │  2    │ FBS        │ Regular  │  80  │  80  │  0  │   │
│  │  3    │ LFT Panel  │ Regular  │ 450  │ 400  │ 50  │   │
│  └───────┴────────────┴──────────┴──────┴──────┴─────┘   │
│                                                            │
│  ค่าใช้จ่ายรวม:      650.00 ฿                             │
│  สิทธิ์เบิกได้:      580.00 ฿                             │
│  ผู้ป่วยจ่าย:         70.00 ฿ 🔴                          │
│  ────────────────────────────────────────────────────     │
│  รวมทั้งสิ้น:        650.00 ฿                             │
│                                                            │
│  [🗑️ ล้างรายการ]  [💾 บันทึกคำสั่งตรวจ]                 │
└────────────────────────────────────────────────────────────┘

Components:

4.1 Diagnosis & Clinical Note - Diagnosis: ฟรีเทกซ์ หรือ ICD-10 Code (Future: Auto-complete) - Clinical Note: Required for STAT Order (Validation ตอนกด Save) - Placeholder: "ระบุเหตุผลการสั่ง STAT, อาการผู้ป่วย, หรือข้อมูลเพิ่มเติม" - Multi-line textarea (4 rows)

4.2 Order Items Table - Columns: 1. ลำดับ (Auto-number) 2. รายการตรวจ (Item Name TH + EN) 3. Priority (STAT Badge แดง, Regular Badge เขียว) 4. ราคาเต็ม (Unit Price) 5. สิทธิ์เบิก (Reimbursement Price) 6. ผู้ป่วยจ่าย (Patient Pay = Unit - Reimb) 7. ลบ (🗑️ Delete Button)

  • Row Styling:
  • STAT Order: Light Red background (#ffe6e6)
  • Regular Order: White background
  • Hover: Light Gray background

4.3 Price Breakdown

// Calculation Logic
totalPrice = sum(item.unitPrice)
insuranceCoverage = sum(item.reimbPrice)
patientPay = sum(item.patientPay)
grandTotal = totalPrice

  • ค่าใช้จ่ายรวม: Total Unit Price (สีดำ)
  • สิทธิ์เบิกได้: Total Reimbursement (สีเขียว)
  • ผู้ป่วยจ่าย: Total Patient Pay (สีแดง, Bold ถ้า > 0)
  • รวมทั้งสิ้น: Grand Total (สีน้ำเงิน, Bold, Large font)

4.4 Action Buttons

🗑️ ล้างรายการ (clearOrder()) - Confirmation: "ต้องการยกเลิกรายการสั่งตรวจทั้งหมด?" - Action: Clear selectedItems[], Reset form, Reload Lab Items

💾 บันทึกคำสั่งตรวจ (saveOrder()) - Validations: 1. Patient selected? → "กรุณาเลือกผู้ป่วยก่อน" 2. Items > 0? → "กรุณาเลือกรายการตรวจอย่างน้อย 1 รายการ" 3. STAT Order + No Clinical Note? → "STAT Order ต้องระบุเหตุผล"

  • Process:
  • Determine overall priority: STAT > Urgent > Routine
  • Create orderData object:
    {
      hn, patientName, vn, orderType,
      priority, doctorId, doctorName,
      department, diagnosis, clinicalNote,
      items[], insuranceRightId, insuranceType,
      createdBy, createdAt
    }
    
  • Call labOrderService.createOrder(orderData)
  • Generate Order Number: LAB-YYYYMMDD-XXXX (e.g., LAB-20240126-0001)
  • Save to LocalStorage (his_lab_orders)
  • Show Success Alert: "บันทึกคำสั่งตรวจเรียบร้อยแล้ว เลขที่: LAB-20240126-0001"
  • Prompt: "ต้องการพิมพ์ใบนำส่งแล็บหรือไม่?"
  • Reset form → Reload page

Trigger: หลังจาก Save Order สำเร็จ (optional) หรือ Click "พิมพ์ใบนำส่ง" Button

Print Layout:

┌─────────────────────────────────────────────────────────────┐
│                   โรงพยาบาลต้นแบบ HIS                       │
│              ใบนำส่งตรวจทางห้องปฏิบัติการ                   │
├─────────────────────────────────────────────────────────────┤
│  เลขที่: LAB-20240126-0001         วันที่: 26/12/2567       │
│  HN: HN000123                      VN: VN20240126001        │
│  ชื่อ-สกุล: นายสมชาย ใจดี          อายุ: 30 ปี             │
│  สิทธิ์: บัตรทอง (UC)              แพทย์: นพ.สมชาย แพทย์ดี  │
├─────────────────────────────────────────────────────────────┤
│  Diagnosis: E11.9 - Diabetes Mellitus Type 2                │
│  Clinical Note: Follow up blood sugar level                 │
├─────────────────────────────────────────────────────────────┤
│  รายการตรวจ:                                                │
│  ┌────┬──────────────────────┬────────────┬──────────────┐ │
│  │ ลำดับ │ รายการตรวจ        │ Priority   │ หลอด/ตัวอย่าง│ │
│  ├────┼──────────────────────┼────────────┼──────────────┤ │
│  │ 1  │ CBC (นับเม็ดเลือด)   │ STAT 🔴   │ EDTA (สีม่วง)│ │
│  │ 2  │ FBS (น้ำตาล)        │ Regular    │ Fluoride     │ │
│  │ 3  │ LFT Panel (ตรวจตับ)  │ Regular    │ Plain/Gel    │ │
│  └────┴──────────────────────┴────────────┴──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  หมายเหตุ: กรุณาเก็บตัวอย่างตามหลอดที่ระบุ                 │
│  STAT Order: ด่วน - ส่งผลภายใน 2 ชั่วโมง                   │
│                                                             │
│  ลงชื่อแพทย์: _____________    วันที่: __/__/____          │
│  ลงชื่อผู้เก็บ: ____________    วันที่: __/__/____          │
└─────────────────────────────────────────────────────────────┘

Print Features: - Window.open(): เปิด Print Preview ใน New Tab - Print Styling: ใช้ @media print ซ่อนปุ่มที่ไม่ต้องการ - Barcode (Future): QR Code สำหรับ Order Number - Copy Count: พิมพ์ 2 ชุด (1 ชุดติดไปกับผู้ป่วย, 1 ชุดเก็บไว้ที่แล็บ)


⚙️ Technical Features

1. Data Loading (loadLabData())

// Fetch 3 JSON files in parallel
Promise.all([
  fetch('/data/lab-items.json'),      // ~200 items
  fetch('/data/lab-panels.json'),     // ~20 panels
  fetch('/data/lab-pricing.json')     // ~300 pricing rules
]);

2. Pricing Calculation (getItemPricing())

// Logic:
// 1. Get insuranceRightId from patient.insuranceType
// 2. Find pricing rule: itemCode + insuranceRightId + priority
// 3. Calculate:
//    - unitPrice (ราคาเต็ม)
//    - reimbPrice (สิทธิ์เบิก)
//    - patientPay = unitPrice - reimbPrice

// Examples:
// UC (บัตรทอง): reimbPrice = 100%, patientPay = 0
// Social Security: reimbPrice = 80%, patientPay = 20%
// Cash: reimbPrice = 0%, patientPay = 100%

3. Panel Item Expansion (selectPanel())

// When click Panel Card:
// 1. Find panel by ID
// 2. Get all items in panel.items[]
// 3. Add each item to selectedItems[] with same priority
// 4. Fetch pricing for each item
// 5. Update Order Summary

4. LocalStorage Keys

'his_lab_orders'        // Array of Lab Orders
'his_lab_order_counter' // Counter for Order Number
'his_patients'          // Patient data (shared)

🎨 Design Patterns

Color Coding by Priority

  • STAT: Red (#dc3545) - ด่วนพิเศษ
  • Urgent: Orange (#ffc107) - ด่วน
  • Routine: Green (#28a745) - ปกติ

Specimen Container Colors

  • 🔴 Purple/Red (EDTA) - Hematology
  • 🟢 Green (Heparin/Plain) - Chemistry
  • 🟡 Yellow (SST) - Immunology
  • 🔵 Blue (Citrate) - Coagulation
  • 🟣 Gray (Fluoride) - Glucose

Badge System

  • Panel Badge: Purple background, "PANEL" text
  • STAT Badge: Red background, "⚡ STAT" text
  • Outsource Badge: Blue background, "🔗 ส่งตรวจนอก"
  • Fasting Badge: Orange background, "⚠️ ต้องงดอาหาร"

📱 Responsive Behavior

Desktop (> 1200px)

  • Lab Items Grid: 3-4 columns
  • Patient Info: 2 columns
  • Full-width Order Summary Table

Tablet (768px - 1200px)

  • Lab Items Grid: 2 columns
  • Patient Info: 2 columns
  • Scrollable Order Table (horizontal)

Mobile (< 768px)

  • Lab Items Grid: 1 column (stacked)
  • Patient Info: 1 column
  • Order Table: Simplified cards instead of table
  • Priority Buttons: Stack vertically

💡 Best Practices (UX/UI)

  1. Visual Feedback:
  2. Selected items มี Border Green + Background Light Green
  3. STAT items มี Border Red + Red Badge
  4. Price แสดงเป็น Bold ถ้า Patient Pay > 0

  5. Error Prevention:

  6. Disable Save Button ถ้า No Items Selected
  7. Show Warning Alert ก่อน Clear Order
  8. Validate STAT Order must have Clinical Note

  9. Performance:

  10. Load Lab Items แบบ Lazy (แยก Tab)
  11. ใช้ Grid Layout แทน Flexbox (faster rendering)
  12. Debounce Search Input (300ms delay)

  13. Accessibility:

  14. Label ชัดเจนทุก Input
  15. Tab Order ถูกต้อง (HN → Search → Items → Save)
  16. Color Contrast ratio > 4.5:1

🐛 Known Issues & Limitations

  1. No VN (Visit Number): ใช้ vn: null ชั่วคราว (Future: Get from OPD Visit)
  2. Mock Doctor: ใช้ DOC001 hardcode (Future: Get from Session/Login)
  3. No ICD-10 Auto-complete: ต้องพิมพ์ Diagnosis เอง
  4. No Lab Item Image: ไม่มีรูปภาพ Specimen Container
  5. Print Format Fixed: ไม่สามารถแก้ไข Layout ใบนำส่งได้

📊 Business Rules

  1. STAT Order Requirements:
  2. Must have Clinical Note (Reason for urgency)
  3. STAT Price = Regular Price × 1.5 (or custom pricing)
  4. STAT Turnaround Time = Regular TAT / 2

  5. Pricing Rules:

  6. UC (Universal Coverage): 100% covered (Patient Pay = 0)
  7. Social Security: 80% covered (Patient Pay = 20%)
  8. Government: 100% covered
  9. Private Insurance: Variable coverage
  10. Cash: 0% covered (Patient Pay = 100%)

  11. Panel vs Individual:

  12. Panel = Grouped tests (cheaper bundle price)
  13. Individual = À la carte (full price per item)
  14. Panels cannot be partially selected (All or None)

3. order-queue.html - จัดการคิว Order แล็บ

📌 ภาพรวม

หน้าสำหรับเจ้าหน้าที่แล็บจัดการคิว Order ที่แพทย์สั่งเข้ามา ทำหน้าที่เป็น Gateway ก่อนเข้าสู่กระบวนการเก็บสิ่งส่งตรวจ ตรวจสอบความถูกต้อง ยืนยัน หรือปฏิเสธ Order

🎯 วัตถุประสงค์

  • ตรวจสอบ Order: ตรวจสอบความถูกต้องของ Order (ข้อมูลครบถ้วน, สิทธิ์ครอบคลุม)
  • จัดการสถานะ: ยืนยันหรือปฏิเสธ Order
  • ส่ง LIS: ส่ง Order เข้าระบบ LIS (Laboratory Information System) เพื่อเริ่มกระบวนการตรวจ
  • ติดตาม: ติดตามสถานะ Order แบบ Real-time

📊 File Statistics

  • Total Lines: 1,313 lines
  • Components: Statistics Cards, Filter Section, Order Table, 3 Modals
  • Dependencies: LabOrder.js, Patient.js, LabOrderService, PatientService, SpecimenService
  • Data Files: lab-items.json (ตรวจสอบ outsourced items)

🧩 UI Components & Workflow

Section 1: Statistics Dashboard

UI Layout:

┌────────────────────────────────────────────────────────────┐
│  [รอยืนยัน: 5] [ยืนยันแล้ว: 12] [STAT: 3] [กำลังเก็บ: 8] │
│  [กำลังตรวจ: 15] [ปฏิเสธ: 2] [เสร็จสิ้น: 45]             │
└────────────────────────────────────────────────────────────┘

Statistics Cards: 1. รอยืนยัน (Pending) - สีส้ม - ไอคอน: fas fa-clock - Count: Orders with status = 'pending' - Click → Auto-filter status = pending

  1. ยืนยันแล้ว (Confirmed) - สีฟ้า
  2. ไอคอน: fas fa-check
  3. Count: Orders with status = 'confirmed'

  4. STAT Orders - สีแดง

  5. ไอคอน: fas fa-bolt
  6. Count: Orders with priority = 'stat'
  7. Special Alert: แสดงเลขโดดเด่น

  8. กำลังเก็บสิ่งส่งตรวจ (Collecting) - สีม่วง

  9. ไอคอน: fas fa-vial
  10. Count: Orders with status = 'collecting'

  11. กำลังตรวจ (In Progress) - สีน้ำเงิน

  12. ไอคอน: fas fa-flask
  13. Count: Orders with status = 'in_progress'

  14. ปฏิเสธ (Rejected) - สีเทา

  15. ไอคอน: fas fa-times
  16. Count: Orders with status = 'rejected'

  17. เสร็จสิ้น (Completed) - สีเขียว

  18. ไอคอน: fas fa-check-double
  19. Count: Orders with status = 'completed'

Calculation Logic:

// labOrderService.getStatistics()
{
  pending: orders.filter(o => o.status === 'pending').length,
  confirmed: orders.filter(o => o.status === 'confirmed').length,
  stat: orders.filter(o => o.priority === 'stat').length,
  collecting: orders.filter(o => o.status === 'collecting').length,
  in_progress: orders.filter(o => o.status === 'in_progress').length,
  rejected: orders.filter(o => o.status === 'rejected').length,
  completed: orders.filter(o => o.status === 'completed').length
}

Styling: - Card Border Left: 4px solid (สีตามประเภท) - Card Shadow: 0 2px 4px rgba(0,0,0,0.1) - Hover Effect: Lift + Cursor pointer - Number Font: 2rem, Bold, Color coded


UI Layout:

┌────────────────────────────────────────────────────────────┐
│  Status: [ทั้งหมด ▼]  Priority: [ทั้งหมด ▼]              │
│  Category: [ทั้งหมด ▼]  Search: [___________] [🔍 ค้นหา] │
│  [🔄 รีเซ็ต]                                               │
└────────────────────────────────────────────────────────────┘

Filter Options:

2.1 Status Filter - ทั้งหมด (Default - แสดงทุกสถานะ) - รอยืนยัน (pending) - ยืนยันแล้ว (confirmed) - กำลังเก็บสิ่งส่งตรวจ (collecting) - เก็บสิ่งส่งตรวจแล้ว (specimen_collected) - ส่ง LIS แล้ว (sent_to_lis) - กำลังตรวจ (in_progress) - ผลบางส่วน (partial_result) - เสร็จสิ้น (completed) - ยกเลิก (cancelled) - ปฏิเสธ (rejected)

2.2 Priority Filter - ทั้งหมด - STAT (stat) - ด่วนพิเศษ - Urgent (urgent) - ด่วน - Routine (routine) - ปกติ

2.3 Category Filter - ทั้งหมด - Hematology (เลือด) - Chemistry (เคมี) - Immunology (ภูมิคุ้มกัน) - Microbiology (จุลชีววิทยา) - Coagulation (การแข็งตัวของเลือด) - Urinalysis (ปัสสาวะ) - Serology (เซรุ่มวิทยา)

2.4 Search Box - ค้นหาจาก: Order Number, HN - Real-time search (กด Enter หรือ Click ปุ่มค้นหา) - Case-insensitive

Filter Logic (applyFilters()):

// 1. Start with all orders
let filtered = labOrderService.getAllOrders();

// 2. Apply status filter
if (status) filtered = filtered.filter(o => o.status === status);

// 3. Apply priority filter
if (priority) filtered = filtered.filter(o => o.priority === priority);

// 4. Apply category filter (check items)
if (category) {
  filtered = filtered.filter(o => 
    o.items.some(item => item.category === category)
  );
}

// 5. Apply search
if (search) {
  filtered = filtered.filter(o => 
    o.orderNumber.includes(search) || o.hn.includes(search)
  );
}

[🔄 รีเซ็ต] Button: - Clear all filters - Reset dropdown to default - Reload full order list


Section 3: Order List Table

UI Layout:

┌────────────────────────────────────────────────────────────────────────────────────┐
│  แสดงผล: 15 รายการ                                                                 │
├──────┬────────┬─────────────────┬──────────┬────────┬─────────┬─────────┬─────────┤
│ Order│  HN    │ ผู้ป่วย         │ Priority │ จำนวน  │ วันที่  │ แพทย์   │ สถานะ   │Actions│
├──────┼────────┼─────────────────┼──────────┼────────┼─────────┼─────────┼─────────┤
│ LAB- │HN00001 │ นายสมชาย ใจดี   │ ⚡STAT   │ 3 รายการ│ 26/12/67│ นพ.สมชาย│ รอยืนยัน│[ยืนยัน]│
│ 2024 │        │                 │          │ 🔗 มีส่ง│ 14:30   │         │         │[ปฏิเสธ]│
│ 1226-│        │                 │          │ ตรวจนอก│         │         │         │        │
│ 0001 │        │                 │          │        │         │         │         │        │
├──────┼────────┼─────────────────┼──────────┼────────┼─────────┼─────────┼─────────┤
│ LAB- │HN00002 │ นางสมศรี ดีมาก  │ Regular  │ 5 รายการ│ 26/12/67│ นพ.วิชัย│ ยืนยัน  │[ส่ง LIS]│
│ 2024 │        │                 │          │        │ 13:15   │         │ แล้ว    │[ดู]    │
│ 1226-│        │                 │          │        │         │         │         │        │
│ 0002 │        │                 │          │        │         │         │         │        │
└──────┴────────┴─────────────────┴──────────┴────────┴─────────┴─────────┴─────────┘

Table Columns:

  1. Order No (Order Number)
  2. Format: LAB-YYYYMMDD-XXXX
  3. Bold text, Primary color
  4. Click → Open Detail Modal

  5. HN (Hospital Number)

  6. Format: HN000001
  7. Link to Patient Record (Future)

  8. ผู้ป่วย (Patient Name)

  9. Format: "title firstName lastName"
  10. Get from PatientService.getPatientByHN(hn)

  11. Priority

  12. Badge Display:

    • STAT (สีแดง) - ด่วนพิเศษ
    • 🔥 Urgent (สีส้ม) - ด่วน
    • Routine (สีเขียว) - ปกติ
  13. จำนวนรายการ (Item Count)

  14. Format: "X รายการ"
  15. Show Badge "🔗 มีส่งตรวจนอก" ถ้ามี outsourced items
  16. Check Logic:

    order.items.some(item => {
      const labItem = labItems.find(li => li.id === item.itemId);
      return labItem?.isOutsourced === true;
    })
    

  17. วันที่สั่ง (Order Date)

  18. Format: DD/MM/YY HH:MM (Thai Buddhist Era)
  19. Color: Gray (muted)

  20. แพทย์ผู้สั่ง (Doctor Name)

  21. Format: "นพ./พญ. ชื่อ นามสกุล"
  22. Default: "ไม่ระบุ"

  23. สถานะ (Status Badge)

  24. Color-coded badges (11 statuses):

    • 🟠 รอยืนยัน (pending) - Orange
    • 🔵 ยืนยันแล้ว (confirmed) - Blue
    • 🟣 กำลังเก็บ (collecting) - Purple
    • 🟢 เก็บแล้ว (specimen_collected) - Green
    • 🔷 ส่ง LIS (sent_to_lis) - Cyan
    • 🔵 กำลังตรวจ (in_progress) - Light Blue
    • 🟡 ผลบางส่วน (partial_result) - Yellow
    • เสร็จสิ้น (completed) - Dark Green
    • 🔴 ปฏิเสธ (rejected) - Red
    • ยกเลิก (cancelled) - Gray
  25. Actions (Action Buttons)

  26. Dynamic Buttons ตามสถานะ:

Status = 'pending': - [✅ ยืนยัน] (btn-confirm, Green) → Show Confirm Modal - [❌ ปฏิเสธ] (btn-reject, Red) → Show Reject Modal

Status = 'confirmed' or 'specimen_collected': - [📤 ส่ง LIS] (btn-send-lis, Blue) → Send to LIS function

Status = 'sent_to_lis' or 'completed': - [🧪 ดูผล] (btn-view, Green) → View Results (Future)

All Statuses: - [👁️ ดู] (btn-view, Gray) → Open Detail Modal

Row Styling: - Hover: Light Gray background (#f8f9fa) - STAT Order: Light Red background (#ffebee) - Rejected Order: Light Gray background + Red border left - Click → viewOrderDetail(orderNumber)

Empty State:

┌────────────────────────────────────┐
│           📥                       │
│     ไม่พบรายการ Order              │
│  ไม่มีรายการที่ตรงกับเงื่อนไข     │
└────────────────────────────────────┘


Section 4: Detail Modal (รายละเอียด Order)

Trigger: Click Order Row หรือ Click [👁️ ดู] Button

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  📋 รายละเอียด Order                                   [✕]  │
├─────────────────────────────────────────────────────────────┤
│  ข้อมูล Order                                               │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ Order Number: LAB-20241226-0001                      │   │
│  │ สถานะ: [รอยืนยัน 🟠]        Priority: [⚡STAT 🔴]    │   │
│  │ วันที่สั่ง: 26/12/2567 14:30:15                     │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                             │
│  ข้อมูลผู้ป่วย                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ HN: HN000123              ชื่อ: นายสมชาย ใจดี        │   │
│  │ อายุ: 30 ปี               เพศ: ชาย                  │   │
│  │ สิทธิ์: บัตรทอง (UC)       แพ้ยา: Penicillin        │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                             │
│  ข้อมูลแพทย์                                                │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ แพทย์: นพ.สมชาย แพทย์ดี    แผนก: OPD               │   │
│  │ Diagnosis: E11.9 - Diabetes Mellitus Type 2         │   │
│  │ Clinical Note: Follow up blood sugar level          │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                             │
│  รายการแล็บ (3 รายการ)                                      │
│  ┌────┬─────────────────┬────────┬────────────┬─────────┐   │
│  │ลำดับ│ รายการ          │หมวดหมู่│ สิ่งส่งตรวจ│ ราคา    │   │
│  ├────┼─────────────────┼────────┼────────────┼─────────┤   │
│  │ 1  │ CBC (นับเม็ดเลือด)│Hemato │ EDTA      │ 120.00 ฿│   │
│  │ 2  │ FBS (น้ำตาล)    │Chemis  │ Fluoride   │  80.00 ฿│   │
│  │ 3  │ HbA1c           │Chemis  │ EDTA       │ 250.00 ฿│   │
│  │    │ 🔗 ส่งตรวจนอก   │        │            │         │   │
│  │    │ (Bangkok Lab)   │        │            │         │   │
│  └────┴─────────────────┴────────┴────────────┴─────────┘   │
│                                                             │
│  ราคารวม: 450.00 บาท                                       │
│                                                             │
│  [ปิด]                                                      │
└─────────────────────────────────────────────────────────────┘

Modal Sections:

4.1 Order Information - Order Number (Unique ID) - Status Badge (Color-coded) - Priority Badge - Order Date & Time (Thai format)

4.2 Patient Information - HN, Full Name - Age, Gender - Insurance Type - Allergies (Alert ถ้ามี)

4.3 Doctor Information - Doctor Name, Department - Diagnosis (ICD-10 code + description) - Clinical Note (Reason for order)

4.4 Lab Items Table - Columns: ลำดับ, รายการ, หมวดหมู่, สิ่งส่งตรวจ, ราคา - Show Badge "🔗 ส่งตรวจนอก (Lab Name)" for outsourced items - Total Price (Bold, Large font)

Modal Actions: - [✕] Close Button (Top-right corner) - [ปิด] Button (Footer) - Click outside → Close modal

Technical:

// viewOrderDetail(orderNumber)
// 1. Get order from service
const order = labOrderService.getOrderByNumber(orderNumber);

// 2. Get patient data
const patient = patientService.getPatientByHN(order.hn);

// 3. Enrich items with lab item details
order.items.forEach(item => {
  const labItem = labItems.find(li => li.id === item.itemId);
  item.isOutsourced = labItem?.isOutsourced;
  item.outsourceLabName = labItem?.outsourceLabName;
});

// 4. Render modal HTML
// 5. Show modal: document.getElementById('detail-modal').classList.add('active');


Section 5: Confirm Modal (ยืนยัน Order)

Trigger: Click [✅ ยืนยัน] Button (เมื่อ status = 'pending')

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ✅ ยืนยัน Order                                       [✕]  │
├─────────────────────────────────────────────────────────────┤
│  คุณต้องการยืนยัน Order นี้หรือไม่?                         │
│                                                             │
│  Order: LAB-20241226-0001                                   │
│  ผู้ป่วย: นายสมชาย ใจดี                                     │
│  รายการ: 3 รายการ                                           │
│                                                             │
│  หมายเหตุ (ถ้ามี):                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  [ยกเลิก]                              [✅ ยืนยัน]         │
└─────────────────────────────────────────────────────────────┘

Form Fields: - Order Number (Display only) - Patient Name (Display only) - Items Count (Display only) - หมายเหตุ (Optional textarea) - Placeholder: "ระบุหมายเหตุเพิ่มเติม..." - Multi-line (3 rows)

Actions: - [ยกเลิก] → Close modal, No changes - [✅ ยืนยัน] → Call confirmOrder():

// 1. Get note from textarea
const note = document.getElementById('confirm-note').value;

// 2. Update order status
labOrderService.updateOrderStatus(
  orderNumber,
  'confirmed',
  note || 'ยืนยัน Order โดยเจ้าหน้าที่แล็บ',
  currentUser.id,
  currentUser.name
);

// 3. Show success message
showSuccess('ยืนยัน Order สำเร็จ');

// 4. Close modal
// 5. Reload order list

Status Transition: - Before: status: 'pending' - After: status: 'confirmed' - History Log: บันทึก Action, User, Timestamp ใน statusHistory[]


Section 6: Reject Modal (ปฏิเสธ Order)

Trigger: Click [❌ ปฏิเสธ] Button (เมื่อ status = 'pending')

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ❌ ปฏิเสธ Order                                       [✕]  │
├─────────────────────────────────────────────────────────────┤
│  คุณต้องการปฏิเสธ Order นี้หรือไม่?                         │
│                                                             │
│  Order: LAB-20241226-0001                                   │
│  ผู้ป่วย: นายสมชาย ใจดี                                     │
│                                                             │
│  เหตุผล: * (Required)                                       │
│  [-- เลือกเหตุผล -- ▼]                                     │
│    - ข้อมูลไม่ครบถ้วน                                       │
│    - รายการซ้ำ                                              │
│    - รายการไม่ครอบคลุมในสิทธิ์                              │
│    - ผู้ป่วยไม่พร้อม                                        │
│    - อื่นๆ                                                  │
│                                                             │
│  หมายเหตุเพิ่มเติม:                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  [ยกเลิก]                            [❌ ปฏิเสธ Order]     │
└─────────────────────────────────────────────────────────────┘

Form Fields:

6.1 เหตุผล (Reason) - Required Dropdown - ข้อมูลไม่ครบถ้วน: Patient info incomplete, Missing diagnosis - รายการซ้ำ: Duplicate order already exists - รายการไม่ครอบคลุมในสิทธิ์: Insurance doesn't cover items - ผู้ป่วยไม่พร้อม: Patient not available, Not fasting - อื่นๆ: Other reasons (must specify in Note)

6.2 หมายเหตุเพิ่มเติม (Additional Note) - Optional - Textarea (4 rows) - Placeholder: "ระบุรายละเอียดเพิ่มเติม..." - Required ถ้าเลือก "อื่นๆ"

Validation:

// rejectOrder()
// 1. Check reason selected
if (!reason) {
  showError('กรุณาเลือกเหตุผลในการปฏิเสธ');
  return;
}

// 2. Combine reason + note
const fullNote = `${reason}${note ? ': ' + note : ''}`;

// 3. Update status
labOrderService.updateOrderStatus(
  orderNumber,
  'rejected',
  fullNote,
  currentUser.id,
  currentUser.name
);

Status Transition: - Before: status: 'pending' - After: status: 'rejected' - Note: บันทึกเหตุผลใน statusHistory[]

Actions: - [ยกเลิก] → Close modal, No changes - [❌ ปฏิเสธ Order] → Call rejectOrder(): - Validate reason selected - Update status to 'rejected' - Show success message - Close modal - Reload list (Rejected orders move to separate tab)


⚙️ Technical Features

1. Auto-Create Mock Orders

// On page load (DOMContentLoaded)
// 1. Check if orders exist in LocalStorage
const existingOrders = labOrderService.getAllOrders();

// 2. If empty, create mock orders
if (existingOrders.length === 0) {
  await labOrderService.createMockOrders();
}

// Mock orders include:
// - Various statuses (pending, confirmed, collecting, etc.)
// - Various priorities (stat, urgent, routine)
// - Multiple patients (HN000001, HN000002, etc.)
// - Different lab categories
// - Some with outsourced items

2. Real-time Statistics Update

// updateStatistics()
// Called after every status change
// Recalculate counts from current orders
const stats = labOrderService.getStatistics();

// Update DOM
document.getElementById('stat-pending').textContent = stats.pending;
document.getElementById('stat-confirmed').textContent = stats.confirmed;
// ... etc

3. Send to LIS (Simulation)

// sendToLIS(orderNumber)
// In real implementation:
// - Call external LIS API
// - Send order data + patient info
// - Receive LIS order number
// - Update order with LIS ID

// Current simulation:
// - Just change status to 'sent_to_lis'
// - Log action in statusHistory
// - Show success alert

4. LocalStorage Keys

'his_lab_orders'           // Array of Lab Orders
'his_patients'             // Patient data (shared)
'his_lab_order_counter'    // Auto-increment counter

🎨 Design System

Status Color Mapping

/* Pending Phase */
.pending          { background: #fff3e0; color: #ef6c00; } /* Orange */
.confirmed        { background: #e3f2fd; color: #1976d2; } /* Blue */
.rejected         { background: #ffebee; color: #c62828; } /* Red */

/* Collection Phase */
.collecting       { background: #f3e5f5; color: #6a1b9a; } /* Purple */
.specimen_collected { background: #e8f5e9; color: #2e7d32; } /* Green */

/* Processing Phase */
.sent_to_lis      { background: #e0f7fa; color: #00838f; } /* Cyan */
.in_progress      { background: #e1f5fe; color: #0277bd; } /* Light Blue */
.partial_result   { background: #fff3cd; color: #856404; } /* Yellow */

/* Final Phase */
.completed        { background: #c8e6c9; color: #1b5e20; } /* Dark Green */
.cancelled        { background: #e0e0e0; color: #424242; } /* Gray */

Action Button Colors

.btn-confirm      { background: #4caf50; } /* Green */
.btn-reject       { background: #f44336; } /* Red */
.btn-send-lis     { background: #2196f3; } /* Blue */
.btn-view         { background: #9e9e9e; } /* Gray */

📱 Responsive Behavior

Desktop (> 1200px)

  • Statistics: 7 cards in 1 row
  • Filter: 4 filters in 1 row
  • Table: Full width, All columns visible

Tablet (768px - 1200px)

  • Statistics: 3-4 cards per row
  • Filter: 2 filters per row
  • Table: Horizontal scroll for overflow

Mobile (< 768px)

  • Statistics: 2 cards per row (stacked)
  • Filter: 1 filter per row (stacked)
  • Table: Convert to Card List view
    ┌──────────────────────────────┐
    │ LAB-20241226-0001  [⚡STAT]  │
    │ นายสมชาย ใจดี (HN000123)    │
    │ 3 รายการ | 26/12/67 14:30   │
    │ สถานะ: [รอยืนยัน]           │
    │ [ยืนยัน] [ปฏิเสธ] [ดู]      │
    └──────────────────────────────┘
    

💡 Best Practices (UX/UI)

  1. Visual Priority:
  2. STAT Orders → Red background + Bold
  3. Rejected Orders → Gray background + Red border left
  4. Confirmed Orders → Light Blue background

  5. Clear Action Hierarchy:

  6. Primary Action (ยืนยัน): Green, Large
  7. Dangerous Action (ปฏิเสธ): Red, Same size as Primary
  8. Secondary Action (ดู): Gray, Smaller

  9. Error Prevention:

  10. Confirm Modal: Extra confirmation step
  11. Reject Modal: Required reason dropdown
  12. STAT Alert: Visual indicator ⚡ สีแดง

  13. Feedback:

  14. Success Alert: Green toast (Top-right, 3s auto-dismiss)
  15. Error Alert: Red toast (Top-right, 3s auto-dismiss)
  16. Loading State: Spinner while processing

🐛 Known Issues & Limitations

  1. No Real LIS Integration: Send to LIS is simulated (just changes status)
  2. Mock User: currentUser is hardcoded (Future: Get from session)
  3. No Notification: Rejected orders don't notify doctor (Future: Add notification system)
  4. No Undo: Cannot undo Confirm/Reject action (Future: Add undo within 30 seconds)
  5. No Batch Operations: Cannot confirm/reject multiple orders at once

📊 Business Rules

  1. Status Transition Rules:
  2. Pending → Can go to: Confirmed, Rejected
  3. Confirmed → Can go to: Collecting, Sent to LIS
  4. Rejected → Cannot change (Final status)

  5. Permission Rules (Future):

  6. Lab Staff: Can confirm/reject pending orders
  7. Lab Supervisor: Can override rejected orders
  8. Doctor: Cannot modify after submission (must contact lab)

  9. STAT Order Priority:

  10. STAT orders แสดงที่ด้านบนสุดเสมอ (Auto-sort by priority)
  11. STAT Alert: แสดงเลขโดดเด่นใน Statistics Card

  12. Outsourced Items:

  13. Show Badge "🔗 มีส่งตรวจนอก"
  14. Extended TAT (Turnaround Time)
  15. Cannot send to internal LIS (must handle separately)

4. specimen-collection.html - เก็บสิ่งส่งตรวจ

📌 ภาพรวม

หน้าสำหรับเจ้าหน้าที่เจาะเลือด (Phlebotomist) เก็บสิ่งส่งตรวจจากผู้ป่วย พร้อมระบบ Quality Control (QC) ตรวจสอบคุณภาพตัวอย่าง และสร้าง Specimen Number อัตโนมัติพร้อม Print Label

🎯 วัตถุประสงค์

  • เก็บสิ่งส่งตรวจ: บันทึกการเก็บตัวอย่าง (Blood, Urine, etc.)
  • Quality Assessment: ประเมินคุณภาพตัวอย่าง (Good, Acceptable, Poor, Rejected)
  • Generate Specimen Number: สร้างเลขสิ่งส่งตรวจอัตโนมัติ (Format: SPEC-YYYYMMDD-XXXX)
  • Print Label: พิมพ์ฉลากติดหลอดตัวอย่าง (Barcode + Patient Info)
  • Recollection: เก็บใหม่สำหรับตัวอย่างที่ถูกปฏิเสธ

📊 File Statistics

  • Total Lines: 2,417 lines
  • Components: Search Section, Order List, Collection Modal, Success Modal, Toast Notifications
  • Dependencies: LabOrder.js, Patient.js, Specimen.js, LabOrderService, PatientService, SpecimenService
  • Data Files: lab-orders-specimen.json, patients.json

🧩 UI Components & Workflow

Section 1: Search Section

UI Layout:

┌────────────────────────────────────────────────────────────┐
│  🔍 ค้นหา Order / ผู้ป่วย                                  │
├────────────────────────────────────────────────────────────┤
│  [____________________________________________] [🔍 ค้นหา]  │
│  Scan Barcode หรือพิมพ์ HN / Order Number / ชื่อผู้ป่วย  │
│                                                            │
│  💡 สามารถ Scan Barcode จากใบนำส่งแล็บได้โดยตรง          │
└────────────────────────────────────────────────────────────┘

คุณสมบัติ: 1. Multi-Search Support - Barcode Scan: รับค่าจาก Barcode Scanner (Order Number) - HN: ค้นหาจากเลข HN (HN000001) - Order Number: ค้นหาจาก Order Number (LAB-20241226-0001) - Patient Name: ค้นหาจากชื่อผู้ป่วย (Thai/English)

  1. Search Logic (searchOrder())

    // Filter orders by:
    // - order.orderNumber.includes(searchText)
    // - order.hn.includes(searchText)
    // - order.vn?.includes(searchText)
    // - order.patientName?.includes(searchText)
    

  2. Auto-Focus: Cursor ใน Search Box เพื่อรอ Scan Barcode


Section 2: Order List (รายการ Order ทั้งหมด)

Filter Logic: - แสดงเฉพาะ Orders ที่ status = 'confirmed', 'collecting', 'specimen_collected' - ไม่แสดง 'pending', 'rejected', 'completed' - เรียงตาม Priority (STAT → Urgent → Routine) → วันที่ (ใหม่ล่าสุด)

UI Layout:

┌────────────────────────────────────────────────────────────┐
│  📋 รายการ Order ทั้งหมด (15 รายการ)                       │
├────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────────────────────────┐   │
│  │ Order: LAB-20241226-0001          [⚡STAT]         │   │
│  │ HN: HN000123 | ผู้ป่วย: นายสมชาย ใจดี              │   │
│  │ VN: VN20241226001 | แผนก: OPD                     │   │
│  │ วันที่: 26/12/2567 14:30 | แพทย์: นพ.สมชาย แพทย์ดี│   │
│  │                                                    │   │
│  │ สถานะ: [ยืนยันแล้ว - พร้อมเก็บสิ่งส่งตรวจ]        │   │
│  │                                                    │   │
│  │ 🧪 รายการตรวจ (เลือกรายการที่ต้องการเก็บ):       │   │
│  │ ┌───────────────────────────────────────────────┐ │   │
│  │ │ [✓] # │ รายการ  │ Specimen │ Container │สถานะ││ │
│  │ ├───────────────────────────────────────────────┤ │   │
│  │ │ [✓] 1 │ CBC     │ Blood    │ EDTA      │รอเก็บ││ │
│  │ │ [✓] 2 │ FBS     │ Blood    │ Fluoride  │รอเก็บ││ │
│  │ │ [✓] 3 │ HbA1c   │ Blood    │ EDTA      │รอเก็บ││ │
│  │ └───────────────────────────────────────────────┘ │   │
│  │                                                    │   │
│  │ รายการที่เลือก: 3/3 รายการ                        │   │
│  │ [เก็บสิ่งส่งตรวจที่เลือก] [ดูรายละเอียด] [พิมพ์ Label] │
│  └────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────┘

Order Card Components:

2.1 Order Header - Order Number: LAB-YYYYMMDD-XXXX (Bold, Primary Color) - Priority Badge: - ⚡ STAT (Red) - ด่วนพิเศษ - 🔥 Urgent (Orange) - ด่วน - ✅ Routine (Green) - ปกติ

2.2 Patient Information - HN: Hospital Number - Patient Name: Title + First Name + Last Name - VN: Visit Number - Department: OPD/IPD

2.3 Order Details - Order Date: วันที่แพทย์สั่ง Order - Doctor Name: แพทย์ผู้สั่ง Order

2.4 Status Display - ยืนยันแล้ว - พร้อมเก็บสิ่งส่งตรวจ (confirmed) - สีฟ้า - กำลังเก็บสิ่งส่งตรวจ (บางส่วน) (collecting) - สีม่วง - เก็บสิ่งส่งตรวจแล้ว (specimen_collected) - สีเขียว

2.5 Item Selection Table

Columns: 1. Checkbox: เลือกรายการที่ต้องการเก็บ - Select All Checkbox (Header) - Individual Checkbox (แต่ละ item) 2. #: ลำดับรายการ 3. รายการ: Item Name (Thai/English) 4. Specimen Type: Blood, Urine, Stool, etc. 5. Container: EDTA (Purple), Plain (Red), Fluoride (Gray), etc. 6. สถานะ: - 🟡 รอเก็บ (pending_collection) - Yellow - 🟢 เก็บแล้ว (collected) - Green - 🔴 ปฏิเสธ (rejected) - Red + [🔄 เก็บใหม่] Button

Container Color Display:

// getContainerDisplay(containerType)
{
  'edta': 'EDTA (Purple)',        // 🟣 CBC, Hematology
  'plain': 'Plain (Red)',          // 🔴 Chemistry
  'heparin': 'Heparin (Green)',    // 🟢 Blood Gas
  'citrate': 'Citrate (Blue)',     // 🔵 Coagulation
  'fluoride': 'Fluoride (Gray)',   // ⚪ Glucose
  'urine_container': 'Urine Container' // 🟡 Urinalysis
}

Selection Logic: - Cannot Select: Items ที่ status = 'collected' (เก็บแล้ว) - Can Select: Items ที่ status = 'pending_collection' (รอเก็บ) - Special: Items ที่ status = 'rejected' → แสดง [🔄 เก็บใหม่] Button แทน Checkbox

Item Count Display:

รายการที่เลือก: 3/5 รายการ
- Update Real-time เมื่อ Check/Uncheck - แสดงจำนวนที่เลือก / ทั้งหมด

2.6 Action Buttons

[เก็บสิ่งส่งตรวจที่เลือก] (Primary Button - Green) - Enabled: เมื่อมี Checkbox ถูกเลือก (>= 1 item) - Disabled: เมื่อไม่มี Checkbox ถูกเลือก - Action: เปิด Collection Modal

[👁️ ดูรายละเอียด] (Secondary Button - Blue) - แสดง Order Detail Modal (Patient Info, Doctor, All Items, Clinical Note)

[🖨️ พิมพ์ Label] (Tertiary Button - Purple) - พิมพ์ฉลากติดหลอดตัวอย่าง (ล่วงหน้า หรือ พิมพ์ซ้ำ) - Support: Print multiple labels (1 label per item)


Section 3: Collection Modal (บันทึกการเก็บสิ่งส่งตรวจ)

Trigger: Click [เก็บสิ่งส่งตรวจที่เลือก] Button

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  🩸 บันทึกการเก็บสิ่งส่งตรวจ                          [✕]  │
├─────────────────────────────────────────────────────────────┤
│  ℹ️ Order Number: LAB-20241226-0001                        │
│     HN: HN000123 | ผู้ป่วย: นายสมชาย ใจดี                 │
│     จำนวนรายการที่เลือก: 3 รายการ                          │
├─────────────────────────────────────────────────────────────┤
│  🧪 รายการที่เลือกเก็บ:                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 1. CBC (นับเม็ดเลือด) (Blood - EDTA (Purple))       │  │
│  │ 2. FBS (น้ำตาลในเลือด) (Blood - Fluoride (Gray))    │  │
│  │ 3. HbA1c (Hemoglobin A1c) (Blood - EDTA (Purple))   │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  🔬 ปริมาตร (ml): * [_______]  📅 เวลาที่เก็บ: * [______] │
│                                                             │
│  ✅ คุณภาพสิ่งส่งตรวจ: *                                   │
│  [Good (ดี) ▼]                                              │
│    - Good (ดี)                                              │
│    - Acceptable (พอใช้ได้)                                  │
│    - Poor (ไม่ดี)                                           │
│    - Rejected (ปฏิเสธ)                                      │
│                                                             │
│  ⚠️ เหตุผล/ปัญหาที่พบ: * (แสดงเมื่อเลือก Poor/Rejected)  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ Hemolysis รุนแรง, เลือดแข็งตัว, ปริมาตรไม่พอ...    │  │
│  │                                                      │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  [ยกเลิก]                        [💾 บันทึกการเก็บสิ่งส่งตรวจ] │
└─────────────────────────────────────────────────────────────┘

Form Fields:

3.1 Order Information (Display Only) - Order Number - HN + Patient Name - Selected Item Count

3.2 Selected Items List - แสดงรายการที่เลือก (Read-only) - Format: "#{index}. {ItemName} ({SpecimenType} - {Container})"

3.3 ปริมาตร (Volume) - Required - Type: Number input (step: 0.1) - Unit: ml (milliliter) - Example: 3.0, 5.5, 10.0 - Validation: Min = 0, Required

3.4 เวลาที่เก็บ (Collected At) - Required - Type: datetime-local input - Default: Current date & time - Format: YYYY-MM-DD HH:MM - Auto-fill: ตั้งค่าเวลาปัจจุบันเมื่อเปิด Modal

3.5 คุณภาพสิ่งส่งตรวจ (Quality Assessment) - Required

Quality Options: 1. Good (ดี) - สีเขียว - ตัวอย่างมีคุณภาพดี ไม่มีปัญหา - ไม่ต้องระบุเหตุผล

  1. Acceptable (พอใช้ได้) - สีเหลือง
  2. ตัวอย่างมีปัญหาเล็กน้อย แต่ยังตรวจได้
  3. ต้องระบุเหตุผล (เช่น Slight Hemolysis, Lipemia เล็กน้อย)

  4. Poor (ไม่ดี) - สีส้ม

  5. ตัวอย่างมีปัญหามาก อาจส่งผลต่อความแม่นยำ
  6. ต้องระบุเหตุผล (เช่น Moderate Hemolysis, Clotted)

  7. Rejected (ปฏิเสธ) - สีแดง

  8. ตัวอย่างไม่ผ่านเกณฑ์ QC ต้องเก็บใหม่
  9. ต้องระบุเหตุผล (เช่น Severe Hemolysis, Insufficient Volume, Wrong Label)

3.6 เหตุผล/ปัญหาที่พบ (Quality Notes) - Conditional Required

Display Logic:

// toggleQualityIssues()
if (quality === 'acceptable' || quality === 'poor' || quality === 'rejected') {
  document.getElementById('qualityNotesGroup').style.display = 'block';
  document.getElementById('qualityNotes').required = true;
} else {
  document.getElementById('qualityNotesGroup').style.display = 'none';
  document.getElementById('qualityNotes').required = false;
}

Textarea Placeholder:

ระบุเหตุผล/ปัญหาที่พบ เช่น:
- Hemolysis รุนแรง
- เลือดแข็งตัว (Clotted)
- ปริมาตรไม่พอ (Insufficient Volume)
- ฉลากผิด (Wrong Label)
- ปนเปื้อน (Contamination)
- Lipemia (Lipemic specimen)
- Icteric (สีเหลืองจากบิลิรูบิน)

Validation: - Required ถ้าเลือก Acceptable/Poor/Rejected - Minimum length: 5 characters - Recommend: ระบุรายละเอียดชัดเจนเพื่อ Technician เข้าใจปัญหา


Section 4: Save Specimen (บันทึกการเก็บสิ่งส่งตรวจ)

Process Flow:

// Form Submit Handler
collectionForm.addEventListener('submit', async (e) => {
  e.preventDefault();

  // 1. Get form data
  const orderNumber = document.getElementById('collectionOrderNumber').value;
  const selectedItems = JSON.parse(document.getElementById('collectionSelectedItems').value);
  const volume = parseFloat(document.getElementById('volume').value);
  const collectedAt = new Date(document.getElementById('collectedAt').value);
  const quality = document.getElementById('quality').value;
  const qualityNotes = document.getElementById('qualityNotes').value;

  // 2. Validate
  if (!orderNumber || !selectedItems.length || !volume || !collectedAt || !quality) {
    alert('กรุณากรอกข้อมูลให้ครบถ้วน');
    return;
  }

  if ((quality === 'acceptable' || quality === 'poor' || quality === 'rejected') 
      && !qualityNotes.trim()) {
    alert('กรุณาระบุเหตุผล/ปัญหาที่พบ');
    return;
  }

  // 3. Create specimen for each selected item
  const createdSpecimens = [];

  for (const item of selectedItems) {
    // Generate Specimen Number
    const specimenNumber = specimenService.generateSpecimenNumber();
    // Format: SPEC-YYYYMMDD-XXXX (e.g., SPEC-20241226-0001)

    // Create specimen data
    const specimenData = {
      specimenNumber,
      orderNumber,
      hn: order.hn,
      labItems: [item], // Single item per specimen
      specimenType: item.specimenType,
      containerType: item.containerType,
      volume,
      collectedAt: collectedAt.toISOString(),
      collectedBy: currentUser.id,
      collectedByName: currentUser.name,
      quality,
      qualityNotes: qualityNotes || null,
      status: quality === 'rejected' ? 'rejected' : 'collected'
    };

    // Save to service
    const result = await specimenService.createSpecimen(specimenData);
    if (result.success) {
      createdSpecimens.push(result.specimen);
    }
  }

  // 4. Update order status
  if (quality === 'rejected') {
    // Keep status as 'collecting' (ยังเก็บไม่ครบ)
    labOrderService.updateOrderStatus(orderNumber, 'collecting', 
      `⚠️ Specimen ถูกปฏิเสธ: ${qualityNotes}`);
  } else {
    // Check if all items collected
    const allSpecimens = specimenService.getSpecimensByOrderNumber(orderNumber);
    const allItemsCodes = order.items.map(i => i.itemCode);
    const collectedItemCodes = allSpecimens
      .filter(s => s.status === 'collected')
      .flatMap(s => s.labItems.map(i => i.itemCode));

    const allCollected = allItemsCodes.every(code => collectedItemCodes.includes(code));

    if (allCollected) {
      labOrderService.updateOrderStatus(orderNumber, 'specimen_collected', 
        'เก็บสิ่งส่งตรวจครบทุกรายการแล้ว');
    } else {
      labOrderService.updateOrderStatus(orderNumber, 'collecting', 
        `เก็บแล้ว ${collectedItemCodes.length}/${allItemsCodes.length} รายการ`);
    }
  }

  // 5. Show success modal
  showSuccessModal(createdSpecimens);

  // 6. Close collection modal
  closeCollectionModal();

  // 7. Reload orders
  loadOrders();
});

Specimen Number Generation:

// specimenService.generateSpecimenNumber()
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');

// Get counter from LocalStorage
const key = `his_specimen_counter_${year}${month}${day}`;
let counter = parseInt(localStorage.getItem(key) || '0') + 1;
localStorage.setItem(key, counter);

// Format: SPEC-YYYYMMDD-XXXX
return `SPEC-${year}${month}${day}-${String(counter).padStart(4, '0')}`;

// Examples:
// SPEC-20241226-0001
// SPEC-20241226-0002
// SPEC-20241227-0001 (reset เมื่อเปลี่ยนวัน)


Section 5: Success Modal (บันทึกสำเร็จ)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ✅ บันทึกการเก็บสิ่งส่งตรวจสำเร็จ!                        │
│     เก็บสิ่งส่งตรวจเรียบร้อยแล้ว                            │
├─────────────────────────────────────────────────────────────┤
│  📋 รายการสิ่งส่งตรวจที่สร้าง:                             │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ SPEC-20241226-0001                                   │  │
│  │ CBC (Blood - EDTA) | Volume: 3.0 ml | Quality: Good │  │
│  │ [🖨️ พิมพ์ Label]                                     │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ SPEC-20241226-0002                                   │  │
│  │ FBS (Blood - Fluoride) | Volume: 3.0 ml | Quality: Good│
│  │ [🖨️ พิมพ์ Label]                                     │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  [🖨️ พิมพ์ Label ทั้งหมด]                [ปิด]            │
└─────────────────────────────────────────────────────────────┘

Specimen Item Display: - Specimen Number: Bold, Monospace font (เลขสิ่งส่งตรวจ) - Details: Item Name, Specimen Type, Container, Volume, Quality - Individual Print Button: พิมพ์ Label แต่ละชิ้น - Print All Button: พิมพ์ Label ทั้งหมดพร้อมกัน

Animation: - Fade In from bottom (slideUp animation) - Checkmark icon: Scale animation (0 → 1.2 → 1) - Green gradient background (Header)


Section 6: Print Label (พิมพ์ฉลากสิ่งส่งตรวจ)

Trigger: - Click [🖨️ พิมพ์ Label] ใน Success Modal (หลัง Save) - Click [🖨️ พิมพ์ Label] ใน Order Card (Print ล่วงหน้า)

Label Layout (100mm × 50mm):

┌────────────────────────────────────────────┐
│        SPECIMEN LABEL                      │
├────────────────────────────────────────────┤
│       SPEC-20241226-0001                   │
│       (Barcode/QR Code)                    │
├────────────────────────────────────────────┤
│ Order: LAB-20241226-0001                   │
│ HN: HN000123                               │
│ Tests: CBC (นับเม็ดเลือด)                  │
│ Specimen: Blood                            │
│ Container: EDTA (Purple)                   │
│ Volume: 3.0 ml                             │
│ Collected: 26/12/2567 14:30                │
│ By: นางสาววรรณา เจ้าหน้าที่               │
└────────────────────────────────────────────┘

Print Features: - Page Size: 100mm × 50mm (Label size) - Barcode: Specimen Number (Courier New, Bold, Large font) - Patient Info: HN (ไม่แสดงชื่อเต็มเพื่อความเป็นส่วนตัว) - Test Info: รายการตรวจ (ชื่อ Item) - Specimen Info: ประเภท + หลอด + ปริมาตร - Collected Info: เวลาที่เก็บ + ผู้เก็บ

Print Layout (Grid for Multiple Labels): - 2 columns × N rows สำหรับ A4 paper - Page Break: ไม่ตัดกลาง Label (break-inside: avoid)


Section 7: Recollection (เก็บใหม่)

Trigger: Click [🔄 เก็บใหม่] Button (เมื่อ Specimen status = 'rejected')

UI Flow:

1. User คลิก [🔄 เก็บใหม่] บน Item ที่ถูกปฏิเสธ
   ↓
2. แสดง Confirmation Dialog:
   "ยืนยันการเก็บสิ่งส่งตรวจใหม่?
    เหตุผลที่ปฏิเสธ: Hemolysis รุนแรง"
   ↓
3. User คลิก [OK]
   ↓
4. เปิด Collection Modal พร้อมข้อมูล:
   - Pre-fill Order Number
   - Pre-select ร ายการที่ถูกปฏิเสธ (Cannot uncheck)
   - แสดง Warning Box:
     "⚠️ การเก็บใหม่: สิ่งส่งตรวจก่อนหน้าถูกปฏิเสธ 
      กรุณาเก็บใหม่"
   ↓
5. User กรอกข้อมูล + Submit
   ↓
6. สร้าง Specimen Number ใหม่:
   - Old: SPEC-20241226-0001 (rejected)
   - New: SPEC-20241226-0001R1 (recollection #1)
   - Next: SPEC-20241226-0001R2 (recollection #2)
   ↓
7. บันทึก Specimen ใหม่ + Update Order Status

Recollection Specimen Number Format:

// Original: SPEC-20241226-0001
// 1st Recollection: SPEC-20241226-0001R1
// 2nd Recollection: SPEC-20241226-0001R2
// 3rd Recollection: SPEC-20241226-0001R3

// Logic:
const baseNumber = rejectedSpecimen.specimenNumber; // SPEC-20241226-0001
const recollectionCount = specimenService.getRecollectionCount(baseNumber);
const newSpecimenNumber = `${baseNumber}R${recollectionCount + 1}`;

Recollection Rules: 1. Cannot Edit: Rejected specimen cannot be edited (immutable) 2. Create New: Always create new specimen with R suffix 3. Link: Store original specimen number in originalSpecimenNumber field 4. History: Keep rejection history for audit trail 5. Status: New specimen status = 'collected' (if QC passed)


⚙️ Technical Features

1. Specimen Model

class Specimen {
  specimenNumber: string;        // SPEC-20241226-0001
  orderNumber: string;           // LAB-20241226-0001
  hn: string;                    // HN000123
  labItems: Array<{              // รายการตรวจที่ใช้ specimen นี้
    itemCode: string;
    itemName: string;
  }>;
  specimenType: string;          // blood, urine, stool
  containerType: string;         // edta, plain, fluoride
  volume: number;                // ml
  collectedAt: Date;
  collectedBy: string;           // User ID
  collectedByName: string;       // User Name
  quality: string;               // good, acceptable, poor, rejected
  qualityNotes: string;          // Reason/Issues
  qualityIssues: Array<string>;  // [hemolysis, clotted, lipemic]
  status: string;                // collected, rejected, testing, completed
  originalSpecimenNumber: string; // For recollection (R1, R2)
  createdAt: Date;
  updatedAt: Date;
}

2. Quality Issue Types

const QUALITY_ISSUES = {
  // Blood Issues
  'hemolysis': 'Hemolysis (เม็ดเลือดแดงแตก)',
  'clotted': 'Clotted (เลือดแข็งตัว)',
  'lipemic': 'Lipemia (ไขมันในเลือดสูง)',
  'icteric': 'Icteric (บิลิรูบินสูง สีเหลือง)',

  // Volume Issues
  'insufficient_volume': 'Insufficient Volume (ปริมาตรไม่พอ)',
  'excessive_volume': 'Excessive Volume (ปริมาตรมากเกินไป)',

  // Labeling Issues
  'wrong_label': 'Wrong Label (ฉลากผิด)',
  'missing_label': 'Missing Label (ไม่มีฉลาก)',
  'unreadable_label': 'Unreadable Label (ฉลากอ่านไม่ได้)',

  // Contamination
  'contamination': 'Contamination (ปนเปื้อน)',
  'bacterial_contamination': 'Bacterial Contamination (ปนเปื้อนเชื้อแบคทีเรีย)',

  // Other
  'wrong_container': 'Wrong Container (หลอดผิดประเภท)',
  'expired_container': 'Expired Container (หลอดหมดอายุ)',
  'damaged_container': 'Damaged Container (หลอดชำรุด)'
};

3. LocalStorage Keys

'his_specimens'                    // Array of Specimens
'his_specimen_counter_YYYYMMDD'    // Daily counter (reset each day)
'his_lab_orders'                   // Array of Lab Orders (shared)

4. Status Transitions

// Specimen Status Flow
'pending_collection'  // → Initial (ยังไม่เก็บ)
  
'collected'          // → เก็บแล้ว (QC: Good/Acceptable/Poor)
  
'received'           // → รับตัวอย่างแล้วที่แล็บ
  
'testing'            // → กำลังตรวจ
  
'completed'          // → ตรวจเสร็จ (มีผลแล้ว)

// Rejected Flow (Separate branch)
'pending_collection'
  
'rejected'           // → ปฏิเสธ (QC: Rejected)
  
[Recollection]       // → เก็บใหม่ (Create new specimen with R suffix)
  
'collected' (New)

🎨 Design System

Quality Color Coding

.quality-good       { background: #d4edda; color: #155724; } /* Green */
.quality-acceptable { background: #fff3cd; color: #856404; } /* Yellow */
.quality-poor       { background: #ffe5d0; color: #d63384; } /* Orange */
.quality-rejected   { background: #f8d7da; color: #721c24; } /* Red */

Specimen Status Colors

.specimen-status-pending_collection { background: #fff3e0; color: #ef6c00; } /* Orange */
.specimen-status-collected          { background: #e8f5e9; color: #2e7d32; } /* Green */
.specimen-status-received           { background: #e3f2fd; color: #1976d2; } /* Blue */
.specimen-status-testing            { background: #e1f5fe; color: #0277bd; } /* Cyan */
.specimen-status-completed          { background: #c8e6c9; color: #1b5e20; } /* Dark Green */
.specimen-status-rejected           { background: #ffebee; color: #c62828; } /* Red */

📱 Responsive Behavior

Desktop (> 1200px)

  • Order Cards: Full width
  • Item Table: All columns visible
  • Modal: 800px width (centered)

Tablet (768px - 1200px)

  • Order Cards: Full width
  • Item Table: Horizontal scroll
  • Modal: 90% width

Mobile (< 768px)

  • Order Cards: Stacked (1 per row)
  • Item Table: Simplified (hide some columns)
  • Modal: 95% width, Vertical scroll

💡 Best Practices (UX/UI)

  1. QC Workflow:
  2. เริ่มต้นที่ "Good" (Default)
  3. Force input เหตุผลถ้าเลือก Poor/Rejected
  4. แสดง Warning ชัดเจนเมื่อปฏิเสธ

  5. Barcode Integration:

  6. Auto-focus Search Box
  7. Support Barcode Scanner input
  8. No need to press Enter (auto-submit)

  9. Print Label Timing:

  10. Option 1: Print ล่วงหน้า (ก่อนเก็บ) - สำหรับ Workflow ที่ติด Label ก่อน
  11. Option 2: Print หลังเก็บ (Success Modal) - สำหรับ Workflow ที่เก็บก่อน

  12. Error Prevention:

  13. Disable Save ถ้า Form ไม่ครบ
  14. Validate Volume > 0
  15. Validate DateTime ไม่เกินปัจจุบัน
  16. Confirm ก่อนปฏิเสธ (Rejected)

🐛 Known Issues & Limitations

  1. No Barcode Generation: แสดงเป็น Text only (Future: Generate QR Code/Barcode image)
  2. No Photo Upload: ไม่สามารถถ่ายรูปตัวอย่าง (Future: Add camera feature)
  3. No Multi-Specimen: 1 Item = 1 Specimen (ไม่รองรับการเก็บ Pooled Specimen)
  4. Mock User: collectedBy hardcoded (Future: Get from session)
  5. No Temperature Log: ไม่บันทึกอุณหภูมิการเก็บ/เก็บรักษา

📊 Business Rules

  1. Specimen Creation Rules:
  2. 1 Item = 1 Specimen (แยก Specimen ต่อรายการ)
  3. Same Container Type → Can group (Future enhancement)
  4. Different Container → Must separate

  5. QC Threshold:

  6. Good: ผ่าน QC ทุกเกณฑ์
  7. Acceptable: ผ่านบางเกณฑ์ แต่ยังตรวจได้ (Note เหตุผล)
  8. Poor: ไม่ผ่านหลายเกณฑ์ แต่ยังตรวจได้ (ผลอาจไม่แม่นยำ)
  9. Rejected: ไม่ผ่าน QC ต้องเก็บใหม่

  10. Recollection Rules:

  11. Maximum 3 recollections (R1, R2, R3)
  12. After R3 → Escalate to Supervisor
  13. Original rejected specimen → Keep for audit (ไม่ลบ)

  14. Label Printing:

  15. Must print within 24 hours of collection
  16. Cannot print label for rejected specimens
  17. Re-print allowed (for damaged labels)

5. result-entry.html - ลงผลแล็บ

📌 ภาพรวม

หน้าสำหรับ Lab Technician ลงผลการตรวจแล็บ พร้อมระบบ Delta Check (เปรียบเทียบผลเก่า-ใหม่), Critical Alert (แจ้งเตือนผลวิกฤต), และ Outlab File Upload (อัพโหลดผลจากแล็บนอก)

🎯 วัตถุประสงค์

  • ลงผลแล็บ: บันทึกค่าผลการตรวจ (Numeric/Text)
  • Delta Check: เปรียบเทียบผลปัจจุบันกับผลก่อนหน้า (%Change)
  • Critical Alert: แจ้งเตือนผลวิกฤต + Countdown Timer (30 นาที)
  • Flag Calculation: คำนวณ Flag อัตโนมัติ (Normal, High, Low, Critical)
  • Outlab Upload: อัพโหลดผลจากแล็บนอก (PDF/Image)
  • Partial Result: รองรับบันทึกผลบางส่วน (ค่อยๆ ลงผล)
  • Lab Note: เพิ่มหมายเหตุแจ้งแพทย์

📊 File Statistics

  • Total Lines: 2,462 lines
  • Components: Search, Order List, Result Entry Panel, Critical Modal, Outlab Upload Modal
  • Dependencies: LabResult.js, LabOrder.js, Patient.js, LabResultService, FileAttachmentService
  • Data Files: lab-previous-results.json, lab-instruments.json, lab-items.json

🧩 UI Components & Workflow

Section 1: Search & Order List

Filter Logic: - แสดงเฉพาะ Orders ที่ status = sent_to_lis, in_progress, partial_result - เรียงตาม Priority (STAT → Urgent → Routine)

Order Card Display:

┌────────────────────────────────────────────────────────────┐
│  LAB-20241226-0001                            [⚡STAT]     │
│  HN: HN000123 | ผู้ป่วย: นายสมชาย ใจดี                   │
│  รายการ: 5 รายการ | ลงผลแล้ว: 2/5 รายการ (40%)          │
│  วันที่: 26/12/2567 | แพทย์: นพ.สมชาย                    │
│  [เลือก Order นี้]                                        │
└────────────────────────────────────────────────────────────┘

Progress Indicator: - แสดง ลงผลแล้ว: X/Y รายการ (Z%) สำหรับ Orders ที่มีผลบางส่วน - สี Progress Bar: - 0-39%: Red (ยังค้างเยอะ) - 40-79%: Orange (กำลังดำเนินการ) - 80-99%: Yellow (เกือบครบ) - 100%: Green (ครบแล้ว)


Section 2: Patient Info Bar

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  👤 HN: HN000123  |  ชื่อ: นายสมชาย ใจดี  |  อายุ: 30 ปี  │
│  📋 Order: LAB-20241226-0001  |  Priority: ⚡STAT           │
│  🏥 VN: VN20241226001  |  แผนก: OPD  |  กรุ๊ปเลือด: O+    │
│  ⚠️ แพ้ยา: Penicillin, Aspirin                            │
└─────────────────────────────────────────────────────────────┘

Background: Gradient Purple-Blue (linear-gradient(135deg, #667eea 0%, #764ba2 100%))

Critical Info Highlight: - แพ้ยา: แสดงเป็นสีแดงถ้ามี (⚠️ Icon) - โรคประจำตัว: แสดงถ้ามี (💊 Icon) - STAT Order: แสดง ⚡ Icon สีแดงโดดเด่น


Section 3: Results Table (ตารางลงผล)

UI Layout:

┌──────────────────────────────────────────────────────────────────────────────┐
│ # │ รายการตรวจ    │ ผล           │ หน่วย │ Flag   │ Reference Range │ หมายเหตุ│
├───┼──────────────┼──────────────┼───────┼────────┼─────────────────┼─────────┤
│ 1 │ WBC          │ [_____] ⬅️    │ x10³/µL│ Normal│ 4.5-11.0       │ [____]  │
│   │              │ ⚠️ Delta: +15%│       │        │                 │         │
│   │              │ (prev: 7.0)  │       │        │                 │         │
├───┼──────────────┼──────────────┼───────┼────────┼─────────────────┼─────────┤
│ 2 │ RBC          │ [4.8___]     │ x10⁶/µL│ Normal│ 4.5-5.5        │ [____]  │
├───┼──────────────┼──────────────┼───────┼────────┼─────────────────┼─────────┤
│ 3 │ Hemoglobin   │ [14.2__]     │ g/dL  │ Normal│ 13.5-17.5      │ [____]  │
├───┼──────────────┼──────────────┼───────┼────────┼─────────────────┼─────────┤
│ 4 │ Platelet     │ [___50_] 🚨  │ x10³/µL│🔴Critical│ 150-400    │ [____]  │
│   │              │              │       │ Low    │                 │         │
├───┼──────────────┼──────────────┼───────┼────────┼─────────────────┼─────────┤
│ 5 │ **Outlab**   │ [📎 Upload]  │ -     │ -      │ -               │ [____]  │
│   │ HIV Viral Load│              │       │        │                 │         │
└──────────────────────────────────────────────────────────────────────────────┘

Columns:

3.1 # (ลำดับ) - Auto-number (1, 2, 3, ...)

3.2 รายการตรวจ (Test Name) - Item Name: ชื่อรายการภาษาไทย/อังกฤษ - Item Code: แสดงด้านล่าง (เทาๆ, เล็ก) - Outlab Indicator: แสดง Badge "🔗 ส่งตรวจนอก" ถ้า isOutsourced: true

3.3 ผล (Result Value) - Input Field

Input Types: - Numeric: Number input (step: 0.01)

<input type="number" step="0.01" min="0" placeholder="0.0">
- Text: Text input (for descriptive results)
<input type="text" placeholder="Negative / Positive / Normal">

Real-time Validation: - On Input → Calculate Flag → Update Flag Badge - Check Critical → Change border color (Red) - Check Abnormal → Change border color (Yellow)

Delta Check Display (แสดงใต้ Input ถ้ามีผลเก่า):

<div class="delta-check delta-warning">
  ⚠️ Delta: +15.3% (ก่อนหน้า: 7.0 x10³/µL วันที่ 20/12/67)
</div>

Delta Calculation:

// Calculate percentage change
const percentChange = ((newValue - oldValue) / oldValue) * 100;

// Delta thresholds (from lab-items.json)
if (Math.abs(percentChange) >= criticalDeltaThreshold) {
  // Critical Delta (e.g., >50% for WBC)
  return 'delta-critical'; // Red background
} else if (Math.abs(percentChange) >= warningDeltaThreshold) {
  // Warning Delta (e.g., >20% for WBC)
  return 'delta-warning'; // Yellow background
} else {
  // Normal Delta
  return 'delta-check'; // Blue background (informational)
}

3.4 หน่วย (Unit) - Display Only - Examples: g/dL, x10³/µL, mg/dL, mEq/L, %

3.5 Flag (Interpretation)

Flag Badge Colors:

.flag-normal          { background: #d4edda; color: #155724; } /* Green */
.flag-high            { background: #fff3cd; color: #856404; } /* Yellow */
.flag-low             { background: #fff3cd; color: #856404; } /* Yellow */
.flag-critical_high   { background: #f8d7da; color: #721c24; } /* Red */
.flag-critical_low    { background: #f8d7da; color: #721c24; } /* Red */

Flag Calculation Logic:

// calculateFlag()
if (result >= criticalHigh || result <= criticalLow) {
  return 'critical_high' or 'critical_low'; // 🔴 Critical
} else if (result > referenceHigh) {
  return 'high'; // 🟡 Abnormal High
} else if (result < referenceLow) {
  return 'low'; // 🟡 Abnormal Low
} else {
  return 'normal'; // 🟢 Normal
}

Reference Ranges (จาก lab-items.json):

{
  "referenceLow": 4.5,
  "referenceHigh": 11.0,
  "criticalLow": 2.0,
  "criticalHigh": 30.0,
  "resultUnit": "x10³/µL"
}

3.6 Reference Range - Display: "4.5 - 11.0" (Text only) - Color: Gray (#666)

3.7 หมายเหตุ (Note) - Optional textarea - Examples: "ตรวจซ้ำ", "Hemolysis เล็กน้อย", "ผลยืนยัน"


Section 4: Outlab File Upload (อัพโหลดผลแล็บนอก)

Trigger: Click [📎 Upload] Button บน Outlab Item Row

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  📤 แนบผลแล็บนอก (Outsourced Lab Result)            [✕]  │
├─────────────────────────────────────────────────────────────┤
│  🔼 อัพโหลดไฟล์ผลการตรวจ                                  │
│  ┌────────────────────────────────────────────────────┐    │
│  │           ☁️                                       │    │
│  │    คลิกหรือลากไฟล์มาวางที่นี่                      │    │
│  │    รองรับไฟล์ PDF, JPG, PNG (สูงสุด 5 MB/ไฟล์)    │    │
│  └────────────────────────────────────────────────────┘    │
│                                                             │
│  📎 ไฟล์ที่แนบ (2 ไฟล์):                                   │
│  ┌────────────────────────────────────────────────────┐    │
│  │ 📄 HIV_Result_26122567.pdf (1.2 MB)      [❌ ลบ]  │    │
│  │ 🖼️ lab_report_scan.jpg (850 KB)          [❌ ลบ]  │    │
│  └────────────────────────────────────────────────────┘    │
│                                                             │
│  📝 หมายเหตุผลการตรวจ:                                     │
│  ┌────────────────────────────────────────────────────┐    │
│  │ HIV Viral Load: < 50 copies/mL (Undetectable)     │    │
│  │ ตรวจที่ห้องแล็บศิริราช วันที่ 15/01/2568          │    │
│  └────────────────────────────────────────────────────┘    │
│                                                             │
│  💡 หมายเหตุนี้จะแสดงร่วมกับไฟล์แนบในระบบ                 │
│                                                             │
│  [ยกเลิก]                              [✅ บันทึกผลแล็บ]  │
└─────────────────────────────────────────────────────────────┘

File Upload Features:

4.1 Drag & Drop Support

// setupDragDrop()
fileUploadArea.addEventListener('dragover', (e) => {
  e.preventDefault();
  fileUploadArea.classList.add('drag-over');
});

fileUploadArea.addEventListener('drop', (e) => {
  e.preventDefault();
  fileUploadArea.classList.remove('drag-over');
  const files = e.dataTransfer.files;
  handleFiles(files);
});

4.2 File Validation

// Allowed file types
const allowedTypes = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png'];

// Max file size: 5 MB
const maxSize = 5 * 1024 * 1024;

// Validation
for (const file of files) {
  if (!allowedTypes.includes(file.type)) {
    alert(`ไฟล์ ${file.name} ไม่รองรับ (รองรับเฉพาะ PDF, JPG, PNG)`);
    continue;
  }

  if (file.size > maxSize) {
    alert(`ไฟล์ ${file.name} ขนาดเกิน 5 MB (${(file.size / 1024 / 1024).toFixed(2)} MB)`);
    continue;
  }

  // File is valid → Add to attachment list
}

4.3 Progress Bar (แสดงเมื่ออัพโหลด)

<div class="upload-progress show">
  <div class="progress-bar-container">
    <div class="progress-bar" style="width: 45%">45%</div>
  </div>
  <div>กำลังอัพโหลด...</div>
</div>

Simulation: - Progress: 0% → 100% (animate in 2 seconds) - Actual storage: Base64 encode + LocalStorage (FileAttachmentService)

4.4 Attachment List Display

<div class="attachment-item">
  <div class="attachment-icon">📄</div>
  <div class="attachment-info">
    <div class="attachment-name">HIV_Result_26122567.pdf</div>
    <div class="attachment-size">1.2 MB</div>
  </div>
  <button class="btn-remove" onclick="removeAttachment(index)">
    <i class="fas fa-trash"></i> ลบ
  </button>
</div>

File Type Icons: - PDF: 📄 - JPG/PNG: 🖼️ - Other: 📎

4.5 Result Note (หมายเหตุผลการตรวจ) - Textarea input (Multi-line) - Purpose: ระบุค่าผลการตรวจที่สำคัญจากรายงาน (ไม่สามารถ OCR ได้) - Example:

HIV Viral Load: < 50 copies/mL (Undetectable)
CD4 Count: 580 cells/µL
ตรวจที่ห้องแล็บศิริราช วันที่ 15/01/2568

Save Logic:

// saveOutlabResult()
const attachmentData = {
  orderNumber: currentOrder.orderNumber,
  itemCode: currentItem.itemCode,
  files: uploadedFiles, // Array of { fileName, fileType, fileSize, fileData (Base64) }
  note: attachmentNote,
  uploadedBy: currentUser.id,
  uploadedByName: currentUser.name,
  uploadedAt: new Date().toISOString()
};

// Save to FileAttachmentService
const result = await fileAttachmentService.uploadAttachment(attachmentData);

// Update result status to 'preliminary'
labResultService.enterResult(
  resultId,
  'See Attachment', // Placeholder text
  currentUser.id,
  currentUser.name,
  attachmentNote
);


Section 5: Lab Note (หมายเหตุแจ้งแพทย์)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  📝 [✓] เพิ่ม Lab Note (หมายเหตุแจ้งแพทย์)                 │
├─────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────────────────────────────┐ │
│  │ ค่า WBC ต่ำกว่าปกติ แนะนำให้ตรวจซ้ำภายใน 1 สัปดาห์  │ │
│  │ เพื่อดู Trend                                         │ │
│  │                                                        │ │
│  │ หรือ: สิ่งส่งตรวจมี Mild Hemolysis อาจส่งผลต่อค่า    │ │
│  │ Potassium                                             │ │
│  └────────────────────────────────────────────────────────┘ │
│  ผู้เขียน: เทคนิคแล็บสมหญิง  |  เวลา: 26/12/67 14:30     │
└─────────────────────────────────────────────────────────────┘

Purpose: - เพิ่มหมายเหตุจากเทคนิคแล็บแจ้งแพทย์เกี่ยวกับผลการตรวจ - Use Cases: - ค่าผิดปกติที่ต้องติดตาม - ปัญหาสิ่งส่งตรวจ (Hemolysis, Lipemia) - แนะนำการตรวจเพิ่มเติม - Panic Value (Critical result)

Toggle Behavior:

// toggleLabNote()
if (checkbox.checked) {
  labNoteContent.style.display = 'block';
  document.getElementById('labNoteAuthor').textContent = currentUser.name;
  document.getElementById('labNoteTime').textContent = new Date().toLocaleString('th-TH');
} else {
  labNoteContent.style.display = 'none';
  document.getElementById('labNoteText').value = '';
}

Save Behavior: - บันทึกพร้อมกับ Results - แสดงใน Result Report (สีเหลือง Highlight) - แสดงใน Result Approval Page (สำหรับ Supervisor)


Section 6: Instrument Section (ข้อมูลการตรวจ)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  🔬 ข้อมูลการตรวจ                                          │
├─────────────────────────────────────────────────────────────┤
│  เครื่องมือที่ใช้: [Sysmex XN-1000 ▼]                     │
│  ผู้ลงผล: เทคนิคแล็บสมหญิง (Read-only)                     │
└─────────────────────────────────────────────────────────────┘

Instrument Dropdown: - Load from lab-instruments.json - Grouped by Category: - Hematology: Sysmex XN-1000, Mindray BC-6800 - Chemistry: Cobas C311, Architect ci4100, Vitros 350 - Immunology: Cobas e411, Architect i2000SR - Microbiology: BacT/ALERT 3D, VITEK 2 - Manual: Manual Microscope, Manual Counting

Instrument Data Model:

{
  "id": "INS001",
  "name": "Sysmex XN-1000",
  "category": "Hematology",
  "manufacturer": "Sysmex Corporation",
  "model": "XN-1000",
  "status": "active",
  "maintenanceDate": "2024-01-15"
}


Section 7: Partial Result Summary

Display Condition: แสดงเมื่อมีผลบางส่วน (completed > 0 && pending > 0)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ℹ️ สถานะการลงผล (Partial Result Status)                  │
├─────────────────────────────────────────────────────────────┤
│  [✅ ลงผลแล้ว: 3]  [⏳ รอลงผล: 2]  [📊 ทั้งหมด: 5]       │
└─────────────────────────────────────────────────────────────┘

Real-time Update:

// updatePartialResultSummary()
const completed = currentResults.filter(r => r.result && r.result !== '').length;
const total = currentResults.length;
const pending = total - completed;

document.getElementById('completedCount').textContent = completed;
document.getElementById('pendingCount').textContent = pending;
document.getElementById('totalCount').textContent = total;


Section 8: Action Buttons

Three Save Options:

8.1 [💾 บันทึกทั้งหมด] (btn-success, Green)

// saveAllResults()
// - Save all results (skip empty inputs)
// - Calculate flags
// - Check for critical results
// - If critical → Show Critical Modal
// - If no critical → Success message
// - Update order status to 'in_progress'

8.2 [💾 บันทึกผลบางส่วน] (btn-partial, Orange)

// savePartialResults()
// - Save only filled inputs (skip empty)
// - Count: savedCount vs skippedCount
// - Update order status to 'partial_result'
// - Show summary: "✅ ลงผลแล้ว: 3 รายการ, ⏳ ยังค้างอยู่: 2 รายการ"
// - Keep panel open (ไม่ปิดหน้า)

8.3 [✅ บันทึกและยืนยัน] (btn-info, Cyan)

// saveAndVerify()
// - saveAllResults() first
// - Then verify all results (status: preliminary → final)
// - Update order status to 'completed'
// - Success message + Close panel


Section 9: Critical Alert Modal

Trigger: เมื่อมี Critical Result (Flag = critical_high or critical_low)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│                         🚨                                  │
│          พบผลวิกฤต (Critical Result)                       │
├─────────────────────────────────────────────────────────────┤
│  ⚠️ คำเตือน: พบผลการตรวจที่อยู่ในระดับวิกฤต              │
│     กรุณาตรวจสอบความถูกต้องและแจ้งแพทย์ทันที              │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ WBC (White Blood Cell)                                │ │
│  │ ค่าที่วัดได้: 1.5 x10³/µL 🔴                         │ │
│  │ ค่าปกติ: 4.5 - 11.0 x10³/µL                          │ │
│  │ Critical Low Threshold: < 2.0 x10³/µL                 │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ Platelet                                              │ │
│  │ ค่าที่วัดได้: 48 x10³/µL 🔴                          │ │
│  │ ค่าปกติ: 150 - 400 x10³/µL                           │ │
│  │ Critical Low Threshold: < 50 x10³/µL                  │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ⏱️ ต้องแจ้งแพทย์ภายใน: **29:45** นาที                   │
│                                                             │
│  [✓] ยืนยันว่าได้ตรวจสอบผลซ้ำแล้ว และผลนี้ถูกต้อง       │
│                                                             │
│  [🔄 ตรวจซ้ำ]                       [✅ ยืนยันและบันทึก]  │
└─────────────────────────────────────────────────────────────┘

Critical Result Item Display:

<div class="critical-result-item">
  <div>
    <strong>WBC (White Blood Cell)</strong><br>
    <span style="font-size: 0.9rem; color: #666;">CBC001</span>
  </div>
  <div style="text-align: right;">
    <div style="font-size: 1.3rem; font-weight: 600; color: #dc3545;">
      1.5 x10³/µL
    </div>
    <div style="font-size: 0.85rem; color: #666;">
      ค่าปกติ: 4.5 - 11.0
    </div>
  </div>
</div>

Countdown Timer:

// startCriticalCountdown(30 * 60) // 30 minutes
let remaining = 1800; // seconds

setInterval(() => {
  const minutes = Math.floor(remaining / 60);
  const secs = remaining % 60;
  countdownDisplay.textContent = `${minutes}:${secs.toString().padStart(2, '0')}`;

  // Warning when < 5 minutes
  if (remaining < 300) {
    countdownDisplay.style.color = '#dc3545';
    countdownDisplay.style.animation = 'pulse 1s infinite';
  }

  remaining--;

  if (remaining < 0) {
    clearInterval(countdownInterval);
    showAlert('⚠️ เวลาในการแจ้งแพทย์หมดแล้ว! กรุณาติดต่อแพทย์ทันที', 'danger');
  }
}, 1000);

Double-Check Confirmation:

<label class="double-check-label">
  <input type="checkbox" id="doubleCheckConfirm">
  <span>✓ ยืนยันว่าได้ตรวจสอบผลซ้ำแล้ว และผลนี้ถูกต้อง</span>
</label>

Actions:

[🔄 ตรวจซ้ำ] (Gray) - Close modal - Stop countdown - Alert: "กรุณาตรวจสอบผลอีกครั้ง และแก้ไขหากจำเป็น" - ไม่บันทึก → User แก้ไขค่าได้

[✅ ยืนยันและบันทึก] (Green, Disabled ถ้าไม่ Checkbox) - Require: Double-check checkbox checked - Stop countdown - Save all results - Alert: "บันทึกผลวิกฤตสำเร็จ X รายการ ระบบได้แจ้งเตือนแพทย์แล้ว" - Update order status


⚙️ Technical Features

1. Delta Check System

// Load previous results from JSON
const previousResults = {
  "HN000123": {
    "WBC": {
      "value": 7.0,
      "unit": "x10³/µL",
      "date": "2024-12-20T10:30:00"
    },
    "Hemoglobin": {
      "value": 14.5,
      "unit": "g/dL",
      "date": "2024-12-20T10:30:00"
    }
  }
};

// Calculate delta
function calculateDelta(hn, itemCode, newValue) {
  const prevResult = previousResults[hn]?.[itemCode];
  if (!prevResult) return null;

  const oldValue = prevResult.value;
  const percentChange = ((newValue - oldValue) / oldValue) * 100;

  return {
    oldValue,
    percentChange: percentChange.toFixed(1),
    date: prevResult.date,
    isDeltaWarning: Math.abs(percentChange) >= 20,
    isDeltaCritical: Math.abs(percentChange) >= 50
  };
}

Delta Display Logic:

// Show delta if change > 10%
if (Math.abs(percentChange) >= 10) {
  const deltaClass = isDeltaCritical ? 'delta-critical' : 
                     isDeltaWarning ? 'delta-warning' : 'delta-check';

  const deltaHTML = `
    <div class="${deltaClass}">
      ${isDeltaCritical ? '🚨' : '⚠️'} Delta: ${percentChange > 0 ? '+' : ''}${percentChange}%
      (ก่อนหน้า: ${oldValue} ${unit} วันที่ ${formatDate(date)})
    </div>
  `;
}

2. Flag Calculation Engine

// Auto-calculate flag on input
resultInput.addEventListener('input', () => {
  const value = parseFloat(resultInput.value);
  if (isNaN(value)) return;

  // Get reference ranges from LabItem
  const { referenceLow, referenceHigh, criticalLow, criticalHigh } = labItem;

  let flag = 'normal';

  if (value <= criticalLow) {
    flag = 'critical_low';
  } else if (value >= criticalHigh) {
    flag = 'critical_high';
  } else if (value < referenceLow) {
    flag = 'low';
  } else if (value > referenceHigh) {
    flag = 'high';
  }

  // Update UI
  updateFlagBadge(flag);
  updateInputBorder(flag);

  // Store for save
  result.flag = flag;
});

3. LocalStorage Keys

'his_lab_results'              // Array of Lab Results
'his_lab_result_counter'       // Auto-increment ID
'his_file_attachments'         // Outlab file attachments (Base64)
'his_lab_orders'               // Orders (shared, status updates)

4. Status Transitions

// Order Status Flow (Result Entry perspective)
'sent_to_lis'     // → Ready for result entry
  
'in_progress'     // → Started result entry (some results entered)
  
'partial_result'  // → Partial results saved (not all items)
  
'pending_approval' // → All results entered, waiting approval
  
'approved'        // → Approved by supervisor
  
'completed'       // → Final status

// Result Status Flow
'pending'         // → Initial (no result yet)
  
'preliminary'     // → Result entered (not verified)
  
'final'           // → Verified and approved
  
'rejected'        // → Rejected (need re-entry or recollection)

🎨 Design System

Input Border Colors

.result-input                { border-color: #ddd; }              /* Normal */
.result-input:focus          { border-color: #2196f3; }          /* Focus */
.result-input.abnormal       { border-color: #ffc107; }          /* Abnormal */
.result-input.critical       { border-color: #dc3545; }          /* Critical */

Delta Check Colors

.delta-check     { background: #e3f2fd; border-left: 4px solid #2196f3; } /* Info */
.delta-warning   { background: #fff3cd; border-left: 4px solid #ffc107; } /* Warning */
.delta-critical  { background: #f8d7da; border-left: 4px solid #dc3545; } /* Critical */

Critical Modal Animation

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

/* Apply to countdown when < 5 min */
.critical-countdown span {
  animation: pulse 1s infinite;
}

📱 Responsive Behavior

Desktop (> 1200px)

  • Table: Full width, All columns visible
  • Modal: 800px width (centered)

Tablet (768px - 1200px)

  • Table: Horizontal scroll
  • Modal: 90% width

Mobile (< 768px)

  • Table: Simplified (hide Note column)
  • Modal: 95% width
  • Stack Input fields vertically

💡 Best Practices (UX/UI)

  1. Real-time Feedback:
  2. Flag updates immediately on input
  3. Delta check shows as you type (if > 10% change)
  4. Input border changes color based on flag

  5. Critical Result Safety:

  6. Cannot save critical results without double-check
  7. 30-minute countdown to ensure prompt notification
  8. Visual alerts (Red, Pulsing, Icon)

  9. Partial Result Support:

  10. Separate "บันทึกผลบางส่วน" button
  11. Progress indicator (X/Y items completed)
  12. Can save and come back later

  13. Error Prevention:

  14. Numeric input validation (min: 0, step: 0.01)
  15. File size/type validation for uploads
  16. Require note for outlab results

🐛 Known Issues & Limitations

  1. No Auto-Import from LIS: Manual entry only (Future: LIS integration)
  2. Delta Check Limited: Only checks last result (Future: Check trends)
  3. Critical Notification Mock: No real SMS/Email (Future: Real notification)
  4. Outlab Files in LocalStorage: Limited size (Future: Cloud storage)
  5. No Batch Entry: One order at a time (Future: Batch mode)

📊 Business Rules

  1. Critical Result Rules:
  2. Must double-check before saving
  3. Must notify doctor within 30 minutes
  4. Cannot bypass confirmation (forced workflow)

  5. Partial Result Rules:

  6. Can save partial results (at least 1 item filled)
  7. Order status = 'partial_result'
  8. Cannot approve until all results entered

  9. Outlab Result Rules:

  10. Must upload file attachment (PDF/Image)
  11. Must provide result note (text description)
  12. Counts as "result entered" for approval

  13. Delta Check Thresholds:

  14. Info: 10-19% change (Blue)
  15. Warning: 20-49% change (Yellow)
  16. Critical: ≥50% change (Red)

6. result-approval.html - อนุมัติผลแล็บ

📌 ภาพรวม

หน้าสำหรับ Lab Supervisor/Lab Chief ตรวจสอบและอนุมัติผลแล็บที่ลงผลเสร็จแล้ว พร้อมระบบ Partial Approval (อนุมัติเลือกทีละรายการ) และ Rejection Workflow (ปฏิเสธพร้อมเหตุผล)

🎯 วัตถุประสงค์

  • ตรวจสอบผล: Review ผลที่ลงเสร็จแล้ว (status: preliminary)
  • Partial Approval: อนุมัติเฉพาะรายการที่เลือก (ไม่จำเป็นต้องทั้งหมด)
  • Quick Approve: อนุมัติทีละรายการแบบเร็ว
  • Reject with Reason: ปฏิเสธผลพร้อมระบุเหตุผล
  • Critical Alert: แจ้งเตือนผลวิกฤต (ต้องแจ้งแพทย์)
  • Outlab Support: แสดงไฟล์ PDF/Image จาก Lab นอก
  • Statistics Dashboard: แสดงสรุปรออนุมัติ, ผลวิกฤต, อนุมัติแล้ว

📊 File Statistics

  • Total Lines: 1,162 lines
  • Components: Statistics Dashboard, Order List, Approval Modal, Reject Modal
  • Dependencies: LabResult.js, LabOrder.js, Patient.js, LabResultService, PatientService
  • Data Files: lab-items.json (for outlab check)

🧩 UI Components & Workflow

Section 1: Statistics Dashboard (สรุปสถิติ)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │     15       │  │      3       │  │     28       │      │
│  │  รออนุมัติ   │  │  ผลวิกฤต    │  │ อนุมัติแล้ว  │      │
│  │              │  │              │  │  (วันนี้)    │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│   🟡 Yellow         🔴 Red           🟢 Green               │
└─────────────────────────────────────────────────────────────┘

Card Borders: - Pending: Yellow (#ffc107) - Critical: Red (#dc3545) - Approved: Green (#28a745)

Data Source:

// labResultService.getStatistics()
{
  preliminary: 15,  // Count of results with status 'preliminary'
  critical: 3,      // Count of results with isCritical() = true
  final: 28         // Count of results approved today
}


Section 2: Order List (รายการรออนุมัติ)

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  📋 รายการรออนุมัติ (12 รายการ)                            │
├─────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────────────────────────────┐ │
│  │ 🚨 พบผลวิกฤต 2 รายการ - กรุณาตรวจสอบและอนุมัติโดยเร็ว│ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  LAB-20241226-0001  [🌐 OUTLAB (3/5)]                      │
│  HN: HN000123 | ชื่อ: นายสมชาย ใจดี | แพทย์: นพ.สมชาย     │
│  วันที่สั่ง: 26/12/67 10:30                                │
│                                                             │
│  📊 สรุปผลการตรวจ (5 รายการ):                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ WBC                    8.5 x10³/µL       🟢 Normal     │ │
│  │ Hemoglobin             14.2 g/dL         🟢 Normal     │ │
│  │ Platelet               50 x10³/µL        🔴 Critical Low│ │
│  │ HIV Viral Load         🌐 Lab นอก       📄 PDF Report  │ │
│  │ CD4 Count              🌐 Lab นอก       📄 PDF Report  │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  🔴 ผลวิกฤต: 1  |  🟡 ผิดปกติ: 0                           │
│  ลงผลโดย: เทคนิคแล็บสมหญิง              [ตรวจสอบและอนุมัติ]│
└─────────────────────────────────────────────────────────────┘

Order Card Features:

2.1 Critical Alert Banner (แสดงถ้ามี Critical)

<div class="critical-alert">
  🚨 พบผลวิกฤต 2 รายการ - กรุณาตรวจสอบและอนุมัติโดยเร็ว
</div>

2.2 Outlab Badge (แสดงถ้ามี Outlab Items)

<!-- Mixed Order (มีทั้ง In-house และ Outlab) -->
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
  🌐 MIXED (3/5)
</span>

<!-- All Outlab (ทุกรายการส่ง Lab นอก) -->
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
  🌐 OUTLAB (3/3)
</span>

Check Logic:

// Check if lab item is outsourced
const hasOutlabItems = order.items.some(item => {
  const labItem = window.labItems?.find(li => li.id === item.itemId);
  return labItem?.isOutsourced === true;
});

// Count outlab items
const outlabItemCount = order.items.filter(item => {
  const labItem = window.labItems?.find(li => li.id === item.itemId);
  return labItem?.isOutsourced === true;
}).length;

// Determine badge type
const isAllOutlab = outlabItemCount === order.items.length;
const badgeText = isAllOutlab ? 'OUTLAB' : 'MIXED';

2.3 Results Summary (สรุปผล 5 รายการแรก) - แสดง Result Value + Flag - Outlab Items: แสดง "📄 PDF Report" แทนค่าตัวเลข - แสดง "... และอีก X รายการ" ถ้ามีเกิน 5 รายการ

2.4 Empty State (ไม่มีรายการ)

┌─────────────────────────────────────────────────────────────┐
│                        ✓                                    │
│              ไม่มีรายการรออนุมัติ                          │
│       ไม่มีผลแล็บที่รออนุมัติในขณะนี้                       │
└─────────────────────────────────────────────────────────────┘


Section 3: Approval Modal (Detail View)

Trigger: Click [ตรวจสอบและอนุมัติ] Button

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ✓ อนุมัติผลแล็บ                                    [✕]   │
├─────────────────────────────────────────────────────────────┤
│  🚨 พบผลวิกฤต 2 รายการ                                    │
│  กรุณาตรวจสอบอย่างละเอียดก่อนอนุมัติและแจ้งแพทย์ทันที      │
│                                                             │
│  👤 ข้อมูลผู้ป่วย                                          │
│  ─────────────────────────────────────────────────────────  │
│  HN: HN000123  |  ชื่อ-สกุล: นายสมชาย ใจดี                │
│  อายุ/เพศ: 30 ปี / ชาย  |  แพทย์ผู้สั่ง: นพ.สมชาย         │
│                                                             │
│  🧪 ผลการตรวจ                                              │
│  ─────────────────────────────────────────────────────────  │
│  [☐ เลือกทั้งหมด (5)]           เลือกแล้ว: 0 รายการ       │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │☐│ รายการตรวจ │ ผลการตรวจ │ ค่าปกติ │ Flag │ หมายเหตุ│ │ │
│  ├─┼────────────┼───────────┼─────────┼──────┼─────────┤ │ │
│  │☐│ WBC        │ 8.5 x10³/µL│ 4.5-11.0│Normal│ -      │[✓][✗]│
│  │☐│ Hemoglobin │ 14.2 g/dL │ 13.5-17.5│Normal│ -     │[✓][✗]│
│  │☐│ Platelet   │ 50 x10³/µL│ 150-400 │🔴Critical│ -  │[✓][✗]│
│  │☐│ HIV Load   │ 📄 PDF    │ -       │ -    │🌐 Outlab│[✓][✗]│
│  │  │ (Outlab)   │ Report    │         │      │        │     │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ลงผลโดย: เทคนิคแล็บสมหญิง  |  วันที่: 26/12/67 14:30     │
│                                                             │
│  [ยกเลิก] [อนุมัติที่เลือก (0)] [ปฏิเสธที่เลือก] [อนุมัติทั้งหมด]│
└─────────────────────────────────────────────────────────────┘

Features:

3.1 Critical Alert Banner (แสดงถ้ามี Critical)

<div class="critical-alert">
  🚨 พบผลวิกฤต 2 รายการ<br>
  กรุณาตรวจสอบอย่างละเอียดก่อนอนุมัติและแจ้งแพทย์ทันที
</div>

3.2 Patient Info Section - HN, ชื่อ-สกุล (จาก currentOrder) - อายุ/เพศ (จาก PatientService, ถ้ามี) - แพทย์ผู้สั่ง - วันที่สั่ง Order

3.3 Selection Toolbar

<div class="selection-toolbar">
  <div>
    <input type="checkbox" id="selectAllResults" onchange="toggleSelectAll()">
    <label>เลือกทั้งหมด (<span id="totalResultsCount">5</span>)</label>
  </div>
  <div>
    เลือกแล้ว: <strong id="selectedCountInModal">0</strong> รายการ
  </div>
</div>

Selection Logic:

// Track selected result IDs
let selectedResultIds = [];

// Update selection
function updateSelection() {
  const checkboxes = document.querySelectorAll('.result-select-cb');
  selectedResultIds = [];

  checkboxes.forEach(cb => {
    if (cb.checked) {
      selectedResultIds.push(parseInt(cb.dataset.resultId));
      // Highlight row
      document.getElementById(`result-row-${cb.dataset.resultId}`).classList.add('selected');
    } else {
      document.getElementById(`result-row-${cb.dataset.resultId}`).classList.remove('selected');
    }
  });

  // Update count displays
  document.getElementById('selectedCount').textContent = selectedResultIds.length;
  document.getElementById('selectedCountInModal').textContent = selectedResultIds.length;

  // Enable/disable buttons
  document.getElementById('approveSelectedBtn').disabled = (selectedResultIds.length === 0);
  document.getElementById('rejectSelectedBtn').disabled = (selectedResultIds.length === 0);
}

// Toggle select all
function toggleSelectAll() {
  const selectAll = document.getElementById('selectAllResults');
  const checkboxes = document.querySelectorAll('.result-select-cb');

  checkboxes.forEach(cb => {
    cb.checked = selectAll.checked;
  });

  updateSelection();
}

3.4 Results Table

Columns: 1. ☐ Checkbox: เลือกรายการ (data-result-id) 2. รายการตรวจ: Item Name + Code + Outlab Badge (ถ้ามี) 3. ผลการตรวจ: - In-house: Result Value + Unit - Outlab: PDF Icon + "PDF Report พร้อมแล้ว" + Attachment Details 4. ค่าปกติ: Reference Range Text 5. Flag: Badge (Normal/High/Low/Critical) 6. หมายเหตุ: Lab Note (ถ้ามี) 7. การดำเนินการ: [✓] Quick Approve | [✗] Quick Reject

Outlab Result Display:

<!-- Outlab with Attachment -->
<div style="background: white; border: 2px solid #667eea; border-radius: 8px;">
  <div style="display: flex; align-items: center; gap: 0.5rem;">
    <i class="fas fa-file-pdf" style="color: #dc3545; font-size: 1.5rem;"></i>
    <div>
      <strong>HIV_Result_26122567.pdf</strong><br>
      <small>1.2 MB | ห้องแล็บศิริราช</small>
    </div>
  </div>
  <small>📥 Upload: 26/12/67 14:30</small><br>
  <small style="color: #667eea; font-style: italic;">
    HIV Viral Load: < 50 copies/mL (Undetectable)
  </small>
</div>

Attachment Model:

{
  "fileName": "HIV_Result_26122567.pdf",
  "fileSize": 1228800,
  "labName": "ห้องแล็บศิริราช",
  "uploadedAt": "2024-12-26T14:30:00",
  "note": "HIV Viral Load: < 50 copies/mL (Undetectable)"
}

3.5 Quick Action Buttons (ในแต่ละ Row)

[✓ Quick Approve] (Green Button)

// quickApprove(resultId)
// - Select only this result
// - Call approveSelectedResults([resultId])
// - Close modal
// - Reload data

[✗ Quick Reject] (Red Button)

// quickReject(resultId)
// - Select only this result checkbox
// - Call showRejectModal()


Four Action Options:

4.1 [ยกเลิก] (Gray) - Close modal - No changes

4.2 [อนุมัติที่เลือก (X)] (Green, Disabled if none selected)

// approveSelectedResults()
// - Validate: selectedResultIds.length > 0
// - Confirm dialog
// - Call labResultService.approveSelectedResults(selectedResultIds, ...)
// - Show success message
// - Close modal, reload data

4.3 [ปฏิเสธที่เลือก] (Orange, Disabled if none selected) - Show Reject Modal (see Section 5)

4.4 [อนุมัติทั้งหมด] (Green, Always enabled)

// approveAllResults()
// - Confirm dialog
// - Call labResultService.approveAllResults(orderNumber, ...)
// - Show success message + Critical alert (if any)
// - Close modal, reload data

Response Handling:

// Success response
{
  success: true,
  approvedCount: 5,
  skippedCount: 0,
  criticalResults: [
    { labItemName: 'Platelet', result: '50 x10³/µL' }
  ]
}

// Display message
let message = `อนุมัติสำเร็จ ${approvedCount} รายการ`;

if (skippedCount > 0) {
  message += `<br>ข้าม ${skippedCount} รายการ`;
}

if (criticalResults.length > 0) {
  message += `<br><strong style="color: #dc3545;">พบผลวิกฤต ${criticalResults.length} รายการ - กรุณาแจ้งแพทย์ทันที</strong>`;
}

showAlert(message, 'success');


Section 5: Reject Modal (ปฏิเสธผลการตรวจ)

Trigger: Click [ปฏิเสธที่เลือก] or [Quick Reject]

UI Layout:

┌─────────────────────────────────────────────────────────────┐
│  ⚠️ ปฏิเสธผลการตรวจ                               [✕]   │
├─────────────────────────────────────────────────────────────┤
│  รายการที่เลือก:                                           │
│  • WBC (8.5 x10³/µL)                                        │
│  • Platelet (50 x10³/µL)                                    │
│                                                             │
│  📝 เหตุผลในการปฏิเสธ: *                                   │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ ผลผิดปกติผิดธรรมชาติ ไม่สอดคล้องกับอาการผู้ป่วย     │ │
│  │ ต้องการตรวจซ้ำเพื่อยืนยัน                             │ │
│  └────────────────────────────────────────────────────────┘ │
│  💡 ตัวอย่างเหตุผล: ผลผิดปกติผิดธรรมชาติ,              │
│     Specimen Hemolysis/Clotted, ผู้ป่วย Fasting ไม่ครบ,  │
│     ต้องการตรวจซ้ำเพื่อยืนยัน, QC ไม่ผ่าน, เครื่องขัดข้อง│
│                                                             │
│  ☐ ต้องการเก็บ Specimen ใหม่                              │
│     (เลือกเมื่อต้องการให้กลับไปขั้นตอน Specimen Collection)│
│                                                             │
│  ⚠️ คำเตือน: รายการที่ถูกปฏิเสธจะส่งกลับไปให้ผู้ลงผล    │
│              แก้ไขและตรวจซ้ำ                               │
│                                                             │
│  [ยกเลิก]                              [✓ ยืนยันปฏิเสธ]  │
└─────────────────────────────────────────────────────────────┘

Form Fields:

5.1 Selected Items List

// Build list from selectedResultIds
const selectedResults = currentResults.filter(r => selectedResultIds.includes(r.id));
const itemsList = selectedResults.map(r => 
  `<li><strong>${r.labItemName}</strong> (${r.getResultWithUnit()})</li>`
).join('');

5.2 Rejection Reason (Required)

<textarea id="rejectionReason" 
          placeholder="กรุณาระบุเหตุผลในการปฏิเสธ..."
          required></textarea>

Common Reasons: - ผลผิดปกติผิดธรรมชาติ - ไม่สอดคล้องกับอาการผู้ป่วย - Specimen Hemolysis/Clotted - ผู้ป่วย Fasting ไม่ครบ - ต้องการตรวจซ้ำเพื่อยืนยัน - QC ไม่ผ่าน - เครื่องขัดข้อง

5.3 Require New Specimen Checkbox

<input type="checkbox" id="requireNewSpecimen">
<label>
  ต้องการเก็บ Specimen ใหม่<br>
  <small>เลือกเมื่อต้องการให้กลับไปขั้นตอน Specimen Collection</small>
</label>

Behavior: - Checked: Order/Specimen status → rejected_specimen - Unchecked: Result status → rejected (กลับไป Result Entry)

5.4 Confirm Reject Logic

async function confirmReject() {
  // Validate
  const reason = document.getElementById('rejectionReason').value.trim();
  if (!reason) {
    showAlert('กรุณาระบุเหตุผลในการปฏิเสธ', 'warning');
    return;
  }

  const requireNewSpecimen = document.getElementById('requireNewSpecimen').checked;

  // Confirm
  if (!confirm(`ยืนยันการปฏิเสธ ${selectedResultIds.length} รายการหรือไม่?\n\nเหตุผล: ${reason}\n${requireNewSpecimen ? '\n⚠️ ต้องเก็บ Specimen ใหม่' : ''}`)) {
    return;
  }

  // Call service
  const result = await labResultService.rejectSelectedResults(
    selectedResultIds,
    currentUser.id,
    currentUser.name,
    'FREE_TEXT', // reasonCode
    reason,      // reason text
    '',          // note (merged with reason)
    requireNewSpecimen
  );

  if (result.success) {
    let message = `ปฏิเสธสำเร็จ ${result.rejectedCount} รายการ`;

    if (requireNewSpecimen) {
      message += '<br>📋 รายการถูกส่งกลับไปขั้นตอน Specimen Collection';
    } else {
      message += '<br>📋 รายการถูกส่งกลับให้ผู้ลงผลแก้ไข';
    }

    showAlert(message, 'success');
    closeRejectModal();
    closeApprovalModal();

    // Reload
    setTimeout(() => {
      loadPendingApprovals();
      updateStatistics();
    }, 1000);
  }
}


⚙️ Technical Features

1. Data Loading & Grouping

// Load pending approvals
function loadPendingApprovals() {
  // 1. Get preliminary results
  const preliminaryResults = labResultService.getPreliminaryResults();

  // 2. Group by order number
  const orderGroups = {};
  for (const result of preliminaryResults) {
    if (!orderGroups[result.orderNumber]) {
      orderGroups[result.orderNumber] = [];
    }
    orderGroups[result.orderNumber].push(result);
  }

  // 3. Get orders
  const orders = [];
  for (const orderNumber in orderGroups) {
    const order = labOrderService.getOrderByNumber(orderNumber);
    if (order) {
      orders.push({
        order: order,
        results: orderGroups[orderNumber]
      });
    }
  }

  // 4. Display
  displayOrders(orders);
}

2. Outlab Detection

// Check if order has outlab items
const hasOutlabItems = order.items && order.items.some(item => {
  const labItem = window.labItems?.find(li => li.id === item.itemId);
  return labItem?.isOutsourced === true;
});

// Count outlab items
const outlabItemCount = order.items.filter(item => {
  const labItem = window.labItems?.find(li => li.id === item.itemId);
  return labItem?.isOutsourced === true;
}).length;

// Determine badge
const isAllOutlab = outlabItemCount === order.items.length;
const badgeText = isAllOutlab ? 'OUTLAB' : `MIXED (${outlabItemCount}/${order.items.length})`;

3. Partial Approval Flow

// Approve selected results
approveSelectedResults: async function(resultIds, approvedBy, approvedByName) {
  let approvedCount = 0;
  let skippedCount = 0;
  const criticalResults = [];

  for (const id of resultIds) {
    const result = this.results.find(r => r.id === id);
    if (!result || result.status !== 'preliminary') {
      skippedCount++;
      continue;
    }

    // Update status
    result.status = 'final';
    result.approvedBy = approvedBy;
    result.approvedByName = approvedByName;
    result.approvedAt = new Date().toISOString();

    // Track critical
    if (result.isCritical()) {
      criticalResults.push({
        labItemName: result.labItemName,
        result: result.getResultWithUnit()
      });
    }

    approvedCount++;
  }

  // Save
  this.saveToLocalStorage();

  // Check if all results in order are approved
  const orderNumber = this.results.find(r => resultIds.includes(r.id))?.orderNumber;
  if (orderNumber) {
    this.checkOrderCompletion(orderNumber);
  }

  return {
    success: true,
    approvedCount,
    skippedCount,
    criticalResults
  };
}

4. Rejection Flow

// Reject selected results
rejectSelectedResults: async function(
  resultIds, 
  rejectedBy, 
  rejectedByName, 
  reasonCode, 
  reason, 
  note, 
  requireNewSpecimen
) {
  let rejectedCount = 0;
  let skippedCount = 0;

  for (const id of resultIds) {
    const result = this.results.find(r => r.id === id);
    if (!result || result.status !== 'preliminary') {
      skippedCount++;
      continue;
    }

    // Update result status
    result.status = 'rejected';
    result.rejectedBy = rejectedBy;
    result.rejectedByName = rejectedByName;
    result.rejectedAt = new Date().toISOString();
    result.rejectionReason = reason;
    result.rejectionReasonCode = reasonCode;

    // Update order/specimen status if require new specimen
    if (requireNewSpecimen) {
      const order = labOrderService.getOrderByNumber(result.orderNumber);
      if (order) {
        order.status = 'rejected_specimen';
        labOrderService.updateOrder(order);
      }
    }

    rejectedCount++;
  }

  // Save
  this.saveToLocalStorage();

  return {
    success: true,
    rejectedCount,
    skippedCount
  };
}

5. Statistics Calculation

// Get statistics
getStatistics: function() {
  const today = new Date().toDateString();

  return {
    preliminary: this.results.filter(r => r.status === 'preliminary').length,
    critical: this.results.filter(r => r.isCritical() && r.status === 'preliminary').length,
    final: this.results.filter(r => {
      return r.status === 'final' && 
             r.approvedAt && 
             new Date(r.approvedAt).toDateString() === today;
    }).length
  };
}

🎨 Design System

Critical Alert Styling

.critical-alert {
  background: #f8d7da;
  border: 2px solid #dc3545;
  padding: 1rem;
  border-radius: 4px;
  margin-bottom: 1rem;
  color: #721c24;
}

.critical-alert i {
  color: #dc3545;
  font-size: 1.2rem;
  margin-right: 0.5rem;
}

Selection Highlighting

.result-select-cb:checked ~ * {
  background: #e3f2fd; /* Light blue highlight */
}

tr.selected {
  background: #e3f2fd !important;
  border-left: 4px solid #2196f3;
}

Outlab Badge Gradient

.outlab-badge {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 0.25rem 0.75rem;
  border-radius: 12px;
  font-weight: 600;
  box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3);
}

📱 Responsive Behavior

Desktop (> 1200px)

  • Statistics: 3 columns (auto-fit)
  • Results Table: Full width, all columns visible
  • Modal: 900px width

Tablet (768px - 1200px)

  • Statistics: 2-3 columns (wrap)
  • Results Table: Horizontal scroll
  • Modal: 90% width

Mobile (< 768px)

  • Statistics: 1 column (stack)
  • Results Table: Hide checkbox/action columns
  • Modal: 95% width, simplified layout

💡 Best Practices (UX/UI)

  1. Critical Visibility:
  2. Red alert banner at order and modal level
  3. Pulse animation on critical badges
  4. Force review before approval

  5. Partial Approval Flexibility:

  6. Can approve/reject individual items
  7. Quick actions ([✓]/[✗]) for single-item workflows
  8. Bulk selection for batch workflows

  9. Clear Rejection Workflow:

  10. Required reason text (cannot bypass)
  11. Option to send back to Specimen Collection vs Result Entry
  12. Clear warning messages

  13. Outlab Integration:

  14. Visual distinction (gradient badge, icon)
  15. Show PDF/Image attachment details
  16. Support mixed orders (some in-house, some outlab)

🐛 Known Issues & Limitations

  1. No PDF Preview: Cannot preview PDF in browser (Future: Inline PDF viewer)
  2. No Batch Edit: Cannot edit rejection reason for multiple items at once
  3. No Approval History: No audit log of who approved what when (Future: Audit table)
  4. No Re-approval: Once rejected, cannot un-reject (must re-enter result)
  5. No Notification: No real-time notification to doctor (Future: SMS/Email)

📊 Business Rules

  1. Approval Requirements:
  2. Only preliminary results can be approved
  3. Must be Lab Supervisor/Lab Chief role
  4. Critical results must be notified to doctor immediately

  5. Partial Approval Rules:

  6. Can approve subset of results in an order
  7. Order status remains pending_approval until all results approved
  8. Once all results approved → Order status = approved

  9. Rejection Rules:

  10. Must provide rejection reason (free text)
  11. If "Require New Specimen" → Order/Specimen status = rejected_specimen
  12. If not → Result status = rejected (back to Result Entry)
  13. Rejected results cannot be approved (must re-enter)

  14. Critical Result Rules:

  15. Show red alert banner
  16. Display in statistics dashboard
  17. Success message includes critical count + reminder to notify doctor
  18. No auto-notification (manual responsibility)

  19. Outlab Result Rules:

  20. Must have file attachment (PDF/Image)
  21. Can approve outlab results same as in-house
  22. Show attachment details in approval modal

7. Utility Pages - เครื่องมือช่วยเหลือ (Debugging & Admin)

📌 ภาพรวม

หน้าสำหรับ Developer/Admin ใช้ในการตรวจสอบ, จัดการ, และดีบักข้อมูล Mock Data ในระบบแล็บ


7.1 debug-lab-data.html - ตรวจสอบข้อมูลแล็บ

🎯 วัตถุประสงค์

  • Debug Tool: ตรวจสอบสถานะข้อมูลใน LocalStorage
  • Statistics Dashboard: แสดงสรุปจำนวน Orders/Results แบบละเอียด
  • Data Management: สร้าง/รีเซ็ต/ลบข้อมูล Mock Data
  • Data Inspection: ตรวจสอบโครงสร้างข้อมูล JSON
  • Export Data: ดาวน์โหลดข้อมูลเป็นไฟล์ JSON

📊 File Statistics

  • Total Lines: 409 lines
  • Components: Statistics Dashboard, Action Buttons, Data Viewers, LocalStorage Inspector
  • Dependencies: LabOrder.js, LabResult.js, LabOrderService, LabResultService

🧩 UI Components

Section 1: Statistics Dashboard (6 Cards)

┌─────────────────────────────────────────────────────────────┐
│  📊 สถิติข้อมูล                                             │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                    │
│  │    25    │ │    12    │ │    45    │                    │
│  │  Total   │ │ ส่ง LIS  │ │  Total   │                    │
│  │  Orders  │ │  แล้ว    │ │ Results  │                    │
│  └──────────┘ └──────────┘ └──────────┘                    │
│   🟢 Green     🔵 Blue       🔵 Blue                        │
│                                                             │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐                    │
│  │    8     │ │    3     │ │    28    │                    │
│  │   รอ     │ │ Critical │ │ Approved │                    │
│  │ Approve  │ │          │ │          │                    │
│  └──────────┘ └──────────┘ └──────────┘                    │
│   🟡 Yellow    🔴 Red       🟢 Green                        │
└─────────────────────────────────────────────────────────────┘

Statistics Calculation:

function refreshStats() {
  const orders = labOrderService.getAllOrders();
  const results = labResultService.getAllResults();

  const ordersByStatus = {
    pending: orders.filter(o => o.status === 'pending').length,
    confirmed: orders.filter(o => o.status === 'confirmed').length,
    sent_to_lis: orders.filter(o => o.status === 'sent_to_lis').length,
    in_progress: orders.filter(o => o.status === 'in_progress').length,
    completed: orders.filter(o => o.status === 'completed').length
  };

  const resultsByStatus = {
    pending: results.filter(r => r.status === 'pending').length,
    preliminary: results.filter(r => r.status === 'preliminary').length,
    final: results.filter(r => r.status === 'final').length,
    critical: results.filter(r => r.isCritical && r.isCritical()).length
  };

  // Display 6 stat boxes...
}


Section 2: Data Management Actions (4 Buttons)

┌─────────────────────────────────────────────────────────────┐
│  ⚙️ การจัดการข้อมูล                                        │
├─────────────────────────────────────────────────────────────┤
│  [➕ สร้าง Mock Data] [🔄 รีเซ็ตข้อมูลทั้งหมด]            │
│  [🗑️ ลบข้อมูลทั้งหมด] [↻ รีเฟรชสถิติ]                    │
│                                                             │
│  ✅ สร้าง Mock Data สำเร็จ!                                 │
└─────────────────────────────────────────────────────────────┘

Button Functions:

2.1 [➕ สร้าง Mock Data] (Green)

async function createMockData() {
  showMessage('กำลังสร้าง Mock Data...', 'info');

  try {
    // Create orders first
    const ordersResult = await labOrderService.createMockOrders();
    console.log('Orders:', ordersResult);

    // Create preliminary results
    const resultsResult = await labResultService.createMockPreliminaryResults();
    console.log('Results:', resultsResult);

    refreshStats();
    showMessage('✅ สร้าง Mock Data สำเร็จ!', 'success');
  } catch (error) {
    console.error('Error:', error);
    showMessage('❌ เกิดข้อผิดพลาด: ' + error.message, 'danger');
  }
}

2.2 [🔄 รีเซ็ตข้อมูลทั้งหมด] (Orange)

async function resetAllData() {
  if (!confirm('ต้องการรีเซ็ตข้อมูลทั้งหมดหรือไม่?')) return;

  showMessage('กำลังรีเซ็ตข้อมูล...', 'info');

  try {
    // Clear localStorage
    localStorage.removeItem('his_lab_orders');
    localStorage.removeItem('his_lab_metadata');
    localStorage.removeItem('his_lab_results');
    localStorage.removeItem('his_lab_results_metadata');

    // Reinitialize services
    await labOrderService.initializeData();
    await labResultService.initializeData();

    // Create fresh mock data
    await labOrderService.createMockOrders();
    await labResultService.createMockPreliminaryResults();

    refreshStats();
    showMessage('✅ รีเซ็ตข้อมูลสำเร็จ! กรุณารีเฟรชหน้าอื่นๆ', 'success');
  } catch (error) {
    console.error('Error:', error);
    showMessage('❌ เกิดข้อผิดพลาด: ' + error.message, 'danger');
  }
}

2.3 [🗑️ ลบข้อมูลทั้งหมด] (Red)

function clearAllData() {
  if (!confirm('ต้องการลบข้อมูลทั้งหมดหรือไม่? (จะไม่สร้างข้อมูลใหม่)')) return;

  // Remove all lab-related keys
  localStorage.removeItem('his_lab_orders');
  localStorage.removeItem('his_lab_metadata');
  localStorage.removeItem('his_lab_results');
  localStorage.removeItem('his_lab_results_metadata');

  refreshStats();
  showMessage('✅ ลบข้อมูลทั้งหมดแล้ว', 'warning');
}

2.4 [↻ รีเฟรชสถิติ] (Blue) - Call refreshStats() function - Update all 6 stat boxes


Section 3: Orders List Viewer

┌─────────────────────────────────────────────────────────────┐
│  📋 รายการ Orders                                           │
│  [โหลดรายการ]                                               │
├─────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────────────────────────────┐ │
│  │ LAB-20241226-0001 - นายสมชาย ใจดี                     │ │
│  │ Status: sent_to_lis | Priority: STAT | Items: 5       │ │
│  └────────────────────────────────────────────────────────┘ │
│  (Border: Green = confirmed, Yellow = pending, Gray = completed)│
└─────────────────────────────────────────────────────────────┘

Load Function:

function loadOrders() {
  const orders = labOrderService.getAllOrders();
  const html = orders.map(order => `
    <div class="order-item ${order.status}">
      <strong>${order.orderNumber}</strong> - ${order.patientName || '-'}<br>
      <small>Status: ${order.status} | Priority: ${order.priority} | Items: ${order.items.length}</small>
    </div>
  `).join('');

  document.getElementById('ordersList').innerHTML = html || '<em>ไม่มีข้อมูล</em>';
}


Section 4: Results List Viewer

┌─────────────────────────────────────────────────────────────┐
│  🧪 รายการ Results                                          │
│  [โหลดรายการ]                                               │
├─────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────────────────────────────┐ │
│  │ LAB-20241226-0001 - WBC                                │ │
│  │ Value: 8.5 | Status: preliminary                      │ │
│  └────────────────────────────────────────────────────────┘ │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ LAB-20241226-0001 - Platelet    ⚠️ CRITICAL           │ │
│  │ Value: 50 | Status: preliminary                       │ │
│  └────────────────────────────────────────────────────────┘ │
│  (Border: Yellow = preliminary, Green = final, Red = critical)│
└─────────────────────────────────────────────────────────────┘

Load Function:

function loadResults() {
  const results = labResultService.getAllResults();
  const html = results.map(result => {
    const critical = result.isCritical && result.isCritical();
    return `
      <div class="result-item ${result.status} ${critical ? 'critical' : ''}">
        <strong>${result.orderNumber}</strong> - ${result.labItemName}<br>
        <small>Value: ${result.resultValue || '-'} | Status: ${result.status} ${critical ? '⚠️ CRITICAL' : ''}</small>
      </div>
    `;
  }).join('');

  document.getElementById('resultsList').innerHTML = html || '<em>ไม่มีข้อมูล</em>';
}


Section 5: LocalStorage Inspector

┌─────────────────────────────────────────────────────────────┐
│  💾 LocalStorage Inspector                                  │
│  [ตรวจสอบ LocalStorage] [ดาวน์โหลดข้อมูล (JSON)]          │
├─────────────────────────────────────────────────────────────┤
│  orders:                                                    │
│  {                                                          │
│    "orders": [                                              │
│      {                                                      │
│        "id": 1,                                             │
│        "orderNumber": "LAB-20241226-0001",                  │
│        "hn": "HN000123",                                    │
│        ...                                                  │
│      }                                                      │
│    ]                                                        │
│  }                                                          │
│                                                             │
│  ordersMeta:                                                │
│  { "nextId": 26, "lastUpdated": "2024-12-26T..." }         │
└─────────────────────────────────────────────────────────────┘

Inspection Function:

function inspectLocalStorage() {
  const data = {
    orders: localStorage.getItem('his_lab_orders'),
    ordersMeta: localStorage.getItem('his_lab_metadata'),
    results: localStorage.getItem('his_lab_results'),
    resultsMeta: localStorage.getItem('his_lab_results_metadata')
  };

  const html = Object.entries(data).map(([key, value]) => {
    if (!value) return `<div><strong>${key}:</strong> <em>null</em></div>`;
    const parsed = JSON.parse(value);
    return `
      <div style="margin-bottom: 1rem;">
        <strong>${key}:</strong>
        <pre style="background: white; padding: 0.5rem; border-radius: 4px; margin-top: 0.25rem; overflow-x: auto;">
${JSON.stringify(parsed, null, 2)}
        </pre>
      </div>
    `;
  }).join('');

  document.getElementById('localStorageData').innerHTML = html;
}

Download Function:

function downloadData() {
  const data = {
    orders: labOrderService.getAllOrders().map(o => o.toJSON ? o.toJSON() : o),
    results: labResultService.getAllResults().map(r => r.toJSON ? r.toJSON() : r),
    metadata: {
      orders: labOrderService.getMetadata(),
      results: labResultService.getMetadata()
    },
    exportedAt: new Date().toISOString()
  };

  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `lab-data-${new Date().getTime()}.json`;
  a.click();
  URL.revokeObjectURL(url);

  showMessage('✅ ดาวน์โหลดข้อมูลแล้ว', 'success');
}

Output File Example:

{
  "orders": [...],
  "results": [...],
  "metadata": {
    "orders": { "nextId": 26, "lastUpdated": "2024-12-26T..." },
    "results": { "nextId": 101, "lastUpdated": "2024-12-26T..." }
  },
  "exportedAt": "2024-12-26T14:30:00.000Z"
}


💡 Use Cases

  1. Development Testing: สร้าง Mock Data เพื่อทดสอบหน้า UI
  2. Bug Investigation: ตรวจสอบข้อมูลใน LocalStorage เมื่อเกิดข้อผิดพลาด
  3. Data Reset: รีเซ็ตข้อมูลเมื่อต้องการเริ่มต้นใหม่
  4. Data Export: ดาวน์โหลดข้อมูลเพื่อวิเคราะห์หรือสำรองข้อมูล
  5. Statistics Monitoring: ตรวจสอบจำนวนข้อมูลในแต่ละ Status

7.2 clear-storage.html - ล้างข้อมูล LocalStorage (Quick Reset)

🎯 วัตถุประสงค์

  • Quick Clear: ล้างข้อมูล LAB LocalStorage อย่างรวดเร็ว
  • Simple UI: หน้าเดียวจบ ไม่ซับซ้อน
  • Redirect: เปลี่ยนหน้าไปยัง Order Queue เพื่อสร้างข้อมูลใหม่

📊 File Statistics

  • Total Lines: ~100 lines (สั้น, minimal)
  • Components: Clear Button, Redirect Button, Success Message
  • Dependencies: None (Pure HTML + JavaScript)

🧩 UI Layout

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│                   🧹 ล้างข้อมูล LocalStorage               │
│                                                             │
│         ล้างข้อมูล LAB Orders ทั้งหมดใน LocalStorage      │
│         และสร้างข้อมูลทดสอบใหม่อีกครั้ง                   │
│                                                             │
│         [🗑️ ล้างข้อมูล LocalStorage]                      │
│                                                             │
│         [→ ไปหน้า Order Queue]                              │
│                                                             │
│         ✅ ล้างข้อมูลสำเร็จ!                                │
│         กรุณาไปหน้า Order Queue เพื่อสร้างข้อมูลทดสอบใหม่  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
Background: Gradient Purple-Blue

⚙️ Functions

Clear Storage Function:

function clearStorage() {
  // Clear LAB-related LocalStorage keys
  localStorage.removeItem('his_lab_orders');
  localStorage.removeItem('his_lab_metadata');
  localStorage.removeItem('his_specimens');
  localStorage.removeItem('his_specimen_metadata');

  // Show success message
  const result = document.getElementById('result');
  result.className = 'success';
  result.style.display = 'block';
  result.innerHTML = `
    <strong>✅ ล้างข้อมูลสำเร็จ!</strong><br>
    <small>กรุณาไปหน้า Order Queue เพื่อสร้างข้อมูลทดสอบใหม่</small>
  `;
}

Redirect Function:

function goToQueue() {
  window.location.href = 'order-queue.html';
}


🎨 Design System

Centered Card Layout:

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.container {
  background: white;
  padding: 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  text-align: center;
  max-width: 500px;
}

Button Styling:

.btn-danger {
  background: #e74c3c;
  color: white;
  padding: 1rem 2rem;
  font-size: 1.1rem;
  border-radius: 8px;
}

.btn-danger:hover {
  background: #c0392b;
  transform: translateY(-2px);
}

.btn-primary {
  background: #667eea;
  color: white;
}


💡 Use Cases

  1. Quick Reset During Development: ลบข้อมูลเก่าและเริ่มต้นใหม่อย่างรวดเร็ว
  2. Demo Preparation: เคลียร์ข้อมูลก่อน Demo
  3. Testing Fresh State: ทดสอบระบบในสถานะเริ่มต้น (ไม่มีข้อมูลเก่า)

📊 Comparison: debug-lab-data.html vs clear-storage.html

Feature debug-lab-data.html clear-storage.html
Purpose Comprehensive debugging Quick reset
UI Complexity Complex (6 sections) Minimal (1 card)
Statistics ✅ Yes (6 cards) ❌ No
Data Viewers ✅ Yes (Orders, Results) ❌ No
LocalStorage Inspector ✅ Yes ❌ No
Export JSON ✅ Yes ❌ No
Create Mock Data ✅ Yes ❌ No
Clear Data ✅ Yes (+ Reset options) ✅ Yes (Simple clear)
Target User Developer Anyone (Simple UI)
Dependencies Lab Services None

🔧 Technical Notes

LocalStorage Keys (Lab Module)

// Orders
'his_lab_orders'         // Array of LabOrder objects
'his_lab_metadata'       // { nextId, lastUpdated }

// Results
'his_lab_results'        // Array of LabResult objects
'his_lab_results_metadata' // { nextId, lastUpdated }

// Specimens
'his_specimens'          // Array of Specimen objects
'his_specimen_metadata'  // { nextId, nextSpecimenNumber }

When to Use Each Tool

Use debug-lab-data.html when: - Investigating data corruption issues - Need to see statistics breakdown - Want to export data for analysis - Need to inspect JSON structure - Testing service methods

Use clear-storage.html when: - Quick reset needed - Starting fresh demo - Simple clear operation without inspection - No need for data export


🐛 Known Limitations

  1. No Undo: ลบแล้วไม่สามารถกู้คืนได้ (ต้องสร้าง Mock Data ใหม่)
  2. No Selective Delete: ไม่สามารถลบเฉพาะ Order/Result ที่เลือก (ลบทั้งหมด)
  3. No Import: ไม่สามารถนำเข้าข้อมูลจากไฟล์ JSON (Export only)
  4. No Backup: ไม่มีระบบสำรองข้อมูลอัตโนมัติ
  5. Browser-Specific: ข้อมูลอยู่ใน Browser localStorage (ไม่ sync ข้าม Browser)

💡 Future Enhancements (Suggestions)

  1. Import Data: นำเข้าข้อมูลจากไฟล์ JSON
  2. Selective Delete: ลบเฉพาะ Order/Result ที่เลือก
  3. Auto Backup: สำรองข้อมูลอัตโนมัติทุก X นาที
  4. Data Validation: ตรวจสอบความถูกต้องของข้อมูล
  5. Performance Metrics: แสดงขนาดข้อมูลใน LocalStorage (KB/MB)
  6. Visual Diff: เปรียบเทียบข้อมูลก่อน-หลัง Reset

สรุป 04-PAGES-FEATURES.md

เอกสารนี้ครอบคลุม 7 หน้าหลักในระบบ LAB:

  1. index.html: Landing Page + Navigation Hub (4 sections)
  2. doctor-order.html: Doctor Order Entry (Patient search, Lab selection, Print)
  3. order-queue.html: Order Queue Management (Statistics, Filters, Modals)
  4. specimen-collection.html: Specimen Collection (QC, Numbering, Print Labels)
  5. result-entry.html: Result Entry (Delta Check, Critical Alert, Outlab Upload)
  6. result-approval.html: Result Approval (Partial Approval, Rejection)
  7. debug-lab-data.html + clear-storage.html: Debugging Tools

จำนวนบรรทัดทั้งหมด: ~7,500+ บรรทัด

Target Audience: UX/UI Team, Product Owners, QA Testers, Developers

ถัดไป: สร้าง 05-DATA-STATUS-FLOW.md (Data Models + Status Transitions)