feat(training): delete group button (admin only) on Training page

- Backend DELETE /api/groups/<gid> (admin, own-org enforced via _get_owned_group);
  cascades removal of the group's sessions. GroupStore.delete added.
- Training.vue: each group card gets a trash (Trash2 line icon) delete button shown only
  to admins (auth.isAdmin); confirm dialog; @click.stop so it doesn't navigate. Card
  restructured so the delete button is not inside the clickable router-link.
- test_user_journey: trainee cannot delete (403), admin deletes, group gone (404).
All suites pass. Rebuilt dist.
This commit is contained in:
Macky
2026-08-09 10:20:10 +07:00
parent 0b92430aab
commit 5b167b4546
30 changed files with 114 additions and 43 deletions

View File

@@ -324,6 +324,28 @@ def update_persona(gid: str, pid: str):
return jsonify({"persona": strip_secret_fields(full)}) return jsonify({"persona": strip_secret_fields(full)})
@groups_bp.delete("/<gid>")
@require_auth
@require_roles("admin")
def delete_group(gid: str):
"""Delete a persona group (admin only, own org)."""
s = _stores()
group = _get_owned_group(s, gid) # also enforces tenant org (super_admin global)
s["groups"].delete(gid)
# cascade: also remove finished/active sessions for this group's personas
try:
sess = s.get("session_store")
if sess and hasattr(sess, "sessions"):
for r in sess.sessions.all():
if r.get("group_id") == gid:
key = r.get("id") or r.get("sid")
if key:
sess.sessions.delete(key)
except Exception:
pass
return jsonify({"ok": True, "deleted": gid})
@groups_bp.post("/<gid>/reanalyze") @groups_bp.post("/<gid>/reanalyze")
@require_auth @require_auth
@require_roles("admin") @require_roles("admin")

View File

@@ -95,3 +95,7 @@ class GroupStore:
if not found: if not found:
raise ValueError("persona not found") raise ValueError("persona not found")
return self.groups.replace(gid, group) return self.groups.replace(gid, group)
def delete(self, gid: str) -> None:
"""Hard-delete a group (personas/report included)."""
self.groups.delete(gid)

View File

@@ -93,4 +93,14 @@ after = len(r.get_json()["personas"])
assert after > before, f"append should add personas (before={before}, after={after})" assert after > before, f"append should add personas (before={before}, after={after})"
print(f"[ok] append adds personas ({before} -> {after}); existing kept") print(f"[ok] append adds personas ({before} -> {after}); existing kept")
# 12. delete group: trainee cannot, admin can
r = C.delete(f"/api/groups/{gid}", headers=TH)
assert r.status_code == 403, ("trainee must not delete group", r.status_code)
print("[ok] trainee cannot delete group (403)")
r = C.delete(f"/api/groups/{gid}", headers=AH)
assert r.status_code == 200, r.get_json()
gone = C.get(f"/api/groups/{gid}", headers=AH)
assert gone.status_code == 404, "deleted group should be gone"
print("[ok] admin deletes group; group no longer reachable")
print("ALL USER-JOURNEY FLOW TESTS PASSED") print("ALL USER-JOURNEY FLOW TESTS PASSED")

View File

@@ -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-DNAVmyB5.js";import{U as z}from"./users-DG0hBplh.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,H 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-CvsGOA4u.js";import{U as z}from"./users-Cs9RtsVY.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
@@ -8,4 +8,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 a
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree. * See the LICENSE file in the root directory of this source tree.
*/const N=k("user-plus",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14",key:"1bvyxn"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]]),B={class:"card guide"},I={class:"card",style:{"margin-bottom":"16px"}},A={style:{"margin-top":"0"}},D={class:"row"},F={class:"row",style:{"margin-top":"10px"}},S=["disabled"],T={key:0,class:"error"},j={key:0,class:"card empty-state"},q={style:{flex:"1"}},E={class:"badge"},G={__name:"AdminUsers",setup(H){const c=y([]),d=y(""),t=y({username:"",name:"",password:"",role:"user"});async function g(){c.value=(await w.adminListUsers()).users}async function _(){d.value="";try{await w.adminCreateUser({...t.value}),t.value={username:"",name:"",password:"",role:"user"},await g()}catch(x){d.value=x.message}}return b(g),(x,s)=>(i(),r("div",null,[e("h2",null,[m(o(z),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),l(" "+n(o(u).t("users")),1)]),e("div",B,[e("strong",null,[m(o(L),{size:17,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),s[4]||(s[4]=l(" ",-1))]),s[5]||(s[5]=e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[l(" "),e("strong",null," ()"),l(", , ")]),e("li",null,[l(": "),e("strong",null,"user"),l(" = , "),e("strong",null,"admin"),l(" = ")]),e("li",null,[l(" "),e("strong",null,""),l(" + ")])],-1))]),e("div",I,[e("h4",A,[m(o(N),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),l(" "+n(o(u).t("create"))+" "+n(o(u).t("users").toLowerCase()),1)]),e("div",D,[p(e("input",{"onUpdate:modelValue":s[0]||(s[0]=a=>t.value.username=a),placeholder:" ()",style:{flex:"1"}},null,512),[[v,t.value.username]]),p(e("input",{"onUpdate:modelValue":s[1]||(s[1]=a=>t.value.name=a),placeholder:"",style:{flex:"1"}},null,512),[[v,t.value.name]]),p(e("input",{"onUpdate:modelValue":s[2]||(s[2]=a=>t.value.password=a),type:"password",placeholder:"",style:{flex:"1"}},null,512),[[v,t.value.password]])]),e("div",F,[p(e("select",{"onUpdate:modelValue":s[3]||(s[3]=a=>t.value.role=a),style:{flex:"1"}},[...s[6]||(s[6]=[e("option",{value:"user"},"🙂 user ",-1),e("option",{value:"admin"},"🛠 admin ",-1)])],512),[[U,t.value.role]]),e("button",{class:"primary",onClick:_,disabled:!t.value.username||!t.value.password}," "+n(o(u).t("create")),9,S)]),d.value?(i(),r("div",T,n(d.value),1)):f("",!0)]),c.value.length===0?(i(),r("div",j,[...s[7]||(s[7]=[e("strong",null,"",-1),e("span",null,"",-1)])])):f("",!0),(i(!0),r(V,null,C(c.value,a=>(i(),r("div",{class:"card",key:a.id,style:{"margin-bottom":"8px",display:"flex","align-items":"center",gap:"12px"}},[e("strong",q,n(a.name)+" ("+n(a.username)+")",1),e("span",E,n(a.role),1),e("span",{class:M(["badge",a.active?"won":"lost"])},n(a.active?"active":"inactive"),3)]))),128))]))}},K=h(G,[["__scopeId","data-v-924406be"]]);export{K as default}; */const N=k("user-plus",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14",key:"1bvyxn"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]]),B={class:"card guide"},I={class:"card",style:{"margin-bottom":"16px"}},A={style:{"margin-top":"0"}},D={class:"row"},F={class:"row",style:{"margin-top":"10px"}},H=["disabled"],S={key:0,class:"error"},T={key:0,class:"card empty-state"},j={style:{flex:"1"}},q={class:"badge"},E={__name:"AdminUsers",setup(P){const c=y([]),d=y(""),t=y({username:"",name:"",password:"",role:"user"});async function g(){c.value=(await w.adminListUsers()).users}async function _(){d.value="";try{await w.adminCreateUser({...t.value}),t.value={username:"",name:"",password:"",role:"user"},await g()}catch(x){d.value=x.message}}return b(g),(x,s)=>(i(),r("div",null,[e("h2",null,[m(o(z),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),l(" "+n(o(u).t("users")),1)]),e("div",B,[e("strong",null,[m(o(L),{size:17,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),s[4]||(s[4]=l(" ",-1))]),s[5]||(s[5]=e("ol",{style:{margin:"8px 0 0","padding-left":"20px","line-height":"1.8"}},[e("li",null,[l(" "),e("strong",null," ()"),l(", , ")]),e("li",null,[l(": "),e("strong",null,"user"),l(" = , "),e("strong",null,"admin"),l(" = ")]),e("li",null,[l(" "),e("strong",null,""),l(" + ")])],-1))]),e("div",I,[e("h4",A,[m(o(N),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),l(" "+n(o(u).t("create"))+" "+n(o(u).t("users").toLowerCase()),1)]),e("div",D,[p(e("input",{"onUpdate:modelValue":s[0]||(s[0]=a=>t.value.username=a),placeholder:" ()",style:{flex:"1"}},null,512),[[v,t.value.username]]),p(e("input",{"onUpdate:modelValue":s[1]||(s[1]=a=>t.value.name=a),placeholder:"",style:{flex:"1"}},null,512),[[v,t.value.name]]),p(e("input",{"onUpdate:modelValue":s[2]||(s[2]=a=>t.value.password=a),type:"password",placeholder:"",style:{flex:"1"}},null,512),[[v,t.value.password]])]),e("div",F,[p(e("select",{"onUpdate:modelValue":s[3]||(s[3]=a=>t.value.role=a),style:{flex:"1"}},[...s[6]||(s[6]=[e("option",{value:"user"},"🙂 user ",-1),e("option",{value:"admin"},"🛠 admin ",-1)])],512),[[U,t.value.role]]),e("button",{class:"primary",onClick:_,disabled:!t.value.username||!t.value.password}," "+n(o(u).t("create")),9,H)]),d.value?(i(),r("div",S,n(d.value),1)):f("",!0)]),c.value.length===0?(i(),r("div",T,[...s[7]||(s[7]=[e("strong",null,"",-1),e("span",null,"",-1)])])):f("",!0),(i(!0),r(V,null,C(c.value,a=>(i(),r("div",{class:"card",key:a.id,style:{"margin-bottom":"8px",display:"flex","align-items":"center",gap:"12px"}},[e("strong",j,n(a.name)+" ("+n(a.username)+")",1),e("span",q,n(a.role),1),e("span",{class:M(["badge",a.active?"won":"lost"])},n(a.active?"active":"inactive"),3)]))),128))]))}},K=h(E,[["__scopeId","data-v-924406be"]]);export{K as default};

View File

@@ -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-DNAVmyB5.js";import{U as P}from"./users-DG0hBplh.js";import{P as S}from"./plus-BGdYQyzB.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-CvsGOA4u.js";import{U as P}from"./users-Cs9RtsVY.js";import{P as S}from"./plus-BSRzoT5b.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,14 @@
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";/** 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,H as V,g as C,z as A,r as m,y as x,j as B,o as w}from"./index-CvsGOA4u.js";import{A as q}from"./arrow-left-DF5NIPgR.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree. * 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"}]]);/** */const E=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 * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree. * 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"}]]);/** */const G=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 * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
@@ -18,4 +18,4 @@ 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
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree. * 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}; */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(E),{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(G),{size:18,"stroke-width":2}),o(" "+r(c.value?"":l(i).t("create")),1)],8,F)])])}}},R=M(T,[["__scopeId","data-v-b566e48c"]]);export{R as default};

File diff suppressed because one or more lines are too long

View File

@@ -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-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";/** 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,G as u,o as d}from"./index-CvsGOA4u.js";import{B as g}from"./book-open-BPDJkLfy.js";import{L as p}from"./layout-dashboard-k0A07o6y.js";import{T as m}from"./target-BHq78myU.js";import{S as y}from"./sparkles-BfSk-aam.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -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-DNAVmyB5.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-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -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-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}; 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-CvsGOA4u.js";import{L as V}from"./layout-dashboard-k0A07o6y.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};

View File

@@ -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-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}; 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-CvsGOA4u.js";import{T as S}from"./target-BHq78myU.js";import{A as F}from"./arrow-left-DF5NIPgR.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};

View File

@@ -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-DNAVmyB5.js";import{L as D}from"./lock-fipmYN8_.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-CvsGOA4u.js";import{L as D}from"./lock-Lq5iEyDR.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -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-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}; 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-CvsGOA4u.js";import{L as C}from"./lock-Lq5iEyDR.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};

View File

@@ -1 +0,0 @@
.grid[data-v-e39df241]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px}.train-card[data-v-e39df241]{display:flex;flex-direction:column;height:100%}.badge.ready[data-v-e39df241]{background:#dcfce7;color:#166534}.badge.draft[data-v-e39df241]{background:#fef9c3;color:#854d0e}button.soft[data-v-e39df241]{background:#f1f3f9;border-color:transparent}.badge.facebook[data-v-e39df241]{background:#e0f2fe;color:#0369a1}.badge.line[data-v-e39df241]{background:#dcfce7;color:#15803d}

View File

@@ -0,0 +1,6 @@
import{c as b,_ as C,p as A,y as f,h as l,a as d,b as e,l as c,u as t,m,t as o,i,q as _,f as z,g as h,F as M,x as T,r as v,z as B,o as r,C as k,D as N}from"./index-CvsGOA4u.js";import{T as P}from"./target-BHq78myU.js";import{B as V}from"./book-open-BPDJkLfy.js";import{P as $}from"./plus-BSRzoT5b.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 j=b("trash-2",[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]]),G={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},L={style:{margin:"0"}},D={class:"row",style:{"margin-left":"auto",gap:"10px"}},F={class:"soft"},I={class:"primary"},S={class:"muted"},q={key:0,class:"card",style:{"min-height":"80px"}},E={key:1,class:"card empty-state"},H={key:0},O={key:1},J={class:"grid"},K={class:"row",style:{"justify-content":"space-between"}},Q={class:"row",style:{gap:"6px","align-items":"center"}},R=["onClick"],U={class:"muted",style:{margin:"6px 0 12px"}},W={class:"row",style:{gap:"8px"}},X={class:"muted"},Y={class:"primary",style:{width:"100%","margin-top":"12px"}},Z={__name:"Training",setup(tt){const u=v([]),y=v(!0);function g(s){return s.sales_kit&&s.sales_kit.productName||s.input&&s.input.product||""}function w(s){return s.personas||s.persona_count||0}async function x(s){if(confirm(`ลบกลุ่ม "${s.title}" และบุคคลต้นแบบทั้งหมด? (ติดลบถาวร)`))try{await f.deleteGroup(s.id),u.value=u.value.filter(n=>n.id!==s.id)}catch(n){alert(n.message)}}return A(async()=>{try{const s=(await f.listGroups()).groups||[];u.value=l.isAdmin?s:s.filter(n=>n.status==="ready")}finally{y.value=!1}}),(s,n)=>{const p=B("router-link");return r(),d("div",null,[e("div",G,[e("h2",L,[c(t(P),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),m(" "+o(t(i).t("training")),1)]),e("div",D,[c(p,{to:"/guide"},{default:_(()=>[e("button",F,[c(t(V),{size:18,"stroke-width":1.8}),n[0]||(n[0]=m(" คู่มือ",-1))])]),_:1}),t(l).isAdmin?(r(),z(p,{key:0,to:"/admin/new-group"},{default:_(()=>[e("button",I,[c(t($),{size:18,"stroke-width":2}),m(" "+o(t(i).t("addProduct")),1)])]),_:1})):h("",!0)])]),e("p",S,o(t(i).t("trainingSubtitle")),1),y.value?(r(),d("div",q,[...n[1]||(n[1]=[e("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):u.value.length===0?(r(),d("div",E,[e("strong",null,o(t(i).t("noTraining")),1),t(l).isAdmin?(r(),d("span",H,'Click "'+o(t(i).t("addProduct"))+'" to create a persona group.',1)):(r(),d("span",O,"Ask an admin to create a persona group first."))])):h("",!0),e("div",J,[(r(!0),d(M,null,T(u.value,a=>(r(),d("div",{key:a.id,class:"card lift train-card"},[e("div",K,[c(p,{to:t(l).isAdmin?`/admin/groups/${a.id}/edit`:`/groups/${a.id}/personas`,style:{"text-decoration":"none",color:"inherit","min-width":"0"}},{default:_(()=>[e("strong",null,o(a.title),1)]),_:2},1032,["to"]),e("div",Q,[e("span",{class:k(["badge",a.status==="ready"?"ready":"draft"])},o(a.status),3),t(l).isAdmin?(r(),d("button",{key:0,class:"del-btn",title:"ลบกลุ่มนี้ (ติดลบถาวร)",onClick:N(et=>x(a),["prevent","stop"])},[c(t(j),{size:16,"stroke-width":1.8})],8,R)):h("",!0)])]),c(p,{to:t(l).isAdmin?`/admin/groups/${a.id}/edit`:`/groups/${a.id}/personas`,style:{"text-decoration":"none",color:"inherit"}},{default:_(()=>[e("div",U,o(g(a)),1),e("div",W,[e("span",{class:k(["badge",a.channel||"line"])},o(a.channel||"line"),3),e("span",X,o(w(a))+" "+o(t(i).t("personas").toLowerCase()),1)]),e("button",Y,o(t(l).isAdmin?a.status==="ready"?t(i).t("managePersonas"):t(i).t("analyze"):t(i).t("selectPersona")),1)]),_:2},1032,["to"])]))),128))])])}}},it=C(Z,[["__scopeId","data-v-b1b30fec"]]);export{it as default};

View File

@@ -1 +0,0 @@
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};

View File

@@ -0,0 +1 @@
.grid[data-v-b1b30fec]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px}.train-card[data-v-b1b30fec]{display:flex;flex-direction:column;height:100%}.badge.ready[data-v-b1b30fec]{background:#dcfce7;color:#166534}.badge.draft[data-v-b1b30fec]{background:#fef9c3;color:#854d0e}button.soft[data-v-b1b30fec]{background:#f1f3f9;border-color:transparent}.badge.facebook[data-v-b1b30fec]{background:#e0f2fe;color:#0369a1}.badge.line[data-v-b1b30fec]{background:#dcfce7;color:#15803d}.del-btn[data-v-b1b30fec]{background:transparent;border:1px solid var(--border);border-radius:8px;min-height:32px;width:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;color:var(--muted);cursor:pointer}.del-btn[data-v-b1b30fec]:hover{background:#fee2e2;border-color:#fecaca;color:var(--red)}

View File

@@ -1,4 +1,4 @@
import{c as e}from"./index-DNAVmyB5.js";/** import{c as e}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c as a}from"./index-DNAVmyB5.js";/** import{c as a}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{c as t}from"./index-DNAVmyB5.js";/** import{c as t}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c as e}from"./index-DNAVmyB5.js";/** import{c as e}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c as e}from"./index-DNAVmyB5.js";/** import{c as e}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c as a}from"./index-DNAVmyB5.js";/** import{c as a}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c}from"./index-DNAVmyB5.js";/** import{c}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -1,4 +1,4 @@
import{c as e}from"./index-DNAVmyB5.js";/** import{c as e}from"./index-CvsGOA4u.js";/**
* @license lucide-vue-next v1.0.0 - ISC * @license lucide-vue-next v1.0.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sales Trainer</title> <title>Sales Trainer</title>
<script type="module" crossorigin src="/assets/index-DNAVmyB5.js"></script> <script type="module" crossorigin src="/assets/index-CvsGOA4u.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B9WjPRES.css"> <link rel="stylesheet" crossorigin href="/assets/index-B9WjPRES.css">
</head> </head>
<body> <body>

View File

@@ -43,6 +43,7 @@ export const api = {
createGroup: (formData) => request('POST', '/api/groups', formData, true), createGroup: (formData) => request('POST', '/api/groups', formData, true),
listGroups: () => request('GET', '/api/groups'), listGroups: () => request('GET', '/api/groups'),
getGroup: (id) => request('GET', `/api/groups/${id}`), getGroup: (id) => request('GET', `/api/groups/${id}`),
deleteGroup: (id) => request('DELETE', `/api/groups/${id}`),
analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`), analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`),
listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`), listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`),
getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`), getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`),

View File

@@ -20,17 +20,30 @@
<span v-else>Ask an admin to create a persona group first.</span> <span v-else>Ask an admin to create a persona group first.</span>
</div> </div>
<div class="grid"> <div class="grid">
<router-link <div v-for="g in groups" :key="g.id" class="card lift train-card">
v-for="g in groups" <div class="row" style="justify-content:space-between">
:key="g.id" <router-link
:to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`" :to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`"
style="text-decoration:none" style="text-decoration:none;color:inherit;min-width:0"
> >
<div class="card lift train-card">
<div class="row" style="justify-content:space-between">
<strong>{{ g.title }}</strong> <strong>{{ g.title }}</strong>
</router-link>
<div class="row" style="gap:6px;align-items:center">
<span class="badge" :class="g.status === 'ready' ? 'ready' : 'draft'">{{ g.status }}</span> <span class="badge" :class="g.status === 'ready' ? 'ready' : 'draft'">{{ g.status }}</span>
<button
v-if="auth.isAdmin"
class="del-btn"
:title="'ลบกลุ่มนี้ (ติดลบถาวร)'"
@click.prevent.stop="removeGroup(g)"
>
<Trash2 :size="16" :stroke-width="1.8" />
</button>
</div> </div>
</div>
<router-link
:to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`"
style="text-decoration:none;color:inherit"
>
<div class="muted" style="margin:6px 0 12px">{{ productName(g) }}</div> <div class="muted" style="margin:6px 0 12px">{{ productName(g) }}</div>
<div class="row" style="gap:8px"> <div class="row" style="gap:8px">
<span class="badge" :class="g.channel || 'line'">{{ g.channel || 'line' }}</span> <span class="badge" :class="g.channel || 'line'">{{ g.channel || 'line' }}</span>
@@ -39,15 +52,15 @@
<button class="primary" style="width:100%;margin-top:12px"> <button class="primary" style="width:100%;margin-top:12px">
{{ auth.isAdmin ? (g.status === 'ready' ? i18n.t('managePersonas') : i18n.t('analyze')) : i18n.t('selectPersona') }} {{ auth.isAdmin ? (g.status === 'ready' ? i18n.t('managePersonas') : i18n.t('analyze')) : i18n.t('selectPersona') }}
</button> </button>
</div> </router-link>
</router-link> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { Target, Plus, BookOpen } from 'lucide-vue-next' import { Target, Plus, BookOpen, Trash2 } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { auth } from '../store/auth' import { auth } from '../store/auth'
import { i18n } from '../i18n' import { i18n } from '../i18n'
@@ -62,6 +75,16 @@ function personaCount(g) {
return (g.personas || g.persona_count || 0) return (g.personas || g.persona_count || 0)
} }
async function removeGroup(g) {
if (!confirm(`ลบกลุ่ม "${g.title}" และบุคคลต้นแบบทั้งหมด? (ติดลบถาวร)`)) return
try {
await api.deleteGroup(g.id)
groups.value = groups.value.filter((x) => x.id !== g.id)
} catch (e) {
alert(e.message)
}
}
onMounted(async () => { onMounted(async () => {
try { try {
const all = (await api.listGroups()).groups || [] const all = (await api.listGroups()).groups || []
@@ -81,4 +104,10 @@ onMounted(async () => {
button.soft { background: #f1f3f9; border-color: transparent; } button.soft { background: #f1f3f9; border-color: transparent; }
.badge.facebook { background: #e0f2fe; color: #0369a1; } .badge.facebook { background: #e0f2fe; color: #0369a1; }
.badge.line { background: #dcfce7; color: #15803d; } .badge.line { background: #dcfce7; color: #15803d; }
.del-btn {
background: transparent; border: 1px solid var(--border); border-radius: 8px;
min-height: 32px; width: 32px; padding: 0; display: inline-flex; align-items: center;
justify-content: center; color: var(--muted); cursor: pointer;
}
.del-btn:hover { background: #fee2e2; border-color: #fecaca; color: var(--red); }
</style> </style>