# JobMatch — สรุปโค้ด + วิธีแก้เอง (ฉบับพกไปสอบ)

> อ่านคู่กับ `docs/code_guide.html` (อธิบายโค้ดละเอียดทุกไฟล์)
> ไฟล์นี้เน้น **"อยากแก้ X → ไปไฟล์ไหน บรรทัดแบบไหน"** และ **สิ่งที่แก้ไปล่าสุด**

---

## 1. ภาพรวมระบบ (3 ส่วน)

| ส่วน | อยู่ที่ (เครื่องเรา) | บนเซิร์ฟเวอร์ | ภาษา |
|---|---|---|---|
| **แอป** (ผู้ว่าจ้าง + ผู้หางาน) | `C:\Users\beer\Documents\jobmatch_employer\lib\` | `/251/jobmatch_app/` | Flutter (Dart) |
| **API** (หลังบ้านของแอป) | `C:\xampp\htdocs\jobmatch_api\` | `/251/jobmatch_api/` | PHP + MySQL |
| **เว็บแอดมิน** | `C:\xampp\htdocs\jobmatch_admin\` | `/251/jobmatch_admin/` | PHP |

เซิร์ฟเวอร์: `http://202.28.34.205:8080/251/...` ฐานข้อมูล `db251` (รหัสอยู่ใน `jobmatch_api/db_connect.php` และ `jobmatch_admin/config/database.php` — **ห้ามเผยแพร่**)

การไหลของข้อมูล: **แอป → เรียก API (PHP) → MySQL → ส่ง JSON กลับ → แอปแสดงผล**
แอดมินเป็น PHP ที่ต่อฐานข้อมูลตรง (ไม่ผ่าน API)

---

## 2. วิธีอัปขึ้นเซิร์ฟเวอร์ (สำคัญที่สุด)

### แก้ PHP (API หรือ แอดมิน)
1. แก้ไฟล์ใน `C:\xampp\htdocs\jobmatch_api\` หรือ `...\jobmatch_admin\`
2. ตรวจ syntax: `C:\xampp\php\php.exe -l ชื่อไฟล์.php` (ต้องขึ้น `No syntax errors`)
3. อัปไฟล์นั้นด้วย FileZilla ไปโฟลเดอร์เดียวกันบนเซิร์ฟเวอร์
4. เปิดหน้าเว็บ กด **Ctrl+F5**

### แก้แอป (ไฟล์ .dart)
1. แก้ไฟล์ใน `lib\...`
2. ตรวจ: `flutter analyze` (ต้องไม่มี **error** — warning เก่ามีอยู่แล้ว ไม่เป็นไร)
3. build สำหรับเซิร์ฟเวอร์ (คำสั่งเดียว พิมพ์ในโฟลเดอร์โปรเจกต์):
   ```
   flutter build web --release --base-href /251/jobmatch_app/ --dart-define=API_URL=/251/jobmatch_api
   ```
4. คัดลอก `build\web\*` ไป `upload_to_server\jobmatch_app\` แล้วอัปทั้งโฟลเดอร์ขึ้น `jobmatch_app`
   (อย่างน้อย `main.dart.js` + `index.html`)
5. มือถือ: รีเฟรช / ปิดแท็บแล้วเปิดใหม่

> ⚠️ ห้ามอัป build ที่ทำด้วย `backend\tools\build_web.ps1` ขึ้นเซิร์ฟเวอร์ — อันนั้นสำหรับ localhost (base-href ต่างกัน → หน้าขาว)

### รันในเครื่องตัวเอง (ถ้าเน็ตเซิร์ฟเวอร์มีปัญหา)
- เปิด XAMPP (Apache) หรือ `C:\xampp\php\php.exe -S 127.0.0.1:8099 -t C:\xampp\htdocs`
- แอดมิน: `http://127.0.0.1:8099/jobmatch_admin/`
- แอป: `flutter run -d chrome`

---

## 3. แผนที่โค้ดแอป (`lib/`)

```
lib/
├─ core/
│  ├─ constants/app_constants.dart   ← URL API, ชื่อ endpoint, ข้อความสถานะ (รอดำเนินการ/นัดสัมภาษณ์/...)
│  ├─ theme/app_theme.dart           ← สีทั้งแอป (AppTheme.primary ฯลฯ)
│  └─ utils/formatters.dart          ← formatThaiDate, formatSalary
├─ data/
│  ├─ models/                        ← แปลง JSON → object (job_posting_model, application_model, ...)
│  └─ services/api_service.dart      ← ทุกฟังก์ชันที่เรียก API (getJobs, applyJob, ...)
└─ presentation/pages/
   ├─ home/                          ← ★ ฝั่งผู้ว่าจ้าง
   │  ├─ main_shell.dart             ← แถบเมนูล่าง 5 แท็บ (+ MainShell.goToTab(i) ให้หน้าอื่นสั่งสลับแท็บ)
   │  └─ tabs/dashboard_tab.dart     ← หน้าแรก: การ์ดสถิติ 4 ใบ, ผู้สมัครล่าสุด, การ์ดงาน
   │     tabs/manage_jobs_tab.dart   ← "งานของฉัน" (แก้/ลบประกาศ, แม่กุญแจถ้าปิดรับ)
   │     tabs/chat_tab.dart, notifications_tab.dart, profile_tab.dart
   ├─ jobs/post_job_page.dart        ← สร้าง/แก้ประกาศงาน
   ├─ applications/
   │  ├─ candidate_list_page.dart    ← รายชื่อผู้สมัครของ 1 งาน
   │  └─ candidate_detail_page.dart  ← รายละเอียดผู้สมัคร + ปุ่มนัดสัมภาษณ์/รับเข้าทำงาน/ไม่ผ่าน
   ├─ seeker/                        ← ★ ฝั่งผู้หางาน
   │  ├─ tabs/seeker_home_tab.dart   ← หน้าแรก งานแนะนำ
   │  ├─ tabs/seeker_search_tab.dart ← ค้นหา + ตัวกรอง (ระยะทาง, เงินเดือน, เรียงใกล้สุด)
   │  ├─ tabs/seeker_applications_tab.dart ← ใบสมัครของฉัน (ตอบรับ/ปฏิเสธงาน)
   │  ├─ tabs/seeker_profile_tab.dart ← โปรไฟล์ + อัป Resume
   │  ├─ seeker_job_detail_page.dart ← รายละเอียดงาน + ปุ่มสมัคร
   │  └─ seeker_company_detail_page.dart ← หน้าบริษัท
   └─ pdf_viewer_page.dart           ← openPdf(): เว็บ=เปิดแท็บใหม่, มือถือ=ตัวดู PDF ในแอป
```

## 4. แผนที่ API (`jobmatch_api/`) ไฟล์หลัก

| ไฟล์ | ทำอะไร |
|---|---|
| `db_connect.php` | ต่อฐานข้อมูล (PDO) |
| `api_auth.php` | `currentUser()` อ่านผู้ใช้จาก header, `ensureOwner()` กันแก้ข้อมูลคนอื่น |
| `matching_service.php` | ★ **หัวใจการจับคู่**: `calculateMatchScore` = \|A∩B\| ÷ \|B\| × 100, `normalizeSkill` (คำพ้อง), `extractKeywordsFromText` (หาคำจาก Resume), `VISIBLE_JOB_STATUSES = ['open','approved']` |
| `pdf_text.php` | ดึงข้อความจากไฟล์ PDF |
| `geocode_service.php` | ที่อยู่ ↔ พิกัด (OpenStreetMap Nominatim), `readableLocation()` แปลง "พิกัด x,y" → ต./อ./จ. |
| `get_jobs.php` | รายการงานให้ผู้หางาน (กรองเฉพาะเปิดรับ **และยังรับไม่ครบ**) + คะแนน + ระยะทาง |
| `post_job.php` / `update_job.php` | สร้าง / แก้ประกาศ (แก้งานที่ปิดแล้วไม่ได้) |
| `apply_job.php` | สมัครงาน (สมัครงานที่ปิด/ครบแล้วไม่ได้) |
| `get_applications.php` | ใบสมัคร (ตาม seeker / job / employer) + คะแนน + `job_closed` |
| `update_application.php` | เปลี่ยนสถานะใบสมัคร + นับ `hired_count` + ปิดงานอัตโนมัติเมื่อรับครบ |
| `post_interview.php` | นัดสัมภาษณ์ (งานที่ครบแล้วนัดไม่ได้) |
| `upload_resume.php` | อัป Resume → ดึงคำสำคัญ → คำนวณจับคู่ใหม่ |
| `get_messages.php` / `send_message.php` / `delete_conversation.php` | แชท |
| `add_keyword.php` | ผู้ใช้เพิ่มคำทักษะใหม่ → แจ้งเตือนแอดมิน |

## 5. แผนที่แอดมิน (`jobmatch_admin/`)

| ไฟล์ | หน้า |
|---|---|
| `dashboard.php` | แดชบอร์ด: การ์ด 6 ใบ (กดได้), กราฟสมาชิก, กราฟประกาศงาน, ประกาศล่าสุด |
| `users.php` | จัดการผู้ใช้ (ผู้ว่าจ้างแสดงชื่อบริษัท, ดูรายการประกาศของบริษัท, ระงับ/เปิดใช้) |
| `job_postings.php` | ประกาศงานทั้งหมด (การ์ดสรุป, คอลัมน์จำนวนรับ/ขาด, ลบ) |
| `keywords.php` | คำสำคัญ (เลือกหมวดก่อนแล้วค่อยพิมพ์คำ) |
| `matching.php` | เกณฑ์การจับคู่ (70/50/40, แจ้งเตือน 70, ผู้สมัครแนะนำ 80) |
| `reports.php` | รายงาน + กราฟ "จำนวนคนที่ต้องการรับ" + บันทึก PDF |
| `includes/header.php` | เมนูซ้าย + กระดิ่งแจ้งเตือนแอดมิน |
| `includes/footer.php` | JS กลาง (openModal, showToast, exportToCSV แบบเก่า) |
| `includes/csv_export.php` | ส่งออก CSV จากฐานข้อมูล (ครบทุกแถว) |
| `assets/css/style.css` | สีและหน้าตาทั้งหมด |

---

## 6. "อยากแก้ X → ไปที่ไหน" (สูตรลัด)

| อยากแก้ | ไฟล์ | วิธี |
|---|---|---|
| ข้อความ/ป้ายบนหน้าแอป | ค้นข้อความไทยนั้นใน `lib\` (VS Code: Ctrl+Shift+F) | แก้ข้อความใน `Text('...')` แล้ว build ใหม่ |
| สีแอป | `lib/core/theme/app_theme.dart` | เปลี่ยนค่า `Color(0xFF......)` |
| เกณฑ์ % การจับคู่ | เว็บแอดมิน → เกณฑ์การจับคู่ | ไม่ต้องแก้โค้ด (เก็บในตาราง `system_settings`) |
| สูตรคำนวณจับคู่ | `jobmatch_api/matching_service.php` → `calculateMatchScore` | |
| คำพ้องความหมาย (เช่น JS = JavaScript) | `matching_service.php` → `normalizeSkill` | เพิ่มคู่คำใน array |
| สถานะที่ถือว่า "เปิดรับ" | `matching_service.php` → `VISIBLE_JOB_STATUSES` | |
| เงื่อนไขงานที่ผู้หางานเห็น | `jobmatch_api/get_jobs.php` → `WHERE j.status IN ... AND hired_count < vacancy` | |
| การ์ดสถิติหน้าแรกผู้ว่าจ้าง | `dashboard_tab.dart` → `_buildStatCards()` | `onTap:` กำหนดว่ากดแล้วไปไหน |
| รายละเอียดในการ์ดงาน (หน้าแรกผู้ว่าจ้าง) | `dashboard_tab.dart` → `_buildJobCard()` และ `_buildJobProgress()` | |
| การ์ดบนแดชบอร์ดแอดมิน | `dashboard.php` → array `$dashCards` | 1 แถว = 1 การ์ด: [ชื่อ, ตัวเลข, ไอคอน, สี, ลิงก์] |
| กราฟแอดมิน | `dashboard.php` / `reports.php` ส่วน `<script>` ล่างไฟล์ | Chart.js: `type: 'line'` หรือ `'bar'`, แกน `suggestedMax`, `stepSize` |
| คอลัมน์ไฟล์ CSV | หน้านั้น ๆ ส่วน `if (isCsvExport()) { ... sendCsv(...) }` | เพิ่มค่าใน `$rows[] = [...]` + หัวคอลัมน์ให้ลำดับตรงกัน |
| URL API ตอน build | คำสั่ง build `--dart-define=API_URL=...` | (ค่าเริ่มต้นอยู่ใน `app_constants.dart`) |

---

## 7. สิ่งที่แก้ไปแล้ว (หลังสอบรอบแรก — 7 ต.ค. 2569)

### แอดมิน
1. **คำสำคัญ** (`keywords.php`): ฟอร์มเพิ่ม/แก้/เพิ่มหลายคำ → ช่อง "หมวดหมู่" ย้ายขึ้นก่อน และบังคับเลือก
2. **รายงาน** (`reports.php`): เพิ่มกราฟ "จำนวนคนที่ต้องการรับ (รวมทุกประกาศ)" แท่ง ต้องการ vs รับแล้ว (วัน/เดือน/ปี) + ตัวเลขรวม ต้องการ/รับแล้ว/ยังขาด; การ์ด 3 ใบกดได้; แก้ PDF มืด/จาง (พื้นขาว + ปิดแอนิเมชันตอนจับภาพ ใน `onclone`)
3. **จัดการผู้ใช้** (`users.php`): ผู้ว่าจ้างแสดง**ชื่อบริษัท**แทนชื่อผู้ใช้ (ทั้งตารางและหน้าต่าง "ดู"), แสดงจำนวนประกาศ/จำนวนคนที่ต้องการ, **รายการประกาศของบริษัท** (`renderEmployerJobs()`), ค้นด้วยชื่อบริษัทได้
4. **ประกาศงาน** (`job_postings.php`): การ์ดสรุป (ประกาศทั้งหมด/ต้องการรับ/รับแล้ว/ยังขาด), คอลัมน์ "จำนวนรับ" (รับแล้ว x/y – ขาดอีก z), แสดงบริษัทชัดขึ้น
5. **แดชบอร์ด** (`dashboard.php`): กราฟเส้นประกาศงาน (แบบเดียวกับกราฟสมาชิก), การ์ด 6 ใบกดได้ (สมาชิก/ผู้หางาน/ผู้ว่าจ้าง/ประกาศ/การสมัคร/รับแล้ว); ลบโค้ดกราฟ applicationsChart ที่ error
6. **Export CSV** ทุกหน้า: ดึงจากฐานข้อมูล → ครบทุกแถว, ตามตัวกรองปัจจุบัน, วันที่ yyyy-mm-dd, เบอร์โทรไม่หาย 0 (`includes/csv_export.php`)

### แอปฝั่งผู้ว่าจ้าง
7. การ์ดสถิติ 4 ใบบนหน้าแรกกดได้ (งานที่เปิดรับ → แท็บงานของฉัน, ผู้สมัคร/รอดำเนินการ/นัดสัมภาษณ์ → หน้ารายชื่อ `_ApplicantListPage` ท้าย `dashboard_tab.dart`)
8. ปุ่ม "ดูทั้งหมด" ทั้ง 2 ปุ่มใช้ได้ (`MainShell.goToTab(1)`)
9. การ์ดงานมีรายละเอียดเพิ่ม: ผู้สมัคร N คน, เงินเดือน, วันที่ประกาศ, ป้าย รอพิจารณา/สัมภาษณ์/ตอบรับงานแล้ว/รับแล้ว x/y
10. **งานที่ปิดรับ/รับครบ = ดูได้อย่างเดียว**: ไม่มีเมนูแก้/ลบ (ไอคอนแม่กุญแจ), หน้าผู้สมัครไม่มีปุ่มนัดสัมภาษณ์/รับ/ไม่ผ่าน, API ก็ปฏิเสธ (`update_job.php`, `update_application.php`, `post_interview.php`)
    - เงื่อนไข "ปิด" ใช้ทุกที่เหมือนกัน: `status == 'closed' หรือ hired_count >= vacancy`

### แอปฝั่งผู้หางาน
11. ไม่แสดงงานที่ปิดรับ/รับครบ (`get_jobs.php`, หน้าบริษัท) และสมัครงานที่ครบแล้วไม่ได้ (`apply_job.php`)
12. การ์ดค้นหาแสดง "เหลือ x/y ที่"
13. ใบสมัครของงานที่ครบแล้ว (ยังรอ/นัดสัมภาษณ์อยู่) ขึ้นกล่อง 🔒 "ตำแหน่งนี้รับคนครบแล้ว..." (`job_closed` จาก `get_applications.php`)

### ข้อมูล
14. แปลงสถานที่ที่เป็น "พิกัด x, y" ของ 6 งาน เป็น ต./อ./จ. (สคริปต์ `backend/tools/fix_coordinate_locations.php`, สำรองไว้ที่ `C:\Users\beer\Documents\jobmatch_backups\`)

---

## 8. ถ้าเจอปัญหา

| อาการ | สาเหตุ/วิธีแก้ |
|---|---|
| แอปหน้าขาวบนเซิร์ฟเวอร์ | อัป build ผิดตัว → build ด้วยคำสั่งในข้อ 2 แล้วอัป `upload_to_server\jobmatch_app\` |
| แก้แล้วไม่เปลี่ยน | ยังไม่ได้อัป / แคช → Ctrl+F5, มือถือปิดแท็บเปิดใหม่ |
| PHP ขึ้น 500 / หน้าว่าง | `php -l ไฟล์.php` หา syntax error; ดูวงเล็บ `{ }` และ `;` |
| `flutter analyze` มี error | อ่านเลขบรรทัดที่บอก ส่วนมากคือลืม `,` หรือ `)` |
| ข้อมูลโปรไฟล์ไม่ขึ้น | บัญชีถูกระงับ → แอดมิน → จัดการผู้ใช้ → เปิดใช้ |
| ปุ่ม GPS ไม่ทำงานบนมือถือ | เบราว์เซอร์อนุญาต GPS เฉพาะ https (เซิร์ฟเวอร์เป็น http) → พิมพ์ที่อยู่แทน ระบบหาพิกัดให้เอง |
| Resume อ่านคำไม่ได้ | PDF เป็นภาพสแกน → ต้องเป็น PDF ที่มีข้อความ |

## 9. ข้อจำกัด (ตอบอาจารย์ว่าเป็นแนวทางพัฒนาต่อ)
- แจ้งเตือน/แชทใช้การดึงข้อมูลทุก 5 วินาที (ยังไม่ใช่ push/WebSocket)
- ยังไม่มีการยืนยันอีเมล/OTP
- ระบุตัวผู้ใช้ด้วย header `X-User-Id` (ควรเปลี่ยนเป็น token)
- อ่าน Resume แบบสแกนไม่ได้ (ต้องใช้ OCR)
- ภาษาไทยค้นคำแบบ substring (ยังไม่ได้ตัดคำ)
- GPS ต้องใช้ https
