Campaign Planner
ภาพรวม
Campaign Planner คือปฏิทินรายเดือนที่นำแคมเปญทั้งหมดมาจัดเรียงตามวัน สำหรับผู้ดูแลระบบและทีมการตลาดที่ต้องวางแผนว่าจะส่งแคมเปญวันไหน วันไหนส่งถี่เกินไป และมีแคมเปญร่างค้างอยู่หรือไม่
จุดสำคัญที่ต้องเข้าใจก่อนใช้งาน — เมนูนี้เป็นฟีเจอร์ อ่านอย่างเดียว และ ไม่มีโมดูลฝั่งเซิร์ฟเวอร์ของตัวเองเลย ทุกอย่างที่เห็นบนปฏิทินคือข้อมูลชุดเดียวกับเมนู Campaign ที่ดึงผ่าน GET /api/campaign ตัวเดิม เพียงแต่หน้าเว็บนำมาจัดวางเป็นปฏิทินแทนตาราง (ค้นทั้งโค้ดฝั่ง backend ไม่พบคำว่า "planner" อยู่ในไฟล์ใดเลย — ไม่มี controller, ไม่มี service, ไม่มี endpoint เฉพาะ)
บทบาทที่ใช้งาน — ผู้ใช้ที่มีสิทธิ์ดูข้อมูลแคมเปญ (ใช้สิทธิ์ตัวเดียวกับเมนู Campaign ทั้งฝั่งหน้าเว็บและฝั่งเซิร์ฟเวอร์ — ดูหัวข้อ สิทธิ์การเข้าถึง)
เส้นทางผู้ใช้งาน
1. เปิดปฏิทินและอ่านการ์ดสรุป
เปิดเมนู Campaign Planner ระบบแสดงปฏิทินของเดือนปัจจุบันพร้อมการ์ดสรุปด้านบน — การ์ดใบซ้ายแสดงชื่อเดือน/ปีกับยอด แคมเปญทั้งหมด ของเดือนนั้น และการ์ดใบขวาแยกยอดตาม 4 สถานะ (ส่งแล้ว / ตั้งเวลา / ยกเลิกแล้ว / ฉบับร่าง)
ระหว่างที่ยังโหลดข้อมูลไม่ครบ ตัวเลข "แคมเปญทั้งหมด" จะแสดงเป็นขีด — และพื้นที่ปฏิทินทั้งผืนจะถูกแทนด้วยวงหมุนรอโหลด ยังไม่แสดงตารางวันใด ๆ
2. อ่านปฏิทินรายวัน
- แต่ละช่องวันแสดงรายการแคมเปญได้สูงสุด 3 รายการ พร้อมจุดสีตามสถานะ ถ้าวันนั้นมีมากกว่านั้นจะมีปุ่ม "+N เพิ่มเติม" ให้กดเปิดแผงรายการทั้งหมดของวันนั้น (แผงนี้เลื่อนดูได้ สูงไม่เกิน 420px และมีหัวข้อบอกวันที่กับจำนวนรวม)
- ลำดับภายในวันเดียวกันถูกจัดให้เสมอ — ตั้งเวลา ขึ้นก่อน แล้วต่อด้วย ส่งแล้ว → ฉบับร่าง → ยกเลิก ถ้าสถานะเท่ากันจึงเรียงตามเวลาที่ตั้งไว้
- มีแถบคำอธิบายสีอยู่เหนือปฏิทินกำกับว่าจุดสีใดหมายถึงสถานะใด
3. ดูข้อมูลย่อและเข้าดูรายละเอียด
- นำเมาส์ไปวางค้างบนรายการ (หน่วง 0.3 วินาที) เพื่อเปิดกล่องข้อมูลย่อ — ป้ายสถานะ ชื่อแคมเปญ กลุ่มผู้รับ (ถ้าไม่มีกลุ่มจะขึ้นว่า "เพื่อนทั้งหมดใน OA") จำนวนผู้รับ (แสดงเฉพาะเมื่อมากกว่า 0) และบรรทัดวันเวลาที่เปลี่ยนป้ายกำกับตามสถานะ — ส่งแล้ว / ตั้งเวลา / ยกเลิกแล้ว / แก้ไขล่าสุด (สำหรับฉบับร่าง)
- คลิกที่รายการเพื่อเข้าดูรายละเอียด — ระบบพาออกจาก Planner ไปที่ฟอร์มของเมนู Campaign ในโหมด ดูอย่างเดียว (
?view=true) ไม่ว่าแคมเปญนั้นจะเป็นสถานะใด แม้เป็นฉบับร่างก็เปิดในโหมดอ่านเช่นกัน ต้องกลับไปที่หน้ารายการของเมนู Campaign ถ้าอยากแก้ไข
4. เปลี่ยนเดือน
เปลี่ยนเดือนด้วยตัวควบคุมบนหัวปฏิทิน ระบบดึงข้อมูลชุดใหม่ให้อัตโนมัติ (เฉพาะการเปลี่ยนแบบ "เดือน" เท่านั้นที่ทำให้ดึงข้อมูลใหม่ — การสลับมุมมองเป็นรายปีไม่ได้เรียก API)
รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง
ยืนยันจาก src/components/campaign-planner/ ของ cms-web-2026 ร่วมกับ internal/modules/campaign/{controller,service}.go และ internal/core/pagination/pagination.go ของ line-management-cms-api-go:
- หน้านี้ยิง API 4 ครั้งขนานกัน ไม่ใช่ครั้งเดียว — เพราะ
GET /api/campaignกรองได้ทีละสถานะเท่านั้น หน้าเว็บจึงเรียกแยกกัน 4 คำขอ (ส่งแล้ว / ตั้งเวลา / ฉบับร่าง / ยกเลิก) แล้วนำผลมาต่อกันเป็นชุดเดียวฝั่ง browser ทุกคำขอส่งlimit: 500และ ไม่มีการแบ่งหน้า — ตัวเลขนี้เป็นเพดานจริง เพราะฝั่ง backend ตรวจlimitแค่Min(1)กับIsIntไม่มีขีดบนไว้เลย จึงยอมรับ 500 ตามที่ขอ แต่ถ้าเดือนใดมีแคมเปญสถานะเดียวเกิน 500 รายการ ส่วนที่เกินจะหายไปจากปฏิทินโดยไม่มีการเตือน - ช่วงวันที่ที่ดึงกว้างกว่าเดือนที่แสดงอยู่ 7 วันทั้งสองข้าง — คำขอทุกตัวส่ง
startDate= ต้นเดือน − 7 วัน และendDate= ปลายเดือน + 7 วัน เพื่อให้ช่องวันของเดือนก่อน/เดือนถัดไปที่โผล่มาในตารางปฏิทินมีข้อมูลด้วย แต่ การ์ดสรุปด้านบนคำนวณใหม่ฝั่งหน้าเว็บโดยตัดให้เหลือเฉพาะวันในเดือนนั้นจริง ๆ จึงเป็นเรื่องปกติที่จะเห็นรายการอยู่ในช่องวันท้าย/ต้นตารางแต่ไม่ถูกนับในการ์ด - คอลัมน์วันที่ที่ backend ใช้กรองเปลี่ยนไปตามสถานะที่กรอง — ในฟังก์ชัน
setFilterถ้าสถานะเป็นsent/scheduledจะกรองด้วยcampaign.start_dateแต่ถ้าเป็นcancel/draftจะกรองด้วยcampaign.updated_dateแทน (และถ้าไม่ระบุสถานะมาเลย เงื่อนไขวันที่จะถูกข้ามไปทั้งหมด) — Planner จัดวางรายการลงช่องวันด้วยกฎเดียวกันเป๊ะ (ส่งแล้ว/ตั้งเวลา ใช้startDate; ฉบับร่าง/ยกเลิก ใช้updatedDateแล้ว fallback เป็นstartDate) ทั้งสองฝั่งจึงตรงกัน แต่ความหมายของช่องวันไม่เท่ากันทุกสถานะ ดูรายละเอียดในหัวข้อจุดที่ควรระวัง - วันที่และตัวเลขบนหน้านี้ถูกล็อกเป็นรูปแบบภาษาอังกฤษไว้ในโค้ด — ทั้งชื่อเดือนบนการ์ดสรุป วันเวลาในกล่องข้อมูลย่อ และหัวข้อของแผง "+N" สร้างจาก
new Intl.DateTimeFormat('en-US', …)ที่ประกาศไว้ตายตัว ส่วนจำนวนผู้รับใช้new Intl.NumberFormat('en-US')— สลับภาษาหน้าเว็บเป็นไทยแล้ว ป้ายกำกับจะเปลี่ยนเป็นไทยแต่ตัวเดือนและวันเวลาจะยังเป็นอังกฤษ (เช่น "August 2026", "Wednesday, August 19, 2026") - ไม่มีการใช้ cache ของ backend —
GET /api/campaignมีโหมด cache ผ่าน Redis ให้ใช้เมื่อส่ง query parametercacheมาด้วย แต่ Planner ไม่ได้ส่งพารามิเตอร์นี้เลย ทุกครั้งที่เปลี่ยนเดือนจึงเป็นการอ่านฐานข้อมูลสด (แคชอยู่ที่ฝั่ง React Query ของ browser ตามคีย์ช่วงวันที่แทน) - ปฏิทินรอครบทั้ง 4 คำขอก่อนจึงวาด — ตัวแปร
isLoadingเป็นการรวม (OR) สถานะโหลดของทุกคำขอ ดังนั้นถ้าคำขอใดคำขอหนึ่งช้า ทั้งปฏิทินจะยังเป็นวงหมุนอยู่ ไม่ได้ทยอยแสดงสถานะที่โหลดเสร็จก่อน - ลิงก์ปลายทางเป็นโหมดอ่านเสมอ — ทั้งรายการในช่องวันและรายการในแผง "+N" ชี้ไปที่
/campaign-management/form?id={id}&view=trueแบบเดียวกันทั้งหมด ไม่มีเส้นทางไหนใน Planner ที่พาไปโหมดแก้ไข
เมนูที่เกี่ยวข้องกันในเส้นทางนี้
- Campaign — เป็นทั้งแหล่งข้อมูลเดียวของหน้านี้ (
GET /api/campaign) และปลายทางของทุกการคลิก การสร้าง/แก้ไข/ยกเลิก/ลบแคมเปญทำได้ที่เมนูนั้นเท่านั้น - Dashboard — ปุ่ม View All บนการ์ด "Upcoming Activities" ของหน้า Dashboard พามาที่ Campaign Planner นี้
- Segment audience — ชื่อกลุ่มผู้รับที่แสดงในกล่องข้อมูลย่อคือกลุ่มเป้าหมายที่เตรียมไว้จากเมนูนั้น (ถ้าแคมเปญเป็นแบบ Broadcast จะขึ้นว่า "เพื่อนทั้งหมดใน OA")
จุดที่ควรระวัง
- ช่องวันของแคมเปญ "ฉบับร่าง" และ "ยกเลิกแล้ว" ไม่ใช่วันที่ตั้งใจจะส่ง — ทั้งสองสถานะถูกจัดลงช่องวันตาม วันที่แก้ไขล่าสุด (ตามคอลัมน์ที่ backend ใช้กรอง) ดังนั้นแคมเปญที่ตั้งใจส่งสิ้นเดือนแต่เพิ่งไปแก้ร่างวันนี้ จะปรากฏที่ วันนี้ ไม่ใช่สิ้นเดือน — อ่านปฏิทินเป็น "แผนการส่ง" ได้เต็มที่เฉพาะสองสถานะแรก (ส่งแล้ว/ตั้งเวลา) เท่านั้น
- เพดาน 500 รายการต่อสถานะต่อเดือน — เกินจากนั้นจะไม่แสดงและไม่มีข้อความเตือน ทั้งการ์ดสรุปและปฏิทินจะแสดงยอดที่ต่ำกว่าความจริง
- แคมเปญที่เพิ่งกด "ส่งทันที" อาจไม่ปรากฏบนปฏิทินชั่วคราว — ระหว่างที่ backend ตั้งสถานะเป็น
sendingรอ worker ประมวลผล แคมเปญนั้นไม่เข้าเงื่อนไขสถานะใดใน 4 คำขอของ Planner เลย จึงหายไปจากปฏิทินเช่นเดียวกับที่หายไปจากทุกแท็บของเมนู Campaign - ไม่มีปุ่มรีเฟรช — วิธีดึงข้อมูลใหม่คือเปลี่ยนเดือนไปแล้วเปลี่ยนกลับ หรือโหลดหน้าใหม่
- ชื่อเดือนและวันเวลาแสดงเป็นภาษาอังกฤษเสมอแม้ใช้หน้าเว็บภาษาไทย (เป็นข้อจำกัดที่ล็อกไว้ในโค้ด ไม่ใช่การตั้งค่า)
สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)
- ฝั่งหน้าเว็บ — หน้านี้ถูกครอบด้วย
PermissionGuardตรวจ actionVIEWของ subjectcampaign-management(ไม่ใช่ subject ของตัวเอง) ในไฟล์src/app/campaign-planner/page.tsx— ใครที่เห็นเมนู Campaign ก็เห็นเมนูนี้ด้วยโดยปริยาย และในทางกลับกัน ถ้าปิดสิทธิ์ดูแคมเปญก็จะเข้าหน้านี้ไม่ได้ทันที - ฝั่งเซิร์ฟเวอร์ — ไม่มีโมดูลหรือ ModuleGate ของ Campaign Planner เลย เพราะไม่มี endpoint ของตัวเอง ทุกคำขอที่หน้านี้ยิงออกไปวิ่งผ่าน
ModuleGate("campaign")ของโมดูล Campaign ที่บังคับใช้จริงอยู่แล้ว — ถ้าผู้ดูแลระดับแพลตฟอร์มปิดโมดูลแคมเปญให้องค์กรใด หน้า Planner ของผู้ใช้ในองค์กรนั้นจะได้ 403 ทั้ง 4 คำขอและแสดงปฏิทินว่าง - policy annotation ที่ติดมากับเส้นทางนั้นคือ
readAllของโมดูลcampaignแต่วิ่งผ่าน middleware ที่เป็น no-op ตามทั้งระบบ — การบังคับจริงจึงอยู่ที่ ModuleGate ระดับโมดูลของ Campaign เท่านั้น
ไฟล์อ้างอิงจากซอร์สโค้ด
Frontend (cms-web-2026)
src/app/campaign-planner/page.tsx— จุดเข้าหน้า,PermissionGuardที่ตรวจ subjectcampaign-managementsrc/components/campaign-planner/campaign-planner.container.tsx— 4 คำขอขนาน, ช่วงวันที่ ±7 วัน, การจัดกลุ่มลงวันและการคำนวณการ์ดสรุปsrc/components/campaign-planner/campaign-calendar.tsx— ตารางปฏิทิน, เพดาน 3 รายการต่อวัน, ปุ่ม "+N", แถบคำอธิบายสีsrc/components/campaign-planner/campaign-event-tooltip.tsx,campaign-day-list.tsx— กล่องข้อมูลย่อและแผงรายการรายวัน (จุดที่ล็อกรูปแบบวันที่เป็นen-US)src/components/campaign-planner/status-badge.tsx— สีและป้ายของแต่ละสถานะsrc/services/campaign-management.service.ts— ฟังก์ชันgetListที่หน้านี้เรียกใช้ร่วมกับเมนู Campaign
Backend (line-management-cms-api-go)
internal/modules/campaign/controller.go— เส้นทางGET /campaignและModuleGate("campaign")ที่ครอบอยู่internal/modules/campaign/service.go— ฟังก์ชันsetFilter(จุดที่คอลัมน์วันที่เปลี่ยนตามสถานะ) และโหมด cache ที่หน้านี้ไม่ได้ใช้internal/core/pagination/pagination.go— จุดที่ยืนยันว่าlimitไม่มีขีดบน
ดูรายละเอียดเชิงเทคนิคเพิ่มเติม (เวอร์ชันก่อนหน้าของทีมเอกสาร) ที่ ปฏิทินวางแผนแคมเปญ