- 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.
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_idmodel); 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
/reportendpoint + 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), defaultrole=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 tosuper_admin(global bootstrap), else staysuserand never super. - B2 roles: a
super_adminmay now grantsuper_adminto another user (trust-based promotion); any non-super actor cannot.list_users: a regular admin filters OUTrole == super_adminrows (invisible); super_admin sees all. - B3 groups/visibility:
create_groupallows 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_toupdated accordingly. - B4 analytics: default last-30-days window; added
team_weak_areas(aggregatedanalyze_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_personaskept for back-compat but superseded in the UI. - B5 file parser:
.docx(python-docx) +.xlsx(openpyxl) added toparse_documentwith size caps + fail-closed;ALLOWED_UPLOAD_EXTSextended;python-docx==1.2.0+openpyxl==3.1.5added to requirements.txt + regenerated lock. - Tests: backend suite now 348 passed (was 336). One pre-existing test
(
test_parser_rejects_unsupported_extension) usedbrief.docxas its "unsupported" example — stale once docx became supported; I changed it tobrief.exeso 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-areasand/my/personaspages removed. Training page now has own-products section + สินค้าขององค์กร section (below) + Persona ของฉัน private personas section (below product list)./admin/groups/:gid/reportroute 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_personascontract), 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_orgmirroring 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.