From 0b92430aab3b99b8c2ec7b3d35089ad6cf7a0287 Mon Sep 17 00:00:00 2001 From: Macky Date: Sun, 9 Aug 2026 10:12:40 +0700 Subject: [PATCH] feat(idea): create group auto-generates personas; channel removed; product->product/idea MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per product-idea focus: - GroupBuilder: 'สินค้า/บริการ/ไอเดีย' label; removed the channel select (channel is now chosen at chat time as a scenario, not at persona create). Create now auto-runs analyze so personas are generated immediately (no separate Analyze button). - GroupEdit: analyze button becomes 'สร้างบุคคลต้นแบบเพิ่มเติม' which APPENDS more personas (backend analyze?append=true reuses sales kit + keeps existing instead of replacing). - i18n product label updated. - User-journey test asserts append adds personas (15->30) and existing kept. All 9 backend suites pass. Rebuilt dist. --- backend/app/api/group_routes.py | 35 +++++++++---- backend/scripts/test_user_journey.py | 8 +++ ...ers-Bev0W-FI.js => AdminUsers-B98Kq2kn.js} | 2 +- ...tics-CWNDFvS5.js => Analytics-C8V4bNnH.js} | 2 +- .../{Chat-BUYTG6XR.js => Chat-5M8zA86W.js} | 2 +- frontend/dist/assets/GroupBuilder-0QeIeG8Q.js | 21 ++++++++ .../dist/assets/GroupBuilder-7-9ZskJo.css | 1 + .../dist/assets/GroupBuilder-CpbMByEC.css | 1 - frontend/dist/assets/GroupBuilder-j2iPAhDG.js | 21 -------- ...it-CFwXUOV9.css => GroupEdit-BdbUBMcX.css} | 2 +- frontend/dist/assets/GroupEdit-CJ3zMSjJ.js | 9 ---- frontend/dist/assets/GroupEdit-yRcPSwSE.js | 9 ++++ .../{Guide-PlIFuOuL.js => Guide-B9uxl_Jn.js} | 2 +- .../{Login-B1BFq5lO.js => Login-O3bvqwVm.js} | 2 +- ...yBoard-C2XKgiYF.js => MyBoard-Daz4fzbD.js} | 2 +- ...sonas-C1zt2UAW.js => Personas-B92S9P5I.js} | 2 +- ...tings-C6ZMNY2Q.js => Settings-C4qI85q5.js} | 2 +- .../{Setup-DlVOMLoz.js => Setup-H6LANAbr.js} | 2 +- ...ining-DuHwYoAr.js => Training-DTTvOhJR.js} | 2 +- ...eft-D6PVN9vk.js => arrow-left-6ACDPyW9.js} | 2 +- ...open-Yn_Ya1If.js => book-open-o2ZLc9m6.js} | 2 +- .../{index-Cr5KFgzf.js => index-DNAVmyB5.js} | 4 +- ...d1g6OI.js => layout-dashboard-dl9BywAR.js} | 2 +- .../{lock-CERtIuBL.js => lock-fipmYN8_.js} | 2 +- .../{plus-zB_wweS5.js => plus-BGdYQyzB.js} | 2 +- ...rkles-CWO9Md85.js => sparkles-0MI5vUsR.js} | 2 +- ...{target-CUBX0ybh.js => target-BYJn0LyF.js} | 2 +- .../{users-CknR8rxo.js => users-DG0hBplh.js} | 2 +- frontend/dist/index.html | 2 +- frontend/src/api/index.js | 2 +- frontend/src/i18n/index.js | 2 +- frontend/src/views/GroupBuilder.vue | 49 +++++++++---------- frontend/src/views/GroupEdit.vue | 10 ++-- 33 files changed, 119 insertions(+), 93 deletions(-) rename frontend/dist/assets/{AdminUsers-Bev0W-FI.js => AdminUsers-B98Kq2kn.js} (98%) rename frontend/dist/assets/{Analytics-CWNDFvS5.js => Analytics-C8V4bNnH.js} (97%) rename frontend/dist/assets/{Chat-BUYTG6XR.js => Chat-5M8zA86W.js} (97%) create mode 100644 frontend/dist/assets/GroupBuilder-0QeIeG8Q.js create mode 100644 frontend/dist/assets/GroupBuilder-7-9ZskJo.css delete mode 100644 frontend/dist/assets/GroupBuilder-CpbMByEC.css delete mode 100644 frontend/dist/assets/GroupBuilder-j2iPAhDG.js rename frontend/dist/assets/{GroupEdit-CFwXUOV9.css => GroupEdit-BdbUBMcX.css} (58%) delete mode 100644 frontend/dist/assets/GroupEdit-CJ3zMSjJ.js create mode 100644 frontend/dist/assets/GroupEdit-yRcPSwSE.js rename frontend/dist/assets/{Guide-PlIFuOuL.js => Guide-B9uxl_Jn.js} (95%) rename frontend/dist/assets/{Login-B1BFq5lO.js => Login-O3bvqwVm.js} (98%) rename frontend/dist/assets/{MyBoard-C2XKgiYF.js => MyBoard-Daz4fzbD.js} (94%) rename frontend/dist/assets/{Personas-C1zt2UAW.js => Personas-B92S9P5I.js} (95%) rename frontend/dist/assets/{Settings-C6ZMNY2Q.js => Settings-C4qI85q5.js} (98%) rename frontend/dist/assets/{Setup-DlVOMLoz.js => Setup-H6LANAbr.js} (94%) rename frontend/dist/assets/{Training-DuHwYoAr.js => Training-DTTvOhJR.js} (93%) rename frontend/dist/assets/{arrow-left-D6PVN9vk.js => arrow-left-6ACDPyW9.js} (86%) rename frontend/dist/assets/{book-open-Yn_Ya1If.js => book-open-o2ZLc9m6.js} (90%) rename frontend/dist/assets/{index-Cr5KFgzf.js => index-DNAVmyB5.js} (86%) rename frontend/dist/assets/{layout-dashboard-D4d1g6OI.js => layout-dashboard-dl9BywAR.js} (91%) rename frontend/dist/assets/{lock-CERtIuBL.js => lock-fipmYN8_.js} (88%) rename frontend/dist/assets/{plus-zB_wweS5.js => plus-BGdYQyzB.js} (86%) rename frontend/dist/assets/{sparkles-CWO9Md85.js => sparkles-0MI5vUsR.js} (93%) rename frontend/dist/assets/{target-CUBX0ybh.js => target-BYJn0LyF.js} (90%) rename frontend/dist/assets/{users-CknR8rxo.js => users-DG0hBplh.js} (90%) diff --git a/backend/app/api/group_routes.py b/backend/app/api/group_routes.py index 880bc57..a3ed874 100644 --- a/backend/app/api/group_routes.py +++ b/backend/app/api/group_routes.py @@ -170,9 +170,13 @@ def list_groups(): @require_auth @require_roles("admin") def analyze_group(gid: str): - """Run analysis: sales kit + 15 personas. Synchronous for v1 (replaces gen).""" + """Run analysis: sales kit + 15 personas. Synchronous for v1 (replaces gen). + + ?append=true generates MORE personas and appends to existing ones instead of + replacing them (used by 'create more personas').""" s = _stores() group = _get_owned_group(s, gid) + append = request.args.get("append") == "true" inp = group.get("input", {}) if not s["llm"]: @@ -181,20 +185,33 @@ def analyze_group(gid: str): from ..services.analyzer import Analyzer from ..services.persona_generator import PersonaGenerator - s["groups"].update(gid, status="analyzing", error=None) + # If appending, reuse the existing sales kit; else re-run the full analysis. + sales_kit = group.get("sales_kit") + if not append or not sales_kit: + s["groups"].update(gid, status="analyzing", error=None) + try: + sales_kit = Analyzer(s["llm"]).analyze( + product=inp.get("product", ""), + segment=inp.get("segment", ""), + description=inp.get("description", ""), + file_text=inp.get("file_text", ""), + channel=inp.get("channel", "facebook"), + ) + except Exception as exc: + s["groups"].update(gid, status="failed", error=str(exc)) + raise ApiError(f"analysis failed: {exc}", 500) + s["groups"].update(gid, sales_kit=sales_kit, status="ready", error=None) + + existing = [] + if append: + existing = s["groups"].get_or_none(gid).get("personas", []) or [] try: - sales_kit = Analyzer(s["llm"]).analyze( - product=inp.get("product", ""), - segment=inp.get("segment", ""), - description=inp.get("description", ""), - file_text=inp.get("file_text", ""), - channel=inp.get("channel", "facebook"), - ) personas = PersonaGenerator(s["llm"]).generate( sales_kit=sales_kit, language=inp.get("language", "th"), channel=inp.get("channel", "facebook"), ) + personas = existing + personas except Exception as exc: s["groups"].update(gid, status="failed", error=str(exc)) raise ApiError(f"analysis failed: {exc}", 500) diff --git a/backend/scripts/test_user_journey.py b/backend/scripts/test_user_journey.py index ed3feb2..496111c 100644 --- a/backend/scripts/test_user_journey.py +++ b/backend/scripts/test_user_journey.py @@ -85,4 +85,12 @@ for hidden in ("pains","tolerance","negotiation_levers","opener","income","backg assert hidden not in pfirst, f"trainee should not see '{hidden}' before chat" print("[ok] trainee sees only revealable persona (latent hidden)") +# 11. 'create more personas' = append (does not replace existing ones) +before = len(C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"]) +r = C.post(f"/api/groups/{gid}/analyze?append=true", headers=AH) +assert r.status_code == 200, r.get_json() +after = len(r.get_json()["personas"]) +assert after > before, f"append should add personas (before={before}, after={after})" +print(f"[ok] append adds personas ({before} -> {after}); existing kept") + print("ALL USER-JOURNEY FLOW TESTS PASSED") diff --git a/frontend/dist/assets/AdminUsers-Bev0W-FI.js b/frontend/dist/assets/AdminUsers-B98Kq2kn.js similarity index 98% rename from frontend/dist/assets/AdminUsers-Bev0W-FI.js rename to frontend/dist/assets/AdminUsers-B98Kq2kn.js index 2185fb0..0b79842 100644 --- a/frontend/dist/assets/AdminUsers-Bev0W-FI.js +++ b/frontend/dist/assets/AdminUsers-B98Kq2kn.js @@ -1,4 +1,4 @@ -import{c as k,_ as h,p 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,G as U,g as f,F as V,x as C,y as w,r as y,o as i,C as M}from"./index-Cr5KFgzf.js";import{U as z}from"./users-CknR8rxo.js";/** +import{c as k,_ as h,p 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,G as U,g as f,F as V,x as C,y as w,r as y,o as i,C as M}from"./index-DNAVmyB5.js";import{U as z}from"./users-DG0hBplh.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-CWNDFvS5.js b/frontend/dist/assets/Analytics-C8V4bNnH.js similarity index 97% rename from frontend/dist/assets/Analytics-CWNDFvS5.js rename to frontend/dist/assets/Analytics-C8V4bNnH.js index ed30d16..dd19aa1 100644 --- a/frontend/dist/assets/Analytics-CWNDFvS5.js +++ b/frontend/dist/assets/Analytics-C8V4bNnH.js @@ -1,4 +1,4 @@ -import{c as x,_ as L,p as M,a as p,b as s,l as i,u as a,m as c,t as e,i as n,q as w,w as f,v as b,s as U,g as N,F as R,x as j,y as A,z as B,r as m,h as E,o as y}from"./index-Cr5KFgzf.js";import{U as P}from"./users-CknR8rxo.js";import{P as S}from"./plus-zB_wweS5.js";/** +import{c as x,_ as L,p as M,a as p,b as s,l as i,u as a,m as c,t as e,i as n,q as w,w as f,v as b,s as U,g as N,F as R,x as j,y as A,z as B,r as m,h as E,o as y}from"./index-DNAVmyB5.js";import{U as P}from"./users-DG0hBplh.js";import{P as S}from"./plus-BGdYQyzB.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-BUYTG6XR.js b/frontend/dist/assets/Chat-5M8zA86W.js similarity index 97% rename from frontend/dist/assets/Chat-BUYTG6XR.js rename to frontend/dist/assets/Chat-5M8zA86W.js index d249ce7..769cce9 100644 --- a/frontend/dist/assets/Chat-BUYTG6XR.js +++ b/frontend/dist/assets/Chat-5M8zA86W.js @@ -1,4 +1,4 @@ -import{c as P,_ as R,p as G,y as S,a as o,l as k,q as V,u as a,b as s,t,i as n,F as T,x as L,m as g,C as w,g as h,w as K,v as $,d as q,k as H,r as d,B as J,D as O,z as U,o as l}from"./index-Cr5KFgzf.js";import{T as Y}from"./target-CUBX0ybh.js";import{A as Q}from"./arrow-left-D6PVN9vk.js";/** +import{c as P,_ as R,p as G,y as S,a as o,l as k,q as V,u as a,b as s,t,i as n,F as T,x as L,m as g,C as w,g as h,w as K,v as $,d as q,k as H,r as d,B as J,D as O,z as U,o as l}from"./index-DNAVmyB5.js";import{T as Y}from"./target-BYJn0LyF.js";import{A as Q}from"./arrow-left-6ACDPyW9.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-0QeIeG8Q.js b/frontend/dist/assets/GroupBuilder-0QeIeG8Q.js new file mode 100644 index 0000000..93af510 --- /dev/null +++ b/frontend/dist/assets/GroupBuilder-0QeIeG8Q.js @@ -0,0 +1,21 @@ +import{c as v,_ as M,a as k,l as u,q as z,b as e,u as l,m as o,t as r,i,w as g,v as y,G as V,g as C,z as A,r as m,y as x,j as B,o as w}from"./index-DNAVmyB5.js";import{A as q}from"./arrow-left-6ACDPyW9.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 G=v("box",[["path",{d:"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z",key:"hh9hay"}],["path",{d:"m3.3 7 8.7 5 8.7-5",key:"g66t2b"}],["path",{d:"M12 22V12",key:"d0xqtd"}]]);/** + * @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 E=v("circle-plus",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M8 12h8",key:"1wcyev"}],["path",{d:"M12 8v8",key:"napkw2"}]]);/** + * @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 N=v("list-checks",[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}]]);/** + * @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 P=v("paperclip",[["path",{d:"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",key:"1miecu"}]]),S={class:"card guide"},U={class:"card"},D={style:{"margin-top":"0"}},I={value:"th"},L={value:"en"},j={key:0,class:"error"},F=["disabled"],T={__name:"GroupBuilder",setup($){const f=B(),a=m({product:"",segment:"",description:"",language:"th"}),h=m([]),d=m(""),c=m(!1);function b(s){h.value=Array.from(s.target.files||[])}async function _(){c.value=!0,d.value="";try{const s=new FormData;s.append("product",a.value.product),s.append("segment",a.value.segment),s.append("description",a.value.description),s.append("language",a.value.language),h.value.forEach(n=>s.append("files",n));const p=(await x.createGroup(s)).group.id;try{await x.analyzeGroup(p)}catch(n){d.value=n.message,f.push(`/admin/groups/${p}/edit`);return}f.push(`/admin/groups/${p}/edit`)}catch(s){d.value=s.message}finally{c.value=!1}}return(s,t)=>{const p=A("router-link");return w(),k("div",null,[u(p,{to:"/training",class:"btn-back"},{default:z(()=>[u(l(q),{size:16,"stroke-width":2}),o(" "+r(l(i).t("training")),1)]),_:1}),e("div",S,[e("strong",null,[u(l(N),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),t[4]||(t[4]=o(" วิธีสร้างกลุ่มบุคคลต้นแบบ",-1))]),t[5]||(t[5]=e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[o("กรอก "),e("strong",null,"สินค้า / บริการ / ไอเดีย"),o(" ที่อยากให้ทีมฝึกนำเสนอ (ช่องด้านล่าง)")]),e("li",null,"(ไม่บังคับ) ระบุกลุ่มเป้าหมาย หรืออัปโหลดไฟล์เอกสาร .pdf/.md/.txt"),e("li",null,[o("กด "),e("strong",null,"สร้าง"),o(" — ระบบจะสร้างบุคคลต้นแบบ (ลูกค้าจำลอง) ให้ทันที")]),e("li",null,[o("ถ้าอยากได้บุคคลต้นแบบเพิ่ม ไปที่หน้านั้นแล้วกด "),e("strong",null,"สร้างบุคคลต้นแบบเพิ่มเติม")])],-1))]),e("div",U,[e("h2",D,[u(l(G),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),o(" "+r(l(i).t("addProduct")),1)]),e("label",null,[o(r(l(i).t("product"))+" ",1),t[6]||(t[6]=e("span",{class:"req"},"*",-1))]),g(e("textarea",{"onUpdate:modelValue":t[0]||(t[0]=n=>a.value.product=n),rows:"3",placeholder:"สินค้า / บริการ / ไอเดีย — เช่น ระบบ POS สำหรับร้านอาหาร, บริการให้คำปรึกษา AI, แนวคิดเปิดร้านกาแฟ…"},null,512),[[y,a.value.product]]),e("label",null,r(l(i).t("segment")),1),g(e("input",{"onUpdate:modelValue":t[1]||(t[1]=n=>a.value.segment=n),placeholder:"เช่น SMEs ร้านอาหารในกรุงเทพ"},null,512),[[y,a.value.segment]]),e("label",null,r(l(i).t("description")),1),g(e("textarea",{"onUpdate:modelValue":t[2]||(t[2]=n=>a.value.description=n),rows:"3",placeholder:"รายละเอียดเพิ่มเติม เช่น จุดเด่น ราคา หรือกลุ่มคนที่เราจะนำเสนอ (ไม่บังคับ)"},null,512),[[y,a.value.description]]),e("label",null,r(l(i).t("language")),1),g(e("select",{"onUpdate:modelValue":t[3]||(t[3]=n=>a.value.language=n)},[e("option",I,r(l(i).t("thai")),1),e("option",L,r(l(i).t("english")),1)],512),[[V,a.value.language]]),e("label",null,[u(l(P),{size:15,"stroke-width":1.8,style:{"vertical-align":"-2px"}}),t[7]||(t[7]=o(" ไฟล์เอกสาร (.pdf/.md/.txt) — ใช้เป็นข้อมูลให้ระบบได้",-1))]),e("input",{type:"file",multiple:"",accept:".pdf,.md,.txt",onChange:b},null,32),d.value?(w(),k("div",j,r(d.value),1)):C("",!0),e("button",{class:"primary",style:{"margin-top":"16px",display:"inline-flex","align-items":"center",gap:"6px"},disabled:c.value||!a.value.product&&!h.value.length,onClick:_},[u(l(E),{size:18,"stroke-width":2}),o(" "+r(c.value?"กำลังสร้างบุคคลต้นแบบ…":l(i).t("create")),1)],8,F)])])}}},Z=M(T,[["__scopeId","data-v-b566e48c"]]);export{Z as default}; diff --git a/frontend/dist/assets/GroupBuilder-7-9ZskJo.css b/frontend/dist/assets/GroupBuilder-7-9ZskJo.css new file mode 100644 index 0000000..f7a3266 --- /dev/null +++ b/frontend/dist/assets/GroupBuilder-7-9ZskJo.css @@ -0,0 +1 @@ +.guide[data-v-b566e48c]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.req[data-v-b566e48c]{color:var(--red)} diff --git a/frontend/dist/assets/GroupBuilder-CpbMByEC.css b/frontend/dist/assets/GroupBuilder-CpbMByEC.css deleted file mode 100644 index 31e688c..0000000 --- a/frontend/dist/assets/GroupBuilder-CpbMByEC.css +++ /dev/null @@ -1 +0,0 @@ -.guide[data-v-d513596d]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.req[data-v-d513596d]{color:var(--red)} diff --git a/frontend/dist/assets/GroupBuilder-j2iPAhDG.js b/frontend/dist/assets/GroupBuilder-j2iPAhDG.js deleted file mode 100644 index 80c1d7f..0000000 --- a/frontend/dist/assets/GroupBuilder-j2iPAhDG.js +++ /dev/null @@ -1,21 +0,0 @@ -import{c as g,_ as M,a as f,l as d,q as V,b as e,u as l,m as r,t as s,i as o,w as u,v as y,G as k,g as C,z,r as v,y as B,j as q,o as x}from"./index-Cr5KFgzf.js";import{A}from"./arrow-left-D6PVN9vk.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 P=g("box",[["path",{d:"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z",key:"hh9hay"}],["path",{d:"m3.3 7 8.7 5 8.7-5",key:"g66t2b"}],["path",{d:"M12 22V12",key:"d0xqtd"}]]);/** - * @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 S=g("circle-plus",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M8 12h8",key:"1wcyev"}],["path",{d:"M12 8v8",key:"napkw2"}]]);/** - * @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 U=g("list-checks",[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}]]);/** - * @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 G=g("paperclip",[["path",{d:"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",key:"1miecu"}]]),N={class:"card guide"},D={class:"card"},E={style:{"margin-top":"0"}},L={class:"row"},j={style:{flex:"1"}},F={value:"facebook"},I={value:"line"},O={style:{flex:"1"}},T={value:"th"},R={value:"en"},Z={key:0,class:"error"},$=["disabled"],H={__name:"GroupBuilder",setup(J){const _=q(),a=v({product:"",segment:"",description:"",channel:"facebook",language:"th"}),h=v([]),p=v(""),c=v(!1);function b(n){h.value=Array.from(n.target.files||[])}async function w(){c.value=!0,p.value="";try{const n=new FormData;n.append("product",a.value.product),n.append("segment",a.value.segment),n.append("description",a.value.description),n.append("channel",a.value.channel),n.append("language",a.value.language),h.value.forEach(i=>n.append("files",i));const m=(await B.createGroup(n)).group.id;_.push(`/admin/groups/${m}/edit`)}catch(n){p.value=n.message}finally{c.value=!1}}return(n,t)=>{const m=z("router-link");return x(),f("div",null,[d(m,{to:"/training",class:"btn-back"},{default:V(()=>[d(l(A),{size:16,"stroke-width":2}),r(" "+s(l(o).t("training")),1)]),_:1}),e("div",N,[e("strong",null,[d(l(U),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),t[5]||(t[5]=r(" วิธีสร้างกลุ่มบุคคลต้นแบบ",-1))]),t[6]||(t[6]=e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[r("กรอก "),e("strong",null,"ชื่อ/รายละเอียดสินค้าหรือบริการ"),r(" ที่อยากให้ทีมฝึกขาย (ช่องด้านล่าง)")]),e("li",null,"(ไม่บังคับ) ระบุกลุ่มลูกค้าเป้าหมาย หรืออัปโหลดไฟล์เอกสารสินค้า .pdf/.md/.txt"),e("li",null,[r("กด "),e("strong",null,"สร้าง"),r(" — ระบบจะพาไปหน้าสร้างบุคคลต้นแบบ (ลูกค้าจำลอง)")])],-1))]),e("div",D,[e("h2",E,[d(l(P),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),r(" "+s(l(o).t("addProduct")),1)]),e("label",null,[r(s(l(o).t("product"))+" ",1),t[7]||(t[7]=e("span",{class:"req"},"*",-1))]),u(e("textarea",{"onUpdate:modelValue":t[0]||(t[0]=i=>a.value.product=i),rows:"3",placeholder:"เช่น ระบบ POS สำหรับร้านอาหาร Cloud POS for small restaurants"},null,512),[[y,a.value.product]]),e("label",null,s(l(o).t("segment")),1),u(e("input",{"onUpdate:modelValue":t[1]||(t[1]=i=>a.value.segment=i),placeholder:"เช่น SMEs ร้านอาหารในกรุงเทพ"},null,512),[[y,a.value.segment]]),e("label",null,s(l(o).t("description")),1),u(e("textarea",{"onUpdate:modelValue":t[2]||(t[2]=i=>a.value.description=i),rows:"3",placeholder:"รายละเอียดเพิ่มเติม เช่น จุดเด่นสินค้า ราคา ฯลฯ (ไม่บังคับ)"},null,512),[[y,a.value.description]]),e("div",L,[e("div",j,[e("label",null,s(l(o).t("channel")),1),u(e("select",{"onUpdate:modelValue":t[3]||(t[3]=i=>a.value.channel=i)},[e("option",F,s(l(o).t("facebook")),1),e("option",I,s(l(o).t("line")),1)],512),[[k,a.value.channel]])]),e("div",O,[e("label",null,s(l(o).t("language")),1),u(e("select",{"onUpdate:modelValue":t[4]||(t[4]=i=>a.value.language=i)},[e("option",T,s(l(o).t("thai")),1),e("option",R,s(l(o).t("english")),1)],512),[[k,a.value.language]])])]),e("label",null,[d(l(G),{size:15,"stroke-width":1.8,style:{"vertical-align":"-2px"}}),t[8]||(t[8]=r(" ไฟล์เอกสารสินค้า (.pdf/.md/.txt) — ใช้เป็นข้อมูลให้ระบบได้",-1))]),e("input",{type:"file",multiple:"",accept:".pdf,.md,.txt",onChange:b},null,32),p.value?(x(),f("div",Z,s(p.value),1)):C("",!0),e("button",{class:"primary",style:{"margin-top":"16px",display:"inline-flex","align-items":"center",gap:"6px"},disabled:c.value||!a.value.product&&!h.value.length,onClick:w},[d(l(S),{size:18,"stroke-width":2}),r(" "+s(c.value?"กำลังสร้าง…":l(o).t("create")),1)],8,$)])])}}},W=M(H,[["__scopeId","data-v-d513596d"]]);export{W as default}; diff --git a/frontend/dist/assets/GroupEdit-CFwXUOV9.css b/frontend/dist/assets/GroupEdit-BdbUBMcX.css similarity index 58% rename from frontend/dist/assets/GroupEdit-CFwXUOV9.css rename to frontend/dist/assets/GroupEdit-BdbUBMcX.css index 1d822f6..0930891 100644 --- a/frontend/dist/assets/GroupEdit-CFwXUOV9.css +++ b/frontend/dist/assets/GroupEdit-BdbUBMcX.css @@ -1 +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} +.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-5517bbc4]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.pcard[data-v-5517bbc4]{display:flex;flex-direction:column}.edit-btn[data-v-5517bbc4]{margin-top:auto;display:inline-flex;align-items:center;gap:6px}.star[data-v-5517bbc4]{color:#d8dbe3}.star.on[data-v-5517bbc4]{color:#f59e0b}.badge.tier-a[data-v-5517bbc4]{background:#dcfce7;color:#166534}.badge.tier-b[data-v-5517bbc4]{background:#fef9c3;color:#854d0e}.badge.tier-c[data-v-5517bbc4]{background:#fee2e2;color:#991b1b}.guide[data-v-5517bbc4]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.modal-backdrop[data-v-5517bbc4]{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-5517bbc4]{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-CJ3zMSjJ.js b/frontend/dist/assets/GroupEdit-CJ3zMSjJ.js deleted file mode 100644 index e64122f..0000000 --- a/frontend/dist/assets/GroupEdit-CJ3zMSjJ.js +++ /dev/null @@ -1,9 +0,0 @@ -import{c as M,_ as G,H as O,o as r,a as m,b as e,t as a,w as o,v as i,G as N,u as k,h as E,g as A,I as J,r as p,p as q,l as z,q as D,m as y,i as B,f as H,F as P,x as T,J as R,k as K,y as S,z as Q,C as F}from"./index-Cr5KFgzf.js";import{U as W}from"./users-CknR8rxo.js";import{S as X}from"./sparkles-CWO9Md85.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:_}){const x=w,U=_,n=J({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(E).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(E).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"}},zl=["disabled"],Sl={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"},Tl={class:"row",style:{"justify-content":"space-between","align-items":"center"}},_l={class:"diff"},El={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 S.getGroup(x);U.value=u.group,n.value=(await S.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 S.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 S.updatePersona(x,f.value.id,u),f.value=null,await V()}catch(d){b.value=d.message}finally{v.value=!1}}return q(V),(u,d)=>{const I=Q("router-link");return r(),m("div",null,[z(I,{to:"/training",class:"btn-back"},{default:D(()=>[y("← "+a(k(B).t("training")),1)]),_:1}),e("div",cl,[e("h2",Bl,[z(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",Sl)):(r(),H(k(X),{key:1,size:18,"stroke-width":1.8})),y(" "+a(v.value?"กำลังสร้าง…":k(B).t("analyze")),1)],8,zl)])]),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,T(["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,T($(c),g=>(r(),m("div",{key:g.id,class:"card pcard"},[e("div",Tl,[e("strong",null,a(g.name),1),e("span",{class:F(["badge",j(g.tier)])},a(g.tier),3)]),e("div",_l,[(r(),m(P,null,T(5,L=>e("span",{key:L,class:F(["star",{on:L<=(g.difficulty||1)}])},"★",2)),64))]),e("div",El,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)},[z(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,[z(Cl,{persona:f.value,onSave:s,onCancel:d[0]||(d[0]=c=>f.value=null)},null,8,["persona"])])])):A("",!0)])}}},Dl=G(Ml,[["__scopeId","data-v-c682ddb6"]]);export{Dl as default}; diff --git a/frontend/dist/assets/GroupEdit-yRcPSwSE.js b/frontend/dist/assets/GroupEdit-yRcPSwSE.js new file mode 100644 index 0000000..d9a3a34 --- /dev/null +++ b/frontend/dist/assets/GroupEdit-yRcPSwSE.js @@ -0,0 +1,9 @@ +import{c as M,_ as G,H as O,o as d,a as m,b as l,t as a,w as o,v as i,G as L,u as k,h as _,g as B,I as J,r as y,p as q,l as S,q as D,m as C,i as A,f as H,E as R,F as P,x as E,J as K,k as Q,y as z,z as W,C as F}from"./index-DNAVmyB5.js";import{U as X}from"./users-DG0hBplh.js";import{S as Y}from"./sparkles-0MI5vUsR.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 Z=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"}]]),h={class:"persona-form"},ee={class:"muted"},le={class:"sec"},te={class:"row"},ne={class:"f"},se={class:"f"},oe={class:"f"},ie={class:"row"},ae={class:"f"},ue={class:"f"},de={class:"f"},re={class:"sec"},me={class:"row"},ve={class:"f"},be={class:"f"},fe={class:"f"},ge={key:0,class:"sec"},ye={key:1,class:"sec locked"},ke={class:"sec"},pe={class:"row"},ce={class:"f"},xe={class:"f"},Ve={key:0,class:"muted"},we={class:"row actions"},Ue=["disabled"],$e={__name:"PersonaForm",props:{persona:{type:Object,required:!0}},emits:["save","cancel"],setup(x,{emit:T}){const p=x,V=T,n=J({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=y(""),g=y(""),b=y(""),c=y(!1);function w(t){return Array.isArray(t)?t.join(` +`):String(t||"")}function U(t){return t.split(` +`).map(e=>e.trim()).filter(Boolean)}O(()=>p.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=w(t==null?void 0:t.pains),g.value=w(t==null?void 0:t.objections),b.value=w(t==null?void 0:t.negotiation_levers)},{immediate:!0});function N(){V("save",{...n,pains:U(v.value),objections:U(g.value),negotiation_levers:U(b.value)})}return(t,e)=>(d(),m("div",h,[l("h3",null,a(x.persona.name||"New persona"),1),l("p",ee,a(x.persona.profession)+" · "+a(x.persona.personality),1),l("div",le,[e[30]||(e[30]=l("h4",null,"👤 ข้อมูลพื้นฐาน",-1)),l("div",te,[l("div",ne,[e[23]||(e[23]=l("label",null,"ชื่อ",-1)),o(l("input",{"onUpdate:modelValue":e[0]||(e[0]=s=>n.name=s)},null,512),[[i,n.name]])]),l("div",se,[e[24]||(e[24]=l("label",null,"ระดับความยาก (1-5)",-1)),o(l("input",{"onUpdate:modelValue":e[1]||(e[1]=s=>n.difficulty=s),type:"number",min:"1",max:"5"},null,512),[[i,n.difficulty,void 0,{number:!0}]])]),l("div",oe,[e[26]||(e[26]=l("label",null,"ระดับลูกค้า",-1)),o(l("select",{"onUpdate:modelValue":e[2]||(e[2]=s=>n.tier=s)},[...e[25]||(e[25]=[l("option",{value:"A"},"A — พร้อมตัดสินใจซื้อ",-1),l("option",{value:"B"},"B — ยังไม่แน่ใจ",-1),l("option",{value:"C"},"C — ไม่สนใจแต่มีปัญหา",-1)])],512),[[L,n.tier]])])]),l("div",ie,[l("div",ae,[e[27]||(e[27]=l("label",null,"อาชีพ",-1)),o(l("input",{"onUpdate:modelValue":e[3]||(e[3]=s=>n.profession=s)},null,512),[[i,n.profession]])]),l("div",ue,[e[28]||(e[28]=l("label",null,"ช่วงอายุ",-1)),o(l("input",{"onUpdate:modelValue":e[4]||(e[4]=s=>n.age_group=s)},null,512),[[i,n.age_group]])]),l("div",de,[e[29]||(e[29]=l("label",null,"พื้นที่",-1)),o(l("input",{"onUpdate:modelValue":e[5]||(e[5]=s=>n.location=s)},null,512),[[i,n.location]])])])]),l("div",re,[e[34]||(e[34]=l("h4",null,"💼 ข้อมูลลูกค้า",-1)),l("div",me,[l("div",ve,[e[31]||(e[31]=l("label",null,"รายได้/สถานะการเงิน",-1)),o(l("input",{"onUpdate:modelValue":e[6]||(e[6]=s=>n.income=s)},null,512),[[i,n.income]])]),l("div",be,[e[32]||(e[32]=l("label",null,"งบประมาณ",-1)),o(l("input",{"onUpdate:modelValue":e[7]||(e[7]=s=>n.budget=s)},null,512),[[i,n.budget]])]),l("div",fe,[e[33]||(e[33]=l("label",null,"ไลฟ์สไตล์",-1)),o(l("input",{"onUpdate:modelValue":e[8]||(e[8]=s=>n.lifestyle=s)},null,512),[[i,n.lifestyle]])])]),e[35]||(e[35]=l("label",null,"ภูมิหลัง",-1)),o(l("textarea",{"onUpdate:modelValue":e[9]||(e[9]=s=>n.background=s)},null,512),[[i,n.background]]),e[36]||(e[36]=l("label",null,"บุคลิก / วิธีพูดคุย",-1)),o(l("textarea",{"onUpdate:modelValue":e[10]||(e[10]=s=>n.personality=s)},null,512),[[i,n.personality]]),e[37]||(e[37]=l("label",null,"สไตล์การสื่อสาร",-1)),o(l("textarea",{"onUpdate:modelValue":e[11]||(e[11]=s=>n.communication_style=s)},null,512),[[i,n.communication_style]])]),k(_).isSuperAdmin?(d(),m("div",ge,[e[38]||(e[38]=l("h4",null,"🎯 การขาย",-1)),e[39]||(e[39]=l("label",null,"เป้าหมาย",-1)),o(l("textarea",{"onUpdate:modelValue":e[12]||(e[12]=s=>n.goal=s)},null,512),[[i,n.goal]]),e[40]||(e[40]=l("label",null,"กรอบเวลาในการตัดสินใจ",-1)),o(l("input",{"onUpdate:modelValue":e[13]||(e[13]=s=>n.decision_timeline=s)},null,512),[[i,n.decision_timeline]]),e[41]||(e[41]=l("label",null,"Pain (ปัญหา) แบบ 1 ต่อบรรทัด",-1)),o(l("textarea",{"onUpdate:modelValue":e[14]||(e[14]=s=>v.value=s),placeholder:`เช่น ต้นทุนสูงเกินไป +ระบบล้าสมัย`},null,512),[[i,v.value]]),e[42]||(e[42]=l("label",null,"ข้อโต้แย้ง (Objections) 1 ต่อบรรทัด",-1)),o(l("textarea",{"onUpdate:modelValue":e[15]||(e[15]=s=>g.value=s)},null,512),[[i,g.value]]),e[43]||(e[43]=l("label",null,"สิ่งที่ใช้ต่อรอง (เช่น ส่วนลด, ฟรีติดตั้ง)",-1)),o(l("textarea",{"onUpdate:modelValue":e[16]||(e[16]=s=>b.value=s)},null,512),[[i,b.value]]),e[44]||(e[44]=l("label",null,"ช่องทางสำหรับลูกค้าเปิดบทสนทนา (opener)",-1)),o(l("textarea",{"onUpdate:modelValue":e[17]||(e[17]=s=>n.opener=s)},null,512),[[i,n.opener]])])):(d(),m("div",ye,[e[45]||(e[45]=l("h4",null,"🔒 ส่วนสูตรการขาย",-1)),e[46]||(e[46]=l("p",{class:"muted"},"ปิดการแก้ไข — เฉพาะผู้ดูแลระดับสูงเท่านั้นที่ดู/แก้ได้ (เพื่อรักษาความได้เปรียบ)",-1)),e[47]||(e[47]=l("label",null,"เป้าหมาย",-1)),o(l("textarea",{"onUpdate:modelValue":e[18]||(e[18]=s=>n.goal=s)},null,512),[[i,n.goal]]),e[48]||(e[48]=l("label",null,"กรอบเวลาในการตัดสินใจ",-1)),o(l("input",{"onUpdate:modelValue":e[19]||(e[19]=s=>n.decision_timeline=s)},null,512),[[i,n.decision_timeline]])])),l("div",ke,[e[53]||(e[53]=l("h4",null,"📡 ช่องทาง & โหมด",-1)),l("div",pe,[l("div",ce,[e[50]||(e[50]=l("label",null,"ช่องทาง",-1)),o(l("select",{"onUpdate:modelValue":e[20]||(e[20]=s=>n.channel=s)},[...e[49]||(e[49]=[l("option",{value:"line"},"LINE",-1),l("option",{value:"facebook"},"Facebook",-1)])],512),[[L,n.channel]])]),l("div",xe,[e[52]||(e[52]=l("label",null,"ใครเริ่มคุยก่อน",-1)),o(l("select",{"onUpdate:modelValue":e[21]||(e[21]=s=>n.initiation_mode=s)},[...e[51]||(e[51]=[l("option",{value:"customer"},"ลูกค้าทักก่อน",-1),l("option",{value:"seller"},"เราต้องเริ่มขาย (เชิงรุก)",-1)])],512),[[L,n.initiation_mode]])])]),k(_).isSuperAdmin&&n.special?(d(),m("label",Ve,"พิเศษ: "+a(n.special),1)):B("",!0)]),l("div",we,[l("button",{class:"primary",onClick:N,disabled:c.value},a(c.value?"กำลังบันทึก…":"💾 บันทึกบุคคลต้นแบบ"),9,Ue),l("button",{onClick:e[22]||(e[22]=s=>t.$emit("cancel"))},"ปิด")])]))}},Ce=G($e,[["__scopeId","data-v-cef61bd8"]]),Be={class:"row",style:{"align-items":"center","margin-bottom":"8px"}},Se={style:{margin:"0"}},Ae={class:"row",style:{"margin-left":"auto",gap:"10px"}},ze=["disabled"],Ne={key:0,class:"spinner"},je={key:0,class:"muted"},Le={key:1,class:"error",style:{margin:"12px 0"}},Pe={key:2,class:"card empty-state"},Ee={class:"grid"},Te={class:"row",style:{"justify-content":"space-between","align-items":"center"}},_e={class:"diff"},Fe={class:"muted"},Ge={class:"muted"},Ie=["onClick"],Me={class:"modal"},Oe={__name:"GroupEdit",setup(x){const p=Q().params.gid,V=y(null),n=y([]),v=y(!1),g=y(""),b=y(null);async function c(){const u=await z.getGroup(p);V.value=u.group,n.value=(await z.listPersonas(p)).personas}function w(u){return n.value.filter(r=>r.tier===u)}function U(u){return A.t(u==="A"?"tierA":u==="B"?"tierB":"tierC")}function N(u){return{A:"tier-a",B:"tier-b",C:"tier-c"}[u]||""}async function t(){v.value=!0,g.value="";try{const u=await z.analyzeGroup(p,{append:!0});n.value=u.personas,await c()}catch(u){g.value=u.message}finally{v.value=!1}}function e(u){b.value=JSON.parse(JSON.stringify(u))}async function s(u){v.value=!0;try{await z.updatePersona(p,b.value.id,u),b.value=null,await c()}catch(r){g.value=r.message}finally{v.value=!1}}return q(c),(u,r)=>{const I=W("router-link");return d(),m("div",null,[S(I,{to:"/training",class:"btn-back"},{default:D(()=>[C("← "+a(k(A).t("training")),1)]),_:1}),l("div",Be,[l("h2",Se,[S(k(X),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),C(" "+a(k(A).t("managePersonas")),1)]),l("div",Ae,[l("button",{onClick:t,disabled:v.value,class:"primary"},[v.value?(d(),m("span",Ne)):(d(),H(k(Y),{key:1,size:18,"stroke-width":1.8})),C(" "+a(v.value?"กำลังสร้าง…":"สร้างบุคคลต้นแบบเพิ่มเติม"),1)],8,ze)])]),V.value?(d(),m("p",je,[r[2]||(r[2]=C("สินค้า: ",-1)),l("strong",null,a(V.value.title),1)])):B("",!0),g.value?(d(),m("div",Le,a(g.value),1)):B("",!0),r[4]||(r[4]=R('
วิธีใช้หน้านี้
  1. ระบบสร้างบุคคลต้นแบบ (ลูกค้าจำลอง) ให้อัตโนมัติแล้ว หลังกดสร้าง
  2. แต่ละการ์ดคือลูกค้าจำลอง 1 คน — กด ✏️ แก้ไข เพื่อปรับรายละเอียดให้ตรงใจ
  3. อยากได้ลูกค้าจำลองเพิ่ม กด สร้างบุคคลต้นแบบเพิ่มเติม
  4. เมื่อพร้อมให้ผู้ใช้ฝึก กด เปิดใช้งาน (ยังทำในขั้นถัดไป)
',1)),n.value.length===0&&!v.value?(d(),m("div",Pe,[...r[3]||(r[3]=[l("strong",null,"ยังไม่มีบุคคลต้นแบบ",-1),l("span",null,'กด "สร้างบุคคลต้นแบบเพิ่มเติม" เพื่อให้ระบบสร้างลูกค้าจำลองให้',-1)])])):B("",!0),(d(),m(P,null,E(["A","B","C"],$=>l("div",{key:$,style:{"margin-bottom":"20px"}},[l("h4",null,a(U($)),1),l("div",Ee,[(d(!0),m(P,null,E(w($),f=>(d(),m("div",{key:f.id,class:"card pcard"},[l("div",Te,[l("strong",null,a(f.name),1),l("span",{class:F(["badge",N(f.tier)])},a(f.tier),3)]),l("div",_e,[(d(),m(P,null,E(5,j=>l("span",{key:j,class:F(["star",{on:j<=(f.difficulty||1)}])},"★",2)),64))]),l("div",Fe,a(f.profession)+" · "+a(f.age_group)+" · "+a(f.location),1),l("div",Ge,a(f.channel)+" · "+a(f.initiation_mode==="seller"?"เริ่มขายเอง":"ลูกค้าทักก่อน"),1),l("button",{class:"edit-btn",onClick:j=>e(f)},[S(k(Z),{size:15,"stroke-width":1.8}),C(" "+a(k(A).t("edit")),1)],8,Ie)]))),128))])])),64)),b.value?(d(),m("div",{key:3,class:"modal-backdrop",onClick:r[1]||(r[1]=K($=>b.value=null,["self"]))},[l("div",Me,[S(Ce,{persona:b.value,onSave:s,onCancel:r[0]||(r[0]=$=>b.value=null)},null,8,["persona"])])])):B("",!0)])}}},He=G(Oe,[["__scopeId","data-v-5517bbc4"]]);export{He as default}; diff --git a/frontend/dist/assets/Guide-PlIFuOuL.js b/frontend/dist/assets/Guide-B9uxl_Jn.js similarity index 95% rename from frontend/dist/assets/Guide-PlIFuOuL.js rename to frontend/dist/assets/Guide-B9uxl_Jn.js index 01f01fe..4cda4a7 100644 --- a/frontend/dist/assets/Guide-PlIFuOuL.js +++ b/frontend/dist/assets/Guide-B9uxl_Jn.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,E as u,o as d}from"./index-Cr5KFgzf.js";import{B as g}from"./book-open-Yn_Ya1If.js";import{L as p}from"./layout-dashboard-D4d1g6OI.js";import{T as m}from"./target-CUBX0ybh.js";import{S as y}from"./sparkles-CWO9Md85.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,E as u,o as d}from"./index-DNAVmyB5.js";import{B as g}from"./book-open-o2ZLc9m6.js";import{L as p}from"./layout-dashboard-dl9BywAR.js";import{T as m}from"./target-BYJn0LyF.js";import{S as y}from"./sparkles-0MI5vUsR.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-B1BFq5lO.js b/frontend/dist/assets/Login-O3bvqwVm.js similarity index 98% rename from frontend/dist/assets/Login-B1BFq5lO.js rename to frontend/dist/assets/Login-O3bvqwVm.js index 420d681..9be7322 100644 --- a/frontend/dist/assets/Login-B1BFq5lO.js +++ b/frontend/dist/assets/Login-O3bvqwVm.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-Cr5KFgzf.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-DNAVmyB5.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-C2XKgiYF.js b/frontend/dist/assets/MyBoard-Daz4fzbD.js similarity index 94% rename from frontend/dist/assets/MyBoard-C2XKgiYF.js rename to frontend/dist/assets/MyBoard-Daz4fzbD.js index 9c1c2b6..94f64f1 100644 --- a/frontend/dist/assets/MyBoard-C2XKgiYF.js +++ b/frontend/dist/assets/MyBoard-Daz4fzbD.js @@ -1 +1 @@ -import{_ as f,p as x,y 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,A as N,F as D,x as S,r as m,B as c,o as i,C as T}from"./index-Cr5KFgzf.js";import{L as V}from"./layout-dashboard-D4d1g6OI.js";const C={style:{margin:"0"}},L={key:0,class:"row stat-row"},M={class:"card stat"},z={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=m([]),u=m([]),p=m(!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{p.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",C,[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))]),p.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",z,[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)])])),p.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,p as x,y 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,A as N,F as D,x as S,r as m,B as c,o as i,C as T}from"./index-DNAVmyB5.js";import{L as V}from"./layout-dashboard-dl9BywAR.js";const C={style:{margin:"0"}},L={key:0,class:"row stat-row"},M={class:"card stat"},z={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=m([]),u=m([]),p=m(!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{p.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",C,[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))]),p.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",z,[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)])])),p.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-C1zt2UAW.js b/frontend/dist/assets/Personas-B92S9P5I.js similarity index 95% rename from frontend/dist/assets/Personas-C1zt2UAW.js rename to frontend/dist/assets/Personas-B92S9P5I.js index 014f068..1ee1940 100644 --- a/frontend/dist/assets/Personas-C1zt2UAW.js +++ b/frontend/dist/assets/Personas-B92S9P5I.js @@ -1 +1 @@ -import{_ as T,p as $,a as r,l as d,q 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,x as p,k as L,y as N,z as V,r as b,o as a,S as I,C as h,f as A}from"./index-Cr5KFgzf.js";import{T as S}from"./target-CUBX0ybh.js";import{A as F}from"./arrow-left-D6PVN9vk.js";const j={class:"row",style:{"align-items":"center"}},q={style:{margin:"0"}},D={class:"muted",style:{"margin-left":"auto"}},E={key:0,class:"card guide"},M={key:1,class:"row",style:{margin:"12px 0",gap:"10px"}},R={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([]),C=b(!0);async function B(){try{k.value=(await N.listPersonas(c)).personas}finally{C.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 $(B),(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",q,s(e(n).t("personas")),1),t("span",D,s(e(n).t("selectPersona")),1)]),e(f).isAdmin?w("",!0):(a(),r("div",E,[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",M,[d(u,{to:`/admin/groups/${e(c)}/edit`},{default:m(()=>[t("button",R,[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,p as $,a as r,l as d,q 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,x as p,k as L,y as N,z as V,r as b,o as a,S as I,C as h,f as A}from"./index-DNAVmyB5.js";import{T as S}from"./target-BYJn0LyF.js";import{A as F}from"./arrow-left-6ACDPyW9.js";const j={class:"row",style:{"align-items":"center"}},q={style:{margin:"0"}},D={class:"muted",style:{"margin-left":"auto"}},E={key:0,class:"card guide"},M={key:1,class:"row",style:{margin:"12px 0",gap:"10px"}},R={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([]),C=b(!0);async function B(){try{k.value=(await N.listPersonas(c)).personas}finally{C.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 $(B),(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",q,s(e(n).t("personas")),1),t("span",D,s(e(n).t("selectPersona")),1)]),e(f).isAdmin?w("",!0):(a(),r("div",E,[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",M,[d(u,{to:`/admin/groups/${e(c)}/edit`},{default:m(()=>[t("button",R,[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-C6ZMNY2Q.js b/frontend/dist/assets/Settings-C4qI85q5.js similarity index 98% rename from frontend/dist/assets/Settings-C6ZMNY2Q.js rename to frontend/dist/assets/Settings-C4qI85q5.js index 8b74546..2a5f9b7 100644 --- a/frontend/dist/assets/Settings-C6ZMNY2Q.js +++ b/frontend/dist/assets/Settings-C4qI85q5.js @@ -1,4 +1,4 @@ -import{c as U,_ 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,C as S,y as M,o as g}from"./index-Cr5KFgzf.js";import{L as D}from"./lock-CERtIuBL.js";/** +import{c as U,_ 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,C as S,y as M,o as g}from"./index-DNAVmyB5.js";import{L as D}from"./lock-fipmYN8_.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-DlVOMLoz.js b/frontend/dist/assets/Setup-H6LANAbr.js similarity index 94% rename from frontend/dist/assets/Setup-DlVOMLoz.js rename to frontend/dist/assets/Setup-H6LANAbr.js index 0704388..aba0ca1 100644 --- a/frontend/dist/assets/Setup-DlVOMLoz.js +++ b/frontend/dist/assets/Setup-H6LANAbr.js @@ -1 +1 @@ -import{_ as U,a as v,b as e,l as K,u as s,m,t as a,i as l,h as _,w,v as b,d as h,n as N,g as T,r,j as B,o as f}from"./index-Cr5KFgzf.js";import{L as C}from"./lock-CERtIuBL.js";const M={class:"setup-wrap"},D={class:"card setup-card"},L={class:"muted"},P={class:"terms"},j={key:0,class:"error",role:"alert"},z=["disabled"],E={key:0,class:"spinner"},I={key:1},V="/legal",R={__name:"Setup",setup(q){const S=B(),i=r(""),o=r(""),p=r(""),y=r(!1),u=r(""),d=r(!1);async function c(){if(u.value="",o.value.length<4){u.value=l.t("passwordTooShort");return}if(o.value!==p.value){u.value=l.t("passwordMismatch");return}d.value=!0;try{await _.finishSetup(i.value.trim(),o.value,!0),S.push("/")}catch(k){u.value=k.message}finally{d.value=!1}}return(k,t)=>{var g,x;return f(),v("div",M,[e("div",D,[e("h1",null,[K(s(C),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),m(" "+a(s(l).t("setupTitle")),1)]),e("p",L,[m(a(s(l).t("setupSubtitle"))+" ",1),e("strong",null,a(((g=s(_).user)==null?void 0:g.name)||((x=s(_).user)==null?void 0:x.username)),1)]),e("label",null,a(s(l).t("email")),1),w(e("input",{"onUpdate:modelValue":t[0]||(t[0]=n=>i.value=n),type:"email",autocomplete:"email",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,i.value]]),e("label",null,a(s(l).t("newPassword")),1),w(e("input",{"onUpdate:modelValue":t[1]||(t[1]=n=>o.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,o.value]]),e("label",null,a(s(l).t("confirmPassword")),1),w(e("input",{"onUpdate:modelValue":t[2]||(t[2]=n=>p.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,p.value]]),e("label",P,[w(e("input",{"onUpdate:modelValue":t[3]||(t[3]=n=>y.value=n),type:"checkbox"},null,512),[[N,y.value]]),e("span",null,[t[4]||(t[4]=m("ฉันยอมรับ ",-1)),e("a",{href:V,target:"_blank",rel:"noopener"},"ข้อกำหนดการใช้งาน"),t[5]||(t[5]=m(" และ ",-1)),e("a",{href:V,target:"_blank",rel:"noopener"},"นโยบายความเป็นส่วนตัว")])]),u.value?(f(),v("div",j,a(u.value),1)):T("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!o.value||o.value!==p.value||!y.value,onClick:c},[d.value?(f(),v("span",E)):(f(),v("span",I,a(s(l).t("save")),1))],8,z)])])}}},G=U(R,[["__scopeId","data-v-a3748c0a"]]);export{G as default}; +import{_ as U,a as v,b as e,l as K,u as s,m,t as a,i as l,h as _,w,v as b,d as h,n as N,g as T,r,j as B,o as f}from"./index-DNAVmyB5.js";import{L as C}from"./lock-fipmYN8_.js";const M={class:"setup-wrap"},D={class:"card setup-card"},L={class:"muted"},P={class:"terms"},j={key:0,class:"error",role:"alert"},z=["disabled"],E={key:0,class:"spinner"},I={key:1},V="/legal",R={__name:"Setup",setup(q){const S=B(),i=r(""),o=r(""),p=r(""),y=r(!1),u=r(""),d=r(!1);async function c(){if(u.value="",o.value.length<4){u.value=l.t("passwordTooShort");return}if(o.value!==p.value){u.value=l.t("passwordMismatch");return}d.value=!0;try{await _.finishSetup(i.value.trim(),o.value,!0),S.push("/")}catch(k){u.value=k.message}finally{d.value=!1}}return(k,t)=>{var g,x;return f(),v("div",M,[e("div",D,[e("h1",null,[K(s(C),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),m(" "+a(s(l).t("setupTitle")),1)]),e("p",L,[m(a(s(l).t("setupSubtitle"))+" ",1),e("strong",null,a(((g=s(_).user)==null?void 0:g.name)||((x=s(_).user)==null?void 0:x.username)),1)]),e("label",null,a(s(l).t("email")),1),w(e("input",{"onUpdate:modelValue":t[0]||(t[0]=n=>i.value=n),type:"email",autocomplete:"email",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,i.value]]),e("label",null,a(s(l).t("newPassword")),1),w(e("input",{"onUpdate:modelValue":t[1]||(t[1]=n=>o.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,o.value]]),e("label",null,a(s(l).t("confirmPassword")),1),w(e("input",{"onUpdate:modelValue":t[2]||(t[2]=n=>p.value=n),type:"password",autocomplete:"new-password",class:"wide",onKeyup:h(c,["enter"])},null,544),[[b,p.value]]),e("label",P,[w(e("input",{"onUpdate:modelValue":t[3]||(t[3]=n=>y.value=n),type:"checkbox"},null,512),[[N,y.value]]),e("span",null,[t[4]||(t[4]=m("ฉันยอมรับ ",-1)),e("a",{href:V,target:"_blank",rel:"noopener"},"ข้อกำหนดการใช้งาน"),t[5]||(t[5]=m(" และ ",-1)),e("a",{href:V,target:"_blank",rel:"noopener"},"นโยบายความเป็นส่วนตัว")])]),u.value?(f(),v("div",j,a(u.value),1)):T("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!o.value||o.value!==p.value||!y.value,onClick:c},[d.value?(f(),v("span",E)):(f(),v("span",I,a(s(l).t("save")),1))],8,z)])])}}},G=U(R,[["__scopeId","data-v-a3748c0a"]]);export{G as default}; diff --git a/frontend/dist/assets/Training-DuHwYoAr.js b/frontend/dist/assets/Training-DTTvOhJR.js similarity index 93% rename from frontend/dist/assets/Training-DuHwYoAr.js rename to frontend/dist/assets/Training-DTTvOhJR.js index eb307f1..d7b91f9 100644 --- a/frontend/dist/assets/Training-DuHwYoAr.js +++ b/frontend/dist/assets/Training-DTTvOhJR.js @@ -1 +1 @@ -import{_ as w,p as b,y as C,h as d,a as l,b as t,l as c,u as s,m as _,t as e,i as n,q as m,f as h,g as f,F as z,x as A,r as g,z as B,o as i,C as k}from"./index-Cr5KFgzf.js";import{T as N}from"./target-CUBX0ybh.js";import{B as P}from"./book-open-Yn_Ya1If.js";import{P as T}from"./plus-zB_wweS5.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"},q={key:0,class:"card",style:{"min-height":"80px"}},D={key:1,class:"card empty-state"},E={key:0},G={key:1},I={class:"grid"},M={class:"card lift train-card"},O={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 C.listGroups()).groups||[];u.value=d.isAdmin?a:a.filter(r=>r.status==="ready")}finally{y.value=!1}}),(a,r)=>{const p=B("router-link");return i(),l("div",null,[t("div",V,[t("h2",F,[c(s(N),{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(P),{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(T),{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",q,[...r[1]||(r[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):u.value.length===0?(i(),l("div",D,[t("strong",null,e(s(n).t("noTraining")),1),s(d).isAdmin?(i(),l("span",E,'Click "'+e(s(n).t("addProduct"))+'" to create a persona group.',1)):(i(),l("span",G,"Ask an admin to create a persona group first."))])):f("",!0),t("div",I,[(i(!0),l(z,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",M,[t("div",O,[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,p as b,y as C,h as d,a as l,b as t,l as c,u as s,m as _,t as e,i as n,q as m,f as h,g as f,F as z,x as A,r as g,z as B,o as i,C as k}from"./index-DNAVmyB5.js";import{T as N}from"./target-BYJn0LyF.js";import{B as P}from"./book-open-o2ZLc9m6.js";import{P as T}from"./plus-BGdYQyzB.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"},q={key:0,class:"card",style:{"min-height":"80px"}},D={key:1,class:"card empty-state"},E={key:0},G={key:1},I={class:"grid"},M={class:"card lift train-card"},O={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 C.listGroups()).groups||[];u.value=d.isAdmin?a:a.filter(r=>r.status==="ready")}finally{y.value=!1}}),(a,r)=>{const p=B("router-link");return i(),l("div",null,[t("div",V,[t("h2",F,[c(s(N),{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(P),{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(T),{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",q,[...r[1]||(r[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):u.value.length===0?(i(),l("div",D,[t("strong",null,e(s(n).t("noTraining")),1),s(d).isAdmin?(i(),l("span",E,'Click "'+e(s(n).t("addProduct"))+'" to create a persona group.',1)):(i(),l("span",G,"Ask an admin to create a persona group first."))])):f("",!0),t("div",I,[(i(!0),l(z,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",M,[t("div",O,[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-D6PVN9vk.js b/frontend/dist/assets/arrow-left-6ACDPyW9.js similarity index 86% rename from frontend/dist/assets/arrow-left-D6PVN9vk.js rename to frontend/dist/assets/arrow-left-6ACDPyW9.js index c169750..7383561 100644 --- a/frontend/dist/assets/arrow-left-D6PVN9vk.js +++ b/frontend/dist/assets/arrow-left-6ACDPyW9.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-Cr5KFgzf.js";/** +import{c as e}from"./index-DNAVmyB5.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-Yn_Ya1If.js b/frontend/dist/assets/book-open-o2ZLc9m6.js similarity index 90% rename from frontend/dist/assets/book-open-Yn_Ya1If.js rename to frontend/dist/assets/book-open-o2ZLc9m6.js index 9df19c9..c37aa40 100644 --- a/frontend/dist/assets/book-open-Yn_Ya1If.js +++ b/frontend/dist/assets/book-open-o2ZLc9m6.js @@ -1,4 +1,4 @@ -import{c as a}from"./index-Cr5KFgzf.js";/** +import{c as a}from"./index-DNAVmyB5.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-Cr5KFgzf.js b/frontend/dist/assets/index-DNAVmyB5.js similarity index 86% rename from frontend/dist/assets/index-Cr5KFgzf.js rename to frontend/dist/assets/index-DNAVmyB5.js index 8a402d3..1b94010 100644 --- a/frontend/dist/assets/index-Cr5KFgzf.js +++ b/frontend/dist/assets/index-DNAVmyB5.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Login-B1BFq5lO.js","assets/Login-j4sHK_z1.css","assets/Setup-DlVOMLoz.js","assets/lock-CERtIuBL.js","assets/Setup-BHyRmSn1.css","assets/Analytics-CWNDFvS5.js","assets/users-CknR8rxo.js","assets/plus-zB_wweS5.js","assets/Analytics-CQgwlaXy.css","assets/MyBoard-C2XKgiYF.js","assets/layout-dashboard-D4d1g6OI.js","assets/MyBoard-B7ypQ1JT.css","assets/Training-DuHwYoAr.js","assets/target-CUBX0ybh.js","assets/book-open-Yn_Ya1If.js","assets/Training-BYBkfggr.css","assets/Personas-C1zt2UAW.js","assets/arrow-left-D6PVN9vk.js","assets/Personas-CskAUbZv.css","assets/Chat-BUYTG6XR.js","assets/Chat-Dhsij-Zk.css","assets/Settings-C6ZMNY2Q.js","assets/Settings-BCN2EZQ5.css","assets/Guide-PlIFuOuL.js","assets/sparkles-CWO9Md85.js","assets/GroupBuilder-j2iPAhDG.js","assets/GroupBuilder-CpbMByEC.css","assets/GroupEdit-CJ3zMSjJ.js","assets/GroupEdit-CFwXUOV9.css","assets/AdminUsers-Bev0W-FI.js","assets/AdminUsers-B0sl0uHS.css"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/Login-O3bvqwVm.js","assets/Login-j4sHK_z1.css","assets/Setup-H6LANAbr.js","assets/lock-fipmYN8_.js","assets/Setup-BHyRmSn1.css","assets/Analytics-C8V4bNnH.js","assets/users-DG0hBplh.js","assets/plus-BGdYQyzB.js","assets/Analytics-CQgwlaXy.css","assets/MyBoard-Daz4fzbD.js","assets/layout-dashboard-dl9BywAR.js","assets/MyBoard-B7ypQ1JT.css","assets/Training-DTTvOhJR.js","assets/target-BYJn0LyF.js","assets/book-open-o2ZLc9m6.js","assets/Training-BYBkfggr.css","assets/Personas-B92S9P5I.js","assets/arrow-left-6ACDPyW9.js","assets/Personas-CskAUbZv.css","assets/Chat-5M8zA86W.js","assets/Chat-Dhsij-Zk.css","assets/Settings-C4qI85q5.js","assets/Settings-BCN2EZQ5.css","assets/Guide-B9uxl_Jn.js","assets/sparkles-0MI5vUsR.js","assets/GroupBuilder-0QeIeG8Q.js","assets/GroupBuilder-7-9ZskJo.css","assets/GroupEdit-yRcPSwSE.js","assets/GroupEdit-BdbUBMcX.css","assets/AdminUsers-B98Kq2kn.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-B1BFq5lO. * * 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=Yt({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,n=!1){const s=await Ss.setup({username:this.user.username||this.user.id,email:e,password:t,accepted_terms:n});return this.user=s.user,this.mustSetup=!1,s.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=Yt({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-B1BFq5lO.js"),__vite__mapDeps([0,1])),meta:{public:!0}},{path:"/setup",component:()=>Se(()=>import("./Setup-DlVOMLoz.js"),__vite__mapDeps([2,3,4]))},{path:"/",component:()=>Se(()=>import("./Analytics-CWNDFvS5.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}},{path:"/my/board",component:()=>Se(()=>import("./MyBoard-C2XKgiYF.js"),__vite__mapDeps([9,10,11]))},{path:"/training",component:()=>Se(()=>import("./Training-DuHwYoAr.js"),__vite__mapDeps([12,13,14,7,15]))},{path:"/groups/:gid/personas",component:()=>Se(()=>import("./Personas-C1zt2UAW.js"),__vite__mapDeps([16,13,17,18]))},{path:"/groups/:gid/chat/:pid",component:()=>Se(()=>import("./Chat-BUYTG6XR.js"),__vite__mapDeps([19,13,17,20]))},{path:"/settings",component:()=>Se(()=>import("./Settings-C6ZMNY2Q.js"),__vite__mapDeps([21,3,22]))},{path:"/guide",component:()=>Se(()=>import("./Guide-PlIFuOuL.js"),__vite__mapDeps([23,14,10,13,24]))},{path:"/admin/new-group",component:()=>Se(()=>import("./GroupBuilder-j2iPAhDG.js"),__vite__mapDeps([25,17,26])),meta:{admin:!0}},{path:"/admin/groups/:gid/edit",component:()=>Se(()=>import("./GroupEdit-CJ3zMSjJ.js"),__vite__mapDeps([27,6,24,28])),meta:{admin:!0}},{path:"/admin/users",component:()=>Se(()=>import("./AdminUsers-Bev0W-FI.js"),__vite__mapDeps([29,6,30])),meta:{admin:!0}},{path:"/admin/analytics",component:()=>Se(()=>import("./Analytics-CWNDFvS5.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{Fu as A,Oe as B,jt as C,Zs as D,Vu as E,tt as F,Xc as G,Mn as H,Yt as I,Uu as J,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,Yc as n,on as o,Wi as p,Vt as q,dl as r,Ks as s,Rt as t,ae as u,jr as v,Du as w,Lu as x,Ss as y,_r 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,t={})=>ee("POST",`/api/groups/${e}/analyze${t.append?"?append=true":""}`),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=Yt({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,n=!1){const s=await Ss.setup({username:this.user.username||this.user.id,email:e,password:t,accepted_terms:n});return this.user=s.user,this.mustSetup=!1,s.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=Yt({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-O3bvqwVm.js"),__vite__mapDeps([0,1])),meta:{public:!0}},{path:"/setup",component:()=>Se(()=>import("./Setup-H6LANAbr.js"),__vite__mapDeps([2,3,4]))},{path:"/",component:()=>Se(()=>import("./Analytics-C8V4bNnH.js"),__vite__mapDeps([5,6,7,8])),meta:{admin:!0}},{path:"/my/board",component:()=>Se(()=>import("./MyBoard-Daz4fzbD.js"),__vite__mapDeps([9,10,11]))},{path:"/training",component:()=>Se(()=>import("./Training-DTTvOhJR.js"),__vite__mapDeps([12,13,14,7,15]))},{path:"/groups/:gid/personas",component:()=>Se(()=>import("./Personas-B92S9P5I.js"),__vite__mapDeps([16,13,17,18]))},{path:"/groups/:gid/chat/:pid",component:()=>Se(()=>import("./Chat-5M8zA86W.js"),__vite__mapDeps([19,13,17,20]))},{path:"/settings",component:()=>Se(()=>import("./Settings-C4qI85q5.js"),__vite__mapDeps([21,3,22]))},{path:"/guide",component:()=>Se(()=>import("./Guide-B9uxl_Jn.js"),__vite__mapDeps([23,14,10,13,24]))},{path:"/admin/new-group",component:()=>Se(()=>import("./GroupBuilder-0QeIeG8Q.js"),__vite__mapDeps([25,17,26])),meta:{admin:!0}},{path:"/admin/groups/:gid/edit",component:()=>Se(()=>import("./GroupEdit-yRcPSwSE.js"),__vite__mapDeps([27,6,24,28])),meta:{admin:!0}},{path:"/admin/users",component:()=>Se(()=>import("./AdminUsers-B98Kq2kn.js"),__vite__mapDeps([29,6,30])),meta:{admin:!0}},{path:"/admin/analytics",component:()=>Se(()=>import("./Analytics-C8V4bNnH.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{Fu as A,Oe as B,jt as C,Zs as D,Vu as E,tt as F,Xc as G,Mn as H,Yt as I,Uu as J,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,Yc as n,on as o,Wi as p,Vt as q,dl as r,Ks as s,Rt as t,ae as u,jr as v,Du as w,Lu as x,Ss as y,_r as z}; diff --git a/frontend/dist/assets/layout-dashboard-D4d1g6OI.js b/frontend/dist/assets/layout-dashboard-dl9BywAR.js similarity index 91% rename from frontend/dist/assets/layout-dashboard-D4d1g6OI.js rename to frontend/dist/assets/layout-dashboard-dl9BywAR.js index 367aaed..2f1f9db 100644 --- a/frontend/dist/assets/layout-dashboard-D4d1g6OI.js +++ b/frontend/dist/assets/layout-dashboard-dl9BywAR.js @@ -1,4 +1,4 @@ -import{c as t}from"./index-Cr5KFgzf.js";/** +import{c as t}from"./index-DNAVmyB5.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-CERtIuBL.js b/frontend/dist/assets/lock-fipmYN8_.js similarity index 88% rename from frontend/dist/assets/lock-CERtIuBL.js rename to frontend/dist/assets/lock-fipmYN8_.js index af9530f..9a9833e 100644 --- a/frontend/dist/assets/lock-CERtIuBL.js +++ b/frontend/dist/assets/lock-fipmYN8_.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-Cr5KFgzf.js";/** +import{c as e}from"./index-DNAVmyB5.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-zB_wweS5.js b/frontend/dist/assets/plus-BGdYQyzB.js similarity index 86% rename from frontend/dist/assets/plus-zB_wweS5.js rename to frontend/dist/assets/plus-BGdYQyzB.js index bdd9646..12e360c 100644 --- a/frontend/dist/assets/plus-zB_wweS5.js +++ b/frontend/dist/assets/plus-BGdYQyzB.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-Cr5KFgzf.js";/** +import{c as e}from"./index-DNAVmyB5.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-CWO9Md85.js b/frontend/dist/assets/sparkles-0MI5vUsR.js similarity index 93% rename from frontend/dist/assets/sparkles-CWO9Md85.js rename to frontend/dist/assets/sparkles-0MI5vUsR.js index ba376ba..0e0e88b 100644 --- a/frontend/dist/assets/sparkles-CWO9Md85.js +++ b/frontend/dist/assets/sparkles-0MI5vUsR.js @@ -1,4 +1,4 @@ -import{c as a}from"./index-Cr5KFgzf.js";/** +import{c as a}from"./index-DNAVmyB5.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-CUBX0ybh.js b/frontend/dist/assets/target-BYJn0LyF.js similarity index 90% rename from frontend/dist/assets/target-CUBX0ybh.js rename to frontend/dist/assets/target-BYJn0LyF.js index 4c00f0b..fe44ddc 100644 --- a/frontend/dist/assets/target-CUBX0ybh.js +++ b/frontend/dist/assets/target-BYJn0LyF.js @@ -1,4 +1,4 @@ -import{c}from"./index-Cr5KFgzf.js";/** +import{c}from"./index-DNAVmyB5.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-CknR8rxo.js b/frontend/dist/assets/users-DG0hBplh.js similarity index 90% rename from frontend/dist/assets/users-CknR8rxo.js rename to frontend/dist/assets/users-DG0hBplh.js index 04a470d..fbd898b 100644 --- a/frontend/dist/assets/users-CknR8rxo.js +++ b/frontend/dist/assets/users-DG0hBplh.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-Cr5KFgzf.js";/** +import{c as e}from"./index-DNAVmyB5.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 0dc1874..d97c7b3 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -4,7 +4,7 @@ Sales Trainer - + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 0e5f504..98e70b8 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -43,7 +43,7 @@ export const api = { createGroup: (formData) => request('POST', '/api/groups', formData, true), listGroups: () => request('GET', '/api/groups'), getGroup: (id) => request('GET', `/api/groups/${id}`), - analyzeGroup: (id) => request('POST', `/api/groups/${id}/analyze`), + analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`), listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`), getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`), updatePersona: (gid, pid, b) => request('PUT', `/api/groups/${gid}/personas/${pid}`, b), diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js index 5fa03db..ac66ea4 100644 --- a/frontend/src/i18n/index.js +++ b/frontend/src/i18n/index.js @@ -139,7 +139,7 @@ const messages = { analyze: 'วิเคราะห์', manual: 'ระบุเอง', edit: 'แก้ไข', - product: 'สินค้า/บริการ', + product: 'สินค้า/บริการ/ไอเดีย', segment: 'กลุ่มลูกค้าเป้าหมายเบื้องต้น (ไม่บังคับ)', description: 'คำอธิบายหรือรายละเอียดเพิ่มเติม (ไม่บังคับ)', channel: 'ช่องทางติดต่อ', diff --git a/frontend/src/views/GroupBuilder.vue b/frontend/src/views/GroupBuilder.vue index c2fa05c..7c06cee 100644 --- a/frontend/src/views/GroupBuilder.vue +++ b/frontend/src/views/GroupBuilder.vue @@ -6,46 +6,36 @@
วิธีสร้างกลุ่มบุคคลต้นแบบ
    -
  1. กรอก ชื่อ/รายละเอียดสินค้าหรือบริการ ที่อยากให้ทีมฝึกขาย (ช่องด้านล่าง)
  2. -
  3. (ไม่บังคับ) ระบุกลุ่มลูกค้าเป้าหมาย หรืออัปโหลดไฟล์เอกสารสินค้า .pdf/.md/.txt
  4. -
  5. กด สร้าง — ระบบจะพาไปหน้าสร้างบุคคลต้นแบบ (ลูกค้าจำลอง)
  6. +
  7. กรอก สินค้า / บริการ / ไอเดีย ที่อยากให้ทีมฝึกนำเสนอ (ช่องด้านล่าง)
  8. +
  9. (ไม่บังคับ) ระบุกลุ่มเป้าหมาย หรืออัปโหลดไฟล์เอกสาร .pdf/.md/.txt
  10. +
  11. กด สร้าง — ระบบจะสร้างบุคคลต้นแบบ (ลูกค้าจำลอง) ให้ทันที
  12. +
  13. ถ้าอยากได้บุคคลต้นแบบเพิ่ม ไปที่หน้านั้นแล้วกด สร้างบุคคลต้นแบบเพิ่มเติม

{{ i18n.t('addProduct') }}

- + - + -
-
- - -
-
- - -
-
+ + - +
{{ error }}
@@ -59,7 +49,7 @@ import { api } from '../api' import { i18n } from '../i18n' const router = useRouter() -const form = ref({ product: '', segment: '', description: '', channel: 'facebook', language: 'th' }) +const form = ref({ product: '', segment: '', description: '', language: 'th' }) const files = ref([]) const error = ref('') const busy = ref(false) @@ -75,11 +65,20 @@ async function create() { fd.append('product', form.value.product) fd.append('segment', form.value.segment) fd.append('description', form.value.description) - fd.append('channel', form.value.channel) fd.append('language', form.value.language) files.value.forEach((f) => fd.append('files', f)) const data = await api.createGroup(fd) const gid = data.group.id + // Auto-generate personas right away (no separate Analyze button needed). + try { + await api.analyzeGroup(gid) + } catch (analyzeErr) { + // Even if analyze fails (e.g. LLM key missing), still go to the edit page so the + // admin sees the group + an error they can act on. + error.value = analyzeErr.message + router.push(`/admin/groups/${gid}/edit`) + return + } router.push(`/admin/groups/${gid}/edit`) } catch (e) { error.value = e.message diff --git a/frontend/src/views/GroupEdit.vue b/frontend/src/views/GroupEdit.vue index 35767be..e43092e 100644 --- a/frontend/src/views/GroupEdit.vue +++ b/frontend/src/views/GroupEdit.vue @@ -5,7 +5,7 @@

{{ i18n.t('managePersonas') }}

@@ -17,15 +17,16 @@
วิธีใช้หน้านี้
    -
  1. กดปุ่ม ✨ สร้าง/วิเคราะห์บุคคลต้นแบบ เพื่อให้ระบบสร้างลูกค้าจำลอง 15 คน (ระดับ A/B/C)
  2. +
  3. ระบบสร้างบุคคลต้นแบบ (ลูกค้าจำลอง) ให้อัตโนมัติแล้ว หลังกดสร้าง
  4. แต่ละการ์ดคือลูกค้าจำลอง 1 คน — กด ✏️ แก้ไข เพื่อปรับรายละเอียดให้ตรงใจ
  5. +
  6. อยากได้ลูกค้าจำลองเพิ่ม กด สร้างบุคคลต้นแบบเพิ่มเติม
  7. เมื่อพร้อมให้ผู้ใช้ฝึก กด เปิดใช้งาน (ยังทำในขั้นถัดไป)
ยังไม่มีบุคคลต้นแบบ - กด "✨ สร้าง/วิเคราะห์บุคคลต้นแบบ" เพื่อให้ระบบสร้างลูกค้าจำลองให้ + กด "สร้างบุคคลต้นแบบเพิ่มเติม" เพื่อให้ระบบสร้างลูกค้าจำลองให้
@@ -83,7 +84,8 @@ function tierClass(t) { return { A: 'tier-a', B: 'tier-b', C: 'tier-c' }[t] || ' async function analyze() { busy.value = true; error.value = '' try { - const data = await api.analyzeGroup(gid) + // Append more personas (keeps existing ones), per the 'create more personas' flow. + const data = await api.analyzeGroup(gid, { append: true }) personas.value = data.personas await load() } catch (e) { error.value = e.message }