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

336 lines
24 KiB
Markdown

# 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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/page.tsx) และ redirect ไป `/dashboard/overview`
- Employee Dashboard จริงถูกวางบน [src/app/dashboard/overview/layout.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/layout.tsx) และ [src/app/dashboard/overview/page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/server/overview-data.ts) และใช้ `cache()` ลดการคำนวณซ้ำใน render เดียวกัน
- Guard ใช้ [src/lib/auth/page-guards.ts](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/server/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/notifications/page.tsx)
- View announcements: Pass ผ่าน [src/app/dashboard/announcements/page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/error.tsx), [src/app/dashboard/overview/@sales/error.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/@sales/error.tsx), [src/features/notifications/components/notifications-page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/error.tsx), [@sales/error.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/@sales/error.tsx), [@area_stats/error.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/@area_stats/error.tsx), [@pie_stats/error.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/@pie_stats/error.tsx), [notifications-page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/notifications/components/notifications-page.tsx)
- Component: `OverviewError`, `SalesError`, `AreaStatsError`, `PieStatsError`, `NotificationsPage`
- Severity: High
- Impact: เสี่ยงเปิดเผยข้อมูลเชิงเทคนิคจาก backend/runtime และทำให้ UX ของผู้ใช้ปลายทางสับสน
- Recommendation: แสดงข้อความกลางที่ปลอดภัยต่อผู้ใช้ และ log รายละเอียดไว้ภายในแทน
- Description: Dashboard ไม่สื่อกรณี “เกินเป้าชั่วโมง” แม้ข้อมูลคำนวณถึงเป้าแล้ว
- File: [overview-data.ts](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/server/overview-data.ts), [ksa-progress.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/components/ksa-progress.tsx)
- Component: `buildEmployeeCards`, `KsaProgress`
- Severity: High
- Impact: ผู้ใช้ที่ทำเกินเป้าจะเห็นเพียง 100% เท่ากับผู้ใช้ที่เพิ่งแตะเป้า ทำให้สูญเสียข้อมูลเชิงบริหารและแรงจูงใจ
- Recommendation: แยกสถานะ completed กับ exceeded และสื่อส่วนเกินบน UI
### Medium
- Description: Recent activities เรียงด้วย `createdAt` แต่แสดงวันที่เป็น `trainingDate`
- File: [overview-data.ts](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/server/overview-data.ts), [recent-sales.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/components/recent-sales.tsx)
- Component: `getOverviewDashboardData`, `RecentSales`
- Severity: Medium
- Impact: ผู้ใช้อาจเข้าใจว่าเป็น “อบรมล่าสุดตามวันที่ฝึก” ทั้งที่จริงคือ “รายการที่สร้างล่าสุด”
- Recommendation: เลือกให้ชัดว่าจะเรียงตามวันฝึกหรือวันส่ง และทำ label ให้ตรง
- Description: พนักงานเข้าหน้า notifications และ announcements ได้ แต่ navigation หลักไม่สอดคล้องกับสิทธิ์จริง
- File: [nav-config.ts](/d:/WY-2569/HRD/training-system-minimal-refactor/src/config/nav-config.ts), [notifications/page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/notifications/page.tsx), [announcements/page.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/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](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview/layout.tsx)
- Component: `OverViewLayout`
- Severity: Medium
- Impact: UI ดูไม่ตั้งใจและเพิ่ม noise ในส่วนสรุปสำคัญ
- Recommendation: render badge แบบมีเงื่อนไข หรือกำหนดข้อความ badge ให้ครบ
- Description: กรณี user ไม่มี employee profile / ไม่มี policy ระบบแสดงศูนย์อย่างเงียบ ๆ
- File: [overview-data.ts](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/server/overview-data.ts)
- Component: `getOverviewDashboardData`
- Severity: Medium
- Impact: ผู้ใช้และทีม support แยกไม่ออกว่า “ไม่มีข้อมูล” หรือ “ข้อมูลเชื่อมไม่ครบ”
- Recommendation: เพิ่ม state หรือ messaging ที่อธิบายสาเหตุของข้อมูลว่าง
### Low
- Description: `OverviewFilterPanel` ใช้ string compare role ตรง ๆ แทน helper กลาง
- File: [overview-filter-panel.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/components/overview-filter-panel.tsx)
- Component: `OverviewFilterPanel`
- Severity: Low
- Impact: ลดความสม่ำเสมอของโค้ดและเพิ่มโอกาส logic drift
- Recommendation: ใช้ role helper กลางเดียวกับส่วนอื่น
- Description: ชื่อ component `RecentSales` ไม่ตรงกับหน้าที่จริง
- File: [recent-sales.tsx](/d:/WY-2569/HRD/training-system-minimal-refactor/src/features/overview/components/recent-sales.tsx)
- Component: `RecentSales`
- Severity: Low
- Impact: อ่านโค้ดยากขึ้นและสะท้อนร่องรอยจาก template เดิม
- Recommendation: เปลี่ยนชื่อให้ตรงกับโดเมนเมื่อมีรอบ refactor
- Description: หน้า overview ไม่มี root loading state ของ route หลัก
- File: [src/app/dashboard/overview](/d:/WY-2569/HRD/training-system-minimal-refactor/src/app/dashboard/overview)
- 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 `overview` route ร่วมกับ 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