24 KiB
Employee Dashboard Audit Report
Executive Summary
Overall Status
- Good
ภาพรวม:
- หน้า Employee Dashboard ใช้สถาปัตยกรรมที่สอดคล้องกับโปรเจ็กต์ค่อนข้างดี โดยมี route guard, server-side data aggregation และแยก component ตาม feature ชัดเจน
- ข้อมูลหลักของพนักงาน เช่น ชั่วโมงที่อนุมัติแล้ว ความคืบหน้า K / S / A กิจกรรมล่าสุด และประกาศล่าสุด ถูกดึงจากฝั่ง server ภายใต้ขอบเขตองค์กรและผู้ใช้ที่กำลังล็อกอิน
- จุดที่ควรปรับหลัก ๆ อยู่ที่ error handling, ความสม่ำเสมอของ navigation/UX, การแสดงผล progress เมื่อเกินเป้า, และการสื่อสารกรณีข้อมูลว่างหรือผู้ใช้ยังไม่ผูก employee profile
Architecture Review
สิ่งที่พบ:
- Route หลักอยู่ที่ src/app/dashboard/page.tsx และ redirect ไป
/dashboard/overview - Employee Dashboard จริงถูกวางบน src/app/dashboard/overview/layout.tsx และ src/app/dashboard/overview/page.tsx
- Layout ใช้ parallel routes แยก slot เป็น
@area_stats,@sales,@bar_stats,@pie_stats - Data หลักรวมอยู่ใน src/features/overview/server/overview-data.ts และใช้
cache()ลดการคำนวณซ้ำใน render เดียวกัน - Guard ใช้ src/lib/auth/page-guards.ts ผ่าน
requireEmployeeDashboardAccess()
ข้อสรุป:
- โครงสร้างโดยรวมดี และแยก server responsibility ไว้ถูกที่
- อย่างไรก็ดี Employee Dashboard ถูกวางชื่อ route เป็น
overviewมากกว่าจะมี bounded context ชัดเจนแบบemployee-dashboardทำให้ความหมายของหน้าและความต่างระหว่าง employee กับ HRD พึ่งพา conditional rendering ค่อนข้างมาก
Functional Review
| Feature | Status | Notes |
|---|---|---|
| View own dashboard | Pass | พนักงานเข้า dashboard ได้ผ่าน requireEmployeeDashboardAccess() |
| Summary cards | Pass | มี approved, pending/revision, rejected, progress |
| Required hours resolution | Partial | fallback ไป policy ล่าสุดหรือ 0 ได้ แต่ไม่มีข้อความอธิบายเมื่อไม่พบ policy/target |
| K / S / A summary | Pass | คิดจาก approved records เท่านั้น ซึ่งสอดคล้องกับการวัดความคืบหน้า |
| Recent activities | Partial | แสดง 5 รายการล่าสุด แต่เรียงตาม createdAt ไม่ใช่ trainingDate |
| Announcements widget | Pass | แสดงประกาศล่าสุดและลิงก์ไปหน้ารายละเอียดได้ |
| Notifications page | Pass | มีหน้ารายการ notifications แยกต่างหาก |
| Progress bar | Partial | รองรับ 0 และ partial ได้ แต่กรณีเกินเป้าถูก cap ที่ 100% ทำให้ไม่เห็นว่าทะลุเป้า |
| Empty states | Partial | มี empty state ในหลาย widget แต่ไม่มีคำอธิบายกรณีผู้ใช้ยังไม่ผูก employee profile |
| Error states | Partial | มี error boundary ครบหลายจุด แต่แสดง raw error.message ต่อผู้ใช้ |
| Responsive layout | Pass | layout ใช้ grid ที่ยุบลงได้ทั้ง mobile และ desktop |
| Dashboard filter | Partial | employee กรองได้ตามปี, admin กรองได้หลายมิติ แต่ใช้ native select และ submit form แบบพื้นฐาน |
| Refresh behavior | Partial | ข้อมูลดึงจาก server render ใหม่ได้ แต่หน้า dashboard ไม่มี evidence ชัดเจนเรื่อง targeted invalidation หลัง action อื่น ๆ |
Business Rule Review
สิ่งที่สอดคล้อง:
- Employee เห็นเฉพาะข้อมูลตนเองผ่าน
getEmployeeIdsForUser()และเงื่อนไข scope ใน overview-data.ts - ชั่วโมง approved ใช้
approvedHoursถ้ามี ไม่เช่นนั้น fallback ไปhours - K / S / A คิดจากรายการที่อนุมัติแล้วเท่านั้น
- ปีที่ใช้กรองแยกจากค่า default ปีปัจจุบัน
จุดที่ควรระวัง:
- การใช้ปีอ้างอิงด้วย
new Date().getUTCFullYear()และช่วงวันแบบ UTC อาจต่างจากการตีความปีตาม timezone ธุรกิจที่ใช้งานจริงปลายปี/ต้นปี - หากพนักงานไม่มี employee profile ที่ผูกกับ user ระบบจะคืนค่าศูนย์เกือบทั้งหมด แต่ไม่ได้บอกผู้ใช้ว่าสาเหตุคือ “ยังไม่พบข้อมูลพนักงาน”
- ความคืบหน้าถูกจำกัดไม่เกิน 100% ทำให้ business outcome แบบ “เกินเป้า” ไม่ถูกสื่อสารบน UI
Role & Permission Review
Employee
- View own dashboard: Pass
- View own statistics: Pass
- View own training history: Pass
- View notifications: Pass ผ่าน src/app/dashboard/notifications/page.tsx
- View announcements: Pass ผ่าน src/app/dashboard/announcements/page.tsx
- View other employees: Not observed in dashboard flow
- Approve training / edit policy / access HRD pages: Guarded by HRD-only routes
ข้อสังเกต:
- ฝั่ง route protection ถือว่าดี
- แต่ navigation ไม่สม่ำเสมอ: employee เข้า announcements และ notifications ได้โดยตรง แต่เมนูใน src/config/nav-config.ts ไม่ได้เปิดเส้นทางเหล่านี้ให้เห็นอย่างชัดเจนใน sidebar หลัก
UI Review
จุดที่ดี:
- ใช้
PageContainer,Card,Badge,Progressและ chart shell เดียวกับโปรเจ็กต์ - spacing โดยรวมสม่ำเสมอ
- widget หลักแบ่งเป็น summary + content blocks อ่านง่ายบน desktop
จุดที่ควรปรับ:
- summary cards render
Badgeทุกใบ แม้หลายใบส่งbadge: ''มา ทำให้มี badge ว่างหรือเหลือเพียง icon ใน src/app/dashboard/overview/layout.tsx - filter panel ใช้ native
<select>แทน shared form/select patterns ของโปรเจ็กต์ - หน้า employee dashboard ไม่มี notification widget โดยตรง ทั้งที่มี notifications page อยู่แล้ว ทำให้ข้อมูลสำคัญถูกแยกบริบทออกจากหน้าหลัก
UX Review
จุดที่ดี:
- ผู้ใช้เห็นภาพรวมข้อมูลสำคัญได้เร็ว
- ประกาศล่าสุดและกิจกรรมล่าสุดช่วยสร้างบริบทการใช้งานประจำวัน
จุดที่ควรปรับ:
- หากเกิด error ผู้ใช้จะเห็นข้อความเชิงเทคนิคเกินไป
- หากไม่มีข้อมูลเพราะยังไม่ผูก employee profile, ไม่มี policy, หรือยังไม่เคยส่งรายการ ระบบไม่อธิบายสาเหตุให้ชัด
- notifications แยกไปอีกหน้า แต่ไม่มีทางเข้าที่เด่นพอจาก dashboard/sidebar สำหรับ employee
- recent activities ใช้วันฝึกอบรมแสดงผล แต่ใช้เวลาสร้างรายการเป็นตัวเรียงลำดับ ซึ่งอาจขัดกับความคาดหวังว่า “ล่าสุด” หมายถึงฝึกเมื่อไร
Accessibility Review
จุดที่ดี:
- ใช้ semantic button/link/card ของ shared UI หลายจุด
- chart components เปิด
accessibilityLayerใน Recharts หลายตัว
จุดที่ควรปรับ:
- error UI หลายจุดพึ่งพาสีและข้อความทั่วไป แต่ไม่มี recovery action ครบทุก slot
- native chart summary ไม่มี text alternative เชิงสรุปมากนักสำหรับ screen reader
- badge/progress สื่อสถานะหลักด้วย visual emphasis มากกว่าคำอธิบายเฉพาะกรณี “เกินเป้า”
Code Quality Review
จุดที่ดี:
- รวม logic ฝั่ง dashboard ไว้ใน data builder เดียว ทำให้ trace business logic ได้ง่าย
- type ค่อนข้างชัด และ model ที่ส่งไป component แยกตามการใช้งานจริง
- ใช้ helper role กลาง เช่น
isOrganizationAdminRole()
จุดที่ควรปรับ:
OverviewFilterPanelเช็ก admin role ด้วย string comparison ตรง ๆ แทน helper กลาง- มีการผสม concern ของ employee/admin dashboard ไว้ใน route เดียวค่อนข้างมาก
- naming บางส่วนยังติด template เดิม เช่น
RecentSalesทั้งที่ใช้แสดงกิจกรรมการอบรม
API Review
สิ่งที่พบ:
- Dashboard data ฝั่ง overview ดึงตรงจาก server helper ไม่ผ่าน client query เป็นหลัก
- Notifications ใช้ route handlers และ React Query ตาม pattern โปรเจ็กต์
- Notifications API validate query params ด้วย Zod
จุดที่ควรปรับ:
- error responses จาก API ถูกส่งกลับไปยัง UI แล้ว render
error.messageตรง ๆ ในบางหน้าของ dashboard/notifications - dashboard เองยังไม่มี contract ที่แยก concern เช่น announcements กับ notifications ให้ชัดในหน้าหลัก
Performance Review
จุดที่ดี:
getOverviewDashboardData()ถูกห่อด้วยcache()ช่วยลดงานซ้ำใน request เดียว- widget หลักเป็น server-driven ลด client state ที่ไม่จำเป็น
จุดที่ควรระวัง:
- หน้า overview มีหลาย parallel route ที่ต่างเรียก data helper เดียวกัน แม้
cache()จะช่วย แต่โครงสร้างยังค่อนข้างซับซ้อนสำหรับ dashboard ที่ข้อมูลสัมพันธ์กันสูง - chart และ card ทั้งหมดโหลดพร้อมกัน ไม่มี loading boundary ระดับหน้า root ของ overview
Security Review
จุดที่ดี:
- route protection ฝั่ง employee/HRD ใช้งานจริง
- dashboard scope ข้อมูลตาม organization และ user
- notifications API จำกัดตาม
organizationIdและuserId
ความเสี่ยง:
- error boundary หลายจุดเปิดเผย
error.messageตรง ๆ เช่น src/app/dashboard/overview/error.tsx, src/app/dashboard/overview/@sales/error.tsx, src/features/notifications/components/notifications-page.tsx
Consistency Review
สอดคล้อง:
- ใช้ shared UI stack และ page guard ตามแนวทางโปรเจ็กต์
- notifications page ใช้ React Query + HydrationBoundary ตาม pattern กลาง
ไม่สอดคล้อง:
- dashboard table-like recent activity ใช้ custom list ซึ่งโอเคตามลักษณะข้อมูล แต่ naming ยังโยงกับ template เดิม
- filter panel ของ overview ยังไม่ใช้ shared select/form wrappers แบบที่หลาย feature ใช้อยู่
- employee-accessible pages บางหน้าไม่มีเมนูนำทางที่สอดคล้องกับสิทธิ์จริง
Edge Case Review
- New employee: แสดงค่า 0 ได้ แต่ไม่บอกว่าเพราะยังไม่มีข้อมูลพนักงาน/ประวัติอบรม
- No training records: รองรับได้
- All completed: รองรับได้
- Over target: คำนวณได้ แต่ UI ไม่สื่อส่วนเกิน
- Missing category: K / S / A จะเหลือค่าตามข้อมูลที่มี แต่ไม่ได้อธิบาย category ที่หาย
- Missing policy: fallback เป็น 0 ได้ แต่ไม่มี warning/explanation
- Missing profile: ไม่พัง แต่ silent failure ในเชิง UX
- API failure: มี error UI แต่ข้อความไม่เหมาะกับผู้ใช้ปลายทาง
- Slow network: มี loading เฉพาะบาง slot แต่ไม่มี root loading ของ overview
Problems Found
High
-
Description: Error UI หลายจุดแสดง raw
error.messageต่อผู้ใช้โดยตรง -
File: overview/error.tsx, @sales/error.tsx, @area_stats/error.tsx, @pie_stats/error.tsx, notifications-page.tsx
-
Component:
OverviewError,SalesError,AreaStatsError,PieStatsError,NotificationsPage -
Severity: High
-
Impact: เสี่ยงเปิดเผยข้อมูลเชิงเทคนิคจาก backend/runtime และทำให้ UX ของผู้ใช้ปลายทางสับสน
-
Recommendation: แสดงข้อความกลางที่ปลอดภัยต่อผู้ใช้ และ log รายละเอียดไว้ภายในแทน
-
Description: Dashboard ไม่สื่อกรณี “เกินเป้าชั่วโมง” แม้ข้อมูลคำนวณถึงเป้าแล้ว
-
File: overview-data.ts, ksa-progress.tsx
-
Component:
buildEmployeeCards,KsaProgress -
Severity: High
-
Impact: ผู้ใช้ที่ทำเกินเป้าจะเห็นเพียง 100% เท่ากับผู้ใช้ที่เพิ่งแตะเป้า ทำให้สูญเสียข้อมูลเชิงบริหารและแรงจูงใจ
-
Recommendation: แยกสถานะ completed กับ exceeded และสื่อส่วนเกินบน UI
Medium
-
Description: Recent activities เรียงด้วย
createdAtแต่แสดงวันที่เป็นtrainingDate -
File: overview-data.ts, recent-sales.tsx
-
Component:
getOverviewDashboardData,RecentSales -
Severity: Medium
-
Impact: ผู้ใช้อาจเข้าใจว่าเป็น “อบรมล่าสุดตามวันที่ฝึก” ทั้งที่จริงคือ “รายการที่สร้างล่าสุด”
-
Recommendation: เลือกให้ชัดว่าจะเรียงตามวันฝึกหรือวันส่ง และทำ label ให้ตรง
-
Description: พนักงานเข้าหน้า notifications และ announcements ได้ แต่ navigation หลักไม่สอดคล้องกับสิทธิ์จริง
-
File: nav-config.ts, notifications/page.tsx, announcements/page.tsx
-
Component: sidebar config, employee-accessible routes
-
Severity: Medium
-
Impact: discoverability ต่ำ ผู้ใช้เข้าฟีเจอร์ที่มีสิทธิ์ได้ยาก และ navigation model ไม่สม่ำเสมอ
-
Recommendation: ทบทวนเมนู employee ให้ตรงกับ route ที่เปิดใช้งานจริง
-
Description: Summary cards แสดง badge แม้ไม่มีข้อความ badge จริง
-
File: overview/layout.tsx
-
Component:
OverViewLayout -
Severity: Medium
-
Impact: UI ดูไม่ตั้งใจและเพิ่ม noise ในส่วนสรุปสำคัญ
-
Recommendation: render badge แบบมีเงื่อนไข หรือกำหนดข้อความ badge ให้ครบ
-
Description: กรณี user ไม่มี employee profile / ไม่มี policy ระบบแสดงศูนย์อย่างเงียบ ๆ
-
File: overview-data.ts
-
Component:
getOverviewDashboardData -
Severity: Medium
-
Impact: ผู้ใช้และทีม support แยกไม่ออกว่า “ไม่มีข้อมูล” หรือ “ข้อมูลเชื่อมไม่ครบ”
-
Recommendation: เพิ่ม state หรือ messaging ที่อธิบายสาเหตุของข้อมูลว่าง
Low
-
Description:
OverviewFilterPanelใช้ string compare role ตรง ๆ แทน helper กลาง -
Component:
OverviewFilterPanel -
Severity: Low
-
Impact: ลดความสม่ำเสมอของโค้ดและเพิ่มโอกาส logic drift
-
Recommendation: ใช้ role helper กลางเดียวกับส่วนอื่น
-
Description: ชื่อ component
RecentSalesไม่ตรงกับหน้าที่จริง -
File: recent-sales.tsx
-
Component:
RecentSales -
Severity: Low
-
Impact: อ่านโค้ดยากขึ้นและสะท้อนร่องรอยจาก template เดิม
-
Recommendation: เปลี่ยนชื่อให้ตรงกับโดเมนเมื่อมีรอบ refactor
-
Description: หน้า overview ไม่มี root loading state ของ route หลัก
-
Component: overview route
-
Severity: Low
-
Impact: initial load ของทั้งหน้าอาจดูนิ่งเกินไปหาก data ช้า และพึ่ง loading ของบาง slot เท่านั้น
-
Recommendation: เพิ่ม loading ระดับหน้าให้สอดคล้องกับหน้า dashboard สำคัญอื่น ๆ
Missing Features
- Notification summary/widget บนหน้า dashboard สำหรับ employee
- ข้อความอธิบายกรณีไม่พบ employee profile หรือ policy เป้าหมาย
- การสื่อสารสถานะ “เกินเป้า” แยกจาก “ครบเป้า”
- Navigation entry ที่สอดคล้องกับ employee-accessible notifications/announcements
Improvement Opportunities
- แยก employee dashboard semantics ให้ชัดกว่าการ reuse
overviewroute ร่วมกับ HRD - เพิ่ม domain-specific naming ให้ component/slot เพื่อให้อ่านโค้ดง่ายขึ้น
- ทำ filter panel ให้ใช้ shared form/select stack ของโปรเจ็กต์
- เพิ่ม explanatory empty states ตามสาเหตุจริงของข้อมูลว่าง
- เพิ่ม text summary สำหรับ charts เพื่อช่วยทั้ง accessibility และการอ่านค่าบน mobile
Final Assessment
Architecture: 8/10
Functionality: 7.5/10
UI: 7/10
UX: 6.8/10
Accessibility: 6.8/10
Performance: 7.5/10
Security: 7.5/10
Maintainability: 7.6/10
Overall Score: 7.3/10