# Deploy: แก้ระบบ autotext + กันหน้าเว็บค้าง (2026-08-10)

เอกสารนี้ครอบคลุม **เฉพาะงาน autotext และการโหลด socket.io** เท่านั้น ไม่เกี่ยวกับฟีเจอร์อื่น

---

## 1. ปัญหาที่แก้

### 1.1 หน้า procedure ค้างกลางคัน — ทุกอย่างในครึ่งหลังของหน้าไม่ทำงาน

`resources/views/case/component/officer.blade.php` โหลด socket.io แบบ synchronous จากโดเมนนอกที่ตายแล้ว

```html
<script src="http://screening.endocapture.asia:3000/socket.io/socket.io.js"></script>
```

โดเมนนี้ต่อไม่ติด (TCP timeout) เบราว์เซอร์จึง**หยุด parse HTML ค้างตรงนั้นถาวร** วัดผลจริงได้ว่า:

| | ก่อนแก้ | หลังแก้ |
|---|---|---|
| `document.readyState` | `loading` ค้างตลอด (วัดถึง 38 วินาที) | `complete` |
| HTML ที่ parse ได้ | 748,823 จาก 1,090,592 bytes (69%) | ครบ |

โค้ด autotext อยู่บรรทัด 13,400 ส่วนจุดค้างอยู่บรรทัด 12,933 → **ไม่มีทางถูกรัน** ไม่ใช่แค่ autotext เท่านั้น แต่คือ**ทุกสคริปต์ใน `@section('script')`**

### 1.2 jQuery ถูกโหลดซ้ำ ทับปลั๊กอินทิ้ง

`reportwrite/photo/all.blade.php` และ `capture/home/index.blade.php` โหลด jQuery ซ้ำ **หลังจาก** ปลั๊กอิน dropdown ลงทะเบียนไปแล้ว ทำให้ `$.fn.inputDropdown` หายทั้งหมด

```
TypeError: $(...).inputDropdown is not a function
```

### 1.3 ตัว autotext เองเปราะและใช้งานไม่สะดวก

- ผูกกับ jQuery + ปลั๊กอินที่ถูกทับได้ (ข้อ 1.2)
- `bind` แบบ static → ช่องที่สร้างทีหลังด้วย ajax ไม่ทำงาน
- หยุดแนะนำเมื่อพิมพ์ในช่องเดิมเกิน 10 ครั้ง
- dropdown ไม่เลื่อนตามเมื่อ scroll และ append `<ul>` ใหม่ทุก keystroke (DOM รั่ว + id ซ้ำ)

### 1.4 จำข้อความผิดจังหวะ

ระบบจำข้อความลง `tb_autotext` ทุกครั้งที่ออกจากช่อง (focusout) ทำให้คำที่พิมพ์ผิดหรือพิมพ์ค้างไว้ถูกจำไปด้วย และจำจาก**ทุกช่องที่มี class `savejson`** ไม่ใช่แค่ช่อง autotext

---

## 2. สิ่งที่เปลี่ยนไปในมุมผู้ใช้

| | เดิม | ใหม่ |
|---|---|---|
| เริ่มแนะนำเมื่อ | คลิกหรือพิมพ์ตัวแรก | พิมพ์ครบ **4 ตัวอักษร** |
| พิมพ์ยาว | หยุดแนะนำหลังพิมพ์ในช่องเดิมเกิน 10 ครั้ง | แนะนำต่อเนื่อง (ใช้ debounce 180ms แทน) |
| เลือกรายการ | คลิกเมาส์ | คลิก หรือ ↑ ↓ + Enter, Esc ปิด |
| เอาคำแนะนำที่ไม่ใช้ออก | ไม่มี | กดปุ่ม **×** ท้ายรายการ (ซ่อน ไม่ได้ลบข้อมูล) |
| dropdown ตอน scroll | ค้างอยู่กับที่ | เลื่อนตามช่อง |
| จำข้อความ | ทุกครั้งที่ออกจากช่อง | **ตอนกดปุ่ม Create Report เท่านั้น** |

> Create Report มี validation "กรุณาระบุแพทย์" อยู่ก่อน — เคสที่ยังไม่เลือกแพทย์จะกดไม่ผ่าน จึงยังไม่จำ

---

## 3. รายการไฟล์

### ไฟล์ใหม่ (2)

| ไฟล์ | หน้าที่ |
|---|---|
| `public/js/autotext.js` | โมดูล autotext เขียนใหม่ทั้งหมดด้วย vanilla JS |
| `resources/views/components/socket_boot.blade.php` | ตัวโหลด socket.io แบบไม่บล็อกการ parse หน้า |

### ไฟล์ที่แก้ (15)

**กลุ่ม A — โครงสร้าง URL / การโหลด socket.io**

| ไฟล์ | แก้อะไร |
|---|---|
| `app/Helpers/Path.php` | เพิ่มฟังก์ชัน `socketurl()` |
| `resources/views/case/component/officer.blade.php` | เปลี่ยนไปใช้ `@include('components.socket_boot')` |
| `resources/views/capture/dashboard02/main.blade.php` | เหมือนกัน |
| `resources/views/capture/home/index.blade.php` | เหมือนกัน + ลบ jQuery ซ้ำ |
| `resources/views/capture/layout/main.blade.php` | เหมือนกัน |
| `resources/views/capture/patient/registration.blade.php` | เหมือนกัน |
| `resources/views/capture/reportendocapture/component/modal_sendto2.blade.php` | เหมือนกัน |
| `resources/views/capture/camera/obs/js_onload.blade.php` | เหมือนกัน |
| `resources/views/case/component/reportwrite/photo/all.blade.php` | ลบ jQuery ซ้ำ |

**กลุ่ม B — ตัว autotext**

| ไฟล์ | แก้อะไร |
|---|---|
| `resources/views/case/script.blade.php` | ลบ inline script เดิม (~100 บรรทัด) เหลือแค่ config + โหลด `autotext.js` |
| `resources/views/case/component/photo/photoselect.blade.php` | ลบ handler `.photo_autotext` เดิม (โมดูลใหม่ดูแลแทน) |
| `resources/views/case/component/diagnostic.blade.php` | ลบ handler `autotextgroup_save` เดิม |

**กลุ่ม C — ย้ายจุดจำข้อความ**

| ไฟล์ | แก้อะไร |
|---|---|
| `app/Http/Controllers/Capture/ProcedureController.php` | เพิ่ม `memorise_autotext()` เรียกจาก `caserecord()` |
| `app/Http/Controllers/Api/ProcedureController.php` | เอา insert `tb_autotext` ออกจาก `savejson()` และ `savejsongroup()` |

**กลุ่ม D — ซ่อนคำแนะนำ**

| ไฟล์ | แก้อะไร |
|---|---|
| `app/Http/Controllers/Api/PhotoController.php` | `jqinputdropdown()` กรอง `hidden` ออก + เพิ่มเมธอด `hideautotext()` |

### ⚠️ ไฟล์ที่ห้ามเอาไปด้วย

ตอนทำงานพบว่าใน working tree มีไฟล์ที่ถูกแก้ค้างไว้อยู่ก่อนแล้ว **ไม่เกี่ยวกับงานนี้** อย่าเอาขึ้น production พร้อมกัน:

```
app/Http/Controllers/EndoCAPTURE/PdfPacsController.php
app/Http/Controllers/EndoCAPTURE/PrintPDFController.php
resources/views/printpdf/index.blade.php
resources/views/printpdf/table/            (ไดเรกทอรีใหม่)
resources/views/capture/home/table/07alltoday.blade.php
```

---

## 4. ขั้นตอน deploy

### 4.1 สร้าง patch เฉพาะงานนี้

```bash
cd /path/to/screening

git diff -- \
  app/Helpers/Path.php \
  app/Http/Controllers/Api/ProcedureController.php \
  app/Http/Controllers/Api/PhotoController.php \
  app/Http/Controllers/Capture/ProcedureController.php \
  resources/views/case/script.blade.php \
  resources/views/case/component/officer.blade.php \
  resources/views/case/component/diagnostic.blade.php \
  resources/views/case/component/photo/photoselect.blade.php \
  resources/views/case/component/reportwrite/photo/all.blade.php \
  resources/views/capture/layout/main.blade.php \
  resources/views/capture/home/index.blade.php \
  resources/views/capture/dashboard02/main.blade.php \
  resources/views/capture/patient/registration.blade.php \
  resources/views/capture/camera/obs/js_onload.blade.php \
  resources/views/capture/reportendocapture/component/modal_sendto2.blade.php \
  > autotext-fix.patch
```

### 4.2 ที่เครื่อง production

```bash
# สำรองก่อน
git stash list && git status

# ลง patch
git apply --check autotext-fix.patch   # ตรวจก่อนว่าลงได้
git apply autotext-fix.patch

# คัดลอกไฟล์ใหม่ 2 ไฟล์
#   public/js/autotext.js
#   resources/views/components/socket_boot.blade.php

# ล้าง cache ของ blade
php artisan view:clear
php artisan config:clear
```

> ไม่ต้องรัน migration และไม่ต้องแตะ composer — ไม่มีการเพิ่ม dependency

### 4.3 ตั้งค่า realtime server (สำคัญสำหรับ cloud)

`socketurl()` อ่านค่าจาก **`tb_config` collection `admin` field `socket_url`** ค่าเดียว

| ค่า `socket_url` | URL ที่ได้ | ใช้เมื่อ |
|---|---|---|
| ไม่มี field นี้ / เป็น `null` | `//<host ของหน้าเว็บ>:3000` | เครื่องโรงพยาบาล, dev (ค่าเริ่มต้น ไม่ต้องตั้งอะไร) |
| `""` (สตริงว่าง) | `/socket.io/socket.io.js` แบบ same-origin | cloud ที่มี reverse proxy หน้า node |
| `"https://rt.example.com"` | ใช้ค่านั้นตรง ๆ | แยก service ออกไปคนละโดเมน |

ตัวอย่างสำหรับ cloud หลัง reverse proxy:

```js
db.tb_config.updateOne(
  { config_type: "admin" },
  { $set: { socket_url: "" } }
)
```

**ห้าม hardcode `http://` ในหน้า blade อีก** — ถ้าเว็บรันบน HTTPS จะโดน mixed-content block และถ้า host ต่อไม่ติดจะทำให้หน้าค้างซ้ำรอยเดิม

---

## 5. Checklist ตรวจหลัง deploy

เปิดหน้า procedure แล้วเปิด DevTools

- [ ] Console ไม่มี error
- [ ] `document.readyState` เป็น `complete`
- [ ] `[...document.scripts].filter(s => /jquery/i.test(s.src)).length` → ควรได้ **1**
- [ ] `typeof io` → `function` (ถ้า realtime server ทำงาน) หรือ `undefined` พร้อม warning `[socket] โหลด socket.io ไม่ได้` — **กรณีหลังหน้าเว็บต้องยังใช้งานได้ปกติ**
- [ ] `window.AUTOTEXT_CONFIG` มีค่าครบ และ `enabled: true`
- [ ] พิมพ์ 3 ตัวอักษรในช่อง Other Diagnosis → **ไม่มี** dropdown
- [ ] พิมพ์ตัวที่ 4 → dropdown ขึ้น
- [ ] กด ↑ ↓ เลือกได้ Enter ใช้ค่า Esc ปิด
- [ ] scroll หน้าจอ → dropdown เลื่อนตามช่อง
- [ ] คลิกที่ตัวข้อความ → ค่าเข้าช่อง dropdown ปิด และ**ต้องไม่ลบคำแนะนำนั้น**
- [ ] คลิกปุ่ม × ท้ายรายการ → คำหายจากรายการ ขึ้นข้อความยืนยัน และ**ข้อความที่พิมพ์ไว้ในช่องต้องไม่หาย**
- [ ] เอกสารใน `tb_autotext` ที่กด × ต้อง**ยังอยู่** แค่มี `hidden: true` — ไม่ใช่ถูกลบทิ้ง
- [ ] พิมพ์คำใหม่แล้วออกจากช่อง → `tb_autotext` **ยังไม่เพิ่ม**
- [ ] กด Create Report → `tb_autotext` เพิ่มเฉพาะคำที่ยังไม่มี

---

## 6. Rollback

```bash
git apply -R autotext-fix.patch
rm public/js/autotext.js
rm resources/views/components/socket_boot.blade.php
php artisan view:clear
```

ไม่มีการเปลี่ยนโครงสร้างข้อมูล จึงไม่ต้อง rollback DB — ยกเว้นถ้าตั้ง `socket_url` ไว้ก็ลบ field นั้นทิ้งได้

---

## 7. การปรับแต่ง

แก้ที่หัวไฟล์ `public/js/autotext.js`

```js
var MIN_CHARS   = 4;     // จำนวนตัวอักษรขั้นต่ำก่อนขึ้นคำแนะนำ
var DEBOUNCE_MS = 180;   // หน่วงก่อนยิง query
var MAX_RENDER  = 50;    // แสดงสูงสุดกี่รายการ
```

ปรับหน้าตาปุ่ม × : แก้ CSS คลาส `.autotext-del` ในฟังก์ชัน `injectStyle()` ไฟล์เดียวกัน

ปิด/เปิดทั้งระบบ: `tb_config.admin.system_autotext` (`"true"` / `"false"`) — เหมือนเดิม ไม่ได้เปลี่ยน

---

## 8. หมายเหตุทางเทคนิค

**ทำไม `socket_boot` ถึงไม่พังโค้ดเดิม** — มันสร้าง `window.socket` เป็น stub ที่คิว `emit`/`on` ไว้ก่อน แล้วค่อยส่งต่อให้ตัวจริงเมื่อ socket.io โหลดเสร็จ (ตอน `DOMContentLoaded`) โค้ดเดิมที่เรียก `socket.emit(...)` จึงไม่ต้องแก้

**ปุ่ม × ลบคำแนะนำ** — แต่ละรายการใน dropdown เป็น `<li>` ที่มี `<span class="autotext-text">` กับ `<span class="autotext-del">` ตัวจัดการ `mousedown` เช็คก่อนว่าคลิกโดน × หรือไม่ ถ้าโดนก็เรียก `deleteValue()` ถ้าไม่โดนก็เรียก `choose()` ตามปกติ ใช้ `mousedown` แทน `click` เพราะ `click` จะมาหลัง blur ซึ่งปิด dropdown ไปแล้ว และ `preventDefault()` เพื่อให้เคอร์เซอร์ยังอยู่ในช่องเดิม

> เคยลองทำเป็นคีย์ลัด Ctrl+Delete ก่อน แต่มันชนกับคำสั่ง "ลบคำถัดไป" ของเบราว์เซอร์ ซึ่ง `preventDefault()` เอาไม่อยู่ในทุกกรณี ต้องเขียนโค้ดกู้ข้อความมาสู้ จึงเปลี่ยนมาใช้ปุ่ม × แทน — ตรงไปตรงมาและไม่ต้องมีโค้ดกันพลาด

**การจำข้อความทำงานยังไง** — `autotext.js` รวบรวมค่าจากช่อง autotext ทั้งหมดตอน submit ใส่ hidden input ชื่อ `autotext_memo` (JSON) → `Capture\ProcedureController::caserecord()` ส่งต่อให้ `memorise_autotext()` บันทึกทีเดียว เช็คซ้ำก่อนใส่ และเคารพสวิตช์ `system_autotext`

**ปุ่ม × = ซ่อน ไม่ใช่ลบ** — `hideautotext()` แค่ `$set` ธงลงเอกสารเดิม ไม่ได้ `delete` ทิ้ง

```js
{ hidden: true, hidden_at: "2026-08-10 13:30:37", hidden_by: 1 }
```

ค่าใช้จ่ายด้านพื้นที่ประมาณ 60 bytes ต่อแถวที่ถูกซ่อน (ทั้ง collection ตอนนี้มีหมื่นกว่าแถว) และตัวเอกสารเดิมยังอยู่ครบ

`jqinputdropdown()` กรองด้วย `where('hidden','!=',true)` ซึ่งใน MongoDB แปลเป็น `$ne` — จึงครอบคลุมเอกสารเก่าที่ยังไม่มี field นี้โดยไม่ต้อง migrate อะไรเลย

**ปลดซ่อน** — ทำผ่าน DB โดยตรง (ยังไม่มีหน้า UI ให้จัดการ)

```js
// ดูว่ามีอะไรถูกซ่อนไว้บ้าง
db.tb_autotext.find({ hidden: true })

// ปลดซ่อนคำใดคำหนึ่ง
db.tb_autotext.updateOne(
  { auto_text: "ข้อความนั้น", auto_textid: "prediagnostic_other", auto_procedure: "gi002" },
  { $unset: { hidden: "", hidden_at: "", hidden_by: "" } }
)

// ปลดซ่อนทั้งหมด
db.tb_autotext.updateMany({ hidden: true }, { $unset: { hidden: "", hidden_at: "", hidden_by: "" } })
```

> คำที่ถูกซ่อนแล้วจะไม่กลับมาเองแม้หมอจะพิมพ์คำนั้นซ้ำและกด Create Report อีกครั้ง (`memorise_autotext()` เจอว่ามีเอกสารอยู่แล้วจึงข้าม ไม่สร้างซ้ำ) ถ้าต้องการให้กลับมาต้องปลดซ่อนตามคำสั่งข้างบน

---

## 9. เรื่องที่ยังไม่ได้ทำ

**9.1 หน้าอื่นที่ใช้ `savejson` ร่วมกันจะไม่จำคำแล้ว**

`Api\ProcedureController::savejson()` เป็นเมธอดกลาง หน้าที่เรียกใช้แล้วได้รับผลกระทบ:

```
resources/views/capture/patient/registration.blade.php
resources/views/capture/reportendocapture/component/ordata.blade.php
resources/views/capture/case/component/*.blade.php
```

ถ้าหน้าไหนต้องการให้จำอยู่ ต้องย้ายไปผูกกับปุ่มบันทึกของหน้านั้นแบบเดียวกับ `caserecord()`

**9.2 `tb_autotext` ไม่มี index เลย (มีแค่ `_id_`)**

ทุกครั้งที่พิมพ์คือ full scan + regex บนเอกสารหมื่นกว่ารายการ บนเครื่องเดียวยังไหว แต่บน cloud หลาย tenant จะเป็นคอขวด แนะนำ:

```js
db.tb_autotext.createIndex({ auto_procedure: 1, auto_textid: 1, auto_text: 1 })
```

ถ้าอยากให้ใช้ index ได้เต็มประสิทธิภาพต้องเปลี่ยนการค้นจาก `like '%คำ%'` เป็น prefix match (`^คำ`) ที่ `Api\PhotoController::jqinputdropdown()` — แต่พฤติกรรมการค้นจะเปลี่ยนไป ต้องตัดสินใจก่อน

**9.3 ยังไม่ได้แยก autotext ตามโรงพยาบาล**

ถ้า cloud ใช้ DB เดียวหลายโรงพยาบาล คำที่ รพ. หนึ่งพิมพ์จะถูกแนะนำให้อีก รพ. เห็น ต้องเพิ่มเงื่อนไข scope

**9.4 ไฟล์ legacy ที่ยัง hardcode โดเมนนอก**

```
resources/views/capture/camera/obs1/01obsbackup.blade.php
resources/views/EndoCAPTURE/camera/component/js.blade.php
resources/views/EndoCAPTURE/camera/new_camera.blade.php
resources/views/EndoCAPTURE/camera/obs/01obs.blade.php
resources/views/EndoCAPTURE/camera/supernewcamera2.blade.php
```

**9.5 ไฟล์ที่จะพังทันทีบน Linux** — 20 ไฟล์ที่เรียก `webconfig('server_name')` ซึ่งอ่าน `D:/laragon/htdocs/config/project/admin.txt` ส่วนใหญ่อยู่ใน `EndoCAPTURE/` แต่มี `endo/expdf.blade.php`, `fileexport/index.blade.php`, `layouts/layout_tack.blade.php` ปนอยู่ด้วย นอกจากนี้ยังมี hardcode path แบบ Windows อีก 86 จุดใน `app/`
