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.
This commit is contained in:
Macky
2026-08-21 12:28:22 +07:00
parent 2950ffad89
commit 9fd748154d
33 changed files with 1674 additions and 504 deletions

View File

@@ -24,6 +24,17 @@ filesystem JSON storage (no SQL). i18n TH/EN. No self-registration (admin provis
- **user** (trainee) — trains against personas, own board.
## Current state — local code/security gate passed; production-operation gate pending
> **2026-08-21:** UX/SAAS 12-point redesign (NOT yet pushed). Self-registration
> (`POST /api/auth/register`, role=user, first-created-user = super_admin), super_admin may now
> promote others + is invisible to regular admin, user-created private product groups + admin
> **สินค้าขององค์กร** (shared) with hidden/public visibility, analytics (team + per-user weak areas,
> close-rate by difficulty bucket, 30-day default, trainee table, weekly trend, active users),
> docx/xlsx uploads, **persona→persona** copy, tabs **การฝึก → ผลการฝึก → ภาพรวม**, admin lands on
> ภาพรวม / non-admin on การฝึก, guide in topbar, weak-areas merged into Results + my-personas into
> Training, ดูรายงาน UI removed (endpoint kept). **348 backend tests pass, frontend build +
> vitest clean.** See `docs/engineering-log/2026-08-21-ux-saas-redesign.md` +
> `docs/plan-2026-08-21-ux-saas-redesign.md`. Deploy pending operator push (+ confirm default-org
> env for self-register; previewMode vs hidden-group semantics to reconcile).
> **2026-08-20:** OAuth login/register (Google + Facebook) added. Public social signup into a
> single default org (`OAUTH_DEFAULT_ORG`, role user, seat-checked); email-match links existing
> users. Server-side token validation via stdlib urllib (no new dep; Google tokeninfo + Facebook

View File

@@ -71,3 +71,4 @@ Informed by MiroFish (CrowdSight engine) + the hermes-brain-and-tools CrowdSight
- `2026-08-18-live-qa-ux-fixes.md` — live-QA UX fixes: persona JSON never leaks into bubble, natural greeting openers (wrong_text cools off only after first reply), auto-close + auto-summarize on buy/walk/try, no manual "สรุปผล" button (336 tests).
- `2026-08-19-ux-redesign-and-marketing-site.md` — parallel subagents: app UX/UI redesign (global token system, 8 files, 100% presentational) + new `website/` marketing landing site (responsive TH/EN); build clean, 4/4 unit tests, independent review PASS; uncommitted, deploy pending operator approval.
- `2026-08-20-oauth-google-facebook.md` — OAuth login/register (Google + FB): public signup into OAUTH_DEFAULT_ORG, email-match linking, stdlib server-side token validation (no new dep), fail-closed, rate-limited; 348 backend tests (11 new), frontend clean; manual security review PASS; deploy pending operator push.
- `2026-08-21-ux-saas-redesign.md`**UX/SAAS 12-point redesign** (this session): self-registration (role=user, first-user=super_admin), super_admin promotion + invisible-to-admin, user private groups + admin สินค้าขององค์กร + hidden/public, analytics (team/per-user weak areas, close-rate-by-difficulty, 30-day default, trainee table, weekly trend, active users), docx/xlsx parser, persona→persona copy, tab reorder + role landing + guide-in-topbar, page consolidation (weak-areas→Results, my-personas→Training), report UI removal, Analytics dashboard rewrite. 348 backend tests pass; frontend build + vitest clean; deploy pending operator approval.

View File

@@ -0,0 +1,101 @@
# 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.

View File

@@ -0,0 +1,136 @@
# UX/SAAS Redesign — Plan (2026-08-21)
Owner review of Sales Trainer → 12-point redesign toward a multi-tenant SAAS model.
Decisions locked via clarify 2026-08-21.
---
## P0 — Locked decisions
1. **Super admin policy**: super_admin MAY promote others to super_admin (operates on trust).
The **first user created** becomes super_admin automatically. Regular admin does NOT see
super_admin usernames in the user list and CANNOT change/see them; only super_admin manages
admins.
2. **SAAS product scoping**: user-created product = **private group** (owner sees it only, existing
`owner_user_id` model). admin-created product = **สินค้าขององค์กร** (shared, org-wide). Two
clearly separated sections in Training UI.
3. **Difficulty split**: 3 buckets — ง่าย (difficulty 12) / กลาง (3) / ยาก (45). Close rate shown
per bucket so viewers cannot misread "many wins = good" when trainees pick only easy personas.
4. **Dashboard extras**: total sessions + active users + weekly training trend + per-trainee table
(each user: sessions · wins · close rate · dominant weak area). **Anti-misread priority**: the
difficulty split must be prominent — never let aggregate wins appear good when wins skew easy.
5. **Report removal**: remove the "ดูรายงาน" button + page from the UI only. Keep the `/report`
endpoint + stored report data (non-breaking; admin tooling still works via API).
---
## P1 — Backend changes
### B1. Auth / registration (point 1, 5)
- New `POST /api/auth/register` (username + password + email) → creates user `role="user"`,
seat-checked, in tenant org. No `must_setup` (password set at signup) unless desired else first-login setup.
- **First-created-user rule**: when the platform has zero users, the first registered/created
user is promoted to `super_admin` automatically (bootstrap). When users already exist, default role = user.
- Keep OAuth path (already role=user).
### B2. Role hierarchy (point 4, 5)
- Allow an existing `super_admin` to set `role="super_admin"` on another user via admin update
(currently blocked: "provisioned only by bootstrap").
- `list users` (admin view): a regular `admin` MUST NOT see rows whose `role == "super_admin"`.
Only `super_admin` sees all. `super_admin` can change status/role of any admin; regular admin
cannot change admin/super_admin.
- Ensure role-change rules: only super_admin can grant `admin` or `super_admin`; admin grants user only.
### B3. Group visibivity / creation (point 6, 7)
- Allow `user` (trainee) to create groups → these become **private** (`owner_user_id = self`,
`status` = ready after analyze). `create_group` route: relax `require_roles("admin")` to allow user
with forced owner marker + private visibility.
- Admin-created group = org-shared **สินค้าขององค์กร** (no `owner_user_id`).
- **New visibility field** on admin/shared groups for point 7: `visibility`/`published`:
- `public` (default when shared) → visible to all trainees.
- `draft`/`hidden` → hidden from trainees; **admin can still train it** (preview mode).
- Admins always see both. A hidden group is trainable by admin (its sessions flagged preview so
they don't pollute trainee analytics — reuse existing `previewMode` logic).
- `list_visible_to` update: user sees (their own private ready groups) + (org shared groups where
`published/public == true` and `status == ready`). Admin sees all org shared (incl hidden/draft)
+ never other users' private groups (except super_admin).
### B4. Analytics (point 3)
- **Default date window = last 30 days** (today30 → today). Keep optional from/to override.
- **Team weak areas**: aggregate finished trainee sessions → same dimension analysis as per-user
weak-areas, but across all org trainees (reuse `analyze_weak_areas`, feed with all finished
trainee sessions).
- **Per-user weak areas**: in the per-trainee table (B5) each row carries that user's top weak area.
- **Close rate by difficulty**: bucket persona by `difficulty` 12 / 3 / 45 → per-bucket
`{plays, wins, losses, close_rate}` in `overall.difficulty` (or top-level `close_by_difficulty`).
- **Anti-misread**: also add an overall breakdown note/flag when bucket win-share skews to ง่าย;
UI will emphasize buckets.
- **Remove `hardest_personas`** from analytics response (replaced by weak areas); keep or drop —
replace with `team_weak_areas`.
- **Extra metrics**: `total_sessions`, `active_users` (distinct trainees with ≥1 session in window),
`weekly_trend` (sessions+wins per ISO week in window), `trainee_table` (per user: play/win/loss/
close_rate/avg_score/top_weak_area).
### B5. File parser (point 12)
- Support `.docx` (python-docx) and `.xlsx` (openpyxl) in `parse_document`, with size caps + safety.
---
## P2 — Frontend changes
### F1. Nav / tabs / landing (point 2, 3, 10)
- Tab order: **การฝึก → ผลการฝึก → ภาพรวม** (training first for everyone).
- Renames: `myDashboard`→**ผลการฝึก**; `adminOverview`→**ภาพรวม** (admin only tab).
- **Admin landing**: after login an admin lands on **ภาพรวม** (analyze-first). Non-admin lands on
**การฝึก** (train-first). (Router redirect + redirectAfterLogin update.)
- First-time user with no products → go to add-product form directly.
- **Guide moved to topbar**: replace standalone `/guide` nav entry with a topbar question-mark/guide
link + dropdown (role-aware). Keep `/guide` route for the full page if desired, but entry point = topbar.
### F2. Page consolidation (point 3, 4, 5)
- **ผลการฝึก page** = MyBoard: weak-areas section at top (before history), then history
**paginated 10/page**. Remove standalone `/my/weak-areas` page (merge logic in).
- **การฝึก page** = Training: product grid (user's own products section, then **สินค้าขององค์กร**
section below), then **Persona ของฉัน (private personas)** section below product list. Remove
standalone `/my/personas` page (merge logic in).
- Admin only: manage/edit product routes stay.
### F3. Copy / language (point 1, 6, 8, 11)
- Replace **บุคคลต้นแบบ → persona** everywhere (i18n EN + TH + docs). Note: user explicitly
reversed the old "persona→บุคคลต้นแบบ" standard.
- "บุคคลต้นแบบส่วนตัว" → **Persona ของฉัน** / **Persona ส่วนตัว** (short, clear).
- Guide rewrite aware of role (user sees train-personas; admin sees manage/analytics/users).
- Polished, plain-Thai helper text throughout.
### F4. Add-product form (point 12)
- Richer example placeholders for product + segment + description (e.g. "เน้นเพศหญิง รายได้สูง…
จำกัดสถานการณ์ที่จำลองได้"). Provide several ready examples.
- Clear **ระดับ A/B/C** explanation panel.
- Upload: accept `.pdf .md .txt .docx .xlsx`; helper text: "อัปโหลดไฟล์กลุ่มเป้าหมาย/รายละเอียด
แทนการกรอกช่องด้านบนได้" (encourage upload-or-fill, not both required).
- Wording pass: product form labels/descriptions clearer.
### F5. Report removal UI (point 9)
- Remove "ดูรายงาน"/Download report button + GroupReport link from Group edit UI. Keep backend
endpoint untouched.
---
## P3 — Deliverables / verification
- Backend pytest suite green (existing 336 + new tests: register default role, first-user super,
super visibility filter, admin promote, user-group creation, draft/hidden visibility, analytics
difficulty buckets + 30-day default, docx/xlsx parse).
- Frontend `vite build` clean + vitest unit green.
- Independent reviewer subagent on the diff.
- Mobile QA at 320×568 + 500×768 after rebuild (per project rule).
- Docs: engineering-log entry + HANDOFF update.
- Deploy: Gitea→EasyPanel path, operator approval before push.
---
## Open items / notes
- Whether first user (username+password) uses `must_setup` first-login (set email/pw) or full
self-signup. Default: self-register sets username+password+email at signup → skip `must_setup`.
- Org/tenancy: self-register lands in `OAUTH_DEFAULT_ORG`-style default org (like OAuth) unless a
tenant-invite flow exists. First-user-super_admin is global bootstrap (org-less) — mirror existing
super_admin boot semantics.
- "สินค้าขององค์กร" naming + section split UI confirmed by owner in clarify.