หมวดหมู่เนื้อหา (Content Category)
ภาพรวม
Content Category คือหมวดหมู่ระดับบนสุด (level 1) ของระบบเนื้อหา ใช้จัดกลุ่มหน้าเนื้อหาให้เป็นระเบียบ และเป็นจุดตั้งต้นของ Content Subcategory ซึ่งแตกย่อยลงไปได้ถึงระดับ 4
หน้าจอนี้เหมาะกับผู้ดูแลระบบหรือทีมคอนเทนต์ที่ต้องวางโครงสร้างการจัดหมวดก่อนเริ่มสร้างหน้าเนื้อหาจริง เพราะหมวดหมู่ที่สร้างไว้จะถูกนำไปใช้เป็นตัวเลือกในอีกหลายหน้าจอ
ข้อมูล 1 หมวดหมู่ประกอบด้วย
| ข้อมูล | คำอธิบาย |
|---|---|
| ชื่อหมวดหมู่ | บังคับกรอก ความยาวไม่เกิน 255 ตัวอักษร |
| Slug | บังคับกรอก ไม่เกิน 255 ตัวอักษร อนุญาตเฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และเครื่องหมายขีด |
| คำอธิบาย | ไม่บังคับ ความยาวไม่เกิน 1,000 ตัวอักษร |
| รูปย่อ | รูปประกอบของหมวดหมู่ |
| ลำดับการแสดงผล | ตัวเลขจำนวนเต็มไม่ติดลบ ใช้จัดลำดับในรายการ |
| สถานะ | เปิด (active) / ปิด (inactive) ค่าเริ่มต้นคือเปิดใช้งาน |
จุดสำคัญที่ต้องทราบ
- ข้อมูลที่ดึงมาแสดงในตารางจะแนบรายการหมวดย่อยมาด้วย จึงแสดงจำนวนหมวดย่อยของแต่ละหมวดได้ทันทีโดยไม่ต้องเรียกข้อมูลเพิ่ม
- การลบหมวดหมู่กระทบทั้งหมวดย่อยและหน้าเนื้อหาที่ผูกอยู่ ระบบจึงเตือนก่อนยืนยันทุกครั้ง
- นอกจากรายการปกติ โมดูลนี้ยังให้บริการรายการหมวดหมู่แบบ dropdown ที่หน้าจออื่นเรียกใช้เป็นจำนวนมาก
Business Flow
การจัดการรายการหมวดหมู่
- เปิดหน้ารายการที่
/content-categoryระบบดึงข้อมูลแบบแบ่งหน้าพร้อมเงื่อนไขค้นหาและตัวกรองสถานะ - ตารางแสดงชื่อหมวดหมู่ (คลิกเพื่อเข้าสู่หน้าแก้ไข) slug จำนวนหมวดย่อย ลำดับการแสดงผล สถานะ วันที่ปรับปรุงล่าสุด และเมนูการดำเนินการ
- ค้นหาด้วยคำค้นและกรองตามสถานะได้ เมื่อกดค้นหาระบบจะกลับไปหน้าแรกของรายการเสมอ
- กดที่ตัวเลขจำนวนหมวดย่อย หรือเลือกเมนู "ดูหมวดย่อย" ระบบจะพาไปหน้า Content Subcategory พร้อมกรองเฉพาะหมวดหมู่ที่เลือกไว้ให้แล้ว
- การลบเปิดกล่องยืนยันที่แจ้งเตือนผลกระทบต่อหมวดย่อยและเนื้อหาที่ผูกอยู่ก่อน เมื่อยืนยันสำเร็จระบบจะดึงข้อมูลตารางใหม่
การสร้างและแก้ไขหมวดหมู่
- เข้าฟอร์มที่
/content-category/formโดยระบบแยกโหมดสร้างใหม่กับโหมดแก้ไขจากรหัสหมวดหมู่ใน query string - โหมดแก้ไขจะโหลดข้อมูลเดิมมาเติมลงฟอร์ม หากโหลดไม่สำเร็จระบบจะแจ้งข้อผิดพลาดและพากลับหน้ารายการ
- เมื่อพิมพ์ชื่อหมวดหมู่ ระบบสร้าง slug ให้อัตโนมัติโดยแปลงเป็นตัวพิมพ์เล็ก ตัดอักขระที่ไม่ใช่ตัวอักษรหรือตัวเลขออก และแทนช่องว่างด้วยเครื่องหมายขีด การสร้างอัตโนมัติจะทำเฉพาะตอนสร้างใหม่หรือเมื่อยังไม่มี slug เดิม เพื่อไม่ให้ slug ของหมวดที่เผยแพร่แล้วเปลี่ยนไปโดยไม่ตั้งใจ
- กรอกข้อมูลที่เหลือ ได้แก่ คำอธิบาย ลำดับการแสดงผล และสถานะ
- กดบันทึกเพื่อสร้างหรืออัปเดตหมวดหมู่ เมื่อสำเร็จระบบจะพากลับสู่หน้ารายการ
การนำหมวดหมู่ไปใช้ในฟีเจอร์อื่น
- หมวดหมู่ที่มีอยู่ในระบบถูกดึงไปแสดงเป็นตัวเลือกผ่าน endpoint แบบ dropdown ซึ่งคืนค่าเป็นคู่ของชื่อที่ใช้แสดงและรหัสอ้างอิง
- หน้าสร้าง/แก้ไขหน้าเนื้อหาใช้รายการนี้เป็นชั้นแรกของการเลือกหมวด แล้วจึงต่อด้วยหมวดย่อยของหมวดที่เลือก
- หน้ารายการและฟอร์มของ Content Subcategory ใช้รายการเดียวกันเพื่อระบุว่าหมวดย่อยนั้นอยู่ใต้หมวดใด
- หน้า Content Links ใช้เป็นเงื่อนไขกรองรายการลิงก์ตามหมวดหมู่
- หมวดหมู่ที่ตั้งสถานะปิดไว้ยังคงอยู่ในระบบและไม่ถูกลบ แต่ควรตรวจสอบก่อนว่าไม่มีเนื้อหาที่ยังต้องใช้งานผูกอยู่
หน้าจอและองค์ประกอบหลัก
หน้ารายการ (/content-category)
- ส่วนกรองข้อมูล — ช่องค้นหาและตัวเลือกสถานะ ใช้รูปแบบตัวควบคุมมาตรฐานเดียวกับหน้ารายการอื่นในระบบ
- ตารางหมวดหมู่ — ชื่อ slug จำนวนหมวดย่อย (แสดงเป็นป้ายตัวเลขที่กดเพื่อข้ามไปหน้าหมวดย่อยได้) ลำดับ สถานะ และวันที่อัปเดต รองรับการแบ่งหน้าและการเลื่อนตารางตามความกว้างจอ
- เมนูการดำเนินการ — แก้ไข ดูหมวดย่อย และลบ
- กล่องยืนยันการลบ — แจ้งผลกระทบต่อข้อมูลที่เกี่ยวข้องก่อนดำเนินการ
ไฟล์อ้างอิงหลัก: src/app/content-category/page.tsx
หน้าฟอร์ม (/content-category/form)
- ช่องชื่อหมวดหมู่ — บังคับกรอก และเป็นตัวตั้งต้นของการสร้าง slug อัตโนมัติ
- ช่อง Slug — บังคับกรอก ตรวจรูปแบบให้ประกอบด้วยตัวพิมพ์เล็ก ตัวเลข และขีดเท่านั้น
- ช่องคำอธิบาย — ข้อความอธิบายหมวดหมู่แบบหลายบรรทัด
- ช่องลำดับการแสดงผล — ตัวเลขไม่ติดลบ
- ตัวเลือกสถานะ — ค่าเริ่มต้นเป็นเปิดใช้งาน
ข้อกำหนดของแต่ละช่องสรุปได้ดังนี้
| ช่อง | ข้อกำหนด |
|---|---|
| ชื่อหมวดหมู่ | บังคับกรอก ไม่เกิน 255 ตัวอักษร |
| Slug | บังคับกรอก ไม่เกิน 255 ตัวอักษร รูปแบบตัวพิมพ์เล็ก ตัวเลข และขีดเท่านั้น |
| คำอธิบาย | ไม่บังคับ ไม่เกิน 1,000 ตัวอักษร |
| ลำดับการแสดงผล | ตัวเลขตั้งแต่ 0 ขึ้นไป |
| สถานะ | เปิด/ปิดใช้งาน ค่าเริ่มต้นคือเปิด |
ไฟล์อ้างอิงหลัก: src/app/content-category/form/page.tsx
บริการฝั่ง API
รวมอยู่ที่ src/services/content-category.service.ts ภายใต้ path หลัก content-categories
| ความสามารถ | Endpoint |
|---|---|
| ดึงรายการหมวดหมู่ | GET /content-categories |
| ดึงรายการสำหรับ dropdown | GET /content-categories/dropdown |
| ดึงข้อมูลหมวดหมู่รายตัว | GET /content-categories/{id} |
| สร้างหมวดหมู่ | POST /content-categories |
| แก้ไขหมวดหมู่ | PATCH /content-categories/{id} |
| ลบหมวดหมู่ | DELETE /content-categories/{id} |
service ยังเตรียม endpoint สำหรับดึงรายการแบบจัดกลุ่มและสถิติสรุปของหมวดหมู่ไว้ด้วย แต่ยังไม่มีหน้าจอใดในระบบเรียกใช้
จุดเชื่อมต่อกับฟีเจอร์อื่น
- สิทธิ์การใช้งาน — เมนูนี้เป็นเมนูย่อยที่ควบคุมด้วยสิทธิ์โมดูล
content-categoryและจะแสดงได้ต่อเมื่อผู้ใช้มีสิทธิ์เข้าถึงเมนูแม่content-managementด้วย ทั้งสองปลดล็อกจากโมดูลline-oaฝั่งหลังบ้าน - Content Subcategory — เป็นหมวดแม่ของโครงสร้างต้นไม้หมวดย่อย และเป็นปลายทางของการกดดูหมวดย่อยจากตาราง
- Content Management — เรียกใช้รายการ dropdown ของหมวดหมู่เพื่อให้ผู้ใช้เลือกหมวดตอนสร้างและแก้ไขหน้าเนื้อหา
- Content Links — ใช้หมวดหมู่เป็นเงื่อนไขในการกรองรายการลิงก์เนื้อหา
- ตัวเลือกหมวดหมู่แบบ Cascader — เป็นคอมโพเนนต์ร่วมที่ดึงหมวดหมู่ระดับบนจากโมดูลนี้ แล้วต่อยอดด้วยหมวดย่อยแบบลำดับชั้น
- โครงสร้างพื้นฐานร่วม — ใช้ HTTP client กลางของ CMS ที่แนบ token ให้อัตโนมัติและออกจากระบบเมื่อ token หมดอายุ พร้อมทั้งใช้ระบบ breadcrumb เมนูด้านข้าง ค่าคงที่ของตาราง/ตัวกรอง และชุดคำแปลหน้าจอร่วมกับโมดูลอื่น
รายละเอียดฝั่ง Backend (CMS API)
โค้ดฝั่ง backend อยู่ที่ internal/modules/contentcategory/ เป็นโมดูลขนาดเล็กที่นอกจาก CRUD ปกติแล้วยังทำหน้าที่เป็น ผู้ให้บริการรายการตัวเลือกหมวดหมู่ ให้หน้าจออื่นอีกหลายหน้า
สิทธิ์ที่ต้องมี
- ทุก route ต้องผ่านการยืนยันตัวตนกลาง (JWT) แต่ ไม่ได้ครอบด้วย module gate และ policy ที่ประกาศไว้อ้างถึงโมดูล
line-oaซึ่งยังเป็นเพียงข้อมูลกำกับที่ยังไม่บังคับใช้ - การควบคุมสิทธิ์รายเมนูจึงเกิดขึ้นที่ฝั่งหน้าจอเป็นหลัก ผู้ใช้ที่ล็อกอินแล้วยังเรียก endpoint จัดการหมวดหมู่ได้โดยตรง
- ข้อมูลถูกจำกัดขอบเขตตาม LINE OA และองค์กรที่กำลังใช้งานอยู่เสมอ
Endpoint ที่ backend มีมากกว่าที่หน้าจอเรียกใช้
นอกจากรายการปกติและ dropdown ที่หน้าจอใช้อยู่ ฝั่ง API ยังเปิดไว้อีกสองตัว
| Endpoint | ใช้ทำอะไร |
|---|---|
GET /api/content-categories/dropdown-group | คืนรายการตัวเลือกแบบจัดกลุ่ม สำหรับหน้าจอที่ต้องการดรอปดาวน์แบบมีหัวข้อกลุ่ม |
GET /api/content-categories/stats | คืนสถิติจำนวนคอนเทนต์ต่อหมวดหมู่ |
หมวดหมู่ที่สร้างไว้ยังถูกนำไปแสดงในเส้นทางสาธารณะสำหรับผู้อ่านด้วย ไม่ได้ใช้เฉพาะในหน้าจอผู้ดูแล
จุดที่ต้องระวังเรื่องการลบ
- ตาราง
content_categoryใช้คอลัมน์ประทับเวลาลบแบบธรรมดา ไม่ใช่กลไก soft delete อัตโนมัติของ ORM ทุก query อ่านข้อมูลจึงต้องเขียนเงื่อนไข "ยังไม่ถูกลบ" เองทุกครั้ง หากลืมใส่ในที่ใดที่หนึ่ง หมวดหมู่ที่ลบไปแล้วจะโผล่กลับมาเป็นตัวเลือกในหน้าจออื่น - Edge case ที่ตั้งใจคงไว้ — คำสั่งอัปเดตหมวดหมู่ ไม่ได้กรองรายการที่ถูกลบไปแล้วออก (เพื่อคงพฤติกรรมเดิมของระบบก่อนหน้า) ผลคือการแก้ไขด้วยรหัสของหมวดหมู่ที่ถูกลบไปแล้วยังทำได้สำเร็จ ทั้งที่หมวดนั้นจะไม่ปรากฏในรายการใด ๆ
- การลบตอบกลับเป็นสถานะสำเร็จแบบไม่มีเนื้อหา (204) ไม่ได้คืนข้อมูลของสิ่งที่ถูกลบกลับมา
- backend ไม่ได้ตรวจการอ้างอิงก่อนลบ คำเตือนเรื่องผลกระทบต่อหมวดย่อยและหน้าเนื้อหาที่เห็นในกล่องยืนยัน เป็นการเตือนจากฝั่งหน้าจอ ไม่ใช่การป้องกันจากฝั่งเซิร์ฟเวอร์ หมวดย่อยและหน้าเนื้อหาที่ผูกไว้จึงอาจกลายเป็นรายการที่อ้างถึงหมวดที่ไม่มีแล้ว
สิ่งที่บันทึกและผลข้างเคียง (Side Effect)
- ข้อมูลอยู่ในตาราง
content_categoryและอ้างอิงกับcontent_subcategoryและcontent_page - โมดูลนี้ไม่ได้เขียน cache และไม่ได้ส่งงานเข้าคิว การเพิ่มหรือแก้หมวดหมู่จึงมีผลกับดรอปดาวน์ในหน้าจออื่นทันทีที่โหลดใหม่
- การดึงรายการแนบข้อมูลหมวดย่อยมาให้ในผลลัพธ์เดียวกัน จึงไม่ต้องเรียก API เพิ่มเพื่อนับจำนวนหมวดย่อย แต่ก็แปลว่ายิ่งมีหมวดย่อยมาก ผลลัพธ์ของการดึงรายการก็ยิ่งใหญ่ตาม
ข้อสังเกตเชิงเทคนิค
เส้นทางที่เป็นชื่อคงที่อย่าง dropdown, dropdown-group และ stats ถูกลงทะเบียนไว้ก่อนเส้นทางที่รับรหัสหมวดหมู่ เพื่อให้ตัวจัดเส้นทางเลือกได้ถูกตัว ไม่เช่นนั้นคำว่า dropdown จะถูกตีความเป็นรหัสหมวดหมู่ — เป็นรายละเอียดที่ต้องรักษาไว้เมื่อเพิ่ม endpoint ใหม่ในโมดูลนี้