From e1d61e1e1ed59efc486d57d6bd383ef7d416acb3 Mon Sep 17 00:00:00 2001 From: Macky Date: Sun, 9 Aug 2026 07:22:50 +0700 Subject: [PATCH] =?UTF-8?q?feat(ip):=20protect=20persona=20'formula'=20?= =?UTF-8?q?=E2=80=94=20secret=20fields=20only=20super=5Fadmin=20sees/edits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IP protection so casual copying yields inferior results: - SECRET_PERSONA_FIELDS (pains/objections/negotiation_levers/opener/tolerance + pain rootCause/resolutionConditions): only super_admin can view/edit them. - list_personas/get_persona/update_persona/get_group strip these for role=admin (and hide sales_kit + pain-fit report from admins too). - update_persona rejects admin attempts to set secret fields (403). - PersonaForm hides the 'การขาย' recipe section for non-super-admin (shows locked note); auth.isSuperAdmin getter added. Rebuilt dist. Added test_ip_protection. --- backend/app/api/group_routes.py | 52 ++++++++++++++-- backend/scripts/test_ip_protection.py | 61 +++++++++++++++++++ ...ers-B8OP5hvL.js => AdminUsers-BXR4J5iM.js} | 2 +- ...tics-Bkjt0mFt.js => Analytics-DzK25pzJ.js} | 2 +- .../{Chat-DszzTVNw.js => Chat-B4cA4NlX.js} | 2 +- ...r-CNxYYLnR.js => GroupBuilder-DpQWbscg.js} | 2 +- frontend/dist/assets/GroupEdit-Bq-uo5sC.js | 9 --- frontend/dist/assets/GroupEdit-CFwXUOV9.css | 1 + frontend/dist/assets/GroupEdit-D9lmggXK.css | 1 - frontend/dist/assets/GroupEdit-PGVs7eZc.js | 9 +++ .../{Guide-GzVXirX-.js => Guide-Bx9T4hBF.js} | 2 +- .../{Login-DxHA8GYa.js => Login-BFjXrLpo.js} | 2 +- ...yBoard-CTHsz6OL.js => MyBoard-wXpWmFYg.js} | 2 +- ...sonas-CGSsKXxA.js => Personas-BK-v3zL-.js} | 2 +- ...tings-CVA3uaSz.js => Settings-k8fzlPYr.js} | 2 +- .../{Setup-C_0Y0xY8.js => Setup-B6q98ArM.js} | 2 +- ...ining-EZ9n5Yut.js => Training-C9KC4ygo.js} | 2 +- ...eft-BUCak2lw.js => arrow-left-diPpM4Dj.js} | 2 +- ...open-Ckxqlz0w.js => book-open-C7SxBVkN.js} | 2 +- .../{index-DOaJiTJw.js => index-BCax2GDh.js} | 4 +- ...UmS5d8.js => layout-dashboard-Djj77ueX.js} | 2 +- .../{lock-WqnzR3ED.js => lock-uERjsrGr.js} | 2 +- .../{plus-CODydpxS.js => plus-B9zMVgv0.js} | 2 +- ...rkles-CwNig9QR.js => sparkles-E3Xd6eY7.js} | 2 +- ...{target-QJk_fUDu.js => target-BFC5gbdb.js} | 2 +- .../{users-SQMCzZpr.js => users-NPxa3eQ_.js} | 2 +- frontend/dist/index.html | 2 +- frontend/src/components/PersonaForm.vue | 15 ++++- frontend/src/store/auth.js | 3 + 29 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 backend/scripts/test_ip_protection.py rename frontend/dist/assets/{AdminUsers-B8OP5hvL.js => AdminUsers-BXR4J5iM.js} (98%) rename frontend/dist/assets/{Analytics-Bkjt0mFt.js => Analytics-DzK25pzJ.js} (97%) rename frontend/dist/assets/{Chat-DszzTVNw.js => Chat-B4cA4NlX.js} (97%) rename frontend/dist/assets/{GroupBuilder-CNxYYLnR.js => GroupBuilder-DpQWbscg.js} (98%) delete mode 100644 frontend/dist/assets/GroupEdit-Bq-uo5sC.js create mode 100644 frontend/dist/assets/GroupEdit-CFwXUOV9.css delete mode 100644 frontend/dist/assets/GroupEdit-D9lmggXK.css create mode 100644 frontend/dist/assets/GroupEdit-PGVs7eZc.js rename frontend/dist/assets/{Guide-GzVXirX-.js => Guide-Bx9T4hBF.js} (95%) rename frontend/dist/assets/{Login-DxHA8GYa.js => Login-BFjXrLpo.js} (98%) rename frontend/dist/assets/{MyBoard-CTHsz6OL.js => MyBoard-wXpWmFYg.js} (94%) rename frontend/dist/assets/{Personas-CGSsKXxA.js => Personas-BK-v3zL-.js} (95%) rename frontend/dist/assets/{Settings-CVA3uaSz.js => Settings-k8fzlPYr.js} (98%) rename frontend/dist/assets/{Setup-C_0Y0xY8.js => Setup-B6q98ArM.js} (93%) rename frontend/dist/assets/{Training-EZ9n5Yut.js => Training-C9KC4ygo.js} (93%) rename frontend/dist/assets/{arrow-left-BUCak2lw.js => arrow-left-diPpM4Dj.js} (86%) rename frontend/dist/assets/{book-open-Ckxqlz0w.js => book-open-C7SxBVkN.js} (90%) rename frontend/dist/assets/{index-DOaJiTJw.js => index-BCax2GDh.js} (87%) rename frontend/dist/assets/{layout-dashboard-CmUmS5d8.js => layout-dashboard-Djj77ueX.js} (91%) rename frontend/dist/assets/{lock-WqnzR3ED.js => lock-uERjsrGr.js} (88%) rename frontend/dist/assets/{plus-CODydpxS.js => plus-B9zMVgv0.js} (86%) rename frontend/dist/assets/{sparkles-CwNig9QR.js => sparkles-E3Xd6eY7.js} (93%) rename frontend/dist/assets/{target-QJk_fUDu.js => target-BFC5gbdb.js} (90%) rename frontend/dist/assets/{users-SQMCzZpr.js => users-NPxa3eQ_.js} (90%) diff --git a/backend/app/api/group_routes.py b/backend/app/api/group_routes.py index 548582d..880bc57 100644 --- a/backend/app/api/group_routes.py +++ b/backend/app/api/group_routes.py @@ -14,6 +14,29 @@ from .helpers import ApiError, current_user, require_auth, require_roles groups_bp = Blueprint("groups", __name__) +# Fields that encode the "formula"/process of a persona. Only super_admin may see/edit +# them; admins get the persona but NOT these — so a casual copy yields inferior results. +SECRET_PERSONA_FIELDS = { + "pains", "objections", "negotiation_levers", "opener", + "rootCause", "resolutionConditions", "tolerance", +} + + +def strip_secret_fields(persona: dict) -> dict: + """Return a copy of a persona with secret/process fields removed.""" + out = dict(persona) + for f in SECRET_PERSONA_FIELDS: + out.pop(f, None) + pains = out.get("pains") + if isinstance(pains, list): + cleaned = [] + for p in pains: + if isinstance(p, dict): + p = {k: v for k, v in p.items() if k not in ("rootCause", "resolutionConditions")} + cleaned.append(p) + out["pains"] = cleaned + return out + _ANALYZE_LOCKS: dict[str, threading.Lock] = {} _ANALYZE_GUARD = threading.Lock() @@ -203,6 +226,12 @@ def get_group(gid: str): view["personas"] = [revealable_view(p) for p in group.get("personas", [])] view["sales_kit"] = None view["report"] = None + elif actor.get("role") != "super_admin": + # admin: see group, but not secret/process persona fields nor the sales-kit/report + # (pain-fit analysis is the IP to protect). + view["personas"] = [strip_secret_fields(p) for p in group.get("personas", [])] + view["sales_kit"] = None + view["report"] = None return jsonify({"group": view}) @@ -216,8 +245,11 @@ def list_personas(gid: str): if group.get("status") != "ready": raise ApiError("group not ready", 403) personas = [revealable_view(p) for p in group.get("personas", [])] - else: + elif actor.get("role") == "super_admin": personas = group.get("personas", []) + else: + # admin: see persona but not the secret/process fields (IP protection) + personas = [strip_secret_fields(p) for p in group.get("personas", [])] # attach per-user status (won/lost/not-tried) for trainees if actor.get("role") == "user": sess = _stores().get("session_store") @@ -244,7 +276,10 @@ def get_persona(gid: str, pid: str): ensure = ensure_persona_shape(p) if actor.get("role") == "user": return jsonify({"persona": revealable_view(ensure)}) - return jsonify({"persona": ensure}) + if actor.get("role") == "super_admin": + return jsonify({"persona": ensure}) + # admin: hidden secret/process fields (IP protection) + return jsonify({"persona": strip_secret_fields(ensure)}) @groups_bp.put("//personas/") @@ -254,13 +289,22 @@ def update_persona(gid: str, pid: str): s = _stores() _get_owned_group(s, gid) data = request.get_json(silent=True) or {} + actor = current_user() + # IP protection: only super_admin may set/alter secret formula fields. + if actor.get("role") != "super_admin": + for f in SECRET_PERSONA_FIELDS: + if f in data: + raise ApiError(f"field '{f}' is locked (super_admin only)", 403) try: updated = s["groups"].update_persona(gid, pid, data) except ValueError as exc: raise ApiError(str(exc), 404) - return jsonify({"persona": ensure_persona_shape(updated["personas"][ + full = ensure_persona_shape(updated["personas"][ next(i for i, p in enumerate(updated["personas"]) if p["id"] == pid) - ])}) + ]) + if actor.get("role") == "super_admin": + return jsonify({"persona": full}) + return jsonify({"persona": strip_secret_fields(full)}) @groups_bp.post("//reanalyze") diff --git a/backend/scripts/test_ip_protection.py b/backend/scripts/test_ip_protection.py new file mode 100644 index 0000000..df88217 --- /dev/null +++ b/backend/scripts/test_ip_protection.py @@ -0,0 +1,61 @@ +"""Test: IP protection — admin cannot see/edit secret persona fields; super_admin can.""" +import os, sys, tempfile, warnings +from pathlib import Path + +warnings.filterwarnings("ignore") +BACKEND = str(Path(__file__).resolve().parents[1]) +sys.path.insert(0, BACKEND) + +from app.factory import create_app +from app.config import Config + +td = tempfile.mkdtemp() +Config.DATA_DIR = Path(td) +sys.path.insert(0, BACKEND + "/scripts") +from mock_llm import MockLLM + +app = create_app() +app.extensions["llm"] = MockLLM() +C = app.test_client() + +def tok(u, p): return C.post("/api/auth/login", json={"username": u, "password": p}).get_json()["token"] + +AT = tok("admin", "1234"); AH = {"Authorization": f"Bearer {AT}"} +C.post("/api/auth/setup", headers=AH, json={"username": "admin", "email": "a@b.co", "password": "newpass"}) +AT = tok("admin", "newpass"); AH = {"Authorization": f"Bearer {AT}"} + +gid = C.post("/api/groups", headers=AH, json={"product": "CRM", "segment": "SME", "channel": "line", "language": "th"}).get_json()["group"]["id"] +C.post(f"/api/groups/{gid}/analyze", headers=AH) +personas = C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"] # super_admin sees all +assert personas and "pains" in personas[0], "super_admin should see secret fields" +print("[ok] super_admin sees secret fields") + +# create an admin (not super) user +C.post("/api/admin/users", headers=AH, json={"username": "adm", "name": "Adm", "password": "pppp", "role": "admin"}) +AT2 = tok("adm", "pppp"); AH2 = {"Authorization": f"Bearer {AT2}"} +pid = personas[0]["id"] + +# admin list_personas: secret fields stripped +admin_list = C.get(f"/api/groups/{gid}/personas", headers=AH2).get_json()["personas"] +assert "pains" not in admin_list[0] and "tolerance" not in admin_list[0], "admin list should strip secrets" +print("[ok] admin list strips secret fields") + +# admin update with a secret field -> 403 +r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH2, json={"name": "X", "tolerance": 1}) +assert r.status_code == 403, r.get_json() +print("[ok] admin cannot set secret field (403)") + +# admin update of non-secret field -> ok, but response still strips secrets +r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH2, json={"name": "Edited Name"}) +assert r.status_code == 200, r.get_json() +body = r.get_json()["persona"] +assert body["name"] == "Edited Name" +assert "pains" not in body, "admin update response should strip secrets" +print("[ok] admin can edit non-secret field; response strips secrets") + +# super_admin can update secret field +r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH, json={"tolerance": 5}) +assert r.status_code == 200, r.get_json() +print("[ok] super_admin can edit secret field") + +print("ALL IP-PROTECTION TESTS PASSED") diff --git a/frontend/dist/assets/AdminUsers-B8OP5hvL.js b/frontend/dist/assets/AdminUsers-BXR4J5iM.js similarity index 98% rename from frontend/dist/assets/AdminUsers-B8OP5hvL.js rename to frontend/dist/assets/AdminUsers-BXR4J5iM.js index 1210bc9..dafbbbf 100644 --- a/frontend/dist/assets/AdminUsers-B8OP5hvL.js +++ b/frontend/dist/assets/AdminUsers-BXR4J5iM.js @@ -1,4 +1,4 @@ -import{c as k,_ as h,n as b,a as r,b as e,l as m,u as o,m as l,t as n,i as u,w as p,v,E as U,g as f,F as V,s as M,x as w,r as y,o as i,B as C}from"./index-DOaJiTJw.js";import{U as z}from"./users-SQMCzZpr.js";/** +import{c as k,_ as h,n as b,a as r,b as e,l as m,u as o,m as l,t as n,i as u,w as p,v,E as U,g as f,F as V,s as M,x as w,r as y,o as i,B as C}from"./index-BCax2GDh.js";import{U as z}from"./users-NPxa3eQ_.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/Analytics-Bkjt0mFt.js b/frontend/dist/assets/Analytics-DzK25pzJ.js similarity index 97% rename from frontend/dist/assets/Analytics-Bkjt0mFt.js rename to frontend/dist/assets/Analytics-DzK25pzJ.js index 0fb9998..6b0af51 100644 --- a/frontend/dist/assets/Analytics-Bkjt0mFt.js +++ b/frontend/dist/assets/Analytics-DzK25pzJ.js @@ -1,4 +1,4 @@ -import{c as x,_ as L,n as M,a as p,b as s,l as i,u as a,m as c,t as e,i as n,p as w,w as f,v as b,q as U,g as N,F as R,s as j,x as A,y as B,r as m,h as E,o as y}from"./index-DOaJiTJw.js";import{U as P}from"./users-SQMCzZpr.js";import{P as S}from"./plus-CODydpxS.js";/** +import{c as x,_ as L,n as M,a as p,b as s,l as i,u as a,m as c,t as e,i as n,p as w,w as f,v as b,q as U,g as N,F as R,s as j,x as A,y as B,r as m,h as E,o as y}from"./index-BCax2GDh.js";import{U as P}from"./users-NPxa3eQ_.js";import{P as S}from"./plus-B9zMVgv0.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/Chat-DszzTVNw.js b/frontend/dist/assets/Chat-B4cA4NlX.js similarity index 97% rename from frontend/dist/assets/Chat-DszzTVNw.js rename to frontend/dist/assets/Chat-B4cA4NlX.js index 66960fd..dbe7bd6 100644 --- a/frontend/dist/assets/Chat-DszzTVNw.js +++ b/frontend/dist/assets/Chat-B4cA4NlX.js @@ -1,4 +1,4 @@ -import{c as P,_ as R,n as G,x as S,a as o,l as k,p as D,u as a,b as s,t,i as n,F as T,s as L,m as g,B as w,g as h,w as K,v as $,d as H,k as J,r as d,A as O,C as U,y as Y,o as l}from"./index-DOaJiTJw.js";import{T as q}from"./target-QJk_fUDu.js";import{A as Q}from"./arrow-left-BUCak2lw.js";/** +import{c as P,_ as R,n as G,x as S,a as o,l as k,p as D,u as a,b as s,t,i as n,F as T,s as L,m as g,B as w,g as h,w as K,v as $,d as H,k as J,r as d,A as O,C as U,y as Y,o as l}from"./index-BCax2GDh.js";import{T as q}from"./target-BFC5gbdb.js";import{A as Q}from"./arrow-left-diPpM4Dj.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/GroupBuilder-CNxYYLnR.js b/frontend/dist/assets/GroupBuilder-DpQWbscg.js similarity index 98% rename from frontend/dist/assets/GroupBuilder-CNxYYLnR.js rename to frontend/dist/assets/GroupBuilder-DpQWbscg.js index f1f4538..b7d6714 100644 --- a/frontend/dist/assets/GroupBuilder-CNxYYLnR.js +++ b/frontend/dist/assets/GroupBuilder-DpQWbscg.js @@ -1,4 +1,4 @@ -import{c as g,_ as M,a as f,l as d,p as V,b as e,u as l,m as r,t as s,i as o,w as u,v as y,E as k,g as C,y as z,r as v,x as B,j as A,o as x}from"./index-DOaJiTJw.js";import{A as P}from"./arrow-left-BUCak2lw.js";/** +import{c as g,_ as M,a as f,l as d,p as V,b as e,u as l,m as r,t as s,i as o,w as u,v as y,E as k,g as C,y as z,r as v,x as B,j as A,o as x}from"./index-BCax2GDh.js";import{A as P}from"./arrow-left-diPpM4Dj.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/GroupEdit-Bq-uo5sC.js b/frontend/dist/assets/GroupEdit-Bq-uo5sC.js deleted file mode 100644 index ad7204c..0000000 --- a/frontend/dist/assets/GroupEdit-Bq-uo5sC.js +++ /dev/null @@ -1,9 +0,0 @@ -import{c as I,_ as F,G as M,o as m,a as f,b as e,t as i,w as o,v as u,E as N,g as _,H as O,r as k,n as D,l as z,p as J,u as c,m as y,i as B,f as q,F as P,s as S,I as H,k as R,x as A,y as K,B as T}from"./index-DOaJiTJw.js";import{U as Q}from"./users-SQMCzZpr.js";import{S as W}from"./sparkles-CwNig9QR.js";/** - * @license lucide-vue-next v1.0.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const X=I("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]),Y={class:"persona-form"},Z={class:"muted"},h={class:"sec"},ee={class:"row"},le={class:"f"},te={class:"f"},ne={class:"f"},se={class:"row"},oe={class:"f"},ie={class:"f"},ae={class:"f"},ue={class:"sec"},re={class:"row"},de={class:"f"},me={class:"f"},ve={class:"f"},fe={class:"sec"},ge={class:"sec"},ye={class:"row"},be={class:"f"},ke={class:"f"},ce={key:0,class:"muted"},xe={class:"row actions"},we=["disabled"],Ve={__name:"PersonaForm",props:{persona:{type:Object,required:!0}},emits:["save","cancel"],setup(V,{emit:E}){const x=V,p=E,n=O({name:"",difficulty:1,tier:"B",profession:"",age_group:"",location:"",income:"",budget:"",lifestyle:"",background:"",personality:"",communication_style:"",goal:"",decision_timeline:"",opener:"",channel:"line",initiation_mode:"customer",special:""}),d=k(""),b=k(""),v=k(""),w=k(!1);function U(t){return Array.isArray(t)?t.join(` -`):String(t||"")}function $(t){return t.split(` -`).map(l=>l.trim()).filter(Boolean)}M(()=>x.persona,t=>{Object.assign(n,{name:(t==null?void 0:t.name)||"",difficulty:(t==null?void 0:t.difficulty)??1,tier:(t==null?void 0:t.tier)||"B",profession:(t==null?void 0:t.profession)||"",age_group:(t==null?void 0:t.age_group)||"",location:(t==null?void 0:t.location)||"",income:(t==null?void 0:t.income)||"",budget:(t==null?void 0:t.budget)||"",lifestyle:(t==null?void 0:t.lifestyle)||"",background:(t==null?void 0:t.background)||"",personality:(t==null?void 0:t.personality)||"",communication_style:(t==null?void 0:t.communication_style)||"",goal:(t==null?void 0:t.goal)||"",decision_timeline:(t==null?void 0:t.decision_timeline)||"",opener:(t==null?void 0:t.opener)||"",channel:(t==null?void 0:t.channel)||"line",initiation_mode:(t==null?void 0:t.initiation_mode)||"customer",special:(t==null?void 0:t.special)||""}),d.value=U(t==null?void 0:t.pains),b.value=U(t==null?void 0:t.objections),v.value=U(t==null?void 0:t.negotiation_levers)},{immediate:!0});function j(){p("save",{...n,pains:$(d.value),objections:$(b.value),negotiation_levers:$(v.value)})}return(t,l)=>(m(),f("div",Y,[e("h3",null,i(V.persona.name||"New persona"),1),e("p",Z,i(V.persona.profession)+" · "+i(V.persona.personality),1),e("div",h,[l[28]||(l[28]=e("h4",null,"👤 ข้อมูลพื้นฐาน",-1)),e("div",ee,[e("div",le,[l[21]||(l[21]=e("label",null,"ชื่อ",-1)),o(e("input",{"onUpdate:modelValue":l[0]||(l[0]=s=>n.name=s)},null,512),[[u,n.name]])]),e("div",te,[l[22]||(l[22]=e("label",null,"ระดับความยาก (1-5)",-1)),o(e("input",{"onUpdate:modelValue":l[1]||(l[1]=s=>n.difficulty=s),type:"number",min:"1",max:"5"},null,512),[[u,n.difficulty,void 0,{number:!0}]])]),e("div",ne,[l[24]||(l[24]=e("label",null,"ระดับลูกค้า",-1)),o(e("select",{"onUpdate:modelValue":l[2]||(l[2]=s=>n.tier=s)},[...l[23]||(l[23]=[e("option",{value:"A"},"A — พร้อมตัดสินใจซื้อ",-1),e("option",{value:"B"},"B — ยังไม่แน่ใจ",-1),e("option",{value:"C"},"C — ไม่สนใจแต่มีปัญหา",-1)])],512),[[N,n.tier]])])]),e("div",se,[e("div",oe,[l[25]||(l[25]=e("label",null,"อาชีพ",-1)),o(e("input",{"onUpdate:modelValue":l[3]||(l[3]=s=>n.profession=s)},null,512),[[u,n.profession]])]),e("div",ie,[l[26]||(l[26]=e("label",null,"ช่วงอายุ",-1)),o(e("input",{"onUpdate:modelValue":l[4]||(l[4]=s=>n.age_group=s)},null,512),[[u,n.age_group]])]),e("div",ae,[l[27]||(l[27]=e("label",null,"พื้นที่",-1)),o(e("input",{"onUpdate:modelValue":l[5]||(l[5]=s=>n.location=s)},null,512),[[u,n.location]])])])]),e("div",ue,[l[32]||(l[32]=e("h4",null,"💼 ข้อมูลลูกค้า",-1)),e("div",re,[e("div",de,[l[29]||(l[29]=e("label",null,"รายได้/สถานะการเงิน",-1)),o(e("input",{"onUpdate:modelValue":l[6]||(l[6]=s=>n.income=s)},null,512),[[u,n.income]])]),e("div",me,[l[30]||(l[30]=e("label",null,"งบประมาณ",-1)),o(e("input",{"onUpdate:modelValue":l[7]||(l[7]=s=>n.budget=s)},null,512),[[u,n.budget]])]),e("div",ve,[l[31]||(l[31]=e("label",null,"ไลฟ์สไตล์",-1)),o(e("input",{"onUpdate:modelValue":l[8]||(l[8]=s=>n.lifestyle=s)},null,512),[[u,n.lifestyle]])])]),l[33]||(l[33]=e("label",null,"ภูมิหลัง",-1)),o(e("textarea",{"onUpdate:modelValue":l[9]||(l[9]=s=>n.background=s)},null,512),[[u,n.background]]),l[34]||(l[34]=e("label",null,"บุคลิก / วิธีพูดคุย",-1)),o(e("textarea",{"onUpdate:modelValue":l[10]||(l[10]=s=>n.personality=s)},null,512),[[u,n.personality]]),l[35]||(l[35]=e("label",null,"สไตล์การสื่อสาร",-1)),o(e("textarea",{"onUpdate:modelValue":l[11]||(l[11]=s=>n.communication_style=s)},null,512),[[u,n.communication_style]])]),e("div",fe,[l[36]||(l[36]=e("h4",null,"🎯 การขาย",-1)),l[37]||(l[37]=e("label",null,"เป้าหมาย",-1)),o(e("textarea",{"onUpdate:modelValue":l[12]||(l[12]=s=>n.goal=s)},null,512),[[u,n.goal]]),l[38]||(l[38]=e("label",null,"กรอบเวลาในการตัดสินใจ",-1)),o(e("input",{"onUpdate:modelValue":l[13]||(l[13]=s=>n.decision_timeline=s)},null,512),[[u,n.decision_timeline]]),l[39]||(l[39]=e("label",null,"Pain (ปัญหา) แบบ 1 ต่อบรรทัด",-1)),o(e("textarea",{"onUpdate:modelValue":l[14]||(l[14]=s=>d.value=s),placeholder:`เช่น ต้นทุนสูงเกินไป -ระบบล้าสมัย`},null,512),[[u,d.value]]),l[40]||(l[40]=e("label",null,"ข้อโต้แย้ง (Objections) 1 ต่อบรรทัด",-1)),o(e("textarea",{"onUpdate:modelValue":l[15]||(l[15]=s=>b.value=s)},null,512),[[u,b.value]]),l[41]||(l[41]=e("label",null,"สิ่งที่ใช้ต่อรอง (เช่น ส่วนลด, ฟรีติดตั้ง)",-1)),o(e("textarea",{"onUpdate:modelValue":l[16]||(l[16]=s=>v.value=s)},null,512),[[u,v.value]]),l[42]||(l[42]=e("label",null,"ช่องทางสำหรับลูกค้าเปิดบทสนทนา (opener)",-1)),o(e("textarea",{"onUpdate:modelValue":l[17]||(l[17]=s=>n.opener=s)},null,512),[[u,n.opener]])]),e("div",ge,[l[47]||(l[47]=e("h4",null,"📡 ช่องทาง & โหมด",-1)),e("div",ye,[e("div",be,[l[44]||(l[44]=e("label",null,"ช่องทาง",-1)),o(e("select",{"onUpdate:modelValue":l[18]||(l[18]=s=>n.channel=s)},[...l[43]||(l[43]=[e("option",{value:"line"},"LINE",-1),e("option",{value:"facebook"},"Facebook",-1)])],512),[[N,n.channel]])]),e("div",ke,[l[46]||(l[46]=e("label",null,"ใครเริ่มคุยก่อน",-1)),o(e("select",{"onUpdate:modelValue":l[19]||(l[19]=s=>n.initiation_mode=s)},[...l[45]||(l[45]=[e("option",{value:"customer"},"ลูกค้าทักก่อน",-1),e("option",{value:"seller"},"เราต้องเริ่มขาย (เชิงรุก)",-1)])],512),[[N,n.initiation_mode]])])]),n.special?(m(),f("label",ce,"พิเศษ: "+i(n.special),1)):_("",!0)]),e("div",xe,[e("button",{class:"primary",onClick:j,disabled:w.value},i(w.value?"กำลังบันทึก…":"💾 บันทึกบุคคลต้นแบบ"),9,we),e("button",{onClick:l[20]||(l[20]=s=>t.$emit("cancel"))},"ปิด")])]))}},pe=F(Ve,[["__scopeId","data-v-738758fc"]]),Ue={class:"row",style:{"align-items":"center","margin-bottom":"8px"}},$e={style:{margin:"0"}},Ce={class:"row",style:{"margin-left":"auto",gap:"10px"}},Be=["disabled"],_e={key:0,class:"spinner"},ze={key:0,class:"muted"},Ae={key:1,class:"error",style:{margin:"12px 0"}},je={key:2,class:"card empty-state"},Le={class:"grid"},Ne={class:"row",style:{"justify-content":"space-between","align-items":"center"}},Pe={class:"diff"},Se={class:"muted"},Ee={class:"muted"},Te=["onClick"],Fe={class:"modal"},Ge={__name:"GroupEdit",setup(V){const x=R().params.gid,p=k(null),n=k([]),d=k(!1),b=k(""),v=k(null);async function w(){const a=await A.getGroup(x);p.value=a.group,n.value=(await A.listPersonas(x)).personas}function U(a){return n.value.filter(r=>r.tier===a)}function $(a){return B.t(a==="A"?"tierA":a==="B"?"tierB":"tierC")}function j(a){return{A:"tier-a",B:"tier-b",C:"tier-c"}[a]||""}async function t(){d.value=!0,b.value="";try{const a=await A.analyzeGroup(x);n.value=a.personas,await w()}catch(a){b.value=a.message}finally{d.value=!1}}function l(a){v.value=JSON.parse(JSON.stringify(a))}async function s(a){d.value=!0;try{await A.updatePersona(x,v.value.id,a),v.value=null,await w()}catch(r){b.value=r.message}finally{d.value=!1}}return D(w),(a,r)=>{const G=K("router-link");return m(),f("div",null,[z(G,{to:"/training",class:"btn-back"},{default:J(()=>[y("← "+i(c(B).t("training")),1)]),_:1}),e("div",Ue,[e("h2",$e,[z(c(Q),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),y(" "+i(c(B).t("managePersonas")),1)]),e("div",Ce,[e("button",{onClick:t,disabled:d.value,class:"primary"},[d.value?(m(),f("span",_e)):(m(),q(c(W),{key:1,size:18,"stroke-width":1.8})),y(" "+i(d.value?"กำลังสร้าง…":c(B).t("analyze")),1)],8,Be)])]),p.value?(m(),f("p",ze,[r[2]||(r[2]=y("สินค้า: ",-1)),e("strong",null,i(p.value.title),1)])):_("",!0),b.value?(m(),f("div",Ae,i(b.value),1)):_("",!0),r[4]||(r[4]=e("div",{class:"card guide"},[e("strong",null,"วิธีใช้หน้านี้"),e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[y("กดปุ่ม "),e("strong",null,"✨ สร้าง/วิเคราะห์บุคคลต้นแบบ"),y(" เพื่อให้ระบบสร้างลูกค้าจำลอง 15 คน (ระดับ A/B/C)")]),e("li",null,[y("แต่ละการ์ดคือลูกค้าจำลอง 1 คน — กด "),e("strong",null,"✏️ แก้ไข"),y(" เพื่อปรับรายละเอียดให้ตรงใจ")]),e("li",null,[y("เมื่อพร้อมให้ผู้ใช้ฝึก กด "),e("strong",null,"เปิดใช้งาน"),y(" (ยังทำในขั้นถัดไป)")])])],-1)),n.value.length===0&&!d.value?(m(),f("div",je,[...r[3]||(r[3]=[e("strong",null,"ยังไม่มีบุคคลต้นแบบ",-1),e("span",null,'กด "✨ สร้าง/วิเคราะห์บุคคลต้นแบบ" เพื่อให้ระบบสร้างลูกค้าจำลองให้',-1)])])):_("",!0),(m(),f(P,null,S(["A","B","C"],C=>e("div",{key:C,style:{"margin-bottom":"20px"}},[e("h4",null,i($(C)),1),e("div",Le,[(m(!0),f(P,null,S(U(C),g=>(m(),f("div",{key:g.id,class:"card pcard"},[e("div",Ne,[e("strong",null,i(g.name),1),e("span",{class:T(["badge",j(g.tier)])},i(g.tier),3)]),e("div",Pe,[(m(),f(P,null,S(5,L=>e("span",{key:L,class:T(["star",{on:L<=(g.difficulty||1)}])},"★",2)),64))]),e("div",Se,i(g.profession)+" · "+i(g.age_group)+" · "+i(g.location),1),e("div",Ee,i(g.channel)+" · "+i(g.initiation_mode==="seller"?"เริ่มขายเอง":"ลูกค้าทักก่อน"),1),e("button",{class:"edit-btn",onClick:L=>l(g)},[z(c(X),{size:15,"stroke-width":1.8}),y(" "+i(c(B).t("edit")),1)],8,Te)]))),128))])])),64)),v.value?(m(),f("div",{key:3,class:"modal-backdrop",onClick:r[1]||(r[1]=H(C=>v.value=null,["self"]))},[e("div",Fe,[z(pe,{persona:v.value,onSave:s,onCancel:r[0]||(r[0]=C=>v.value=null)},null,8,["persona"])])])):_("",!0)])}}},De=F(Ge,[["__scopeId","data-v-c682ddb6"]]);export{De as default}; diff --git a/frontend/dist/assets/GroupEdit-CFwXUOV9.css b/frontend/dist/assets/GroupEdit-CFwXUOV9.css new file mode 100644 index 0000000..1d822f6 --- /dev/null +++ b/frontend/dist/assets/GroupEdit-CFwXUOV9.css @@ -0,0 +1 @@ +.sec[data-v-cef61bd8]{margin-top:20px}.sec h4[data-v-cef61bd8]{margin:0 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)}.row[data-v-cef61bd8]{display:flex;gap:12px;flex-wrap:wrap}.f[data-v-cef61bd8]{flex:1;min-width:140px}label[data-v-cef61bd8]{font-size:13px;color:var(--muted);display:block;margin:10px 0 4px}input[data-v-cef61bd8],select[data-v-cef61bd8],textarea[data-v-cef61bd8]{width:100%}.actions[data-v-cef61bd8]{margin-top:20px}.locked[data-v-cef61bd8]{background:#fffaf5;border:1px dashed #d6c7a1;border-radius:12px;padding:12px 14px}.locked h4[data-v-cef61bd8]{color:#b45309}.grid[data-v-c682ddb6]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.pcard[data-v-c682ddb6]{display:flex;flex-direction:column}.edit-btn[data-v-c682ddb6]{margin-top:auto;display:inline-flex;align-items:center;gap:6px}.star[data-v-c682ddb6]{color:#d8dbe3}.star.on[data-v-c682ddb6]{color:#f59e0b}.badge.tier-a[data-v-c682ddb6]{background:#dcfce7;color:#166534}.badge.tier-b[data-v-c682ddb6]{background:#fef9c3;color:#854d0e}.badge.tier-c[data-v-c682ddb6]{background:#fee2e2;color:#991b1b}.guide[data-v-c682ddb6]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.modal-backdrop[data-v-c682ddb6]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0f172a80;display:flex;justify-content:center;align-items:flex-start;padding:40px 16px;z-index:50;overflow:auto}.modal[data-v-c682ddb6]{background:#fff;border-radius:14px;padding:24px;width:100%;max-width:720px;box-shadow:0 20px 50px #00000040} diff --git a/frontend/dist/assets/GroupEdit-D9lmggXK.css b/frontend/dist/assets/GroupEdit-D9lmggXK.css deleted file mode 100644 index ba265c9..0000000 --- a/frontend/dist/assets/GroupEdit-D9lmggXK.css +++ /dev/null @@ -1 +0,0 @@ -.sec[data-v-738758fc]{margin-top:20px}.sec h4[data-v-738758fc]{margin:0 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)}.row[data-v-738758fc]{display:flex;gap:12px;flex-wrap:wrap}.f[data-v-738758fc]{flex:1;min-width:140px}label[data-v-738758fc]{font-size:13px;color:var(--muted);display:block;margin:10px 0 4px}input[data-v-738758fc],select[data-v-738758fc],textarea[data-v-738758fc]{width:100%}.actions[data-v-738758fc]{margin-top:20px}.grid[data-v-c682ddb6]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.pcard[data-v-c682ddb6]{display:flex;flex-direction:column}.edit-btn[data-v-c682ddb6]{margin-top:auto;display:inline-flex;align-items:center;gap:6px}.star[data-v-c682ddb6]{color:#d8dbe3}.star.on[data-v-c682ddb6]{color:#f59e0b}.badge.tier-a[data-v-c682ddb6]{background:#dcfce7;color:#166534}.badge.tier-b[data-v-c682ddb6]{background:#fef9c3;color:#854d0e}.badge.tier-c[data-v-c682ddb6]{background:#fee2e2;color:#991b1b}.guide[data-v-c682ddb6]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.modal-backdrop[data-v-c682ddb6]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0f172a80;display:flex;justify-content:center;align-items:flex-start;padding:40px 16px;z-index:50;overflow:auto}.modal[data-v-c682ddb6]{background:#fff;border-radius:14px;padding:24px;width:100%;max-width:720px;box-shadow:0 20px 50px #00000040} diff --git a/frontend/dist/assets/GroupEdit-PGVs7eZc.js b/frontend/dist/assets/GroupEdit-PGVs7eZc.js new file mode 100644 index 0000000..e1154a6 --- /dev/null +++ b/frontend/dist/assets/GroupEdit-PGVs7eZc.js @@ -0,0 +1,9 @@ +import{c as M,_ as G,G as O,o as r,a as m,b as e,t as a,w as o,v as i,E as N,u as k,h as _,g as A,H as D,r as p,n as J,l as S,p as q,m as y,i as B,f as H,F as P,s as E,I as R,k as K,x as z,y as Q,B as F}from"./index-BCax2GDh.js";import{U as W}from"./users-NPxa3eQ_.js";import{S as X}from"./sparkles-E3Xd6eY7.js";/** + * @license lucide-vue-next v1.0.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Y=M("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]),Z={class:"persona-form"},h={class:"muted"},ll={class:"sec"},el={class:"row"},tl={class:"f"},nl={class:"f"},sl={class:"f"},ol={class:"row"},il={class:"f"},al={class:"f"},ul={class:"f"},rl={class:"sec"},dl={class:"row"},ml={class:"f"},vl={class:"f"},fl={class:"f"},gl={key:0,class:"sec"},yl={key:1,class:"sec locked"},bl={class:"sec"},kl={class:"row"},pl={class:"f"},xl={class:"f"},Vl={key:0,class:"muted"},wl={class:"row actions"},Ul=["disabled"],$l={__name:"PersonaForm",props:{persona:{type:Object,required:!0}},emits:["save","cancel"],setup(w,{emit:T}){const x=w,U=T,n=D({name:"",difficulty:1,tier:"B",profession:"",age_group:"",location:"",income:"",budget:"",lifestyle:"",background:"",personality:"",communication_style:"",goal:"",decision_timeline:"",opener:"",channel:"line",initiation_mode:"customer",special:""}),v=p(""),b=p(""),f=p(""),V=p(!1);function $(t){return Array.isArray(t)?t.join(` +`):String(t||"")}function C(t){return t.split(` +`).map(l=>l.trim()).filter(Boolean)}O(()=>x.persona,t=>{Object.assign(n,{name:(t==null?void 0:t.name)||"",difficulty:(t==null?void 0:t.difficulty)??1,tier:(t==null?void 0:t.tier)||"B",profession:(t==null?void 0:t.profession)||"",age_group:(t==null?void 0:t.age_group)||"",location:(t==null?void 0:t.location)||"",income:(t==null?void 0:t.income)||"",budget:(t==null?void 0:t.budget)||"",lifestyle:(t==null?void 0:t.lifestyle)||"",background:(t==null?void 0:t.background)||"",personality:(t==null?void 0:t.personality)||"",communication_style:(t==null?void 0:t.communication_style)||"",goal:(t==null?void 0:t.goal)||"",decision_timeline:(t==null?void 0:t.decision_timeline)||"",opener:(t==null?void 0:t.opener)||"",channel:(t==null?void 0:t.channel)||"line",initiation_mode:(t==null?void 0:t.initiation_mode)||"customer",special:(t==null?void 0:t.special)||""}),v.value=$(t==null?void 0:t.pains),b.value=$(t==null?void 0:t.objections),f.value=$(t==null?void 0:t.negotiation_levers)},{immediate:!0});function j(){U("save",{...n,pains:C(v.value),objections:C(b.value),negotiation_levers:C(f.value)})}return(t,l)=>(r(),m("div",Z,[e("h3",null,a(w.persona.name||"New persona"),1),e("p",h,a(w.persona.profession)+" · "+a(w.persona.personality),1),e("div",ll,[l[30]||(l[30]=e("h4",null,"👤 ข้อมูลพื้นฐาน",-1)),e("div",el,[e("div",tl,[l[23]||(l[23]=e("label",null,"ชื่อ",-1)),o(e("input",{"onUpdate:modelValue":l[0]||(l[0]=s=>n.name=s)},null,512),[[i,n.name]])]),e("div",nl,[l[24]||(l[24]=e("label",null,"ระดับความยาก (1-5)",-1)),o(e("input",{"onUpdate:modelValue":l[1]||(l[1]=s=>n.difficulty=s),type:"number",min:"1",max:"5"},null,512),[[i,n.difficulty,void 0,{number:!0}]])]),e("div",sl,[l[26]||(l[26]=e("label",null,"ระดับลูกค้า",-1)),o(e("select",{"onUpdate:modelValue":l[2]||(l[2]=s=>n.tier=s)},[...l[25]||(l[25]=[e("option",{value:"A"},"A — พร้อมตัดสินใจซื้อ",-1),e("option",{value:"B"},"B — ยังไม่แน่ใจ",-1),e("option",{value:"C"},"C — ไม่สนใจแต่มีปัญหา",-1)])],512),[[N,n.tier]])])]),e("div",ol,[e("div",il,[l[27]||(l[27]=e("label",null,"อาชีพ",-1)),o(e("input",{"onUpdate:modelValue":l[3]||(l[3]=s=>n.profession=s)},null,512),[[i,n.profession]])]),e("div",al,[l[28]||(l[28]=e("label",null,"ช่วงอายุ",-1)),o(e("input",{"onUpdate:modelValue":l[4]||(l[4]=s=>n.age_group=s)},null,512),[[i,n.age_group]])]),e("div",ul,[l[29]||(l[29]=e("label",null,"พื้นที่",-1)),o(e("input",{"onUpdate:modelValue":l[5]||(l[5]=s=>n.location=s)},null,512),[[i,n.location]])])])]),e("div",rl,[l[34]||(l[34]=e("h4",null,"💼 ข้อมูลลูกค้า",-1)),e("div",dl,[e("div",ml,[l[31]||(l[31]=e("label",null,"รายได้/สถานะการเงิน",-1)),o(e("input",{"onUpdate:modelValue":l[6]||(l[6]=s=>n.income=s)},null,512),[[i,n.income]])]),e("div",vl,[l[32]||(l[32]=e("label",null,"งบประมาณ",-1)),o(e("input",{"onUpdate:modelValue":l[7]||(l[7]=s=>n.budget=s)},null,512),[[i,n.budget]])]),e("div",fl,[l[33]||(l[33]=e("label",null,"ไลฟ์สไตล์",-1)),o(e("input",{"onUpdate:modelValue":l[8]||(l[8]=s=>n.lifestyle=s)},null,512),[[i,n.lifestyle]])])]),l[35]||(l[35]=e("label",null,"ภูมิหลัง",-1)),o(e("textarea",{"onUpdate:modelValue":l[9]||(l[9]=s=>n.background=s)},null,512),[[i,n.background]]),l[36]||(l[36]=e("label",null,"บุคลิก / วิธีพูดคุย",-1)),o(e("textarea",{"onUpdate:modelValue":l[10]||(l[10]=s=>n.personality=s)},null,512),[[i,n.personality]]),l[37]||(l[37]=e("label",null,"สไตล์การสื่อสาร",-1)),o(e("textarea",{"onUpdate:modelValue":l[11]||(l[11]=s=>n.communication_style=s)},null,512),[[i,n.communication_style]])]),k(_).isSuperAdmin?(r(),m("div",gl,[l[38]||(l[38]=e("h4",null,"🎯 การขาย",-1)),l[39]||(l[39]=e("label",null,"เป้าหมาย",-1)),o(e("textarea",{"onUpdate:modelValue":l[12]||(l[12]=s=>n.goal=s)},null,512),[[i,n.goal]]),l[40]||(l[40]=e("label",null,"กรอบเวลาในการตัดสินใจ",-1)),o(e("input",{"onUpdate:modelValue":l[13]||(l[13]=s=>n.decision_timeline=s)},null,512),[[i,n.decision_timeline]]),l[41]||(l[41]=e("label",null,"Pain (ปัญหา) แบบ 1 ต่อบรรทัด",-1)),o(e("textarea",{"onUpdate:modelValue":l[14]||(l[14]=s=>v.value=s),placeholder:`เช่น ต้นทุนสูงเกินไป +ระบบล้าสมัย`},null,512),[[i,v.value]]),l[42]||(l[42]=e("label",null,"ข้อโต้แย้ง (Objections) 1 ต่อบรรทัด",-1)),o(e("textarea",{"onUpdate:modelValue":l[15]||(l[15]=s=>b.value=s)},null,512),[[i,b.value]]),l[43]||(l[43]=e("label",null,"สิ่งที่ใช้ต่อรอง (เช่น ส่วนลด, ฟรีติดตั้ง)",-1)),o(e("textarea",{"onUpdate:modelValue":l[16]||(l[16]=s=>f.value=s)},null,512),[[i,f.value]]),l[44]||(l[44]=e("label",null,"ช่องทางสำหรับลูกค้าเปิดบทสนทนา (opener)",-1)),o(e("textarea",{"onUpdate:modelValue":l[17]||(l[17]=s=>n.opener=s)},null,512),[[i,n.opener]])])):(r(),m("div",yl,[l[45]||(l[45]=e("h4",null,"🔒 ส่วนสูตรการขาย",-1)),l[46]||(l[46]=e("p",{class:"muted"},"ปิดการแก้ไข — เฉพาะผู้ดูแลระดับสูงเท่านั้นที่ดู/แก้ได้ (เพื่อรักษาความได้เปรียบ)",-1)),l[47]||(l[47]=e("label",null,"เป้าหมาย",-1)),o(e("textarea",{"onUpdate:modelValue":l[18]||(l[18]=s=>n.goal=s)},null,512),[[i,n.goal]]),l[48]||(l[48]=e("label",null,"กรอบเวลาในการตัดสินใจ",-1)),o(e("input",{"onUpdate:modelValue":l[19]||(l[19]=s=>n.decision_timeline=s)},null,512),[[i,n.decision_timeline]])])),e("div",bl,[l[53]||(l[53]=e("h4",null,"📡 ช่องทาง & โหมด",-1)),e("div",kl,[e("div",pl,[l[50]||(l[50]=e("label",null,"ช่องทาง",-1)),o(e("select",{"onUpdate:modelValue":l[20]||(l[20]=s=>n.channel=s)},[...l[49]||(l[49]=[e("option",{value:"line"},"LINE",-1),e("option",{value:"facebook"},"Facebook",-1)])],512),[[N,n.channel]])]),e("div",xl,[l[52]||(l[52]=e("label",null,"ใครเริ่มคุยก่อน",-1)),o(e("select",{"onUpdate:modelValue":l[21]||(l[21]=s=>n.initiation_mode=s)},[...l[51]||(l[51]=[e("option",{value:"customer"},"ลูกค้าทักก่อน",-1),e("option",{value:"seller"},"เราต้องเริ่มขาย (เชิงรุก)",-1)])],512),[[N,n.initiation_mode]])])]),k(_).isSuperAdmin&&n.special?(r(),m("label",Vl,"พิเศษ: "+a(n.special),1)):A("",!0)]),e("div",wl,[e("button",{class:"primary",onClick:j,disabled:V.value},a(V.value?"กำลังบันทึก…":"💾 บันทึกบุคคลต้นแบบ"),9,Ul),e("button",{onClick:l[22]||(l[22]=s=>t.$emit("cancel"))},"ปิด")])]))}},Cl=G($l,[["__scopeId","data-v-cef61bd8"]]),cl={class:"row",style:{"align-items":"center","margin-bottom":"8px"}},Bl={style:{margin:"0"}},Al={class:"row",style:{"margin-left":"auto",gap:"10px"}},Sl=["disabled"],zl={key:0,class:"spinner"},jl={key:0,class:"muted"},Ll={key:1,class:"error",style:{margin:"12px 0"}},Nl={key:2,class:"card empty-state"},Pl={class:"grid"},El={class:"row",style:{"justify-content":"space-between","align-items":"center"}},Tl={class:"diff"},_l={class:"muted"},Fl={class:"muted"},Gl=["onClick"],Il={class:"modal"},Ml={__name:"GroupEdit",setup(w){const x=K().params.gid,U=p(null),n=p([]),v=p(!1),b=p(""),f=p(null);async function V(){const u=await z.getGroup(x);U.value=u.group,n.value=(await z.listPersonas(x)).personas}function $(u){return n.value.filter(d=>d.tier===u)}function C(u){return B.t(u==="A"?"tierA":u==="B"?"tierB":"tierC")}function j(u){return{A:"tier-a",B:"tier-b",C:"tier-c"}[u]||""}async function t(){v.value=!0,b.value="";try{const u=await z.analyzeGroup(x);n.value=u.personas,await V()}catch(u){b.value=u.message}finally{v.value=!1}}function l(u){f.value=JSON.parse(JSON.stringify(u))}async function s(u){v.value=!0;try{await z.updatePersona(x,f.value.id,u),f.value=null,await V()}catch(d){b.value=d.message}finally{v.value=!1}}return J(V),(u,d)=>{const I=Q("router-link");return r(),m("div",null,[S(I,{to:"/training",class:"btn-back"},{default:q(()=>[y("← "+a(k(B).t("training")),1)]),_:1}),e("div",cl,[e("h2",Bl,[S(k(W),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),y(" "+a(k(B).t("managePersonas")),1)]),e("div",Al,[e("button",{onClick:t,disabled:v.value,class:"primary"},[v.value?(r(),m("span",zl)):(r(),H(k(X),{key:1,size:18,"stroke-width":1.8})),y(" "+a(v.value?"กำลังสร้าง…":k(B).t("analyze")),1)],8,Sl)])]),U.value?(r(),m("p",jl,[d[2]||(d[2]=y("สินค้า: ",-1)),e("strong",null,a(U.value.title),1)])):A("",!0),b.value?(r(),m("div",Ll,a(b.value),1)):A("",!0),d[4]||(d[4]=e("div",{class:"card guide"},[e("strong",null,"วิธีใช้หน้านี้"),e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[y("กดปุ่ม "),e("strong",null,"✨ สร้าง/วิเคราะห์บุคคลต้นแบบ"),y(" เพื่อให้ระบบสร้างลูกค้าจำลอง 15 คน (ระดับ A/B/C)")]),e("li",null,[y("แต่ละการ์ดคือลูกค้าจำลอง 1 คน — กด "),e("strong",null,"✏️ แก้ไข"),y(" เพื่อปรับรายละเอียดให้ตรงใจ")]),e("li",null,[y("เมื่อพร้อมให้ผู้ใช้ฝึก กด "),e("strong",null,"เปิดใช้งาน"),y(" (ยังทำในขั้นถัดไป)")])])],-1)),n.value.length===0&&!v.value?(r(),m("div",Nl,[...d[3]||(d[3]=[e("strong",null,"ยังไม่มีบุคคลต้นแบบ",-1),e("span",null,'กด "✨ สร้าง/วิเคราะห์บุคคลต้นแบบ" เพื่อให้ระบบสร้างลูกค้าจำลองให้',-1)])])):A("",!0),(r(),m(P,null,E(["A","B","C"],c=>e("div",{key:c,style:{"margin-bottom":"20px"}},[e("h4",null,a(C(c)),1),e("div",Pl,[(r(!0),m(P,null,E($(c),g=>(r(),m("div",{key:g.id,class:"card pcard"},[e("div",El,[e("strong",null,a(g.name),1),e("span",{class:F(["badge",j(g.tier)])},a(g.tier),3)]),e("div",Tl,[(r(),m(P,null,E(5,L=>e("span",{key:L,class:F(["star",{on:L<=(g.difficulty||1)}])},"★",2)),64))]),e("div",_l,a(g.profession)+" · "+a(g.age_group)+" · "+a(g.location),1),e("div",Fl,a(g.channel)+" · "+a(g.initiation_mode==="seller"?"เริ่มขายเอง":"ลูกค้าทักก่อน"),1),e("button",{class:"edit-btn",onClick:L=>l(g)},[S(k(Y),{size:15,"stroke-width":1.8}),y(" "+a(k(B).t("edit")),1)],8,Gl)]))),128))])])),64)),f.value?(r(),m("div",{key:3,class:"modal-backdrop",onClick:d[1]||(d[1]=R(c=>f.value=null,["self"]))},[e("div",Il,[S(Cl,{persona:f.value,onSave:s,onCancel:d[0]||(d[0]=c=>f.value=null)},null,8,["persona"])])])):A("",!0)])}}},ql=G(Ml,[["__scopeId","data-v-c682ddb6"]]);export{ql as default}; diff --git a/frontend/dist/assets/Guide-GzVXirX-.js b/frontend/dist/assets/Guide-Bx9T4hBF.js similarity index 95% rename from frontend/dist/assets/Guide-GzVXirX-.js rename to frontend/dist/assets/Guide-Bx9T4hBF.js index c44e321..2a3258f 100644 --- a/frontend/dist/assets/Guide-GzVXirX-.js +++ b/frontend/dist/assets/Guide-Bx9T4hBF.js @@ -1,4 +1,4 @@ -import{c as e,a as o,b as l,l as n,u as i,m as s,t as r,i as a,D as u,o as d}from"./index-DOaJiTJw.js";import{B as g}from"./book-open-Ckxqlz0w.js";import{L as p}from"./layout-dashboard-CmUmS5d8.js";import{T as m}from"./target-QJk_fUDu.js";import{S as y}from"./sparkles-CwNig9QR.js";/** +import{c as e,a as o,b as l,l as n,u as i,m as s,t as r,i as a,D as u,o as d}from"./index-BCax2GDh.js";import{B as g}from"./book-open-C7SxBVkN.js";import{L as p}from"./layout-dashboard-Djj77ueX.js";import{T as m}from"./target-BFC5gbdb.js";import{S as y}from"./sparkles-E3Xd6eY7.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/Login-DxHA8GYa.js b/frontend/dist/assets/Login-BFjXrLpo.js similarity index 98% rename from frontend/dist/assets/Login-DxHA8GYa.js rename to frontend/dist/assets/Login-BFjXrLpo.js index 767fdfd..ed4fa42 100644 --- a/frontend/dist/assets/Login-DxHA8GYa.js +++ b/frontend/dist/assets/Login-BFjXrLpo.js @@ -1,4 +1,4 @@ -import{c as k,_ as x,a as y,b as e,t as l,u as a,i as n,w as h,v as M,d as w,e as B,f as _,g as E,r,h as f,j as S,k as V,o as s}from"./index-DOaJiTJw.js";/** +import{c as k,_ as x,a as y,b as e,t as l,u as a,i as n,w as h,v as M,d as w,e as B,f as _,g as E,r,h as f,j as S,k as V,o as s}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/MyBoard-CTHsz6OL.js b/frontend/dist/assets/MyBoard-wXpWmFYg.js similarity index 94% rename from frontend/dist/assets/MyBoard-CTHsz6OL.js rename to frontend/dist/assets/MyBoard-wXpWmFYg.js index 2b50eb2..500c87f 100644 --- a/frontend/dist/assets/MyBoard-CTHsz6OL.js +++ b/frontend/dist/assets/MyBoard-wXpWmFYg.js @@ -1 +1 @@ -import{_ as f,n as x,x as v,a as r,b as t,l as b,u as e,m as k,t as s,i as a,g as _,w as B,z as N,F as D,s as S,r as p,A as c,o as i,B as T}from"./index-DOaJiTJw.js";import{L as V}from"./layout-dashboard-CmUmS5d8.js";const z={style:{margin:"0"}},L={key:0,class:"row stat-row"},M={class:"card stat"},C={class:"card stat won"},F={class:"card stat lost"},j={class:"card stat"},A={key:1,class:"card"},E={key:2,class:"card empty-state"},G={class:"card",style:{"margin-top":"20px"}},I={style:{"margin-top":"0"}},q={__name:"MyBoard",setup(H){const o=p([]),u=p([]),m=p(!0),y=c(()=>o.value.length),g=c(()=>o.value.filter(n=>n.my_outcome==="won").length),h=c(()=>o.value.filter(n=>n.my_outcome==="lost").length),w=c(()=>o.value.filter(n=>n.my_outcome==="not_tried").length);return x(async()=>{try{o.value=(await v.myBoard()).board||[],u.value=(await v.mySessions()).sessions||[]}finally{m.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",z,[b(e(V),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),k(" "+s(e(a).t("myDashboard")),1)]),l[0]||(l[0]=t("div",{class:"muted",style:{"margin-top":"4px","margin-bottom":"16px"}},"สรุปผลการฝึกของตัวคุณเอง — ดูว่าปิดการขายได้กี่ครั้ง ยังฝึกกับใครบ้าง",-1))]),m.value?_("",!0):(i(),r("div",L,[t("div",M,[t("span",null,s(e(a).t("total")),1),t("strong",null,s(y.value),1)]),t("div",C,[t("span",null,s(e(a).t("won")),1),t("strong",null,s(g.value),1)]),t("div",F,[t("span",null,s(e(a).t("lost")),1),t("strong",null,s(h.value),1)]),t("div",j,[t("span",null,s(e(a).t("notTried")),1),t("strong",null,s(w.value),1)])])),m.value?(i(),r("div",A,[...l[1]||(l[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):o.value.length===0?(i(),r("div",E,[...l[2]||(l[2]=[t("strong",null,"No practice yet",-1),t("span",null,"Go to Training and try closing a sale with a persona.",-1)])])):_("",!0),B(t("div",G,[t("h3",I,s(e(a).t("mySessions")),1),(i(!0),r(D,null,S(u.value,d=>(i(),r("div",{key:d.id,style:{display:"flex","justify-content":"space-between",padding:"8px 0","border-bottom":"1px solid var(--border)"}},[t("span",null,s(d.persona_name),1),t("span",{class:T(["badge",d.outcome])},s(d.outcome==="won"?e(a).t("won"):e(a).t("lost")),3)]))),128))],512),[[N,u.value.length]])]))}},O=f(q,[["__scopeId","data-v-6767b6b8"]]);export{O as default}; +import{_ as f,n as x,x as v,a as r,b as t,l as b,u as e,m as k,t as s,i as a,g as _,w as B,z as N,F as D,s as S,r as p,A as c,o as i,B as T}from"./index-BCax2GDh.js";import{L as V}from"./layout-dashboard-Djj77ueX.js";const z={style:{margin:"0"}},L={key:0,class:"row stat-row"},M={class:"card stat"},C={class:"card stat won"},F={class:"card stat lost"},j={class:"card stat"},A={key:1,class:"card"},E={key:2,class:"card empty-state"},G={class:"card",style:{"margin-top":"20px"}},I={style:{"margin-top":"0"}},q={__name:"MyBoard",setup(H){const o=p([]),u=p([]),m=p(!0),y=c(()=>o.value.length),g=c(()=>o.value.filter(n=>n.my_outcome==="won").length),h=c(()=>o.value.filter(n=>n.my_outcome==="lost").length),w=c(()=>o.value.filter(n=>n.my_outcome==="not_tried").length);return x(async()=>{try{o.value=(await v.myBoard()).board||[],u.value=(await v.mySessions()).sessions||[]}finally{m.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",z,[b(e(V),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),k(" "+s(e(a).t("myDashboard")),1)]),l[0]||(l[0]=t("div",{class:"muted",style:{"margin-top":"4px","margin-bottom":"16px"}},"สรุปผลการฝึกของตัวคุณเอง — ดูว่าปิดการขายได้กี่ครั้ง ยังฝึกกับใครบ้าง",-1))]),m.value?_("",!0):(i(),r("div",L,[t("div",M,[t("span",null,s(e(a).t("total")),1),t("strong",null,s(y.value),1)]),t("div",C,[t("span",null,s(e(a).t("won")),1),t("strong",null,s(g.value),1)]),t("div",F,[t("span",null,s(e(a).t("lost")),1),t("strong",null,s(h.value),1)]),t("div",j,[t("span",null,s(e(a).t("notTried")),1),t("strong",null,s(w.value),1)])])),m.value?(i(),r("div",A,[...l[1]||(l[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):o.value.length===0?(i(),r("div",E,[...l[2]||(l[2]=[t("strong",null,"No practice yet",-1),t("span",null,"Go to Training and try closing a sale with a persona.",-1)])])):_("",!0),B(t("div",G,[t("h3",I,s(e(a).t("mySessions")),1),(i(!0),r(D,null,S(u.value,d=>(i(),r("div",{key:d.id,style:{display:"flex","justify-content":"space-between",padding:"8px 0","border-bottom":"1px solid var(--border)"}},[t("span",null,s(d.persona_name),1),t("span",{class:T(["badge",d.outcome])},s(d.outcome==="won"?e(a).t("won"):e(a).t("lost")),3)]))),128))],512),[[N,u.value.length]])]))}},O=f(q,[["__scopeId","data-v-6767b6b8"]]);export{O as default}; diff --git a/frontend/dist/assets/Personas-CGSsKXxA.js b/frontend/dist/assets/Personas-BK-v3zL-.js similarity index 95% rename from frontend/dist/assets/Personas-CGSsKXxA.js rename to frontend/dist/assets/Personas-BK-v3zL-.js index c6a9bb2..9cfcbca 100644 --- a/frontend/dist/assets/Personas-CGSsKXxA.js +++ b/frontend/dist/assets/Personas-BK-v3zL-.js @@ -1 +1 @@ -import{_ as T,n as $,a as r,l as d,p as m,b as t,t as s,u as e,i as n,h as f,m as _,g as w,F as y,s as p,k as L,x as N,y as V,r as b,o as a,S as I,B as h,f as A}from"./index-DOaJiTJw.js";import{T as S}from"./target-QJk_fUDu.js";import{A as F}from"./arrow-left-BUCak2lw.js";const j={class:"row",style:{"align-items":"center"}},D={style:{margin:"0"}},E={class:"muted",style:{"margin-left":"auto"}},M={key:0,class:"card guide"},R={key:1,class:"row",style:{margin:"12px 0",gap:"10px"}},q={class:"primary"},G={class:"grid"},H={class:"row",style:{"justify-content":"space-between"}},J={class:"diff"},K={class:"muted"},O={class:"muted"},Q={class:"muted"},U={class:"muted",style:{"margin-top":"6px"}},W={class:"primary",style:{width:"100%"}},X={class:"primary",style:{width:"100%"}},Y={key:1,class:"muted",style:{"margin-top":"auto","font-size":"12px"}},Z={__name:"Personas",setup(tt){const c=L().params.gid,k=b([]),B=b(!0);async function C(){try{k.value=(await N.listPersonas(c)).personas}finally{B.value=!1}}function z(l){return k.value.filter(i=>i.tier===l)}function P(l){return n.t(l==="A"?"tierA":l==="B"?"tierB":"tierC")}function v(l){return l==="won"?n.t("won"):l==="lost"?n.t("lost"):n.t("notTried")}return $(C),(l,i)=>{const u=V("router-link");return a(),r("div",null,[d(u,{to:"/training",class:"btn-back"},{default:m(()=>[d(e(F),{size:16,"stroke-width":2}),_(" "+s(e(n).t("training")),1)]),_:1}),t("div",j,[t("h2",D,s(e(n).t("personas")),1),t("span",E,s(e(n).t("selectPersona")),1)]),e(f).isAdmin?w("",!0):(a(),r("div",M,[t("strong",null,[d(e(S),{size:17,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),i[0]||(i[0]=_(" วิธีฝึก",-1))]),i[1]||(i[1]=t("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[t("li",null,"เลือกลูกค้าจำลอง (บุคคลต้นแบบ) คนหนึ่งที่อยากฝึกด้วย"),t("li",null,"ระดับ A ง่ายสุด → ระดับ C ยากสุด (ดูจากดาว ★ ความยาก)"),t("li",null,"กดปุ่มสำหรับฝึกแชทกับลูกค้าคนนั้น (ฝึกได้คนละครั้งเท่านั้น)")],-1))])),e(f).isAdmin?(a(),r("div",R,[d(u,{to:`/admin/groups/${e(c)}/edit`},{default:m(()=>[t("button",q,[d(e(I),{size:18,"stroke-width":1.8}),_(" "+s(e(n).t("managePersonas")),1)])]),_:1},8,["to"]),i[2]||(i[2]=t("span",{class:"muted"},"Admin: จัดการรายละเอียดบุคคลต้นแบบได้ที่นี่",-1))])):w("",!0),(a(),r(y,null,p(["A","B","C"],g=>t("div",{key:g,style:{margin:"20px 0"}},[t("h4",null,s(P(g)),1),t("div",G,[(a(!0),r(y,null,p(z(g),o=>(a(),r("div",{key:o.id,class:"card pcard lift"},[t("div",H,[t("strong",null,s(o.name),1),t("span",{class:h(["badge",o.my_outcome])},s(v(o.my_outcome)),3)]),t("div",J,[(a(),r(y,null,p(5,x=>t("span",{key:x,class:h(["star",{on:x<=(o.difficulty||1)}])},"★",2)),64)),t("span",K,s(e(n).t("difficulty"))+" "+s(o.difficulty||1)+"/5",1)]),t("div",O,[_(s(o.profession)+" · "+s(o.age_group)+" · "+s(o.location),1),i[3]||(i[3]=t("br",null,null,-1)),t("span",{class:h(["badge",o.channel])},s(o.channel),3),t("span",Q," · "+s(o.initiation_mode==="seller"?e(n).t("sellerInitiated"):e(n).t("customerInitiated")),1)]),t("div",U,s(o.product_context),1),e(f).isAdmin?(a(),A(u,{key:0,to:`/groups/${e(c)}/chat/${o.id}`,style:{"margin-top":"auto"}},{default:m(()=>[t("button",W,s(e(n).t("chat")),1)]),_:1},8,["to"])):(a(),r(y,{key:1},[o.my_outcome==="not_tried"?(a(),A(u,{key:0,to:`/groups/${e(c)}/chat/${o.id}`,style:{"margin-top":"auto"}},{default:m(()=>[t("button",X,s(e(n).t("chat")),1)]),_:1},8,["to"])):(a(),r("div",Y,"✓ "+s(e(n).t("trained"))+" ("+s(v(o.my_outcome))+")",1))],64))]))),128))])])),64))])}}},at=T(Z,[["__scopeId","data-v-46d50c99"]]);export{at as default}; +import{_ as T,n as $,a as r,l as d,p as m,b as t,t as s,u as e,i as n,h as f,m as _,g as w,F as y,s as p,k as L,x as N,y as V,r as b,o as a,S as I,B as h,f as A}from"./index-BCax2GDh.js";import{T as S}from"./target-BFC5gbdb.js";import{A as F}from"./arrow-left-diPpM4Dj.js";const j={class:"row",style:{"align-items":"center"}},D={style:{margin:"0"}},E={class:"muted",style:{"margin-left":"auto"}},M={key:0,class:"card guide"},R={key:1,class:"row",style:{margin:"12px 0",gap:"10px"}},q={class:"primary"},G={class:"grid"},H={class:"row",style:{"justify-content":"space-between"}},J={class:"diff"},K={class:"muted"},O={class:"muted"},Q={class:"muted"},U={class:"muted",style:{"margin-top":"6px"}},W={class:"primary",style:{width:"100%"}},X={class:"primary",style:{width:"100%"}},Y={key:1,class:"muted",style:{"margin-top":"auto","font-size":"12px"}},Z={__name:"Personas",setup(tt){const c=L().params.gid,k=b([]),B=b(!0);async function C(){try{k.value=(await N.listPersonas(c)).personas}finally{B.value=!1}}function z(l){return k.value.filter(i=>i.tier===l)}function P(l){return n.t(l==="A"?"tierA":l==="B"?"tierB":"tierC")}function v(l){return l==="won"?n.t("won"):l==="lost"?n.t("lost"):n.t("notTried")}return $(C),(l,i)=>{const u=V("router-link");return a(),r("div",null,[d(u,{to:"/training",class:"btn-back"},{default:m(()=>[d(e(F),{size:16,"stroke-width":2}),_(" "+s(e(n).t("training")),1)]),_:1}),t("div",j,[t("h2",D,s(e(n).t("personas")),1),t("span",E,s(e(n).t("selectPersona")),1)]),e(f).isAdmin?w("",!0):(a(),r("div",M,[t("strong",null,[d(e(S),{size:17,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),i[0]||(i[0]=_(" วิธีฝึก",-1))]),i[1]||(i[1]=t("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[t("li",null,"เลือกลูกค้าจำลอง (บุคคลต้นแบบ) คนหนึ่งที่อยากฝึกด้วย"),t("li",null,"ระดับ A ง่ายสุด → ระดับ C ยากสุด (ดูจากดาว ★ ความยาก)"),t("li",null,"กดปุ่มสำหรับฝึกแชทกับลูกค้าคนนั้น (ฝึกได้คนละครั้งเท่านั้น)")],-1))])),e(f).isAdmin?(a(),r("div",R,[d(u,{to:`/admin/groups/${e(c)}/edit`},{default:m(()=>[t("button",q,[d(e(I),{size:18,"stroke-width":1.8}),_(" "+s(e(n).t("managePersonas")),1)])]),_:1},8,["to"]),i[2]||(i[2]=t("span",{class:"muted"},"Admin: จัดการรายละเอียดบุคคลต้นแบบได้ที่นี่",-1))])):w("",!0),(a(),r(y,null,p(["A","B","C"],g=>t("div",{key:g,style:{margin:"20px 0"}},[t("h4",null,s(P(g)),1),t("div",G,[(a(!0),r(y,null,p(z(g),o=>(a(),r("div",{key:o.id,class:"card pcard lift"},[t("div",H,[t("strong",null,s(o.name),1),t("span",{class:h(["badge",o.my_outcome])},s(v(o.my_outcome)),3)]),t("div",J,[(a(),r(y,null,p(5,x=>t("span",{key:x,class:h(["star",{on:x<=(o.difficulty||1)}])},"★",2)),64)),t("span",K,s(e(n).t("difficulty"))+" "+s(o.difficulty||1)+"/5",1)]),t("div",O,[_(s(o.profession)+" · "+s(o.age_group)+" · "+s(o.location),1),i[3]||(i[3]=t("br",null,null,-1)),t("span",{class:h(["badge",o.channel])},s(o.channel),3),t("span",Q," · "+s(o.initiation_mode==="seller"?e(n).t("sellerInitiated"):e(n).t("customerInitiated")),1)]),t("div",U,s(o.product_context),1),e(f).isAdmin?(a(),A(u,{key:0,to:`/groups/${e(c)}/chat/${o.id}`,style:{"margin-top":"auto"}},{default:m(()=>[t("button",W,s(e(n).t("chat")),1)]),_:1},8,["to"])):(a(),r(y,{key:1},[o.my_outcome==="not_tried"?(a(),A(u,{key:0,to:`/groups/${e(c)}/chat/${o.id}`,style:{"margin-top":"auto"}},{default:m(()=>[t("button",X,s(e(n).t("chat")),1)]),_:1},8,["to"])):(a(),r("div",Y,"✓ "+s(e(n).t("trained"))+" ("+s(v(o.my_outcome))+")",1))],64))]))),128))])])),64))])}}},at=T(Z,[["__scopeId","data-v-46d50c99"]]);export{at as default}; diff --git a/frontend/dist/assets/Settings-CVA3uaSz.js b/frontend/dist/assets/Settings-k8fzlPYr.js similarity index 98% rename from frontend/dist/assets/Settings-CVA3uaSz.js rename to frontend/dist/assets/Settings-k8fzlPYr.js index f138647..41415b2 100644 --- a/frontend/dist/assets/Settings-CVA3uaSz.js +++ b/frontend/dist/assets/Settings-k8fzlPYr.js @@ -1,4 +1,4 @@ -import{c as M,_ as T,r as c,h as u,a as h,b as e,l as k,u as a,S as $,m as v,t as o,i as s,w as b,v as x,g as V,B as S,x as B,o as g}from"./index-DOaJiTJw.js";import{L as D}from"./lock-WqnzR3ED.js";/** +import{c as M,_ as T,r as c,h as u,a as h,b as e,l as k,u as a,S as $,m as v,t as o,i as s,w as b,v as x,g as V,B as S,x as B,o as g}from"./index-BCax2GDh.js";import{L as D}from"./lock-uERjsrGr.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/Setup-C_0Y0xY8.js b/frontend/dist/assets/Setup-B6q98ArM.js similarity index 93% rename from frontend/dist/assets/Setup-C_0Y0xY8.js rename to frontend/dist/assets/Setup-B6q98ArM.js index 928dda8..5e6668b 100644 --- a/frontend/dist/assets/Setup-C_0Y0xY8.js +++ b/frontend/dist/assets/Setup-B6q98ArM.js @@ -1 +1 @@ -import{_ as V,a as v,b as e,l as S,u as s,m as g,t,i as a,h as w,w as _,v as y,d as f,g as K,r,j as N,o as m}from"./index-DOaJiTJw.js";import{L as T}from"./lock-WqnzR3ED.js";const B={class:"setup-wrap"},U={class:"card setup-card"},C={class:"muted"},D={key:0,class:"error",role:"alert"},L=["disabled"],M={key:0,class:"spinner"},P={key:1},j={__name:"Setup",setup(z){const x=N(),i=r(""),l=r(""),p=r(""),o=r(""),d=r(!1);async function c(){if(o.value="",l.value.length<4){o.value=a.t("passwordTooShort");return}if(l.value!==p.value){o.value=a.t("passwordMismatch");return}d.value=!0;try{await w.finishSetup(i.value.trim(),l.value),x.push("/")}catch(h){o.value=h.message}finally{d.value=!1}}return(h,u)=>{var b,k;return m(),v("div",B,[e("div",U,[e("h1",null,[S(s(T),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),g(" "+t(s(a).t("setupTitle")),1)]),e("p",C,[g(t(s(a).t("setupSubtitle"))+" ",1),e("strong",null,t(((b=s(w).user)==null?void 0:b.name)||((k=s(w).user)==null?void 0:k.username)),1)]),e("label",null,t(s(a).t("email")),1),_(e("input",{"onUpdate:modelValue":u[0]||(u[0]=n=>i.value=n),type:"email",autocomplete:"email",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,i.value]]),e("label",null,t(s(a).t("newPassword")),1),_(e("input",{"onUpdate:modelValue":u[1]||(u[1]=n=>l.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,l.value]]),e("label",null,t(s(a).t("confirmPassword")),1),_(e("input",{"onUpdate:modelValue":u[2]||(u[2]=n=>p.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,p.value]]),o.value?(m(),v("div",D,t(o.value),1)):K("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!l.value||l.value!==p.value,onClick:c},[d.value?(m(),v("span",M)):(m(),v("span",P,t(s(a).t("save")),1))],8,L)])])}}},R=V(j,[["__scopeId","data-v-477fc775"]]);export{R as default}; +import{_ as V,a as v,b as e,l as S,u as s,m as g,t,i as a,h as w,w as _,v as y,d as f,g as K,r,j as N,o as m}from"./index-BCax2GDh.js";import{L as T}from"./lock-uERjsrGr.js";const B={class:"setup-wrap"},U={class:"card setup-card"},C={class:"muted"},D={key:0,class:"error",role:"alert"},L=["disabled"],M={key:0,class:"spinner"},P={key:1},j={__name:"Setup",setup(z){const x=N(),i=r(""),l=r(""),p=r(""),o=r(""),d=r(!1);async function c(){if(o.value="",l.value.length<4){o.value=a.t("passwordTooShort");return}if(l.value!==p.value){o.value=a.t("passwordMismatch");return}d.value=!0;try{await w.finishSetup(i.value.trim(),l.value),x.push("/")}catch(h){o.value=h.message}finally{d.value=!1}}return(h,u)=>{var b,k;return m(),v("div",B,[e("div",U,[e("h1",null,[S(s(T),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),g(" "+t(s(a).t("setupTitle")),1)]),e("p",C,[g(t(s(a).t("setupSubtitle"))+" ",1),e("strong",null,t(((b=s(w).user)==null?void 0:b.name)||((k=s(w).user)==null?void 0:k.username)),1)]),e("label",null,t(s(a).t("email")),1),_(e("input",{"onUpdate:modelValue":u[0]||(u[0]=n=>i.value=n),type:"email",autocomplete:"email",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,i.value]]),e("label",null,t(s(a).t("newPassword")),1),_(e("input",{"onUpdate:modelValue":u[1]||(u[1]=n=>l.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,l.value]]),e("label",null,t(s(a).t("confirmPassword")),1),_(e("input",{"onUpdate:modelValue":u[2]||(u[2]=n=>p.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:f(c,["enter"])},null,544),[[y,p.value]]),o.value?(m(),v("div",D,t(o.value),1)):K("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!l.value||l.value!==p.value,onClick:c},[d.value?(m(),v("span",M)):(m(),v("span",P,t(s(a).t("save")),1))],8,L)])])}}},R=V(j,[["__scopeId","data-v-477fc775"]]);export{R as default}; diff --git a/frontend/dist/assets/Training-EZ9n5Yut.js b/frontend/dist/assets/Training-C9KC4ygo.js similarity index 93% rename from frontend/dist/assets/Training-EZ9n5Yut.js rename to frontend/dist/assets/Training-C9KC4ygo.js index f151f79..0f3a473 100644 --- a/frontend/dist/assets/Training-EZ9n5Yut.js +++ b/frontend/dist/assets/Training-C9KC4ygo.js @@ -1 +1 @@ -import{_ as w,n as b,x as B,h as d,a as l,b as t,l as c,u as s,m as _,t as e,i as n,p as m,f as h,g as f,F as C,s as A,r as g,y as N,o as i,B as k}from"./index-DOaJiTJw.js";import{T as P}from"./target-QJk_fUDu.js";import{B as T}from"./book-open-Ckxqlz0w.js";import{P as z}from"./plus-CODydpxS.js";const V={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},F={style:{margin:"0"}},L={class:"row",style:{"margin-left":"auto",gap:"10px"}},S={class:"soft"},$={class:"primary"},j={class:"muted"},D={key:0,class:"card",style:{"min-height":"80px"}},E={key:1,class:"card empty-state"},G={key:0},I={key:1},M={class:"grid"},O={class:"card lift train-card"},q={class:"row",style:{"justify-content":"space-between"}},H={class:"muted",style:{margin:"6px 0 12px"}},J={class:"row",style:{gap:"8px"}},K={class:"muted"},Q={class:"primary",style:{width:"100%","margin-top":"12px"}},R={__name:"Training",setup(U){const u=g([]),y=g(!0);function v(a){return a.sales_kit&&a.sales_kit.productName||a.input&&a.input.product||""}function x(a){return a.personas||a.persona_count||0}return b(async()=>{try{const a=(await B.listGroups()).groups||[];u.value=d.isAdmin?a:a.filter(r=>r.status==="ready")}finally{y.value=!1}}),(a,r)=>{const p=N("router-link");return i(),l("div",null,[t("div",V,[t("h2",F,[c(s(P),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),_(" "+e(s(n).t("training")),1)]),t("div",L,[c(p,{to:"/guide"},{default:m(()=>[t("button",S,[c(s(T),{size:18,"stroke-width":1.8}),r[0]||(r[0]=_(" คู่มือ",-1))])]),_:1}),s(d).isAdmin?(i(),h(p,{key:0,to:"/admin/new-group"},{default:m(()=>[t("button",$,[c(s(z),{size:18,"stroke-width":2}),_(" "+e(s(n).t("addProduct")),1)])]),_:1})):f("",!0)])]),t("p",j,e(s(n).t("trainingSubtitle")),1),y.value?(i(),l("div",D,[...r[1]||(r[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):u.value.length===0?(i(),l("div",E,[t("strong",null,e(s(n).t("noTraining")),1),s(d).isAdmin?(i(),l("span",G,'Click "'+e(s(n).t("addProduct"))+'" to create a persona group.',1)):(i(),l("span",I,"Ask an admin to create a persona group first."))])):f("",!0),t("div",M,[(i(!0),l(C,null,A(u.value,o=>(i(),h(p,{key:o.id,to:s(d).isAdmin?`/admin/groups/${o.id}/edit`:`/groups/${o.id}/personas`,style:{"text-decoration":"none"}},{default:m(()=>[t("div",O,[t("div",q,[t("strong",null,e(o.title),1),t("span",{class:k(["badge",o.status==="ready"?"ready":"draft"])},e(o.status),3)]),t("div",H,e(v(o)),1),t("div",J,[t("span",{class:k(["badge",o.channel||"line"])},e(o.channel||"line"),3),t("span",K,e(x(o))+" "+e(s(n).t("personas").toLowerCase()),1)]),t("button",Q,e(s(d).isAdmin?o.status==="ready"?s(n).t("managePersonas"):s(n).t("analyze"):s(n).t("selectPersona")),1)])]),_:2},1032,["to"]))),128))])])}}},tt=w(R,[["__scopeId","data-v-e39df241"]]);export{tt as default}; +import{_ as w,n as b,x as B,h as d,a as l,b as t,l as c,u as s,m as _,t as e,i as n,p as m,f as h,g as f,F as C,s as A,r as g,y as N,o as i,B as k}from"./index-BCax2GDh.js";import{T as P}from"./target-BFC5gbdb.js";import{B as T}from"./book-open-C7SxBVkN.js";import{P as z}from"./plus-B9zMVgv0.js";const V={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},F={style:{margin:"0"}},L={class:"row",style:{"margin-left":"auto",gap:"10px"}},S={class:"soft"},$={class:"primary"},j={class:"muted"},D={key:0,class:"card",style:{"min-height":"80px"}},E={key:1,class:"card empty-state"},G={key:0},I={key:1},M={class:"grid"},O={class:"card lift train-card"},q={class:"row",style:{"justify-content":"space-between"}},H={class:"muted",style:{margin:"6px 0 12px"}},J={class:"row",style:{gap:"8px"}},K={class:"muted"},Q={class:"primary",style:{width:"100%","margin-top":"12px"}},R={__name:"Training",setup(U){const u=g([]),y=g(!0);function v(a){return a.sales_kit&&a.sales_kit.productName||a.input&&a.input.product||""}function x(a){return a.personas||a.persona_count||0}return b(async()=>{try{const a=(await B.listGroups()).groups||[];u.value=d.isAdmin?a:a.filter(r=>r.status==="ready")}finally{y.value=!1}}),(a,r)=>{const p=N("router-link");return i(),l("div",null,[t("div",V,[t("h2",F,[c(s(P),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),_(" "+e(s(n).t("training")),1)]),t("div",L,[c(p,{to:"/guide"},{default:m(()=>[t("button",S,[c(s(T),{size:18,"stroke-width":1.8}),r[0]||(r[0]=_(" คู่มือ",-1))])]),_:1}),s(d).isAdmin?(i(),h(p,{key:0,to:"/admin/new-group"},{default:m(()=>[t("button",$,[c(s(z),{size:18,"stroke-width":2}),_(" "+e(s(n).t("addProduct")),1)])]),_:1})):f("",!0)])]),t("p",j,e(s(n).t("trainingSubtitle")),1),y.value?(i(),l("div",D,[...r[1]||(r[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):u.value.length===0?(i(),l("div",E,[t("strong",null,e(s(n).t("noTraining")),1),s(d).isAdmin?(i(),l("span",G,'Click "'+e(s(n).t("addProduct"))+'" to create a persona group.',1)):(i(),l("span",I,"Ask an admin to create a persona group first."))])):f("",!0),t("div",M,[(i(!0),l(C,null,A(u.value,o=>(i(),h(p,{key:o.id,to:s(d).isAdmin?`/admin/groups/${o.id}/edit`:`/groups/${o.id}/personas`,style:{"text-decoration":"none"}},{default:m(()=>[t("div",O,[t("div",q,[t("strong",null,e(o.title),1),t("span",{class:k(["badge",o.status==="ready"?"ready":"draft"])},e(o.status),3)]),t("div",H,e(v(o)),1),t("div",J,[t("span",{class:k(["badge",o.channel||"line"])},e(o.channel||"line"),3),t("span",K,e(x(o))+" "+e(s(n).t("personas").toLowerCase()),1)]),t("button",Q,e(s(d).isAdmin?o.status==="ready"?s(n).t("managePersonas"):s(n).t("analyze"):s(n).t("selectPersona")),1)])]),_:2},1032,["to"]))),128))])])}}},tt=w(R,[["__scopeId","data-v-e39df241"]]);export{tt as default}; diff --git a/frontend/dist/assets/arrow-left-BUCak2lw.js b/frontend/dist/assets/arrow-left-diPpM4Dj.js similarity index 86% rename from frontend/dist/assets/arrow-left-BUCak2lw.js rename to frontend/dist/assets/arrow-left-diPpM4Dj.js index 531d7f9..b8a42ac 100644 --- a/frontend/dist/assets/arrow-left-BUCak2lw.js +++ b/frontend/dist/assets/arrow-left-diPpM4Dj.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-DOaJiTJw.js";/** +import{c as e}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/book-open-Ckxqlz0w.js b/frontend/dist/assets/book-open-C7SxBVkN.js similarity index 90% rename from frontend/dist/assets/book-open-Ckxqlz0w.js rename to frontend/dist/assets/book-open-C7SxBVkN.js index 18292a6..735b650 100644 --- a/frontend/dist/assets/book-open-Ckxqlz0w.js +++ b/frontend/dist/assets/book-open-C7SxBVkN.js @@ -1,4 +1,4 @@ -import{c as a}from"./index-DOaJiTJw.js";/** +import{c as a}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/index-DOaJiTJw.js b/frontend/dist/assets/index-BCax2GDh.js similarity index 87% rename from frontend/dist/assets/index-DOaJiTJw.js rename to frontend/dist/assets/index-BCax2GDh.js index a39322b..5360221 100644 --- a/frontend/dist/assets/index-DOaJiTJw.js +++ b/frontend/dist/assets/index-BCax2GDh.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Login-DxHA8GYa.js","assets/Login-j4sHK_z1.css","assets/Setup-C_0Y0xY8.js","assets/lock-WqnzR3ED.js","assets/Setup-Cy1qc0PW.css","assets/Analytics-Bkjt0mFt.js","assets/users-SQMCzZpr.js","assets/plus-CODydpxS.js","assets/Analytics-CQgwlaXy.css","assets/MyBoard-CTHsz6OL.js","assets/layout-dashboard-CmUmS5d8.js","assets/MyBoard-B7ypQ1JT.css","assets/Training-EZ9n5Yut.js","assets/target-QJk_fUDu.js","assets/book-open-Ckxqlz0w.js","assets/Training-BYBkfggr.css","assets/Personas-CGSsKXxA.js","assets/arrow-left-BUCak2lw.js","assets/Personas-CskAUbZv.css","assets/Chat-DszzTVNw.js","assets/Chat-Dhsij-Zk.css","assets/Settings-CVA3uaSz.js","assets/Settings-BCN2EZQ5.css","assets/Guide-GzVXirX-.js","assets/sparkles-CwNig9QR.js","assets/GroupBuilder-CNxYYLnR.js","assets/GroupBuilder-CpbMByEC.css","assets/GroupEdit-Bq-uo5sC.js","assets/GroupEdit-D9lmggXK.css","assets/AdminUsers-B8OP5hvL.js","assets/AdminUsers-B0sl0uHS.css"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Login-BFjXrLpo.js","assets/Login-j4sHK_z1.css","assets/Setup-B6q98ArM.js","assets/lock-uERjsrGr.js","assets/Setup-Cy1qc0PW.css","assets/Analytics-DzK25pzJ.js","assets/users-NPxa3eQ_.js","assets/plus-B9zMVgv0.js","assets/Analytics-CQgwlaXy.css","assets/MyBoard-wXpWmFYg.js","assets/layout-dashboard-Djj77ueX.js","assets/MyBoard-B7ypQ1JT.css","assets/Training-C9KC4ygo.js","assets/target-BFC5gbdb.js","assets/book-open-C7SxBVkN.js","assets/Training-BYBkfggr.css","assets/Personas-BK-v3zL-.js","assets/arrow-left-diPpM4Dj.js","assets/Personas-CskAUbZv.css","assets/Chat-B4cA4NlX.js","assets/Chat-Dhsij-Zk.css","assets/Settings-k8fzlPYr.js","assets/Settings-BCN2EZQ5.css","assets/Guide-Bx9T4hBF.js","assets/sparkles-E3Xd6eY7.js","assets/GroupBuilder-DpQWbscg.js","assets/GroupBuilder-CpbMByEC.css","assets/GroupEdit-PGVs7eZc.js","assets/GroupEdit-CFwXUOV9.css","assets/AdminUsers-BXR4J5iM.js","assets/AdminUsers-B0sl0uHS.css"])))=>i.map(i=>d[i]); (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))s(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&s(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function s(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();/** * @vue/shared v3.5.41 * (c) 2018-present Yuxi (Evan) You and Vue contributors @@ -79,4 +79,4 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Login-DxHA8GYa. * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Eu=No("settings",[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",key:"1i5ecw"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]),Bs="st_token";function Mo(){return localStorage.getItem(Bs)}function ws(e){e?localStorage.setItem(Bs,e):localStorage.removeItem(Bs)}async function ee(e,t,n,s=!1){const r={},i=Mo();i&&(r.Authorization=`Bearer ${i}`);let o=n;!s&&n!==void 0&&n!==null&&(r["Content-Type"]="application/json",o=JSON.stringify(n));const l=await fetch(t,{method:e,headers:r,body:o});let c=null;try{c=await l.json()}catch{}if(!l.ok){const f=c&&(c.error||c.message)||`HTTP ${l.status}`;throw new Error(f)}return c}const Ss={login:(e,t)=>ee("POST","/api/auth/login",{username:e,password:t}),me:()=>ee("GET","/api/auth/me"),setup:e=>ee("POST","/api/auth/setup",e),updateProfile:e=>ee("PATCH","/api/auth/profile",e),adminCreateUser:e=>ee("POST","/api/admin/users",e),adminListUsers:()=>ee("GET","/api/admin/users"),adminUpdateUser:(e,t)=>ee("PUT",`/api/admin/users/${e}`,t),createGroup:e=>ee("POST","/api/groups",e,!0),listGroups:()=>ee("GET","/api/groups"),getGroup:e=>ee("GET",`/api/groups/${e}`),analyzeGroup:e=>ee("POST",`/api/groups/${e}/analyze`),listPersonas:e=>ee("GET",`/api/groups/${e}/personas`),getPersona:(e,t)=>ee("GET",`/api/groups/${e}/personas/${t}`),updatePersona:(e,t,n)=>ee("PUT",`/api/groups/${e}/personas/${t}`,n),chatStart:(e,t,n,s)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/start`,{scenario:n,locale:s}),chatResume:(e,t)=>ee("GET",`/api/chat/${e}/personas/${t}/chat/resume`),chatSend:(e,t,n)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/send`,{text:n}),chatFinish:(e,t)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/finish`),mySessions:()=>ee("GET","/api/chat/sessions"),myBoard:()=>ee("GET","/api/me/board"),weakAreas:()=>ee("GET","/api/me/weak-areas"),myPersonas:()=>ee("GET","/api/me/personas"),generatePersona:e=>ee("POST","/api/me/personas/generate",e),analytics:(e={})=>{const t=new URLSearchParams;e.from&&t.set("from",e.from),e.to&&t.set("to",e.to);const n=t.toString();return ee("GET",`/api/analytics${n?`?${n}`:""}`)}},Ce=Jt({user:null,token:Mo(),mustSetup:!1,get role(){return this.user?this.user.role:null},get isAdmin(){return this.role==="admin"||this.role==="super_admin"},async load(){var e;if(!this.token)return null;try{const t=await Ss.me();return this.user=t.user,this.mustSetup=!!((e=t.user)!=null&&e.must_setup),this.user}catch{return this.user=null,this.mustSetup=!1,ws(null),null}},async login(e,t){const n=await Ss.login(e,t);return this.token=n.token,ws(n.token),this.user=n.user,this.mustSetup=!!n.must_setup,n.user},async finishSetup(e,t){const n=await Ss.setup({username:this.user.username||this.user.id,email:e,password:t});return this.user=n.user,this.mustSetup=!1,n.user},logout(){this.user=null,this.token=null,this.mustSetup=!1,ws(null)}}),As={en:{app:"Sales Trainer",login:"Login",logout:"Logout",username:"Username",email:"Email",password:"Password",newPassword:"New password",confirmPassword:"Confirm password",save:"Save",passwordTooShort:"Password must be at least 4 characters",passwordMismatch:"Passwords do not match",setupTitle:"Set up your account",setupSubtitle:"First login for ",loginError:"Invalid credentials",dashboard:"Dashboard",training:"Training",myDashboard:"My dashboard",adminOverview:"Admin overview",guideTitle:"How to use / คู่มือใช้งาน",settings:"Settings",account:"Account",profile:"Profile",readonly:"read-only",name:"Display name",saveProfile:"Save profile",changePassword:"Change password",optional:"optional",saved:"Saved",total:"Total",dateRange:"Date range",apply:"Apply",clear:"Clear",hardestPersonas:"Hardest personas",closeRate:"Close rate",managePersonas:"Manage personas",difficulty:"Difficulty",trained:"Trained",addProduct:"Add product",trainingSubtitle:"Pick a product group, then choose a persona to practice closing the sale.",noTraining:"No training groups available",groups:"Persona Groups",myTraining:"My Training",adminTools:"Admin Tools",users:"Users",analytics:"Analytics",groupBuilder:"Group Builder",create:"Create",analyze:"Analyze",manual:"Manual",edit:"Edit",product:"Product",segment:"Initial customer segment (optional)",description:"Additional description / scenario (optional)",channel:"Channel",facebook:"Facebook",line:"LINE",language:"Language",thai:"Thai",english:"English",personas:"Personas",tierA:"Tier A — Ready to buy",tierB:"Tier B — Unsure",tierC:"Tier C — Not interested but has pain",selectPersona:"Select a persona to practice",chooseScenario:"Choose a scenario",chatGuideTitle:"How to practice",chatGuideText:'You are playing the salesperson. Chat with this customer and try to close the sale. Ask about their needs, solve their pain, and handle their objections. When you are done, press "Finish & get result" to see your score and coaching.',chat:"Chat",start:"Start",send:"Send",finish:"Finish & get result",debrief:"Result & Coaching",won:"Won",lost:"Lost",notTried:"Not tried",score:"Score",pain:"Pain",why:"Reason",reveal:"Revealed persona details",generatePersona:"Generate my persona",weakAreas:"My weak areas",mySessions:"My sessions",openSaleTask:"The customer did NOT message first. You must open the sale.",sellerInitiated:"You must open the sale (outbound)",customerInitiated:"The customer will message you first"},th:{app:"ระบบฝึกทักษะการขาย",login:"เข้าสู่ระบบ",logout:"ออกจากระบบ",username:"ชื่อผู้ใช้",email:"อีเมล",password:"รหัสผ่าน",newPassword:"รหัสผ่านใหม่",confirmPassword:"ยืนยันรหัสผ่าน",save:"บันทึก",passwordTooShort:"รหัสผ่านต้องมีความยาวอย่างน้อย 4 ตัวอักษร",passwordMismatch:"รหัสผ่านที่ยืนยันไม่ตรงกัน",setupTitle:"ตั้งค่าบัญชีของคุณ",setupSubtitle:"เข้าสู่ระบบครั้งแรกสำหรับ ",loginError:"ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง กรุณาลองอีกครั้ง",dashboard:"หน้าหลัก",training:"การฝึก",myDashboard:"ภาพรวมผลการฝึก",adminOverview:"ภาพรวมผู้ดูแล",settings:"การตั้งค่า",account:"บัญชี",profile:"ข้อมูลส่วนตัว",readonly:"อ่านอย่างเดียว",name:"ชื่อที่แสดง",saveProfile:"บันทึกข้อมูล",changePassword:"เปลี่ยนรหัสผ่าน",optional:"ไม่บังคับ",saved:"บันทึกเรียบร้อย",total:"ทั้งหมด",dateRange:"ช่วงเวลา",apply:"กรอง",clear:"ล้าง",hardestPersonas:"บุคคลต้นแบบที่ขายยากที่สุด",closeRate:"อัตราปิดการขาย",managePersonas:"จัดการบุคคลต้นแบบ",difficulty:"ระดับความยาก",trained:"ฝึกแล้ว",addProduct:"เพิ่มสินค้า",trainingSubtitle:"เลือกกลุ่มสินค้า แล้วเลือกบุคคลต้นแบบเพื่อฝึกปิดการขาย",noTraining:"ยังไม่มีกลุ่มฝึก",groups:"กลุ่มบุคคลต้นแบบลูกค้า (Persona)",myTraining:"ประวัติการฝึก",adminTools:"เครื่องมือผู้ดูแลระบบ",users:"ผู้ใช้งาน",analytics:"สถิติ",groupBuilder:"สร้างกลุ่มบุคคลต้นแบบ",create:"สร้าง",analyze:"วิเคราะห์",manual:"ระบุเอง",edit:"แก้ไข",product:"สินค้า/บริการ",segment:"กลุ่มลูกค้าเป้าหมายเบื้องต้น (ไม่บังคับ)",description:"คำอธิบายหรือรายละเอียดเพิ่มเติม (ไม่บังคับ)",channel:"ช่องทางติดต่อ",facebook:"Facebook",line:"LINE",language:"ภาษา",thai:"ไทย",english:"อังกฤษ",personas:"บุคคลต้นแบบ",tierA:"ระดับ A — พร้อมตัดสินใจซื้อ",tierB:"ระดับ B — ยังไม่แน่ใจ",tierC:"ระดับ C — ไม่สนใจแต่มีปัญหา",selectPersona:"เลือกบุคคลต้นแบบเพื่อฝึก",chooseScenario:"เลือกสถานการณ์",chatGuideTitle:"วิธีฝึก",chatGuideText:'คุณรับบทเป็นพนักงานขาย พูดคุยกับลูกค้าคนนี้เพื่อพยายามปิดการขายให้ได้ สอบถามความต้องการ แก้ไขปัญหาของลูกค้า และรับมือกับข้อโต้แย้ง เมื่อพอใจแล้วกด "สรุปผล" เพื่อดูคะแนนและข้อเสนอแนะ',chat:"แชท",start:"เริ่มต้น",send:"ส่ง",finish:"สรุปผล",debrief:"ผลลัพธ์และข้อเสนอแนะ",won:"ปิดการขายได้",lost:"ปิดการขายไม่ได้",notTried:"ยังไม่ได้ฝึก",score:"คะแนน",pain:"ปัญหาของลูกค้า",why:"เหตุผล",reveal:"รายละเอียดบุคคลต้นแบบที่ถูกซ่อนไว้",generatePersona:"สร้างบุคคลต้นแบบเพิ่มเติม",weakAreas:"จุดอ่อนที่ควรฝึกเพิ่มเติม",mySessions:"ประวัติการฝึกของฉัน",openSaleTask:"ลูกค้ายังไม่ได้ทักเข้ามา คุณต้องเป็นฝ่ายเริ่มบทสนทนาการขายเอง",sellerInitiated:"คุณต้องเริ่มการขายในเชิงรุก",customerInitiated:"ลูกค้าจะติดต่อเข้ามาก่อน"}},Ke=Jt({locale:localStorage.getItem("locale")||"th",t(e){return As[this.locale]&&As[this.locale][e]||As.en[e]||e},set(e){this.locale=e,localStorage.setItem("locale",e)}}),wu=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},Su={class:"app"},Au={key:0,class:"topnav"},Ru={class:"tabs"},xu={class:"nav-right"},Tu={class:"txt"},Cu={class:"main"},Pu={__name:"App",setup(e){const t=pu();function n(){Ke.set(Ke.locale==="th"?"en":"th")}function s(){Ce.logout(),t.push("/login")}return Wi(()=>{Ce.token&&!Ce.user&&Ce.load()}),(r,i)=>{const o=_r("router-link"),l=_r("router-view");return on(),Tr("div",Su,[ae(Ce).user?(on(),Tr("nav",Au,[ie(o,{to:"/",class:"brand"},{default:Vt(()=>[Ft(Rt(ae(Ke).t("app")),1)]),_:1}),mt("div",Ru,[ae(Ce).isAdmin?(on(),Ds(o,{key:0,to:"/",class:jt(["tab",{active:r.$route.path==="/"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("adminOverview")),1)]),_:1},8,["class"])):Cr("",!0),ie(o,{to:"/my/board",class:jt(["tab",{active:r.$route.path==="/my/board"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("myDashboard")),1)]),_:1},8,["class"]),ie(o,{to:"/training",class:jt(["tab",{active:r.$route.path==="/training"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("training")),1)]),_:1},8,["class"])]),mt("div",xu,[mt("button",{onClick:n,class:"lang"},Rt(ae(Ke).locale==="th"?"EN":"TH"),1),ie(o,{to:"/settings",class:"icon-btn",title:ae(Ke).t("settings")},{default:Vt(()=>[ie(ae(Eu),{size:20,"stroke-width":1.8})]),_:1},8,["title"]),mt("button",{onClick:s,class:"logout-btn"},[ie(ae(bu),{size:18,"stroke-width":1.8}),i[0]||(i[0]=Ft()),mt("span",Tu,Rt(ae(Ke).t("logout")),1)])])])):Cr("",!0),mt("main",Cu,[(on(),Ds(l,{key:ae(Ke).locale}))])])}}},Ou=wu(Pu,[["__scopeId","data-v-f29a3a4f"]]),Iu="modulepreload",Nu=function(e){return"/"+e},ai={},Se=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));r=Promise.allSettled(n.map(c=>{if(c=Nu(c),c in ai)return;ai[c]=!0;const f=c.endsWith(".css"),u=f?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${u}`))return;const h=document.createElement("link");if(h.rel=f?"stylesheet":Iu,f||(h.as="script"),h.crossOrigin="",h.href=c,l&&h.setAttribute("nonce",l),document.head.appendChild(h),f)return new Promise((g,m)=>{h.addEventListener("load",g),h.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${c}`)))})}))}function i(o){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o}return r.then(o=>{for(const l of o||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})},Mu=[{path:"/login",component:()=>Se(()=>import("./Login-DxHA8GYa.js"),__vite__mapDeps([0,1])),meta:{public:!0}},{path:"/setup",component:()=>Se(()=>import("./Setup-C_0Y0xY8.js"),__vite__mapDeps([2,3,4]))},{path:"/",component:()=>Se(()=>import("./Analytics-Bkjt0mFt.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}},{path:"/my/board",component:()=>Se(()=>import("./MyBoard-CTHsz6OL.js"),__vite__mapDeps([9,10,11]))},{path:"/training",component:()=>Se(()=>import("./Training-EZ9n5Yut.js"),__vite__mapDeps([12,13,14,7,15]))},{path:"/groups/:gid/personas",component:()=>Se(()=>import("./Personas-CGSsKXxA.js"),__vite__mapDeps([16,13,17,18]))},{path:"/groups/:gid/chat/:pid",component:()=>Se(()=>import("./Chat-DszzTVNw.js"),__vite__mapDeps([19,13,17,20]))},{path:"/settings",component:()=>Se(()=>import("./Settings-CVA3uaSz.js"),__vite__mapDeps([21,3,22]))},{path:"/guide",component:()=>Se(()=>import("./Guide-GzVXirX-.js"),__vite__mapDeps([23,14,10,13,24]))},{path:"/admin/new-group",component:()=>Se(()=>import("./GroupBuilder-CNxYYLnR.js"),__vite__mapDeps([25,17,26])),meta:{admin:!0}},{path:"/admin/groups/:gid/edit",component:()=>Se(()=>import("./GroupEdit-Bq-uo5sC.js"),__vite__mapDeps([27,6,24,28])),meta:{admin:!0}},{path:"/admin/users",component:()=>Se(()=>import("./AdminUsers-B8OP5hvL.js"),__vite__mapDeps([29,6,30])),meta:{admin:!0}},{path:"/admin/analytics",component:()=>Se(()=>import("./Analytics-Bkjt0mFt.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}}],Do=hu({history:$a(),routes:Mu});Do.beforeEach(async e=>e.meta.public?!0:(Ce.user||await Ce.load(),Ce.user?Ce.mustSetup&&e.path!=="/setup"?{path:"/setup"}:e.path==="/"&&!Ce.isAdmin?{path:"/my/board"}:e.meta.admin&&!Ce.isAdmin?{path:"/my/board"}:!0:{path:"/login",query:{redirect:e.fullPath}}));ia(Ou).use(Do).mount("#app");export{Oe as A,jt as B,Zs as C,Vu as D,Xc as E,tt as F,Mn as G,Jt as H,Uu as I,Eu as S,wu as _,Tr as a,mt as b,No as c,Bu as d,Hu as e,Ds as f,Cr as g,Ce as h,Ke as i,pu as j,ju as k,ie as l,Ft as m,Wi as n,on as o,Vt as p,Ks as q,dl as r,Lu as s,Rt as t,ae as u,jr as v,Du as w,Ss as x,_r as y,Fu as z}; + */const Eu=No("settings",[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",key:"1i5ecw"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]),Bs="st_token";function Mo(){return localStorage.getItem(Bs)}function ws(e){e?localStorage.setItem(Bs,e):localStorage.removeItem(Bs)}async function ee(e,t,n,s=!1){const r={},i=Mo();i&&(r.Authorization=`Bearer ${i}`);let o=n;!s&&n!==void 0&&n!==null&&(r["Content-Type"]="application/json",o=JSON.stringify(n));const l=await fetch(t,{method:e,headers:r,body:o});let c=null;try{c=await l.json()}catch{}if(!l.ok){const f=c&&(c.error||c.message)||`HTTP ${l.status}`;throw new Error(f)}return c}const Ss={login:(e,t)=>ee("POST","/api/auth/login",{username:e,password:t}),me:()=>ee("GET","/api/auth/me"),setup:e=>ee("POST","/api/auth/setup",e),updateProfile:e=>ee("PATCH","/api/auth/profile",e),adminCreateUser:e=>ee("POST","/api/admin/users",e),adminListUsers:()=>ee("GET","/api/admin/users"),adminUpdateUser:(e,t)=>ee("PUT",`/api/admin/users/${e}`,t),createGroup:e=>ee("POST","/api/groups",e,!0),listGroups:()=>ee("GET","/api/groups"),getGroup:e=>ee("GET",`/api/groups/${e}`),analyzeGroup:e=>ee("POST",`/api/groups/${e}/analyze`),listPersonas:e=>ee("GET",`/api/groups/${e}/personas`),getPersona:(e,t)=>ee("GET",`/api/groups/${e}/personas/${t}`),updatePersona:(e,t,n)=>ee("PUT",`/api/groups/${e}/personas/${t}`,n),chatStart:(e,t,n,s)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/start`,{scenario:n,locale:s}),chatResume:(e,t)=>ee("GET",`/api/chat/${e}/personas/${t}/chat/resume`),chatSend:(e,t,n)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/send`,{text:n}),chatFinish:(e,t)=>ee("POST",`/api/chat/${e}/personas/${t}/chat/finish`),mySessions:()=>ee("GET","/api/chat/sessions"),myBoard:()=>ee("GET","/api/me/board"),weakAreas:()=>ee("GET","/api/me/weak-areas"),myPersonas:()=>ee("GET","/api/me/personas"),generatePersona:e=>ee("POST","/api/me/personas/generate",e),analytics:(e={})=>{const t=new URLSearchParams;e.from&&t.set("from",e.from),e.to&&t.set("to",e.to);const n=t.toString();return ee("GET",`/api/analytics${n?`?${n}`:""}`)}},Ce=Jt({user:null,token:Mo(),mustSetup:!1,get role(){return this.user?this.user.role:null},get isAdmin(){return this.role==="admin"||this.role==="super_admin"},get isSuperAdmin(){return this.role==="super_admin"},async load(){var e;if(!this.token)return null;try{const t=await Ss.me();return this.user=t.user,this.mustSetup=!!((e=t.user)!=null&&e.must_setup),this.user}catch{return this.user=null,this.mustSetup=!1,ws(null),null}},async login(e,t){const n=await Ss.login(e,t);return this.token=n.token,ws(n.token),this.user=n.user,this.mustSetup=!!n.must_setup,n.user},async finishSetup(e,t){const n=await Ss.setup({username:this.user.username||this.user.id,email:e,password:t});return this.user=n.user,this.mustSetup=!1,n.user},logout(){this.user=null,this.token=null,this.mustSetup=!1,ws(null)}}),As={en:{app:"Sales Trainer",login:"Login",logout:"Logout",username:"Username",email:"Email",password:"Password",newPassword:"New password",confirmPassword:"Confirm password",save:"Save",passwordTooShort:"Password must be at least 4 characters",passwordMismatch:"Passwords do not match",setupTitle:"Set up your account",setupSubtitle:"First login for ",loginError:"Invalid credentials",dashboard:"Dashboard",training:"Training",myDashboard:"My dashboard",adminOverview:"Admin overview",guideTitle:"How to use / คู่มือใช้งาน",settings:"Settings",account:"Account",profile:"Profile",readonly:"read-only",name:"Display name",saveProfile:"Save profile",changePassword:"Change password",optional:"optional",saved:"Saved",total:"Total",dateRange:"Date range",apply:"Apply",clear:"Clear",hardestPersonas:"Hardest personas",closeRate:"Close rate",managePersonas:"Manage personas",difficulty:"Difficulty",trained:"Trained",addProduct:"Add product",trainingSubtitle:"Pick a product group, then choose a persona to practice closing the sale.",noTraining:"No training groups available",groups:"Persona Groups",myTraining:"My Training",adminTools:"Admin Tools",users:"Users",analytics:"Analytics",groupBuilder:"Group Builder",create:"Create",analyze:"Analyze",manual:"Manual",edit:"Edit",product:"Product",segment:"Initial customer segment (optional)",description:"Additional description / scenario (optional)",channel:"Channel",facebook:"Facebook",line:"LINE",language:"Language",thai:"Thai",english:"English",personas:"Personas",tierA:"Tier A — Ready to buy",tierB:"Tier B — Unsure",tierC:"Tier C — Not interested but has pain",selectPersona:"Select a persona to practice",chooseScenario:"Choose a scenario",chatGuideTitle:"How to practice",chatGuideText:'You are playing the salesperson. Chat with this customer and try to close the sale. Ask about their needs, solve their pain, and handle their objections. When you are done, press "Finish & get result" to see your score and coaching.',chat:"Chat",start:"Start",send:"Send",finish:"Finish & get result",debrief:"Result & Coaching",won:"Won",lost:"Lost",notTried:"Not tried",score:"Score",pain:"Pain",why:"Reason",reveal:"Revealed persona details",generatePersona:"Generate my persona",weakAreas:"My weak areas",mySessions:"My sessions",openSaleTask:"The customer did NOT message first. You must open the sale.",sellerInitiated:"You must open the sale (outbound)",customerInitiated:"The customer will message you first"},th:{app:"ระบบฝึกทักษะการขาย",login:"เข้าสู่ระบบ",logout:"ออกจากระบบ",username:"ชื่อผู้ใช้",email:"อีเมล",password:"รหัสผ่าน",newPassword:"รหัสผ่านใหม่",confirmPassword:"ยืนยันรหัสผ่าน",save:"บันทึก",passwordTooShort:"รหัสผ่านต้องมีความยาวอย่างน้อย 4 ตัวอักษร",passwordMismatch:"รหัสผ่านที่ยืนยันไม่ตรงกัน",setupTitle:"ตั้งค่าบัญชีของคุณ",setupSubtitle:"เข้าสู่ระบบครั้งแรกสำหรับ ",loginError:"ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง กรุณาลองอีกครั้ง",dashboard:"หน้าหลัก",training:"การฝึก",myDashboard:"ภาพรวมผลการฝึก",adminOverview:"ภาพรวมผู้ดูแล",settings:"การตั้งค่า",account:"บัญชี",profile:"ข้อมูลส่วนตัว",readonly:"อ่านอย่างเดียว",name:"ชื่อที่แสดง",saveProfile:"บันทึกข้อมูล",changePassword:"เปลี่ยนรหัสผ่าน",optional:"ไม่บังคับ",saved:"บันทึกเรียบร้อย",total:"ทั้งหมด",dateRange:"ช่วงเวลา",apply:"กรอง",clear:"ล้าง",hardestPersonas:"บุคคลต้นแบบที่ขายยากที่สุด",closeRate:"อัตราปิดการขาย",managePersonas:"จัดการบุคคลต้นแบบ",difficulty:"ระดับความยาก",trained:"ฝึกแล้ว",addProduct:"เพิ่มสินค้า",trainingSubtitle:"เลือกกลุ่มสินค้า แล้วเลือกบุคคลต้นแบบเพื่อฝึกปิดการขาย",noTraining:"ยังไม่มีกลุ่มฝึก",groups:"กลุ่มบุคคลต้นแบบลูกค้า (Persona)",myTraining:"ประวัติการฝึก",adminTools:"เครื่องมือผู้ดูแลระบบ",users:"ผู้ใช้งาน",analytics:"สถิติ",groupBuilder:"สร้างกลุ่มบุคคลต้นแบบ",create:"สร้าง",analyze:"วิเคราะห์",manual:"ระบุเอง",edit:"แก้ไข",product:"สินค้า/บริการ",segment:"กลุ่มลูกค้าเป้าหมายเบื้องต้น (ไม่บังคับ)",description:"คำอธิบายหรือรายละเอียดเพิ่มเติม (ไม่บังคับ)",channel:"ช่องทางติดต่อ",facebook:"Facebook",line:"LINE",language:"ภาษา",thai:"ไทย",english:"อังกฤษ",personas:"บุคคลต้นแบบ",tierA:"ระดับ A — พร้อมตัดสินใจซื้อ",tierB:"ระดับ B — ยังไม่แน่ใจ",tierC:"ระดับ C — ไม่สนใจแต่มีปัญหา",selectPersona:"เลือกบุคคลต้นแบบเพื่อฝึก",chooseScenario:"เลือกสถานการณ์",chatGuideTitle:"วิธีฝึก",chatGuideText:'คุณรับบทเป็นพนักงานขาย พูดคุยกับลูกค้าคนนี้เพื่อพยายามปิดการขายให้ได้ สอบถามความต้องการ แก้ไขปัญหาของลูกค้า และรับมือกับข้อโต้แย้ง เมื่อพอใจแล้วกด "สรุปผล" เพื่อดูคะแนนและข้อเสนอแนะ',chat:"แชท",start:"เริ่มต้น",send:"ส่ง",finish:"สรุปผล",debrief:"ผลลัพธ์และข้อเสนอแนะ",won:"ปิดการขายได้",lost:"ปิดการขายไม่ได้",notTried:"ยังไม่ได้ฝึก",score:"คะแนน",pain:"ปัญหาของลูกค้า",why:"เหตุผล",reveal:"รายละเอียดบุคคลต้นแบบที่ถูกซ่อนไว้",generatePersona:"สร้างบุคคลต้นแบบเพิ่มเติม",weakAreas:"จุดอ่อนที่ควรฝึกเพิ่มเติม",mySessions:"ประวัติการฝึกของฉัน",openSaleTask:"ลูกค้ายังไม่ได้ทักเข้ามา คุณต้องเป็นฝ่ายเริ่มบทสนทนาการขายเอง",sellerInitiated:"คุณต้องเริ่มการขายในเชิงรุก",customerInitiated:"ลูกค้าจะติดต่อเข้ามาก่อน"}},Ke=Jt({locale:localStorage.getItem("locale")||"th",t(e){return As[this.locale]&&As[this.locale][e]||As.en[e]||e},set(e){this.locale=e,localStorage.setItem("locale",e)}}),wu=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},Su={class:"app"},Au={key:0,class:"topnav"},Ru={class:"tabs"},xu={class:"nav-right"},Tu={class:"txt"},Cu={class:"main"},Pu={__name:"App",setup(e){const t=pu();function n(){Ke.set(Ke.locale==="th"?"en":"th")}function s(){Ce.logout(),t.push("/login")}return Wi(()=>{Ce.token&&!Ce.user&&Ce.load()}),(r,i)=>{const o=_r("router-link"),l=_r("router-view");return on(),Tr("div",Su,[ae(Ce).user?(on(),Tr("nav",Au,[ie(o,{to:"/",class:"brand"},{default:Vt(()=>[Ft(Rt(ae(Ke).t("app")),1)]),_:1}),mt("div",Ru,[ae(Ce).isAdmin?(on(),Ds(o,{key:0,to:"/",class:jt(["tab",{active:r.$route.path==="/"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("adminOverview")),1)]),_:1},8,["class"])):Cr("",!0),ie(o,{to:"/my/board",class:jt(["tab",{active:r.$route.path==="/my/board"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("myDashboard")),1)]),_:1},8,["class"]),ie(o,{to:"/training",class:jt(["tab",{active:r.$route.path==="/training"}])},{default:Vt(()=>[Ft(Rt(ae(Ke).t("training")),1)]),_:1},8,["class"])]),mt("div",xu,[mt("button",{onClick:n,class:"lang"},Rt(ae(Ke).locale==="th"?"EN":"TH"),1),ie(o,{to:"/settings",class:"icon-btn",title:ae(Ke).t("settings")},{default:Vt(()=>[ie(ae(Eu),{size:20,"stroke-width":1.8})]),_:1},8,["title"]),mt("button",{onClick:s,class:"logout-btn"},[ie(ae(bu),{size:18,"stroke-width":1.8}),i[0]||(i[0]=Ft()),mt("span",Tu,Rt(ae(Ke).t("logout")),1)])])])):Cr("",!0),mt("main",Cu,[(on(),Ds(l,{key:ae(Ke).locale}))])])}}},Ou=wu(Pu,[["__scopeId","data-v-f29a3a4f"]]),Iu="modulepreload",Nu=function(e){return"/"+e},ai={},Se=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));r=Promise.allSettled(n.map(c=>{if(c=Nu(c),c in ai)return;ai[c]=!0;const f=c.endsWith(".css"),u=f?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${u}`))return;const h=document.createElement("link");if(h.rel=f?"stylesheet":Iu,f||(h.as="script"),h.crossOrigin="",h.href=c,l&&h.setAttribute("nonce",l),document.head.appendChild(h),f)return new Promise((g,m)=>{h.addEventListener("load",g),h.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${c}`)))})}))}function i(o){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o}return r.then(o=>{for(const l of o||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})},Mu=[{path:"/login",component:()=>Se(()=>import("./Login-BFjXrLpo.js"),__vite__mapDeps([0,1])),meta:{public:!0}},{path:"/setup",component:()=>Se(()=>import("./Setup-B6q98ArM.js"),__vite__mapDeps([2,3,4]))},{path:"/",component:()=>Se(()=>import("./Analytics-DzK25pzJ.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}},{path:"/my/board",component:()=>Se(()=>import("./MyBoard-wXpWmFYg.js"),__vite__mapDeps([9,10,11]))},{path:"/training",component:()=>Se(()=>import("./Training-C9KC4ygo.js"),__vite__mapDeps([12,13,14,7,15]))},{path:"/groups/:gid/personas",component:()=>Se(()=>import("./Personas-BK-v3zL-.js"),__vite__mapDeps([16,13,17,18]))},{path:"/groups/:gid/chat/:pid",component:()=>Se(()=>import("./Chat-B4cA4NlX.js"),__vite__mapDeps([19,13,17,20]))},{path:"/settings",component:()=>Se(()=>import("./Settings-k8fzlPYr.js"),__vite__mapDeps([21,3,22]))},{path:"/guide",component:()=>Se(()=>import("./Guide-Bx9T4hBF.js"),__vite__mapDeps([23,14,10,13,24]))},{path:"/admin/new-group",component:()=>Se(()=>import("./GroupBuilder-DpQWbscg.js"),__vite__mapDeps([25,17,26])),meta:{admin:!0}},{path:"/admin/groups/:gid/edit",component:()=>Se(()=>import("./GroupEdit-PGVs7eZc.js"),__vite__mapDeps([27,6,24,28])),meta:{admin:!0}},{path:"/admin/users",component:()=>Se(()=>import("./AdminUsers-BXR4J5iM.js"),__vite__mapDeps([29,6,30])),meta:{admin:!0}},{path:"/admin/analytics",component:()=>Se(()=>import("./Analytics-DzK25pzJ.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}}],Do=hu({history:$a(),routes:Mu});Do.beforeEach(async e=>e.meta.public?!0:(Ce.user||await Ce.load(),Ce.user?Ce.mustSetup&&e.path!=="/setup"?{path:"/setup"}:e.path==="/"&&!Ce.isAdmin?{path:"/my/board"}:e.meta.admin&&!Ce.isAdmin?{path:"/my/board"}:!0:{path:"/login",query:{redirect:e.fullPath}}));ia(Ou).use(Do).mount("#app");export{Oe as A,jt as B,Zs as C,Vu as D,Xc as E,tt as F,Mn as G,Jt as H,Uu as I,Eu as S,wu as _,Tr as a,mt as b,No as c,Bu as d,Hu as e,Ds as f,Cr as g,Ce as h,Ke as i,pu as j,ju as k,ie as l,Ft as m,Wi as n,on as o,Vt as p,Ks as q,dl as r,Lu as s,Rt as t,ae as u,jr as v,Du as w,Ss as x,_r as y,Fu as z}; diff --git a/frontend/dist/assets/layout-dashboard-CmUmS5d8.js b/frontend/dist/assets/layout-dashboard-Djj77ueX.js similarity index 91% rename from frontend/dist/assets/layout-dashboard-CmUmS5d8.js rename to frontend/dist/assets/layout-dashboard-Djj77ueX.js index a2ae215..2b12c72 100644 --- a/frontend/dist/assets/layout-dashboard-CmUmS5d8.js +++ b/frontend/dist/assets/layout-dashboard-Djj77ueX.js @@ -1,4 +1,4 @@ -import{c as t}from"./index-DOaJiTJw.js";/** +import{c as t}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/lock-WqnzR3ED.js b/frontend/dist/assets/lock-uERjsrGr.js similarity index 88% rename from frontend/dist/assets/lock-WqnzR3ED.js rename to frontend/dist/assets/lock-uERjsrGr.js index d34fd65..f15f508 100644 --- a/frontend/dist/assets/lock-WqnzR3ED.js +++ b/frontend/dist/assets/lock-uERjsrGr.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-DOaJiTJw.js";/** +import{c as e}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/plus-CODydpxS.js b/frontend/dist/assets/plus-B9zMVgv0.js similarity index 86% rename from frontend/dist/assets/plus-CODydpxS.js rename to frontend/dist/assets/plus-B9zMVgv0.js index 936bbcb..9d36835 100644 --- a/frontend/dist/assets/plus-CODydpxS.js +++ b/frontend/dist/assets/plus-B9zMVgv0.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-DOaJiTJw.js";/** +import{c as e}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/sparkles-CwNig9QR.js b/frontend/dist/assets/sparkles-E3Xd6eY7.js similarity index 93% rename from frontend/dist/assets/sparkles-CwNig9QR.js rename to frontend/dist/assets/sparkles-E3Xd6eY7.js index 93f1317..e9f5238 100644 --- a/frontend/dist/assets/sparkles-CwNig9QR.js +++ b/frontend/dist/assets/sparkles-E3Xd6eY7.js @@ -1,4 +1,4 @@ -import{c as a}from"./index-DOaJiTJw.js";/** +import{c as a}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/target-QJk_fUDu.js b/frontend/dist/assets/target-BFC5gbdb.js similarity index 90% rename from frontend/dist/assets/target-QJk_fUDu.js rename to frontend/dist/assets/target-BFC5gbdb.js index 21d3b75..250f3d5 100644 --- a/frontend/dist/assets/target-QJk_fUDu.js +++ b/frontend/dist/assets/target-BFC5gbdb.js @@ -1,4 +1,4 @@ -import{c}from"./index-DOaJiTJw.js";/** +import{c}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/assets/users-SQMCzZpr.js b/frontend/dist/assets/users-NPxa3eQ_.js similarity index 90% rename from frontend/dist/assets/users-SQMCzZpr.js rename to frontend/dist/assets/users-NPxa3eQ_.js index e499df8..b817a69 100644 --- a/frontend/dist/assets/users-SQMCzZpr.js +++ b/frontend/dist/assets/users-NPxa3eQ_.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-DOaJiTJw.js";/** +import{c as e}from"./index-BCax2GDh.js";/** * @license lucide-vue-next v1.0.0 - ISC * * This source code is licensed under the ISC license. diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 4717b4b..b0582a3 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -4,7 +4,7 @@ Sales Trainer - + diff --git a/frontend/src/components/PersonaForm.vue b/frontend/src/components/PersonaForm.vue index 97e32e8..0c2e7f2 100644 --- a/frontend/src/components/PersonaForm.vue +++ b/frontend/src/components/PersonaForm.vue @@ -36,8 +36,8 @@ - -
+ +

🎯 การขาย

@@ -50,6 +50,12 @@
+
+

🔒 ส่วนสูตรการขาย

+

ปิดการแก้ไข — เฉพาะผู้ดูแลระดับสูงเท่านั้นที่ดู/แก้ได้ (เพื่อรักษาความได้เปรียบ)

+ + +
@@ -62,7 +68,7 @@
- +
@@ -74,6 +80,7 @@