This commit is contained in:
2026-07-16 09:53:14 +07:00
parent 0fc112a2e8
commit 29cec708a3
1236 changed files with 212848 additions and 0 deletions

View File

@@ -0,0 +1,401 @@
# Employee Online Lessons Audit Report
## Executive Summary
Overall Status
- Needs Improvement
The employee-facing online lessons module follows the project's core architecture well: server-guarded dashboard routes, feature-local API contracts, React Query prefetch/hydration, and organization-scoped route handlers are all in place. Employee access to unpublished lessons is also enforced server-side, not only in the UI.
The main gaps are functional completeness and employee experience. The current implementation delivers a published lesson list and detail page, but the scope in `plans/audit-employee-online-lessons.md` expects more than content viewing. There is no employee progress tracking, no completion state, no learning history, no dashboard integration for lesson completion, no certificate section, no dedicated loading/error routes, and only basic attachment/video fallback handling. The module is structurally solid, but the employee learning workflow is still only partially implemented.
---
## Scope Reviewed
Routes
- `src/app/dashboard/online-lessons/page.tsx`
- `src/app/dashboard/online-lessons/[id]/page.tsx`
- `src/app/dashboard/online-lessons/manage/new/page.tsx`
- `src/app/dashboard/online-lessons/manage/[id]/page.tsx`
- `src/app/api/online-lessons/route.ts`
- `src/app/api/online-lessons/[id]/route.ts`
Feature components and contracts
- `src/features/online-lessons/components/online-lessons-listing.tsx`
- `src/features/online-lessons/components/online-lessons-page.tsx`
- `src/features/online-lessons/components/online-lesson-detail-page.tsx`
- `src/features/online-lessons/components/online-lesson-form.tsx`
- `src/features/online-lessons/components/online-lesson-action-menu.tsx`
- `src/features/online-lessons/api/types.ts`
- `src/features/online-lessons/api/service.ts`
- `src/features/online-lessons/api/queries.ts`
- `src/features/online-lessons/api/mutations.ts`
- `src/features/online-lessons/server/online-lesson-data.ts`
- `src/features/online-lessons/schemas/online-lesson.ts`
Shared/auth/navigation/storage
- `src/lib/auth/page-guards.ts`
- `src/lib/auth/session.ts`
- `src/lib/auth/roles.ts`
- `src/lib/online-lesson-storage.ts`
- `src/config/nav-config.ts`
- `docs/AI_DEVELOPMENT_GUIDE.md`
- `docs/PROJECT_ARCHITECTURE.md`
---
## Functional Review
| Feature | Status | Notes |
| ------- | ------ | ----- |
| Lesson list loads | Pass | `page.tsx` and `online-lessons-listing.tsx` use server prefetch + `HydrationBoundary`, then `useSuspenseQuery()` in `online-lessons-page.tsx`. |
| Pagination | Pass | Backed by `page` and `limit` params and returned from `/api/online-lessons`. |
| Search | Pass | Employee list supports text search through query state and server filtering. |
| Category filter | Pass | Employee list supports category filtering; categories are scoped server-side. |
| Sorting | Partial | Server sorts by published status and publish/create dates, but there is no employee-facing sort control. |
| Responsive cards | Fail | The employee list is a `DataTable`, not lesson cards. |
| Empty state | Pass | The page renders a clear empty state with filter reset support. |
| Loading state | Partial | There is inline fetch feedback in the list, but no route-level `loading.tsx` for list or detail. |
| Error state | Fail | No route-level `error.tsx` exists under `src/app/dashboard/online-lessons`. |
| Lesson detail basics | Partial | Title, description, category, published date, thumbnail, and attachment links exist. Instructor and duration do not. |
| Uploaded video support | Pass | Route validation and detail rendering support uploaded MP4, WebM, and MOV. |
| YouTube support | Pass | Detail page converts standard, short, shorts, and embed URLs into iframe embeds. |
| Video fallback UI | Partial | Non-embeddable external URLs fall back to an open-link card, but uploaded-video failure handling is minimal. |
| Attachment handling | Partial | Open/download links exist, but there is no inline image or PDF preview. |
| Certificate section | Fail | No certificate field or UI exists in the employee lesson detail. |
| Progress display | Fail | No lesson progress model, API field, or UI is present. |
| Completed status | Fail | No completion badge or completion tracking exists. |
| Learning history | Fail | No viewed/completed/recent lesson history is implemented. |
| Dashboard integration | Fail | No evidence that online lesson completion feeds employee dashboard metrics or training hours. |
---
## Business Rule Review
What is aligned
- Employee lesson pages use `requireEmployeeDashboardAccess()` or the organization-scoped API layer before rendering or returning data.
- Employee lesson queries are filtered to published lessons only through `listOnlineLessonsForAccess()` using `eq(onlineLessons.isPublished, true)` for non-HRD users.
- Direct detail access is also server-scoped through `getOnlineLessonByIdForAccess()`, so unpublished lessons are not exposed by URL guessing alone.
- Create, update, delete, and publish/archive actions are protected by `requireHRD()` in route handlers and `requireHRDDashboardAccess()` in manage pages.
Inconsistencies or gaps
- The business rule protection for employee visibility depends on `isPublished` being kept in sync with `status`. The implementation does keep them aligned today, but the access rule is coupled to that derived flag instead of checking `status === 'published'` directly.
- The audit scope expects progress, completion, history, dashboard updates, training-hour rules, and certificate visibility rules, but there is no runtime contract for those concepts anywhere in the online lessons feature.
---
## Role & Permission Review
Employee permissions
- View lesson list: Pass
- View lesson detail: Pass
- Watch embedded or uploaded video: Pass
- Search lessons: Pass
- Filter by category: Pass
- Download allowed files: Pass
- Create/edit/delete/publish lessons: Guarded server-side and route-side
- Access HRD manage pages: Guarded
- Access hidden or draft lessons directly: Guarded server-side
HRD/Admin permissions
- Manage pages are limited to HRD through `requireHRDDashboardAccess()`.
- Mutating API endpoints are limited to HRD through `requireHRD()`.
Notes
- UI permission separation is generally clean. Employee list rows only show a single "open lesson" action, while HRD users additionally see status/creator columns and the action menu.
- The main employee route also exposes the "add online lesson" CTA when the signed-in user is HRD. This is reasonable for a shared route, but it means the page mixes employee and management affordances in one surface.
---
## UI Review
Strengths
- The page uses `PageContainer`, card sections, and shared dashboard primitives consistent with the rest of the app.
- Thumbnail, metadata, badges, and buttons are visually consistent with the existing design system.
- Empty state handling is clearer than many legacy areas because search/filter reset remains available.
Weaknesses
- The employee list is table-centric, while the plan explicitly calls for responsive lesson cards. On mobile, this is usable but less content-first than a card layout.
- The detail page lacks dedicated sections for instructor, duration, progress, completion, certificate availability, or related learning context.
- Attachment UX is basic. Users can open/download files, but there is no preview affordance for images or PDFs.
- There is no loading skeleton or route-level loading view for the employee route family.
---
## UX Review
Strengths
- Discovery is straightforward for published lessons: search, category filter, and a single row CTA keep the main flow simple.
- Detail-page video behavior covers the common YouTube URL shapes and also supports uploaded files.
Weaknesses
- The module currently behaves more like a content library than a learning workflow.
- Users do not get progress feedback, watched/completed state, or "continue where you left off" guidance.
- Missing video, broken uploaded video, and broken embed cases do not provide especially strong recovery guidance.
- The detail page exposes creator/status metadata but omits learner-oriented metadata such as duration and completion cues.
---
## Accessibility Review
What is good
- Buttons and links are based on shared UI primitives, so baseline keyboard and focus behavior should be reasonable.
- Thumbnail images include `alt` text derived from lesson title.
- Native video controls are available for uploaded videos.
Risks
- The `<track kind="captions" />` element in the uploaded-video player has no `src`, so captions are not actually implemented.
- There is no explicit accessibility treatment for attachment previews because previews do not exist yet.
- Video error states and fallback messaging are limited, which can leave assistive users with less context when media fails.
---
## Code Quality Review
Strengths
- The feature follows the expected app-owned structure with `api/types.ts`, `service.ts`, `queries.ts`, `mutations.ts`, schema validation, and server data helpers.
- Route handlers keep persistence and authorization logic on the server.
- Query invalidation is simple and predictable through `onlineLessonKeys`.
Weaknesses
- `online-lessons-page.tsx` is doing list rendering, filters, columns, empty state, and role-based behavior in one client component, which makes the employee list surface larger than necessary.
- The employee-facing feature has no shared learner-progress contracts, so expanding the scope later will likely require contract changes across types, routes, and UI together.
- There is no shared video/attachment display component even though the plan suggests richer media behavior that may need reuse.
---
## API Review
Strengths
- `/api/online-lessons` and `/api/online-lessons/[id]` enforce organization access and HRD-only mutation rules.
- List endpoint supports pagination, search, category filtering, and status filtering for managers.
- File validation for video, attachment, and thumbnail uploads is handled server-side before storage.
Weaknesses
- Employee-facing API contracts do not include progress, completion, last-viewed, certificate, duration, or instructor data.
- There is no separate endpoint for attachment download authorization; attachments are exposed by stored file URL once the lesson payload is returned.
- Video and attachment runtime errors are left mostly to browser-native behavior instead of richer API/UI recovery states.
---
## Security Review
Strengths
- Route handlers use shared auth helpers instead of trusting client state.
- Employee direct access to hidden/draft lessons is blocked in both list and detail server queries.
- HRD-only mutations are enforced in API handlers and manage pages.
- Storage helper code validates file types, size limits, and upload-path safety.
Risks
- Lesson assets appear to be served from public upload URLs. That is convenient, but it means asset access is not re-authorized per request once a URL is known.
- The access policy is derived from `isPublished`; if future code ever desynchronizes `status` and `isPublished`, employee visibility could drift from the intended business rule.
---
## Performance Review
Strengths
- List and detail pages use server prefetch + React Query hydration.
- Pagination is server-driven, which keeps the list payload bounded.
- Detail pages fetch only one lesson at a time.
Weaknesses
- The detail page uses raw `<img>` instead of `next/image`, so thumbnail optimization is limited.
- Uploaded video elements do not appear to use lazy or staged loading strategies beyond browser defaults.
- There is no dedicated progressive loading UX for media-heavy content on slow networks.
---
## Data Validation Review
What is covered
- Title is required through `onlineLessonSchema`.
- Status is restricted by schema.
- Video URL must be an `http` or `https` URL when provided.
- Route handlers enforce that at least one video source exists on create and retained update flows.
- File type and file size checks exist for uploaded video, attachment, and thumbnail files.
What is missing
- No validation exists for instructor, duration, certificate rules, or learner progress because those fields are absent from the feature model.
- No YouTube-specific validation exists beyond generic URL parsing and best-effort embed conversion.
---
## Edge Case Review
- No lessons: handled with an empty state.
- One lesson: should work cleanly with the current list/detail flow.
- Many lessons: pagination exists, but the table-first UI is less optimized for content browsing than cards.
- Missing thumbnail: handled with a placeholder block on the detail page.
- Missing video: handled with a "no video" message.
- Broken YouTube link: partial handling only; non-embeddable links get a fallback card, but broken embeds still depend on iframe/browser behavior.
- Broken uploaded video: partial handling only; there is no custom error state or recovery UI.
- Missing attachment: handled by omitting the attachment section.
- Invalid PDF/image preview: not applicable because inline previews are not implemented.
- Slow network: partial handling only; fetch feedback exists, but there are no dedicated loading routes or skeletons.
- API failure: no dedicated route error boundary exists for the employee online-lessons routes.
- Unauthorized direct access to hidden lessons: guarded server-side.
- Archived lesson via direct URL: guarded as long as `isPublished` remains false for archived lessons.
---
## Consistency Review
Consistent with project patterns
- Uses `PageContainer`, local feature contracts, route handlers, Drizzle-backed server helpers, React Query prefetch, and shared auth/page guard helpers.
- Reuses shared dashboard UI primitives and badges.
- Follows the feature-module architecture documented in `docs/AI_DEVELOPMENT_GUIDE.md` and `docs/PROJECT_ARCHITECTURE.md`.
Not fully consistent
- The list UI uses `DataTable`, but it does not reuse the richer shared `DataTableToolbar` pattern described in the project guide.
- The employee online lesson experience is much lighter than nearby employee-facing areas such as dashboard and training records, especially around loading/error states and learner feedback.
---
## Problems Found
### Critical
- None found.
### High
- Description: The employee online lessons feature has no implementation for progress tracking, completion state, learning history, certificate availability, or dashboard integration, even though these are core audit-scope expectations.
- File: `src/features/online-lessons/api/types.ts`, `src/features/online-lessons/components/online-lessons-page.tsx`, `src/features/online-lessons/components/online-lesson-detail-page.tsx`, `src/app/api/online-lessons/route.ts`, `src/app/api/online-lessons/[id]/route.ts`
- Component: API contracts, employee list page, employee detail page, list/detail route handlers
- Severity: High
- Impact: The module supports browsing lessons, but not the employee learning lifecycle expected by the plan. Downstream reporting and dashboard experiences cannot reflect lesson completion because the feature model does not capture it.
- Evidence: `serializeOnlineLesson()` only returns content/admin metadata in `src/features/online-lessons/server/online-lesson-data.ts:31`; there are no progress/completion fields in `src/features/online-lessons/api/types.ts`; employee list/detail components render no such state.
- Recommendation: Define learner-state contracts first, then add server-side tracking, employee UI surfaces, and dashboard/report integration on top of that contract.
### Medium
- Description: There are no dedicated `loading.tsx` or `error.tsx` route files for the employee online-lessons routes.
- File: `src/app/dashboard/online-lessons`
- Component: employee list and detail route tree
- Severity: Medium
- Impact: Slow-network and failure states are less consistent with the rest of the dashboard and provide weaker recovery UX.
- Evidence: No `loading.tsx` or `error.tsx` files exist under `src/app/dashboard/online-lessons`; list page only shows inline fetching text and detail relies on default route behavior.
- Recommendation: Add route-level loading and error surfaces consistent with the app's employee-facing patterns.
- Description: The employee lesson detail page does not include instructor, duration, certificate, or progress-oriented sections, while it does expose management-oriented metadata such as creator name and publication status.
- File: `src/features/online-lessons/components/online-lesson-detail-page.tsx`
- Component: `OnlineLessonDetailPage`
- Severity: Medium
- Impact: The page gives weaker learning context than expected and prioritizes admin metadata over learner metadata.
- Evidence: The metadata block renders `status`, `category`, `published_at`, and `created_by_name` around `src/features/online-lessons/components/online-lesson-detail-page.tsx:111`, but no instructor/duration/progress/certificate fields exist.
- Recommendation: Rebalance the detail layout around employee learning needs and move non-essential admin metadata out of the primary reading flow.
- Description: Attachment handling is limited to link-based open/download actions and does not implement inline image preview, PDF preview, or unsupported-file guidance.
- File: `src/features/online-lessons/components/online-lesson-detail-page.tsx`
- Component: attachment section
- Severity: Medium
- Impact: Learners have less confidence about what a file contains before opening it, and the module does not meet the richer attachment scope in the audit plan.
- Evidence: The attachment block only renders file name plus open/download buttons around `src/features/online-lessons/components/online-lesson-detail-page.tsx:208`.
- Recommendation: Add file-type-aware previews and clearer unsupported-file messaging where preview is not possible.
- Description: Uploaded-video failure handling is minimal, and the captions track is not actually configured.
- File: `src/features/online-lessons/components/online-lesson-detail-page.tsx`
- Component: uploaded video player
- Severity: Medium
- Impact: Broken media leads to a weak recovery path, and caption accessibility is not functional.
- Evidence: The uploaded-video block uses native `<video controls>` with a blank `<track kind="captions" />` around `src/features/online-lessons/components/online-lesson-detail-page.tsx:158`.
- Recommendation: Add explicit media error UI and only render captions tracks when real caption files exist.
- Description: Lesson assets are served from public upload URLs rather than protected download endpoints.
- File: `src/lib/online-lesson-storage.ts`, `src/features/online-lessons/server/online-lesson-data.ts`
- Component: lesson asset storage and serialized payload
- Severity: Medium
- Impact: Once a client receives a file URL, asset access is no longer re-checked per request.
- Evidence: Serialized lesson payload includes `video_file_url`, `attachment_url`, and `thumbnail_url` from storage-backed public paths in `src/features/online-lessons/server/online-lesson-data.ts:38-43`.
- Recommendation: If the business wants stricter asset protection, move file delivery behind authorized download endpoints or signed URLs.
### Low
- Description: The employee list experience is table-based rather than card-based, which is less aligned with content browsing and the audit plan's responsive-card expectation.
- File: `src/features/online-lessons/components/online-lessons-page.tsx`
- Component: `OnlineLessonsPage`
- Severity: Low
- Impact: Usability is acceptable, but the presentation is less content-first on mobile and less aligned with lesson-library expectations.
- Evidence: The list renders a `DataTable` with columns and row CTAs rather than lesson cards.
- Recommendation: Consider a dedicated employee card layout if lesson discovery and media browsing become a priority.
---
## Missing Features
- Lesson progress display
- Completed lesson state
- Remaining lessons or progress calculation
- Completion badge
- Learning history for viewed/completed/recent lessons
- Dashboard integration for lesson completion or training-hour impact
- Certificate availability section
- Instructor metadata
- Duration metadata
- Attachment previews for images and PDFs
- Dedicated route-level loading UI
- Dedicated route-level error UI
- Stronger broken-video fallback and recovery UX
---
## Improvement Opportunities
- Separate the employee learning experience from the HRD management experience more clearly, especially in detail-page metadata and list presentation.
- Introduce learner-state contracts at the API layer before adding UI for progress/history/completion.
- Reuse a richer shared toolbar/filter pattern if the employee list gains more discovery controls.
- Consider protected delivery for lesson assets if access revocation or stricter confidentiality becomes important.
- Extract video and attachment presentation into reusable components if the feature expands to richer media states.
---
## Final Assessment
Architecture: 8.2/10
Functionality: 6.7/10
Role & Permission: 8.4/10
UI: 7.0/10
UX: 6.6/10
Accessibility: 6.8/10
Performance: 7.4/10
Security: 7.7/10
Maintainability: 7.8/10
Overall Score: 7.3/10