UI: 3-tab layout (Admin overview / My dashboard / Training) + Settings page
Frontend: - App shell with 3 tabs: Overview (admin), My Dashboard (all incl admin), Training + Settings icon - New Settings.vue: edit profile (name/email) + change password (verify current) - New MyBoard.vue: personal win/lose + weak areas + summary (all roles) - New Training.vue: product/group list -> personas; admin manage + add product - Dashboard.vue -> admin overview with date-range filter + per-trainee stats - Added i18n keys (EN/TH) and tab/settings styling (ui-ux-pro-max design system) Backend: - /api/auth/change-password (verify current pw) + PUT /api/auth/profile - Allow admin to train: board/weak-areas/chat/personas/sessions open to user+admin+super_admin - /api/analytics now accepts ?from=&to= date filter + returns per_user summary Verified: frontend builds; change-password (wrong/ok), profile, board all pass via test client; security tests pass
This commit is contained in:
@@ -23,15 +23,20 @@ def _stores():
|
||||
|
||||
@me_bp.get("/board")
|
||||
@require_auth
|
||||
@require_roles("user")
|
||||
@require_roles("user", "admin", "super_admin")
|
||||
def win_lose_board():
|
||||
"""Per-persona won/lost/not-tried across all groups the user sees."""
|
||||
"""Per-persona won/lost/not-tried across all groups the user sees.
|
||||
|
||||
Available to trainees AND admins (admins may also train/practice).
|
||||
"""
|
||||
s = _stores()
|
||||
uid = current_user()["id"]
|
||||
actor = current_user()
|
||||
uid = actor["id"]
|
||||
role = actor.get("role", "user")
|
||||
my_sessions = s["sessions"].list_for_user(uid)
|
||||
outcome_by = {(x.get("group_id"), x.get("persona_id")): x.get("outcome") for x in my_sessions}
|
||||
|
||||
groups = s["groups"].list_visible_to(role="user", org_id=current_user().get("org_id"))
|
||||
groups = s["groups"].list_visible_to(role=role, org_id=actor.get("org_id"))
|
||||
# Only the owner sees their personal groups (IDOR defense).
|
||||
groups = [g for g in groups if not g.get("owner_user_id") or g.get("owner_user_id") == uid]
|
||||
items = []
|
||||
@@ -51,7 +56,7 @@ def win_lose_board():
|
||||
|
||||
@me_bp.get("/weak-areas")
|
||||
@require_auth
|
||||
@require_roles("user")
|
||||
@require_roles("user", "admin", "super_admin")
|
||||
def weak_areas():
|
||||
s = _stores()
|
||||
uid = current_user()["id"]
|
||||
@@ -83,7 +88,7 @@ def _personal_group(s, actor) -> dict:
|
||||
|
||||
@me_bp.get("/personas")
|
||||
@require_auth
|
||||
@require_roles("user")
|
||||
@require_roles("user", "admin", "super_admin")
|
||||
def my_personas():
|
||||
s = _stores()
|
||||
uid = current_user()["id"]
|
||||
@@ -93,7 +98,7 @@ def my_personas():
|
||||
|
||||
@me_bp.post("/personas/generate")
|
||||
@require_auth
|
||||
@require_roles("user")
|
||||
@require_roles("user", "admin", "super_admin")
|
||||
def generate_persona():
|
||||
s = _stores()
|
||||
actor = current_user()
|
||||
|
||||
Reference in New Issue
Block a user