# Task: Refactor Staff Course Page Filters to Single Select ## Context ในส่วนของเจ้าหน้าที่ หน้าหลักสูตร (Courses) ยังใช้ Filter รูปแบบเก่า ซึ่งอาจเป็น Multi Select, Faceted Filter, Dropdown แบบ Checkbox หรือ Component เดิมที่ไม่เป็นมาตรฐานเดียวกับหน้ารายชื่อพนักงานและหน้าอื่นของระบบ ต้องปรับ Filter ในหน้าหลักสูตรให้เป็นรูปแบบ **Select แบบเลือกได้เพียง 1 ค่าในแต่ละ Filter (Single Select)** และทำให้การทำงานสอดคล้องกับมาตรฐาน Filter ของระบบ งานนี้ต้องแก้ไขครบทั้ง - UI - Filter state - URL Search Params - Server-side filtering - API validation - Pagination - Sorting - Empty state - Responsive - Automated tests - Legacy code cleanup ห้ามแก้เฉพาะหน้าตาของ Component โดยไม่ตรวจสอบ Logic ฝั่ง Backend และผลกระทบที่เกี่ยวข้อง --- # Objective 1. เปลี่ยน Filter เก่าของหน้าหลักสูตรเป็น Single Select 2. แต่ละ Filter เลือกได้เพียง 1 ค่า 3. ใช้ Component และรูปแบบเดียวกับ Filter มาตรฐานของระบบ 4. URL Search Params ต้องเก็บค่าเดียวต่อ Filter 5. Backend ต้องรองรับ Single Value อย่างถูกต้อง 6. เมื่อเปลี่ยน Filter ต้อง Reset Pagination ไปหน้าแรก 7. Refresh หน้าแล้วค่าที่เลือกต้องยังคงแสดงตาม URL 8. Search, Sorting และ Pagination เดิมต้องทำงานร่วมกับ Filter ได้ 9. ลบ Logic Multi Select หรือ Legacy Filter ที่ไม่ใช้งานแล้ว 10. ไม่กระทบ Permission และสิทธิ์การเข้าถึงหน้าหลักสูตร --- # Phase 1: Inspect Before Editing ก่อนแก้ไข ให้ตรวจสอบโครงสร้างปัจจุบันของหน้าหลักสูตรและรายงานสั้น ๆ ว่า 1. Route ของหน้าหลักสูตรอยู่ที่ใด 2. Toolbar หรือ Filter Component อยู่ไฟล์ใด 3. Table state ถูกจัดการด้วยวิธีใด 4. ใช้ TanStack Table, URL Search Params หรือ Local State หรือไม่ 5. Filter ปัจจุบันเป็น Component ประเภทใด 6. Filter ใดเป็น Multi Select หรือ Faceted Filter 7. Search Params ปัจจุบันมีชื่ออะไร 8. API หรือ Server Action ใดรับค่าจาก Filter 9. Backend ปัจจุบันรองรับค่าแบบ Array, CSV หรือ Single Value 10. มี Shared Filter Component มาตรฐานในระบบอยู่แล้วหรือไม่ 11. หน้า Employee Directory หรือ Online Lessons ใช้ Component ใดเป็นตัวอย่าง 12. มี Test เดิมที่เกี่ยวข้องหรือไม่ ให้ใช้โครงสร้างเดิมของโปรเจกต์เป็นหลัก และหลีกเลี่ยงการสร้าง Abstraction ใหม่โดยไม่จำเป็น --- # Phase 2: Target Filters ตรวจสอบ Filter ทั้งหมดที่มีอยู่จริงในหน้าหลักสูตร เช่น - หมวดหมู่ - สถานะ - ประเภท - ผู้ให้บริการ - Mandatory - Certificate Required - บริษัท - ปี - หรือ Filter อื่นที่ระบบมีอยู่จริง ปรับ Filter ทุกตัวที่เป็นข้อมูลแบบเลือกหนึ่งค่าให้เป็น **Single Select** ## Default Policy Filter ประเภทต่อไปนี้ให้เป็น Single Select โดยค่าเริ่มต้น - Category - Status - Type - Provider - Mandatory - Certificate Required - Company - Year - Course state หรือ Active state ห้ามคง Multi Select ไว้ เว้นแต่พบ Business Requirement ในโค้ดหรือเอกสารที่ระบุชัดเจนว่าต้องเลือกหลายค่า หากพบ Filter ที่จำเป็นต้องเป็น Multi Select จริง ให้ยังไม่เปลี่ยนโดยพลการ และบันทึกไว้ใน Final Report พร้อมเหตุผล --- # Phase 3: UI Requirements ## 3.1 Replace Legacy Filter เปลี่ยน Component เดิม เช่น - Faceted Filter - Checkbox Dropdown - Multi Select - Command Menu แบบเลือกหลายค่า - Badge Filter - Popover Filter แบบ Toggle หลายค่า ให้เป็น Select แบบเลือกได้เพียง 1 ค่า ควรใช้ Component มาตรฐานของโปรเจกต์ เช่น ```tsx ``` หากโปรเจกต์มี Shared Single Select Component อยู่แล้ว ให้ใช้ของเดิมแทนการสร้างใหม่ --- ## 3.2 Required Placeholders กำหนดข้อความ Placeholder ตาม Filter ที่มีจริง เช่น - หมวดหมู่: `เลือกหมวดหมู่` - สถานะ: `เลือกสถานะ` - ประเภท: `เลือกประเภท` - ผู้ให้บริการ: `เลือกผู้ให้บริการ` - บริษัท: `เลือกบริษัท` - ปี: `เลือกปี` ข้อความต้องเป็นภาษาเดียวกับหน้าเดิมและใช้คำศัพท์เดียวกับระบบ --- ## 3.3 All Option ทุก Filter ต้องสามารถกลับไปแสดงข้อมูลทั้งหมดได้ ใช้รูปแบบใดรูปแบบหนึ่งตามมาตรฐานโปรเจกต์ ```text ทั้งหมด ``` หรือ ```text ทุกสถานะ ทุกหมวดหมู่ ``` ห้ามใช้ค่า `undefined` เป็น `SelectItem value` หาก Component ไม่รองรับ สามารถใช้ค่าภายใน เช่น ```ts const ALL_FILTER_VALUE = "all"; ``` แล้วแปลงกลับเป็น `undefined` ก่อนอัปเดต URL หรือส่ง API --- ## 3.4 Single Selection ในแต่ละ Filter - เลือกได้เพียงค่าเดียว - เมื่อเลือกค่าใหม่ ให้แทนค่าก่อนหน้า - ห้ามแสดง Checkbox หลายรายการ - ห้ามเก็บค่าเป็น Array - ห้ามแสดงหลาย Badge ใน Filter เดียว - ห้ามส่งค่าหลายรายการใน Query String --- ## 3.5 Toolbar Layout จัด Toolbar ให้เป็นมาตรฐานเดียวกับหน้า Employee Directory หรือหน้าที่ใช้ Filter มาตรฐานล่าสุด ลำดับที่แนะนำ ```text [ค้นหาหลักสูตร] [หมวดหมู่] [สถานะ] [Filter อื่น] [ล้างตัวกรอง] ``` ข้อกำหนด - Search อยู่ด้านซ้าย - Filter เรียงต่อจาก Search - ปุ่มล้างตัวกรองอยู่ท้ายแถว - Select แต่ละตัวมีความกว้างเหมาะสมและสม่ำเสมอ - ใช้ `flex-wrap` - ไม่ล้น Container - ไม่เกิด Horizontal Scroll ที่ Toolbar ตัวอย่าง Class ที่พิจารณาใช้ได้ตาม Design System เดิม ```tsx className = "flex flex-wrap items-center gap-2"; ``` และ Select Trigger อาจใช้ ```tsx className = "w-full sm:w-[180px]"; ``` ห้ามบังคับใช้ Class นี้หากขัดกับ Component มาตรฐานเดิมของโปรเจกต์ --- ## 3.6 Active Filter Visibility ผู้ใช้ต้องมองเห็นค่าที่กำลังเลือกอยู่จาก Select Trigger โดยตรง ไม่จำเป็นต้องสร้าง Badge ซ้ำอีก เว้นแต่ระบบมีมาตรฐาน Active Filter Summary อยู่แล้ว --- # Phase 4: Filter State Refactor เปลี่ยน State จากรูปแบบหลายค่า เช่น ```ts categories: string[]; statuses: string[]; selectedCategories: Set; selectedStatuses: Set; ``` เป็นค่าเดียว เช่น ```ts category?: string; status?: string; provider?: string; company?: string; year?: string; ``` หรือใช้ Type ที่เหมาะสมกับข้อมูลจริง เช่น ```ts type CourseCategory = "K" | "S" | "A"; type CourseStatus = "active" | "inactive" | "draft"; ``` ห้ามใช้ `any` --- # Phase 5: URL Search Params Standard ## 5.1 Single Value Params แต่ละ Filter ต้องใช้ Query Parameter ค่าเดียว เช่น ```text /dashboard/courses?category=K /dashboard/courses?status=active /dashboard/courses?provider=internal /dashboard/courses?category=K&status=active ``` ห้ามใช้รูปแบบ ```text category=K,S category[]=K&category[]=S categories=K|S status=active,inactive ``` --- ## 5.2 Preserve Other Params เมื่อเปลี่ยน Filter ต้องรักษา Parameter อื่นที่ยังเกี่ยวข้อง เช่น - search - sort - pageSize - company - year แต่ต้อง Reset ```text page=1 ``` ทุกครั้งที่ Search หรือ Filter เปลี่ยน --- ## 5.3 Remove Empty Params เมื่อเลือก `ทั้งหมด` ให้ลบ Parameter นั้นออกจาก URL ตัวอย่าง จาก ```text /dashboard/courses?category=K&status=active ``` เมื่อเลือกหมวดหมู่ทั้งหมด ให้เหลือ ```text /dashboard/courses?status=active ``` ห้ามคงค่า ```text category=all category= category=undefined category=null ``` ใน URL เว้นแต่โครงการมี Standard ระบุไว้ชัดเจน --- ## 5.4 Browser Navigation ต้องรองรับ - Refresh หน้า - Back - Forward - Copy URL แล้วเปิดใหม่ โดย Select ต้องแสดงค่าตรงกับ URL เสมอ --- # Phase 6: Search Behavior Search หลักสูตรต้องยังทำงานร่วมกับ Filter ได้ ข้อกำหนด - ใช้ Debounce ตามมาตรฐานเดิมของระบบ - เมื่อ Search เปลี่ยน ให้ Reset หน้าเป็น 1 - Search และ Filter ต้องใช้พร้อมกันได้ - ห้ามทำ Request ซ้ำโดยไม่จำเป็น - ห้ามเกิด Race Condition - ห้าม Search ด้วยค่าก่อนหน้า หลัง URL เปลี่ยนแล้ว หากหน้าเดิมใช้ Submit Search แบบกด Enter และเป็นมาตรฐานของโปรเจกต์ ให้คง Behavior เดิม ไม่ต้องเปลี่ยนเป็น Auto Search โดยไม่มีเหตุผล --- # Phase 7: Backend and Data Query ตรวจสอบทุกชั้นที่เกี่ยวข้อง ได้แก่ - Page searchParams - Parser - Zod schema - Query DTO - Repository - Data access - API route - Server Action - SQL/ORM query - Count query - Export query หากใช้ Filter ชุดเดียวกัน ## 7.1 Accept Single Value Backend ต้องรับค่าเดียว เช่น ```ts category?: string; status?: string; ``` ไม่ใช่ ```ts category?: string[]; status?: string[]; ``` --- ## 7.2 Remove Array Logic หากมี Logic เช่น ```ts value.split(","); Array.isArray(value); inArray(column, values); values.map(...); ``` ให้ตรวจสอบและ Refactor เป็นเงื่อนไขค่าเดียว เช่น ```ts eq(courses.category, category); ``` หรือ ORM syntax ที่โปรเจกต์ใช้อยู่ ห้ามลบ `inArray` ที่ใช้กับเงื่อนไขอื่นซึ่งยังจำเป็น --- ## 7.3 Validation ปรับ Zod Schema หรือ Parser ให้รองรับค่าที่อนุญาตเท่านั้น ตัวอย่าง ```ts const categorySchema = z.enum(["K", "S", "A"]).optional(); ``` ```ts const statusSchema = z.enum(["active", "inactive"]).optional(); ``` กรณีได้รับค่าที่ไม่ถูกต้อง ให้ใช้พฤติกรรมตามมาตรฐานระบบ เช่น - Ignore invalid filter - Return 400 - Redirect to sanitized URL เลือกให้สอดคล้องกับ Module อื่น ห้ามสร้าง Behavior ใหม่เฉพาะหน้าหลักสูตร --- ## 7.4 Query Count Consistency Query สำหรับนับจำนวนรายการและ Query สำหรับดึงข้อมูลต้องใช้ Filter ชุดเดียวกัน เพื่อป้องกันปัญหา - จำนวนรวมไม่ตรงกับข้อมูล - Pagination แสดงหน้ามากเกินจริง - หน้าเปล่าทั้งที่ Total มากกว่า 0 --- # Phase 8: Pagination and Sorting ## Pagination เมื่อเปลี่ยน - Search - Category - Status - Filter อื่น ต้องกลับหน้า 1 เมื่อเปลี่ยน Page Size ให้กลับหน้า 1 ตามมาตรฐานระบบ ## Sorting - Sorting เดิมต้องยังทำงาน - เปลี่ยน Filter แล้วไม่จำเป็นต้องล้าง Sorting - Refresh หน้าแล้ว Sorting ต้องยังตรงกับ URL หากระบบเก็บ Sorting ใน URL - Query ต้อง Apply Filter และ Sort อย่างถูกลำดับ --- # Phase 9: Reset Filters เพิ่มหรือปรับปุ่ม ```text ล้างตัวกรอง ``` ให้แสดงเฉพาะเมื่อมี Search หรือ Filter ที่ Active อย่างน้อยหนึ่งรายการ เมื่อกดแล้วต้อง 1. ล้าง Search 2. ล้าง Category 3. ล้าง Status 4. ล้าง Filter อื่นทั้งหมดของหน้าหลักสูตร 5. Reset หน้าเป็น 1 6. รักษา Page Size ตามมาตรฐานเดิม 7. ล้าง Search Params ที่เกี่ยวข้อง 8. Reload ข้อมูลทั้งหมด 9. Select ทุกตัวกลับเป็นค่า `ทั้งหมด` หากหน้ามีปุ่ม Reset เดิม ให้ปรับของเดิม ไม่สร้างซ้ำ --- # Phase 10: Empty State แยก Empty State อย่างน้อย 2 กรณี ## ไม่มีข้อมูลหลักสูตรในระบบ ```text ยังไม่มีข้อมูลหลักสูตร ``` และคง Action สร้างหลักสูตรตาม Permission เดิม ## มีข้อมูลแต่ไม่ตรงกับตัวกรอง ```text ไม่พบหลักสูตรที่ตรงกับเงื่อนไขการค้นหา ``` พร้อมปุ่ม ```text ล้างตัวกรอง ``` ปุ่มดังกล่าวต้องใช้ Reset Logic เดียวกับ Toolbar --- # Phase 11: Permission Safety ห้ามเปลี่ยนหรือทำให้ Permission ของหน้าหลักสูตรอ่อนลง ต้องคงหลักการ - `courses:read` สำหรับการอ่าน - `courses:write` สำหรับสร้าง แก้ไข ลบ หรือ Action ที่เปลี่ยนข้อมูล - ผู้ใช้ที่อ่านได้แต่เขียนไม่ได้ ต้องเห็น Filter และรายการได้ - ปุ่มสร้าง แก้ไข ลบ ต้องแสดงตาม Write Permission - API Mutation ต้องตรวจ Permission ฝั่ง Server เสมอ การปรับ Filter ห้ามกลับไปใช้ `requireHRD()` หรือ Role-based Guard หาก Module ถูกปรับเป็น Permission-first แล้ว --- # Phase 12: Shared Filter Standardization ตรวจสอบว่าหน้าหลักสูตรสามารถใช้ Shared Component หรือ Shared Utility ที่มีอยู่แล้วได้หรือไม่ เช่น ```tsx ``` หรือ Hook เช่น ```ts useTableFilters(); useQueryState(); useFilterSearchParams(); ``` หลักการ - ใช้ของเดิมหากรองรับ Requirement - ปรับปรุง Shared Component ได้ หากไม่ทำให้หน้าอื่น Regression - ห้ามสร้าง Shared Component ที่ซับซ้อนเกินความจำเป็น - ห้ามรวม Business Logic ของ Course เข้าไปใน Generic Component - Generic Component ควรรับ Options, Value, Placeholder และ Callback ผ่าน Props --- # Phase 13: Legacy Cleanup หลังแก้ไข ให้ลบเฉพาะโค้ดที่ไม่ใช้งานแล้ว เช่น - Multi Select state - Checkbox Filter - Faceted Filter config - Array query parser - CSV serialization - Unused imports - Dead utility - Unused type - Duplicate Reset handler ห้ามลบ Shared Component เดิม หากยังมีหน้าอื่นใช้งาน --- # Phase 14: Responsive and Accessibility ## Responsive ตรวจสอบอย่างน้อย - Desktop - Tablet - Mobile Toolbar ต้อง - Wrap ได้ - ไม่ล้น - Select ใช้งานได้ - Search ไม่แคบจนอ่านไม่ได้ - ปุ่ม Reset ไม่ทับกับ Component อื่น ## Accessibility Select ต้องมี - Label หรือ Accessible Name - `aria-label` เมื่อไม่มี Label ที่มองเห็น - Keyboard navigation - Focus state - Disabled state ที่ถูกต้อง - ไม่ใช้สีเป็นข้อมูลเพียงอย่างเดียว --- # Phase 15: Testing Requirements เพิ่มหรือปรับ Test ตาม Test Framework ที่โปรเจกต์ใช้อยู่ ## Unit Tests ทดสอบอย่างน้อย 1. Parser รับค่า Category ค่าเดียว 2. Parser รับค่า Status ค่าเดียว 3. ค่า `all` ถูกแปลงเป็น `undefined` 4. Invalid enum ถูกจัดการถูกต้อง 5. Reset ล้างเฉพาะ Filter ที่เกี่ยวข้อง 6. เปลี่ยน Filter แล้ว Page กลับเป็น 1 ## Component Tests ทดสอบอย่างน้อย 1. Select แสดง Placeholder 2. เลือกค่าได้เพียง 1 ค่า 3. เลือกค่าใหม่แทนค่าเดิม 4. ค่าใน Select ตรงกับ URL 5. Reset แล้วกลับเป็นทั้งหมด 6. ปุ่ม Reset แสดงเมื่อมี Active Filter 7. ปุ่ม Reset ซ่อนเมื่อไม่มี Active Filter ## Integration Tests ทดสอบอย่างน้อย 1. Search + Category ทำงานร่วมกัน 2. Category + Status ทำงานร่วมกัน 3. Filter + Sorting ทำงานร่วมกัน 4. Filter + Pagination ทำงานร่วมกัน 5. Refresh แล้วยังคง Filter 6. Back/Forward อัปเดต Select ถูกต้อง 7. Total Count ตรงกับรายการ 8. Empty State ถูกต้อง ## Permission Tests ทดสอบอย่างน้อย 1. ผู้มี `courses:read` เปิดหน้าและใช้งาน Filter ได้ 2. ผู้ไม่มี `courses:read` เข้าไม่ได้ 3. ผู้มี Read แต่ไม่มี Write ไม่เห็น Action เขียนข้อมูล 4. Filter ไม่ทำให้ API Mutation ข้าม Permission --- # Phase 16: Manual Verification Scenarios ทดสอบด้วย Browser ตามกรณีต่อไปนี้ ## Scenario A: No Filter - เปิดหน้าหลักสูตร - เห็นข้อมูลทั้งหมด - Select ทุกตัวแสดง `ทั้งหมด` หรือ Placeholder - URL ไม่มี Filter Param ที่ว่างเปล่า ## Scenario B: Category - เลือก K - URL มี `category=K` - ตารางแสดงเฉพาะ K - Pagination กลับหน้า 1 ## Scenario C: Change Selection - จาก K เปลี่ยนเป็น S - URL เปลี่ยนเป็น `category=S` - ไม่มีค่า K ค้างอยู่ - ไม่เกิด Query แบบหลายค่า ## Scenario D: Combined Filters - เลือก Category และ Status - ตารางต้องตรงทั้งสองเงื่อนไข - Total Count ถูกต้อง ## Scenario E: Clear One Filter - เลือก Category เป็น `ทั้งหมด` - ลบเฉพาะ `category` จาก URL - Status และ Search ยังอยู่ ## Scenario F: Reset All - กดล้างตัวกรอง - Search และทุก Select ถูกล้าง - กลับหน้า 1 - ตารางแสดงทั้งหมด ## Scenario G: Refresh - เลือก Filter - Refresh หน้า - Select และข้อมูลยังตรงกับ URL ## Scenario H: Mobile - Toolbar Wrap ถูกต้อง - Select เปิดและเลือกได้ - ไม่มี Horizontal Overflow --- # Acceptance Criteria ถือว่างานเสร็จเมื่อผ่านครบทุกข้อ - หน้าหลักสูตรใช้ Select แทน Filter แบบเก่า - Filter ที่กำหนดเลือกได้เพียง 1 ค่า - ไม่มี Checkbox Multi Select ใน Filter ที่ถูกปรับ - State ไม่ใช้ Array สำหรับ Single Select - URL เก็บเพียงค่าเดียวต่อ Filter - เลือก `ทั้งหมด` แล้วลบ Parameter ออกจาก URL - เปลี่ยน Filter แล้ว Reset หน้าเป็น 1 - Search ทำงานร่วมกับ Filter - Sorting ทำงานร่วมกับ Filter - Pagination ทำงานถูกต้อง - Total Count ตรงกับข้อมูล - Refresh แล้วยังคง Filter - Back/Forward ทำงานถูกต้อง - Reset Filter ทำงานครบ - Empty State ถูกต้อง - Responsive - Accessibility ผ่าน - Permission เดิมไม่เสีย - ไม่มี Console Error - ไม่มี React Warning - ไม่มี Hydration Error - ไม่มี TypeScript Error - ไม่มี ESLint Error - Test ที่เกี่ยวข้องผ่าน - Production Build ผ่าน --- # Commands to Run ตรวจสอบ `package.json` และใช้คำสั่งจริงของโปรเจกต์ อย่างน้อยต้องรัน ```bash npm run lint npm run typecheck npm run test npm run build ``` หากโปรเจกต์ใช้ `pnpm`, `yarn` หรือ `bun` ให้ใช้ Package Manager เดิมของโปรเจกต์ หากไม่มี Script บางรายการ ห้ามสร้างคำสั่งปลอม ให้รายงานตามจริงว่าใช้คำสั่งใดแทน --- # Constraints 1. ห้ามแก้ Database Schema หากไม่จำเป็น 2. ห้ามเปลี่ยน Permission Model 3. ห้ามลด Server-side Authorization 4. ห้ามแก้ API Contract อื่นที่ไม่เกี่ยวข้อง 5. ห้ามสร้าง Component ซ้ำ หากมี Shared Component รองรับ 6. ห้ามเปลี่ยนทุก Filter ทั้งระบบโดยไม่มีการตรวจสอบ Regression 7. ห้ามใช้ `any` เพื่อหลบ TypeScript 8. ห้ามซ่อน Error ด้วย `eslint-disable` หรือ `@ts-ignore` 9. ห้ามเปลี่ยน Business Logic ของหลักสูตร 10. ห้ามลบ Feature เดิม เช่น Search, Sorting, Pagination, Export 11. ห้ามถือว่างานเสร็จจาก UI อย่างเดียว 12. ห้ามแก้ข้อมูล Production หรือ Seed Data โดยไม่จำเป็น --- # Required Final Report หลังดำเนินการเสร็จ ให้ตอบกลับด้วยรายงานตามหัวข้อต่อไปนี้ ## 1. Root Cause อธิบายว่า Filter เดิมใช้รูปแบบใด และเหตุใดจึงยังเป็นรูปแบบเก่า ## 2. Files Changed แสดงรายการไฟล์ที่แก้ไข พร้อมสรุปหน้าที่ของแต่ละไฟล์ ## 3. Filters Updated แสดงตาราง | Filter | Before | After | URL Param | | -------- | ------------ | ------------- | ---------- | | Category | Multi Select | Single Select | `category` | | Status | Multi Select | Single Select | `status` | ให้ใช้รายการ Filter จริงที่พบในระบบ ## 4. Frontend Changes สรุป - Component ที่ใช้ - State ที่เปลี่ยน - Reset behavior - Responsive behavior ## 5. Backend Changes สรุป - Parser - Validation - Query conditions - Count query - API compatibility ## 6. Legacy Cleanup ระบุโค้ด Multi Select หรือ Array Logic ที่ลบออก ## 7. Permission Verification ยืนยันว่า `courses:read/write` ยังคงถูกใช้ถูกต้องทั้ง UI และ Server ## 8. Tests Added or Updated แสดง Test Cases และผลลัพธ์ ## 9. Validation Results รายงานผลจริงของ ```text Lint: Typecheck: Tests: Build: ``` ห้ามรายงานว่า Pass หากไม่ได้รันจริง ## 10. Remaining Issues ระบุสิ่งที่ยังไม่ได้แก้ พร้อมเหตุผล หากไม่มีให้ระบุ ```text ไม่พบประเด็นคงค้างในขอบเขตงานนี้ ``` --- # Definition of Done งานนี้จะถือว่าเสร็จสมบูรณ์เมื่อหน้าหลักสูตรของเจ้าหน้าที่ใช้ Filter แบบ Single Select อย่างครบถ้วนทั้ง UI และ Backend มีพฤติกรรมสอดคล้องกับมาตรฐาน Filter ของระบบ ไม่เกิด Regression และผ่านการตรวจสอบตาม Acceptance Criteria ทั้งหมด