16 KiB
Pending Review Audit Report
Summary
Overall Status
- Good
ภาพรวม:
- หน้า
Pending Reviewมีโครงสร้างค่อนข้างดี ใช้ shared DataTable stack, React Query, route handler, server-side guard และ validation ตาม pattern ของโปรเจกต์ - มี loading state, error state, empty state, pagination, search, sorting, filtering และ review flow ครบในระดับใช้งานจริง
- อย่างไรก็ตาม ยังมีจุดสำคัญที่ทำให้ semantics ของหน้าไม่คมพอ โดยเฉพาะเรื่องหน้า
Pending Reviewยังไม่ได้บังคับให้เป็น “pending only” จริงทั้ง flow
Architecture Review
สิ่งที่พบ:
- Route หลักใช้ src/app/dashboard/pending-review/page.tsx
- หน้า list ใช้ server prefetch ผ่าน src/features/training-records/components/pending-review-listing.tsx
- table ใช้ shared
DataTable,DataTableToolbar,useDataTable - data source ใช้ feature เดียวกับ
training-recordsผ่านtrainingRecordsQueryOptions() - review action ใช้ dedicated route
/dashboard/training-records/[trainingRecordId]/review - API ใช้ route handler และ Drizzle
- ไม่พบ Prisma หรือ Server Actions ใน flow นี้
ข้อสรุป:
- สถาปัตยกรรมโดยรวมสอดคล้องกับมาตรฐานโปรเจกต์
- จุดที่ยังไม่คมคือหน้า
pending-reviewยัง reuse list endpoint แบบ generic มากกว่าจะมี scope ที่ชัดว่าเป็น review queue โดยตรง
UI Review
จุดที่ดี:
- ใช้
PageContainerถูกต้อง - ปุ่ม action หน้า page header และปุ่มในฟอร์มมี responsive behavior พอใช้
- ตารางใช้ shell กลางของโปรเจกต์ ทำให้ spacing และ pagination ค่อนข้างสม่ำเสมอ
- action menu ในตารางมีขนาดพอเหมาะและมี
aria-label
จุดที่ควรระวัง:
- filter
company,department,yearเป็น text input ทั้งหมด ทำให้ UX ไม่แน่นเท่า faceted/select filter - badge status ใช้
variant='outline'เหมือนกันทุกสถานะ ทำให้ความแตกต่างทางสายตาระหว่างpending,approved,rejected,needs_revisionไม่ชัด - empty state ไม่มีทางลัดให้ล้าง filter จากหน้าจอเดียว
UX Review
จุดที่ดี:
- หากไม่มี
statusใน URL จะ redirect ไป?status=pending - มีเส้นทางกลับไปหน้ารายการอบรมหลัก
- review form หลัง submit สำเร็จจะ toast, redirect กลับ queue, และ refresh ข้อมูล
จุดที่ควรปรับ:
- ผู้ใช้ยังสามารถเปลี่ยน filter status ไปดู
approved,rejected,needs_revisionบนหน้าPending Reviewได้ ทำให้ชื่อหน้ากับพฤติกรรมจริงไม่ตรงกัน - เมื่อกรองแล้วไม่เจอข้อมูล ผู้ใช้ไม่เห็น toolbar และไม่เห็นปุ่ม reset filter
- error boundary แสดง
error.messageตรง ๆ ซึ่งอาจไม่เหมาะกับผู้ใช้ปลายทาง
Permission Review
Employee
Can View:
- ไม่ควรเข้าหน้า
Pending Review - ไม่ควรเข้าหน้า review รายการ
- ไม่ควรเรียก review API
Can Edit:
- ไม่มีสิทธิ์
Can Delete:
- ไม่มีสิทธิ์
หลักฐาน:
- หน้า
Pending Reviewใช้requireHRDDashboardAccess() - review API เช็ก
canReviewTrainingRecords(membership.role)
HRD
Can View:
- เข้าหน้า
Pending Review - เข้าหน้า review รายการ
- ดูรายการใน queue ได้ทั้งองค์กร
Can Edit:
- อนุมัติ / ไม่อนุมัติรายการที่ยัง
pending
Can Delete:
- ไม่มี delete action ในหน้านี้
Admin IT / Super Admin
Can View:
- เข้าหน้า
Pending Reviewได้ผ่าน business role mapping - เข้าหน้า review และ review API ได้ ถ้ามี organization membership ในบริบทนั้น
Can Edit:
- อนุมัติ / ไม่อนุมัติได้เหมือน HRD
Can Delete:
- ไม่มี delete action ในหน้านี้
ข้อสังเกต:
- สิทธิ์ review จริงที่ API อิงกับ
membership.role === owner/adminไม่ได้อิงชื่อ business role โดยตรง - ในทางปฏิบัติถือว่า HRD และ IT ใช้งานได้ แต่ logic สิทธิ์ยังเป็น “ตีความร่วม” มากกว่า matrix ที่ชัดเจน
Functional Review
Checklist
| Feature | Status | Notes |
|---|---|---|
| Display pending records | Partial | หน้า default เป็น status=pending แต่ผู้ใช้ยังเปลี่ยนไปดู status อื่นได้ |
| Pagination | Pass | ใช้ shared useDataTable + DataTablePagination |
| Search | Pass | ใช้ text filter ที่ column search |
| Sorting | Pass | รองรับ server-side sorting |
| Filtering | Pass | มี filter ตาม status, company, department, year |
| Responsive layout | Pass | table shell รองรับ overflow และปุ่มหลักไม่ล้นง่าย |
| Loading state | Pass | มี loading.tsx |
| Empty state | Partial | มี empty state แต่ไม่มี reset filter / toolbar ใน state นี้ |
| Error state | Partial | มี error.tsx แต่แสดง raw error message |
| Refresh after action | Pass | mutation invalidate query + push + refresh |
| Correct badge/status | Partial | label ถูก แต่ visual distinction ยังไม่ชัด |
| Correct action buttons | Pass | มี ตรวจสอบ และ ดูรายละเอียด |
| Correct permissions | Pass | page/API กันสิทธิ์ค่อนข้างถูกต้อง |
Code Quality Review
จุดที่ดี:
- แยก route, listing, table, columns, action menu, schema, service, queries, mutations, server data helper ชัดเจน
- ใช้ shared DataTable stack ตาม guideline
- ใช้ Zod validation ใน review API
- ใช้ React Query cache invalidation ตาม feature key
- ไม่มีการ import mock data
จุดที่ควรสังเกต:
- หน้า
Pending Reviewreuse generictraining-recordslist แทนที่จะมี query helper ที่สื่อชัดว่าเป็น review queue TrainingRecordReviewDialogยังอยู่ใน feature แต่ไม่ได้เป็น flow หลักของหน้า review ปัจจุบัน ดูเหมือนมีโอกาสเป็น dead/legacy path- การใช้ generic endpoint ทำให้ semantics ของหน้า review queue พึ่งพา URL filter มากเกินไป
Security Review
จุดที่ดี:
- หน้า
Pending Reviewกัน direct URL ด้วยrequireHRDDashboardAccess() - review page กัน direct URL ด้วย
requireHRDDashboardAccess() - review API ใช้ server-side auth ผ่าน
requireOrganizationAccess() - review API เช็กสิทธิ์ซ้ำผ่าน
canReviewTrainingRecords() - review API ป้องกันการ review ซ้ำโดยเช็กว่า record ต้องอยู่สถานะ
pending - data list ถูก scope ด้วย organization และ membership role
ความเสี่ยง:
error.tsxแสดงerror.messageตรง ๆ ซึ่งอาจเผยรายละเอียดภายใน เช่นข้อความจาก backend หรือฐานข้อมูล
Performance Review
จุดที่ดี:
- ใช้ server prefetch + hydration
- ใช้ manual pagination / filtering / sorting ที่สอดคล้องกับ server-side data source
- query invalidation หลัง review ทำครบทั้ง list และ detail key
- ไม่พบ N+1 query ที่แย่ในระดับ row-by-row สำหรับตารางนี้
จุดที่ควรปรับ:
- หน้า
pending-reviewใช้ generic list query ที่โหลด certificate preview map สำหรับทุก row แม้ตารางนี้ไม่ได้แสดง certificate preview โดยตรง - ถ้าหน้านี้มีการใช้งานบ่อยและข้อมูลเยอะ อาจแยก endpoint/query ของ review queue โดยตัด field ที่ไม่จำเป็นออกได้
Problems Found
High
- Description: หน้า
Pending Reviewไม่ได้บังคับให้แสดงเฉพาะรายการpendingจริง - File: page.tsx, pending-review-table.tsx, pending-review-columns.tsx
- Component:
Page,PendingReviewTable,pendingReviewColumns - Reason: หน้า redirect แค่กรณีไม่มี
statusแต่ยังเปิดให้ filter เป็นapproved,rejected,needs_revision - Impact: ชื่อหน้าและความหมายของ queue ไม่ตรงกับพฤติกรรมจริง ทำให้ผู้ใช้สับสน และทำให้ขอบเขตของ review queue ไม่ชัด
Medium
-
Description: เมื่อกรองแล้วไม่เจอข้อมูล ระบบซ่อน toolbar ทั้งก้อน ทำให้ผู้ใช้ไม่มีปุ่มล้าง filter จากหน้าจอเดียว
-
File: pending-review-table.tsx
-
Component:
PendingReviewTable -
Reason: empty state return ก่อน render
DataTableToolbar -
Impact: UX ติดขัด โดยเฉพาะเมื่อ user search/filter พลาดแล้วไม่รู้จะ reset อย่างไร
-
Description: error boundary แสดง raw
error.messageให้ผู้ใช้ -
File: error.tsx
-
Component:
PendingReviewError -
Reason: render ข้อความ error จาก runtime ตรง ๆ
-
Impact: เสี่ยงเผยข้อความภายในระบบ และทำให้ UX ดู technical เกินไป
-
Description: หน้า queue ใช้ endpoint generic ของ training records แทน query ที่สื่อเฉพาะ review queue
-
Component:
PendingReviewListingPage,listTrainingRecordsForOrganization -
Reason: reuse ได้ แต่ semantics ของ feature ไม่ชัด
-
Impact: maintainability ลดลง และทำให้หน้า queue รับ status/filter เกินขอบเขตหน้าที่
Low
-
Description: badge status ใช้รูปแบบ visual เดียวกันทุกสถานะ
-
Component:
pendingReviewColumns -
Reason: ใช้
Badge variant="outline"ทุกค่า -
Impact: สแกนสถานะได้ช้ากว่าที่ควร
-
Description: filter
year,company,departmentยังเป็น text-based ทั้งหมด -
Component:
pendingReviewColumns -
Reason: ใช้งานได้ แต่ UX ไม่แข็งแรงเท่า select/faceted options
-
Impact: ใช้งานได้แต่ความเร็วในการกรองต่ำลง โดยเฉพาะกับผู้ใช้ธุรการ
-
Description:
TrainingRecordReviewDialogดูเป็น flow เก่าที่ไม่ได้เป็น entry point หลักของ pending review ปัจจุบัน -
Component:
TrainingRecordReviewDialog -
Reason: หน้าปัจจุบันพาไป review page เต็มแทน dialog
-
Impact: เพิ่มภาระในการดูแลโค้ดและทำให้ feature surface ดูซ้ำซ้อน
Recommendations
- บังคับให้หน้า
Pending Reviewเป็นpending-onlyจริงทั้ง route, filter UI, และ query contract - ใน empty state ควรยังแสดง toolbar หรืออย่างน้อยมีปุ่ม
ล้างตัวกรอง - เปลี่ยน error message สำหรับผู้ใช้ให้เป็นข้อความที่ปลอดภัยและอ่านง่าย แล้ว log รายละเอียดไว้ภายในแทน
- ถ้าหน้า review queue เป็นฟีเจอร์สำคัญ ควรแยก query/helper สำหรับ review queue โดยเฉพาะ เพื่อให้ field และ logic ชัดกว่าการ reuse list generic
- เพิ่ม visual distinction ของ status badge
- พิจารณาปรับ filter บางตัวเป็น faceted/select หากมี option data พร้อมใช้อยู่แล้ว
- ตรวจสอบว่า
TrainingRecordReviewDialogยังจำเป็นหรือไม่ และถ้าไม่ใช้จริงควรวางแผน cleanup ในรอบถัดไป
Missing Features
- การบังคับ queue ให้เป็น
pending-onlyแบบชัดเจน - ปุ่ม reset filter ใน empty state
- visual emphasis ของแต่ละ status
- query contract เฉพาะสำหรับ review queue
Final Score
Architecture: 8/10
UI: 7/10
UX: 6.5/10
Performance: 7.5/10
Security: 8/10
Maintainability: 7.5/10
Overall: 7.4/10