Files
alla-tms/docs/review/pending-review-audit.md
2026-07-16 09:53:14 +07:00

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 Review reuse generic training-records list แทนที่จะมี 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

  • File: pending-review-listing.tsx, training-record-data.ts

  • Component: PendingReviewListingPage, listTrainingRecordsForOrganization

  • Reason: reuse ได้ แต่ semantics ของ feature ไม่ชัด

  • Impact: maintainability ลดลง และทำให้หน้า queue รับ status/filter เกินขอบเขตหน้าที่

Low

  • Description: badge status ใช้รูปแบบ visual เดียวกันทุกสถานะ

  • File: pending-review-columns.tsx

  • Component: pendingReviewColumns

  • Reason: ใช้ Badge variant="outline" ทุกค่า

  • Impact: สแกนสถานะได้ช้ากว่าที่ควร

  • Description: filter year, company, department ยังเป็น text-based ทั้งหมด

  • File: pending-review-columns.tsx

  • Component: pendingReviewColumns

  • Reason: ใช้งานได้ แต่ UX ไม่แข็งแรงเท่า select/faceted options

  • Impact: ใช้งานได้แต่ความเร็วในการกรองต่ำลง โดยเฉพาะกับผู้ใช้ธุรการ

  • Description: TrainingRecordReviewDialog ดูเป็น flow เก่าที่ไม่ได้เป็น entry point หลักของ pending review ปัจจุบัน

  • File: training-record-review-dialog.tsx

  • 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