Files
sales-trainer/docs/engineering-log/2026-08-21-ux-saas-redesign.md
Macky 9fd748154d feat: UX/SAAS 12-point redesign
- auth: self-registration (role=user); first-created-user becomes super_admin
- roles: super_admin may promote others; regular admin cannot see super_admin accounts
- products: user-created private groups; admin 'สินค้าขององค์กร' (shared) with hidden/public; users can create groups
- analytics: team + per-user weak areas, close-rate-by-difficulty buckets, 30-day default, weekly trend, trainee table, active users; dashboard redesigned
- files: docx + xlsx upload support (python-docx + openpyxl)
- ui: tabs การฝึก→ผลการฝึก→ภาพรวม; admin lands on ภาพรวม / user on การฝึก; guide in topbar
- consolidate: weak-areas merged into Results (10/page), my-personas merged into Training
- copy: บุคคลต้นแบบ→persona everywhere; clearer add-product form (A/B/C, upload-or-fill)
- report: remove ดูรายงาน UI entry (endpoint kept)

Backend 348 tests pass; frontend build + vitest clean.
2026-08-21 12:28:22 +07:00

7.4 KiB

2026-08-21 — UX/SAAS redesign (12-point): registration, roles, products, analytics, parser, nav

Date: 2026-08-21 Status: implemented + locally verified (backend 348 pass, frontend build + vitest clean); deploy = repo's normal Gitea→EasyPanel path (operator runs it, must approve push)

Context

Owner gave a 12-point UX/UI redesign that moves the app toward a multi-tenant SAAS model. Reviewed current state first (no self-registration except OAuth; admin-provisioned users; 3 tabs admin-overview → my-dashboard → training; standalone weak-areas + my-personas pages; group creation admin-only; analytics had overall close rate + hardest_personas). Clarified 5 decisions with the owner, wrote docs/plan-2026-08-21-ux-saas-redesign.md, and dispatched 2 parallel subagents (backend + frontend).

Locked decisions (owner)

  • super_admin policy: super_admin may promote others to super_admin; first-created user becomes super_admin automatically; regular admin does NOT see super_admin accounts.
  • SAAS products: user-created product = private group (owner-only, existing owner_user_id model); admin-created product = สินค้าขององค์กร (shared, org-wide) — two clearly separated sections in Training.
  • Difficulty split: close rate bucketed ง่าย (1-2) / กลาง (3) / ยาก (4-5) — anti-misread core.
  • Dashboard extras: total sessions + active users + weekly trend + per-trainee table (each: plays/wins/losses/close_rate/avg_score/top_weak_area).
  • Report: remove the ดูรายงาน button/page from UI only; keep /report endpoint + data.

What was done

Backend (P1) — subagent timed out at 600s after completing all B1-B5 edits; verified by me

  • B1 auth/registration: new POST /api/auth/register (username+password+email), default role=user, seat-checked, rate-limited (per-IP + per-ident), default-org via _ensure_register_org; first-created-user rule: when the user store is empty the first account is promoted to super_admin (global bootstrap), else stays user and never super.
  • B2 roles: a super_admin may now grant super_admin to another user (trust-based promotion); any non-super actor cannot. list_users: a regular admin filters OUT role == super_admin rows (invisible); super_admin sees all.
  • B3 groups/visibility: create_group allows role=user → creates a private group (owner_user_id=self). Admin-created group = org-shared (no owner marker). New published/hidden visibility for shared groups: hidden groups invisible to trainees but trainable by admin (preview). list_visible_to updated accordingly.
  • B4 analytics: default last-30-days window; added team_weak_areas (aggregated analyze_team_weak_areas), close_by_difficulty (easy/medium/hard buckets with label+range), active_users, weekly_trend (ISO week sessions+wins), trainee_table (per-user username/plays/wins/losses/close_rate/avg_score/top_weak_area). hardest_personas kept for back-compat but superseded in the UI.
  • B5 file parser: .docx (python-docx) + .xlsx (openpyxl) added to parse_document with size caps + fail-closed; ALLOWED_UPLOAD_EXTS extended; python-docx==1.2.0 + openpyxl==3.1.5 added to requirements.txt + regenerated lock.
  • Tests: backend suite now 348 passed (was 336). One pre-existing test (test_parser_rejects_unsupported_extension) used brief.docx as its "unsupported" example — stale once docx became supported; I changed it to brief.exe so it still asserts the real invariant. Fix confirmed by full re-run.

Frontend (P2) — subagent completed; build + vitest green; I added the Analytics dashboard redesign

  • F1 nav/tabs: tab order การฝึก → ผลการฝึก → ภาพรวม (training first for everyone; ภาพรวม admin-only). Router guards + login redirect: admin → / (ภาพรวม), non-admin → /training (train-first); explicit ?redirect= wins. Guide moved into a topbar question-mark dropdown (role-aware: guide + admin-only ภาพรวม + super-admin-only ผู้ใช้งาน).
  • F2 consolidation: Results page (MyBoard) now has weak areas at top then history paginated 10/page; standalone /my/weak-areas and /my/personas pages removed. Training page now has own-products section + สินค้าขององค์กร section (below) + Persona ของฉัน private personas section (below product list). /admin/groups/:gid/report route removed.
  • F3 copy: บุคคลต้นแบบ → persona everywhere (i18n EN+TH, zero remaining). "บุคคลต้นแบบส่วนตัว" → "Persona ของฉัน". Role-aware Guide + plain-Thai helper text.
  • F4 add-product form: docx/xlsx added to accept + guide; clear ระดับ A/B/C explanation panel; "upload-or-fill" helper text (uploading a target-group/doc file can substitute for filling the fields); richer example placeholders.
  • F5 report removal: ดูรายงาน/View-report button + page removed from Group edit UI (endpoint
    • data kept).
  • F6 (added by me after review): Analytics.vue dashboard redesigned to the new backend contract — headline stats incl. active users, close-rate-by-difficulty buckets with anti-misread skew-easy flag, team weak areas, weekly activity bar chart, and the per-trainee table with top weak area; date filter defaults to last 30 days. Added 13 new i18n keys (EN + TH). The frontend subagent had not touched Analytics.vue (still bound to the old hardest_personas contract), so I implemented it, fixed two apostrophe syntax errors in i18n, and verified build + vitest.

Verification evidence

Check Result
Backend full pytest (uv run pytest -q) 348 passed, 0 failed
Frontend vite build clean
Frontend vitest unit 4/4 pass
Removed pages (WeakAreas/MyPersonas/GroupReport) no dangling refs; absent from dist
บุคคลต้นแบบ in source zero remaining
Analytics bindings use new backend fields (close_by_difficulty/team_weak_areas/trainee_table/weekly_trend/active_users)

Files changed (uncommitted)

Backend: admin_routes, analytics_routes, auth_routes, chat_routes, group_routes, config, models/entities, services/file_parser, services/groups, services/trainee, requirements.txt, requirements.lock.txt, tests/test_upload_security.py, tests/test_sprint1_review_findings.py. Frontend: App.vue, i18n/index.js, router/index.js, router.spec.js, GroupBuilder, GroupEdit, Guide, Login, MyBoard, Personas, Training, Analytics (+ deleted WeakAreas, MyPersonas, GroupReport). Docs: plan-2026-08-21-ux-saas-redesign.md (new).

Notes / next action

  • Do NOT push without owner approval (project rule). Push → Gitea→EasyPanel auto-redeploy (~3 min).
  • Before push, confirm environment: self-register needs a default org (register uses _ensure_register_org mirroring OAuth default-org); docx/xlsx deps will install on the build.
  • The previewMode (admin practice excluded from analytics) should be reconciled with the new hidden-group admin training path (point 7) — verify semantics in the gap before shipping.
  • Independent reviewer subagent on the combined diff is RECOMMENDED before the production gate.