ui(icons): replace colored emoji with modern single-color line icons (lucide-vue-next)

Add lucide-vue-next (stroke-based, single-color, currentColor) and swap emoji icons
across nav + all main views: Settings/LogOut (App), BarChart3/Users/Plus (Analytics),
LayoutDashboard (MyBoard), Sparkles/Users/Pencil (GroupEdit), Box/ListChecks/Paperclip/
PlusCircle/ArrowLeft (GroupBuilder), Target/ArrowLeft/Search (Chat/Personas/Training),
User/Lock/Globe (Settings), Users/UserPlus/Info (AdminUsers).
- Buttons become inline-flex for icon+text alignment.
- Icons inherit currentColor (single color, modern line style).
- Rebuild dist with lucide.
This commit is contained in:
Macky
2026-08-08 10:55:45 +07:00
parent 22d3e51980
commit 8670addcc3
60 changed files with 265 additions and 89 deletions

View File

@@ -0,0 +1 @@
.guide[data-v-924406be]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}

View File

@@ -0,0 +1,11 @@
import{k as w,_ as h,l as U,c as r,a as e,m,u as o,j as l,t as n,i as u,w as p,v,D as b,e as f,F as V,q as M,s as k,r as y,o as i,A as C}from"./index-C0Orcg9B.js";import{U as z}from"./users-D5qk1Nmy.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 L=w("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/**
* @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=w("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"}]]),A={class:"card guide"},B={class:"card",style:{"margin-bottom":"16px"}},D={style:{"margin-top":"0"}},I={class:"row"},j={class:"row",style:{"margin-top":"10px"}},q=["disabled"],F={key:0,class:"error"},S={key:0,class:"card empty-state"},T={style:{flex:"1"}},E={class:"badge"},H={__name:"AdminUsers",setup(P){const c=y([]),d=y(""),t=y({username:"",name:"",password:"",role:"user"});async function g(){c.value=(await k.adminListUsers()).users}async function _(){d.value="";try{await k.adminCreateUser({...t.value}),t.value={username:"",name:"",password:"",role:"user"},await g()}catch(x){d.value=x.message}}return U(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",A,[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",B,[e("h4",D,[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",I,[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",j,[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),[[b,t.value.role]]),e("button",{class:"primary",onClick:_,disabled:!t.value.username||!t.value.password}," "+n(o(u).t("create")),9,q)]),d.value?(i(),r("div",F,n(d.value),1)):f("",!0)]),c.value.length===0?(i(),r("div",S,[...s[7]||(s[7]=[e("strong",null,"ยังไม่มีผู้ใช้งาน",-1),e("span",null,"สร้างผู้ใช้งานคนแรกด้วยฟอร์มด้านบน",-1)])])):f("",!0),(i(!0),r(V,null,M(c.value,a=>(i(),r("div",{class:"card",key:a.id,style:{"margin-bottom":"8px",display:"flex","align-items":"center",gap:"12px"}},[e("strong",T,n(a.name)+" ("+n(a.username)+")",1),e("span",E,n(a.role),1),e("span",{class:C(["badge",a.active?"won":"lost"])},n(a.active?"active":"inactive"),3)]))),128))]))}},K=h(H,[["__scopeId","data-v-924406be"]]);export{K as default};

View File

@@ -1 +0,0 @@
import{_ as f,k as x,c as o,a as e,t as l,u as d,i,H as w,w as u,v as c,C as U,e as g,F as k,p as V,q as y,r as p,o as n,z as h}from"./index-BhdfH8-z.js";const C={class:"card",style:{"margin-bottom":"16px"}},B={style:{"margin-top":"0"}},L={class:"row"},M={class:"row",style:{"margin-top":"10px"}},N=["disabled"],S={key:0,class:"error"},z={key:0,class:"card empty-state"},A={style:{flex:"1"}},D={class:"badge"},F={__name:"AdminUsers",setup(q){const v=p([]),r=p(""),t=p({username:"",name:"",password:"",role:"user"});async function m(){v.value=(await y.adminListUsers()).users}async function _(){r.value="";try{await y.adminCreateUser({...t.value}),t.value={username:"",name:"",password:"",role:"user"},await m()}catch(b){r.value=b.message}}return x(m),(b,a)=>(n(),o("div",null,[e("h2",null,"👥 "+l(d(i).t("users")),1),a[6]||(a[6]=w('<div class="card guide" data-v-6b85bc80><strong data-v-6b85bc80>📋 วิธีเพิ่มผู้ใช้งาน</strong><ol style="margin:8px 0 0;padding-left:20px;line-height:1.8;" data-v-6b85bc80><li data-v-6b85bc80>กรอก <strong data-v-6b85bc80>ชื่อผู้ใช้ (สำหรับเข้าสู่ระบบ)</strong>, ชื่อ, และรหัสผ่านเริ่มต้น</li><li data-v-6b85bc80>เลือกสิทธิ์: <strong data-v-6b85bc80>user</strong> = ผู้เข้าฝึกขาย, <strong data-v-6b85bc80>admin</strong> = จัดการระบบ</li><li data-v-6b85bc80>กด <strong data-v-6b85bc80>สร้าง</strong> — นำชื่อผู้ใช้ + รหัสผ่านไปแจ้งให้ผู้ใช้นั้นเข้าสู่ระบบได้เลย</li></ol></div>',1)),e("div",C,[e("h4",B," "+l(d(i).t("create"))+" "+l(d(i).t("users").toLowerCase()),1),e("div",L,[u(e("input",{"onUpdate:modelValue":a[0]||(a[0]=s=>t.value.username=s),placeholder:"ชื่อผู้ใช้ (เข้าสู่ระบบ)",style:{flex:"1"}},null,512),[[c,t.value.username]]),u(e("input",{"onUpdate:modelValue":a[1]||(a[1]=s=>t.value.name=s),placeholder:"ชื่อจริง",style:{flex:"1"}},null,512),[[c,t.value.name]]),u(e("input",{"onUpdate:modelValue":a[2]||(a[2]=s=>t.value.password=s),type:"password",placeholder:"รหัสผ่านเริ่มต้น",style:{flex:"1"}},null,512),[[c,t.value.password]])]),e("div",M,[u(e("select",{"onUpdate:modelValue":a[3]||(a[3]=s=>t.value.role=s),style:{flex:"1"}},[...a[4]||(a[4]=[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}," "+l(d(i).t("create")),9,N)]),r.value?(n(),o("div",S,l(r.value),1)):g("",!0)]),v.value.length===0?(n(),o("div",z,[...a[5]||(a[5]=[e("strong",null,"ยังไม่มีผู้ใช้งาน",-1),e("span",null,"สร้างผู้ใช้งานคนแรกด้วยฟอร์มด้านบน",-1)])])):g("",!0),(n(!0),o(k,null,V(v.value,s=>(n(),o("div",{class:"card",key:s.id,style:{"margin-bottom":"8px",display:"flex","align-items":"center",gap:"12px"}},[e("strong",A,l(s.name)+" ("+l(s.username)+")",1),e("span",D,l(s.role),1),e("span",{class:h(["badge",s.active?"won":"lost"])},l(s.active?"active":"inactive"),3)]))),128))]))}},H=f(F,[["__scopeId","data-v-6b85bc80"]]);export{H as default};

View File

@@ -1 +0,0 @@
.guide[data-v-6b85bc80]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}

View File

@@ -0,0 +1,6 @@
import{k,_ as C,l as V,c as u,a as s,m as i,u as a,j as m,t,i as l,n as h,w as f,v as w,p as z,e as M,F as N,q as L,s as P,x as U,r as v,o as _}from"./index-C0Orcg9B.js";import{U as j}from"./users-D5qk1Nmy.js";import{P as A}from"./plus-Cw7xc8qj.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 B=k("chart-column",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]),S={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},T={style:{margin:"0"}},D={class:"row",style:{"margin-left":"auto",gap:"10px"}},F={class:"users-btn"},I={class:"primary"},R={class:"card filter-bar"},W={class:"muted"},q=["disabled"],E=["disabled"],O={class:"row",style:{gap:"16px",margin:"16px 0"}},G={class:"card stat"},H={class:"card stat"},J={style:{color:"var(--green)"}},K={class:"card stat"},Q={style:{color:"var(--red)"}},X={class:"card stat"},Y={class:"card stat"},Z={class:"card"},$={class:"row",style:{"justify-content":"space-between","align-items":"center"}},ss={class:"bar"},ts={class:"muted",style:{"font-size":"12px","margin-top":"6px"}},es={style:{"margin-top":"20px"}},as={key:0,class:"card empty-state"},os={class:"grid"},ls={class:"row",style:{"justify-content":"space-between"}},ns={class:"muted"},is={class:"row",style:{"margin-top":"10px",gap:"8px"}},rs={class:"badge won"},ds={class:"badge lost"},cs={class:"badge not_tried"},us={class:"muted",style:{"margin-top":"8px"}},vs={__name:"Analytics",setup(_s){const o=v({overall:{total_sessions:0,wins:0,losses:0,close_rate:0,avg_score:0},trainee_count:0,hardest_personas:[]}),r=v(""),d=v(""),c=v(!1);async function p(){c.value=!0;try{o.value=await P.analytics({from:r.value,to:d.value})}finally{c.value=!1}}function b(){p()}function x(){r.value="",d.value="",p()}return V(p),(ps,e)=>{const g=U("router-link");return _(),u("div",null,[s("div",S,[s("div",null,[s("h2",T,[i(a(B),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),m(" "+t(a(l).t("adminOverview")),1)]),e[2]||(e[2]=s("div",{class:"muted",style:{"margin-top":"4px"}},"ภาพรวมผลการฝึกของทีมทั้งหมด — เลือกช่วงเวลาเพื่อดูสถิติ",-1))]),s("div",D,[i(g,{to:"/admin/users"},{default:h(()=>[s("button",F,[i(a(j),{size:18,"stroke-width":2}),m(" "+t(a(l).t("users")),1)])]),_:1}),i(g,{to:"/admin/new-group"},{default:h(()=>[s("button",I,[i(a(A),{size:18,"stroke-width":2}),m(" "+t(a(l).t("addProduct")),1)])]),_:1})])]),s("div",R,[s("span",W,"🗓 "+t(a(l).t("dateRange")),1),f(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=n=>r.value=n)},null,512),[[w,r.value]]),e[3]||(e[3]=s("span",null,"",-1)),f(s("input",{type:"date","onUpdate:modelValue":e[1]||(e[1]=n=>d.value=n)},null,512),[[w,d.value]]),s("button",{class:"primary",onClick:b,disabled:c.value},"→ "+t(a(l).t("apply")),9,q),s("button",{onClick:x,disabled:c.value},t(a(l).t("clear")),9,E)]),s("div",O,[s("div",G,[e[4]||(e[4]=s("div",null,"Sessions",-1)),s("strong",null,t(o.value.overall.total_sessions),1)]),s("div",H,[e[5]||(e[5]=s("div",null,"Wins",-1)),s("strong",J,t(o.value.overall.wins),1)]),s("div",K,[e[6]||(e[6]=s("div",null,"Losses",-1)),s("strong",Q,t(o.value.overall.losses),1)]),s("div",X,[e[7]||(e[7]=s("div",null,"Avg score",-1)),s("strong",null,t(o.value.overall.avg_score),1)]),s("div",Y,[e[8]||(e[8]=s("div",null,"Trainees",-1)),s("strong",null,t(o.value.trainee_count),1)])]),s("div",Z,[s("div",$,[s("strong",null,t(a(l).t("closeRate")),1),s("strong",null,t(o.value.overall.close_rate)+"%",1)]),s("div",ss,[s("div",{class:"bar-fill",style:z({width:(o.value.overall.close_rate||0)+"%"})},null,4)]),s("div",ts,t(o.value.overall.wins)+" "+t(a(l).t("won").toLowerCase())+" / "+t(o.value.overall.total_sessions)+" sessions ",1)]),s("h3",es,t(a(l).t("hardestPersonas")),1),o.value.hardest_personas.length===0?(_(),u("div",as,[...e[9]||(e[9]=[s("strong",null,"No data",-1),s("span",null,"No sessions in this date range.",-1)])])):M("",!0),s("div",os,[(_(!0),u(N,null,L(o.value.hardest_personas,(n,y)=>(_(),u("div",{key:y,class:"card lift tier-card"},[s("div",ls,[s("strong",null,t(n.persona_name),1),s("span",ns,"#"+t(y+1),1)]),s("div",is,[s("span",rs,t(n.wins)+"W",1),s("span",ds,t(n.losses)+"L",1),s("span",cs,t(n.plays)+" plays",1)]),s("div",us,t(a(l).t("score"))+": "+t(n.avg_score),1)]))),128))])])}}},hs=C(vs,[["__scopeId","data-v-f2feb744"]]);export{hs as default};

View File

@@ -0,0 +1 @@
.stat[data-v-f2feb744]{text-align:center;min-width:110px}.stat div[data-v-f2feb744]{color:var(--muted);font-size:12px}.stat strong[data-v-f2feb744]{font-size:20px}.filter-bar[data-v-f2feb744]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.filter-bar input[type=date][data-v-f2feb744]{width:auto}.grid[data-v-f2feb744]{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px}.tier-card[data-v-f2feb744]{display:flex;flex-direction:column}.badge.won[data-v-f2feb744]{background:#dcfce7;color:#166534}.badge.lost[data-v-f2feb744]{background:#fee2e2;color:#991b1b}.badge.not_tried[data-v-f2feb744]{background:#eef2ff;color:#4338ca}.bar[data-v-f2feb744]{background:#eef0f5;border-radius:999px;height:10px;overflow:hidden;margin-top:10px}.bar-fill[data-v-f2feb744]{background:linear-gradient(90deg,var(--accent),var(--accent-2));height:100%;border-radius:999px;transition:width .4s ease}.users-btn[data-v-f2feb744]{display:inline-flex;align-items:center;gap:6px;background:#0ea5e9;border-color:transparent;color:#fff;font-weight:600}.users-btn[data-v-f2feb744]:not(:disabled):hover{background:#0284c7;box-shadow:0 6px 16px #0ea5e959}

View File

@@ -1 +0,0 @@
import{_ as x,k,c as u,a as s,t,u as a,i as l,l as g,m as y,w as f,v as h,n as C,e as N,F as V,p as L,q as A,s as B,r as c,o as _}from"./index-BhdfH8-z.js";const S={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},j={style:{margin:"0"}},z={class:"row",style:{"margin-left":"auto",gap:"10px"}},D={class:"users-btn"},F={class:"primary"},M={class:"card filter-bar"},P={class:"muted"},R=["disabled"],T=["disabled"],U={class:"row",style:{gap:"16px",margin:"16px 0"}},W={class:"card stat"},q={class:"card stat"},E={style:{color:"var(--green)"}},I={class:"card stat"},O={style:{color:"var(--red)"}},G={class:"card stat"},H={class:"card stat"},J={class:"card"},K={class:"row",style:{"justify-content":"space-between","align-items":"center"}},Q={class:"bar"},X={class:"muted",style:{"font-size":"12px","margin-top":"6px"}},Y={style:{"margin-top":"20px"}},Z={key:0,class:"card empty-state"},$={class:"grid"},ss={class:"row",style:{"justify-content":"space-between"}},ts={class:"muted"},es={class:"row",style:{"margin-top":"10px",gap:"8px"}},os={class:"badge won"},as={class:"badge lost"},ls={class:"badge not_tried"},ns={class:"muted",style:{"margin-top":"8px"}},is={__name:"Analytics",setup(rs){const o=c({overall:{total_sessions:0,wins:0,losses:0,close_rate:0,avg_score:0},trainee_count:0,hardest_personas:[]}),i=c(""),r=c(""),d=c(!1);async function v(){d.value=!0;try{o.value=await A.analytics({from:i.value,to:r.value})}finally{d.value=!1}}function w(){v()}function b(){i.value="",r.value="",v()}return k(v),(ds,e)=>{const p=B("router-link");return _(),u("div",null,[s("div",S,[s("div",null,[s("h2",j,"📊 "+t(a(l).t("adminOverview")),1),e[2]||(e[2]=s("div",{class:"muted",style:{"margin-top":"4px"}},"ภาพรวมผลการฝึกของทีมทั้งหมด — เลือกช่วงเวลาเพื่อดูสถิติ",-1))]),s("div",z,[g(p,{to:"/admin/users"},{default:y(()=>[s("button",D,"👥 "+t(a(l).t("users")),1)]),_:1}),g(p,{to:"/admin/new-group"},{default:y(()=>[s("button",F," "+t(a(l).t("addProduct")),1)]),_:1})])]),s("div",M,[s("span",P,"🗓 "+t(a(l).t("dateRange")),1),f(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=n=>i.value=n)},null,512),[[h,i.value]]),e[3]||(e[3]=s("span",null,"",-1)),f(s("input",{type:"date","onUpdate:modelValue":e[1]||(e[1]=n=>r.value=n)},null,512),[[h,r.value]]),s("button",{class:"primary",onClick:w,disabled:d.value},"→ "+t(a(l).t("apply")),9,R),s("button",{onClick:b,disabled:d.value},t(a(l).t("clear")),9,T)]),s("div",U,[s("div",W,[e[4]||(e[4]=s("div",null,"Sessions",-1)),s("strong",null,t(o.value.overall.total_sessions),1)]),s("div",q,[e[5]||(e[5]=s("div",null,"Wins",-1)),s("strong",E,t(o.value.overall.wins),1)]),s("div",I,[e[6]||(e[6]=s("div",null,"Losses",-1)),s("strong",O,t(o.value.overall.losses),1)]),s("div",G,[e[7]||(e[7]=s("div",null,"Avg score",-1)),s("strong",null,t(o.value.overall.avg_score),1)]),s("div",H,[e[8]||(e[8]=s("div",null,"Trainees",-1)),s("strong",null,t(o.value.trainee_count),1)])]),s("div",J,[s("div",K,[s("strong",null,t(a(l).t("closeRate")),1),s("strong",null,t(o.value.overall.close_rate)+"%",1)]),s("div",Q,[s("div",{class:"bar-fill",style:C({width:(o.value.overall.close_rate||0)+"%"})},null,4)]),s("div",X,t(o.value.overall.wins)+" "+t(a(l).t("won").toLowerCase())+" / "+t(o.value.overall.total_sessions)+" sessions ",1)]),s("h3",Y,t(a(l).t("hardestPersonas")),1),o.value.hardest_personas.length===0?(_(),u("div",Z,[...e[9]||(e[9]=[s("strong",null,"No data",-1),s("span",null,"No sessions in this date range.",-1)])])):N("",!0),s("div",$,[(_(!0),u(V,null,L(o.value.hardest_personas,(n,m)=>(_(),u("div",{key:m,class:"card lift tier-card"},[s("div",ss,[s("strong",null,t(n.persona_name),1),s("span",ts,"#"+t(m+1),1)]),s("div",es,[s("span",os,t(n.wins)+"W",1),s("span",as,t(n.losses)+"L",1),s("span",ls,t(n.plays)+" plays",1)]),s("div",ns,t(a(l).t("score"))+": "+t(n.avg_score),1)]))),128))])])}}},cs=x(is,[["__scopeId","data-v-45823937"]]);export{cs as default};

View File

@@ -1 +0,0 @@
.stat[data-v-45823937]{text-align:center;min-width:110px}.stat div[data-v-45823937]{color:var(--muted);font-size:12px}.stat strong[data-v-45823937]{font-size:20px}.filter-bar[data-v-45823937]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.filter-bar input[type=date][data-v-45823937]{width:auto}.grid[data-v-45823937]{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px}.tier-card[data-v-45823937]{display:flex;flex-direction:column}.badge.won[data-v-45823937]{background:#dcfce7;color:#166534}.badge.lost[data-v-45823937]{background:#fee2e2;color:#991b1b}.badge.not_tried[data-v-45823937]{background:#eef2ff;color:#4338ca}.bar[data-v-45823937]{background:#eef0f5;border-radius:999px;height:10px;overflow:hidden;margin-top:10px}.bar-fill[data-v-45823937]{background:linear-gradient(90deg,var(--accent),var(--accent-2));height:100%;border-radius:999px;transition:width .4s ease}.users-btn[data-v-45823937]{background:#0ea5e9;border-color:transparent;color:#fff;font-weight:600}.users-btn[data-v-45823937]:not(:disabled):hover{background:#0284c7;box-shadow:0 6px 16px #0ea5e959}

6
frontend/dist/assets/Chat-Bo6laZZd.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.thread[data-v-16c880f2]{background:#eceff4;border:1px solid var(--border);border-radius:var(--radius);padding:16px;min-height:320px;max-height:52vh;overflow-y:auto;display:flex;flex-direction:column;gap:8px}.bubble[data-v-16c880f2]{max-width:72%;padding:10px 14px;white-space:pre-wrap;word-break:break-word}.composer[data-v-16c880f2]{display:flex;gap:8px;margin-top:12px}.task[data-v-16c880f2]{margin-bottom:12px;background:#fff7ed;border-color:#fed7aa}.debrief[data-v-16c880f2]{margin-top:16px}.json[data-v-16c880f2]{background:#0f172a;color:#9ca3af;padding:10px;border-radius:8px;font-size:11px;overflow:auto;max-height:260px}button.danger[data-v-16c880f2]{background:var(--red);color:#fff;border:none}.guide[data-v-16c880f2]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:12px}.reveal-grid[data-v-16c880f2]{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px}.rev[data-v-16c880f2]{display:flex;flex-direction:column;background:#f8fafc;border:1px solid var(--border);border-radius:8px;padding:8px 10px}.rk[data-v-16c880f2]{font-size:12px;color:var(--muted)}.rv[data-v-16c880f2]{font-size:13px;color:var(--ink);margin-top:2px}@media (max-width: 640px){.reveal-grid[data-v-16c880f2]{grid-template-columns:1fr}}

View File

@@ -0,0 +1 @@
.thread[data-v-b21475ac]{background:#eceff4;border:1px solid var(--border);border-radius:var(--radius);padding:16px;min-height:320px;max-height:52vh;overflow-y:auto;display:flex;flex-direction:column;gap:8px}.bubble[data-v-b21475ac]{max-width:72%;padding:10px 14px;white-space:pre-wrap;word-break:break-word}.composer[data-v-b21475ac]{display:flex;gap:8px;margin-top:12px}.task[data-v-b21475ac]{margin-bottom:12px;background:#fff7ed;border-color:#fed7aa}.debrief[data-v-b21475ac]{margin-top:16px}.json[data-v-b21475ac]{background:#0f172a;color:#9ca3af;padding:10px;border-radius:8px;font-size:11px;overflow:auto;max-height:260px}button.danger[data-v-b21475ac]{background:var(--red);color:#fff;border:none}.guide[data-v-b21475ac]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:12px}.reveal-grid[data-v-b21475ac]{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px}.rev[data-v-b21475ac]{display:flex;flex-direction:column;background:#f8fafc;border:1px solid var(--border);border-radius:8px;padding:8px 10px}.rk[data-v-b21475ac]{font-size:12px;color:var(--muted)}.rv[data-v-b21475ac]{font-size:13px;color:var(--ink);margin-top:2px}@media (max-width: 640px){.reveal-grid[data-v-b21475ac]{grid-template-columns:1fr}}

View File

@@ -1 +0,0 @@
.guide[data-v-c7f3b0ac]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.req[data-v-c7f3b0ac]{color:var(--red)}

View File

@@ -0,0 +1,21 @@
import{k as g,_ as M,c as f,m as d,n as V,a as e,u as l,j as r,t as s,i as o,w as u,v as y,D as k,e as C,x as z,r as v,s as B,g as A,o as x}from"./index-C0Orcg9B.js";import{A as P}from"./arrow-left-BoUMQ_f_.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 S=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 U=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 q=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 D=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"},E={class:"card"},G={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 _=A(),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(P),{size:16,"stroke-width":2}),r(" "+s(l(o).t("training")),1)]),_:1}),e("div",N,[e("strong",null,[d(l(q),{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",E,[e("h2",G,[d(l(S),{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(D),{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(U),{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};

View File

@@ -1 +0,0 @@
import{_ as k,c as f,l as V,m as w,a as e,j as r,t as a,u as s,i as u,w as d,v as m,C as _,e as C,s as B,r as c,q as S,g as U,o as h}from"./index-BhdfH8-z.js";const N={class:"card"},D={style:{"margin-top":"0"}},E={class:"row"},G={style:{flex:"1"}},M={value:"facebook"},P={value:"line"},q={style:{flex:"1"}},F={value:"th"},O={value:"en"},T={key:0,class:"error"},j=["disabled"],A={__name:"GroupBuilder",setup(I){const b=U(),t=c({product:"",segment:"",description:"",channel:"facebook",language:"th"}),v=c([]),i=c(""),p=c(!1);function x(n){v.value=Array.from(n.target.files||[])}async function y(){p.value=!0,i.value="";try{const n=new FormData;n.append("product",t.value.product),n.append("segment",t.value.segment),n.append("description",t.value.description),n.append("channel",t.value.channel),n.append("language",t.value.language),v.value.forEach(o=>n.append("files",o));const g=(await S.createGroup(n)).group.id;b.push(`/admin/groups/${g}/edit`)}catch(n){i.value=n.message}finally{p.value=!1}}return(n,l)=>{const g=B("router-link");return h(),f("div",null,[V(g,{to:"/training",class:"btn-back"},{default:w(()=>[r("← "+a(s(u).t("training")),1)]),_:1}),l[7]||(l[7]=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,[r("กรอก "),e("strong",null,"ชื่อ/รายละเอียดสินค้าหรือบริการ"),r(" ที่อยากให้ทีมฝึกขาย (ช่องด้านล่าง)")]),e("li",null,"(ไม่บังคับ) ระบุกลุ่มลูกค้าเป้าหมาย หรืออัปโหลดไฟล์เอกสารสินค้า .pdf/.md/.txt"),e("li",null,[r("กด "),e("strong",null,"สร้าง"),r(" — ระบบจะพาไปหน้าสร้างบุคคลต้นแบบ (ลูกค้าจำลอง)")])])],-1)),e("div",N,[e("h2",D,"📦 "+a(s(u).t("addProduct")),1),e("label",null,[r(a(s(u).t("product"))+" ",1),l[5]||(l[5]=e("span",{class:"req"},"*",-1))]),d(e("textarea",{"onUpdate:modelValue":l[0]||(l[0]=o=>t.value.product=o),rows:"3",placeholder:"เช่น ระบบ POS สำหรับร้านอาหาร Cloud POS for small restaurants"},null,512),[[m,t.value.product]]),e("label",null,a(s(u).t("segment")),1),d(e("input",{"onUpdate:modelValue":l[1]||(l[1]=o=>t.value.segment=o),placeholder:"เช่น SMEs ร้านอาหารในกรุงเทพ"},null,512),[[m,t.value.segment]]),e("label",null,a(s(u).t("description")),1),d(e("textarea",{"onUpdate:modelValue":l[2]||(l[2]=o=>t.value.description=o),rows:"3",placeholder:"รายละเอียดเพิ่มเติม เช่น จุดเด่นสินค้า ราคา ฯลฯ (ไม่บังคับ)"},null,512),[[m,t.value.description]]),e("div",E,[e("div",G,[e("label",null,a(s(u).t("channel")),1),d(e("select",{"onUpdate:modelValue":l[3]||(l[3]=o=>t.value.channel=o)},[e("option",M,a(s(u).t("facebook")),1),e("option",P,a(s(u).t("line")),1)],512),[[_,t.value.channel]])]),e("div",q,[e("label",null,a(s(u).t("language")),1),d(e("select",{"onUpdate:modelValue":l[4]||(l[4]=o=>t.value.language=o)},[e("option",F,a(s(u).t("thai")),1),e("option",O,a(s(u).t("english")),1)],512),[[_,t.value.language]])])]),l[6]||(l[6]=e("label",null,"📎 ไฟล์เอกสารสินค้า (.pdf/.md/.txt) — ใช้เป็นข้อมูลให้ระบบได้",-1)),e("input",{type:"file",multiple:"",accept:".pdf,.md,.txt",onChange:x},null,32),i.value?(h(),f("div",T,a(i.value),1)):C("",!0),e("button",{class:"primary",style:{"margin-top":"16px"},disabled:p.value||!t.value.product&&!v.value.length,onClick:y},a(p.value?"กำลังสร้าง…":" สร้างกลุ่มบุคคลต้นแบบ"),9,j)])])}}},$=k(A,[["__scopeId","data-v-c7f3b0ac"]]);export{$ as default};

View File

@@ -0,0 +1 @@
.guide[data-v-d513596d]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.req[data-v-d513596d]{color:var(--red)}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.sec[data-v-738758fc]{margin-top:20px}.sec h4[data-v-738758fc]{margin:0 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)}.row[data-v-738758fc]{display:flex;gap:12px;flex-wrap:wrap}.f[data-v-738758fc]{flex:1;min-width:140px}label[data-v-738758fc]{font-size:13px;color:var(--muted);display:block;margin:10px 0 4px}input[data-v-738758fc],select[data-v-738758fc],textarea[data-v-738758fc]{width:100%}.actions[data-v-738758fc]{margin-top:20px}.grid[data-v-c682ddb6]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.pcard[data-v-c682ddb6]{display:flex;flex-direction:column}.edit-btn[data-v-c682ddb6]{margin-top:auto;display:inline-flex;align-items:center;gap:6px}.star[data-v-c682ddb6]{color:#d8dbe3}.star.on[data-v-c682ddb6]{color:#f59e0b}.badge.tier-a[data-v-c682ddb6]{background:#dcfce7;color:#166534}.badge.tier-b[data-v-c682ddb6]{background:#fef9c3;color:#854d0e}.badge.tier-c[data-v-c682ddb6]{background:#fee2e2;color:#991b1b}.guide[data-v-c682ddb6]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.modal-backdrop[data-v-c682ddb6]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0f172a80;display:flex;justify-content:center;align-items:flex-start;padding:40px 16px;z-index:50;overflow:auto}.modal[data-v-c682ddb6]{background:#fff;border-radius:14px;padding:24px;width:100%;max-width:720px;box-shadow:0 20px 50px #00000040}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.sec[data-v-738758fc]{margin-top:20px}.sec h4[data-v-738758fc]{margin:0 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border)}.row[data-v-738758fc]{display:flex;gap:12px;flex-wrap:wrap}.f[data-v-738758fc]{flex:1;min-width:140px}label[data-v-738758fc]{font-size:13px;color:var(--muted);display:block;margin:10px 0 4px}input[data-v-738758fc],select[data-v-738758fc],textarea[data-v-738758fc]{width:100%}.actions[data-v-738758fc]{margin-top:20px}.grid[data-v-812ba03f]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.pcard[data-v-812ba03f]{display:flex;flex-direction:column}.edit-btn[data-v-812ba03f]{margin-top:auto}.star[data-v-812ba03f]{color:#d8dbe3}.star.on[data-v-812ba03f]{color:#f59e0b}.badge.tier-a[data-v-812ba03f]{background:#dcfce7;color:#166534}.badge.tier-b[data-v-812ba03f]{background:#fef9c3;color:#854d0e}.badge.tier-c[data-v-812ba03f]{background:#fee2e2;color:#991b1b}.guide[data-v-812ba03f]{background:#eef2ff;border-color:#c7d2fe;margin-bottom:16px}.modal-backdrop[data-v-812ba03f]{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-812ba03f]{background:#fff;border-radius:14px;padding:24px;width:100%;max-width:720px;box-shadow:0 20px 50px #00000040}

View File

@@ -1 +1 @@
import{_ as b,c as d,a as e,t as s,u as c,i as o,w as y,v as x,b as w,d as k,e as S,r as l,f,g as V,h as B,o as v}from"./index-BhdfH8-z.js";const C={class:"login-wrap"},D={class:"card login-card"},K={class:"pw-wrap"},E=["type"],L=["aria-label"],M={key:0,class:"error",role:"alert"},N=["disabled"],R={key:0,class:"spinner"},U={key:1},q={__name:"Login",setup(H){const g=B(),_=V(),n=l(""),u=l(""),a=l(!1),r=l(""),i=l(!1);async function m(){r.value="",i.value=!0;try{await f.login(n.value.trim(),u.value),f.mustSetup?_.push({path:"/setup"}):_.push(g.query.redirect||"/")}catch{r.value=o.t("loginError")}finally{i.value=!1}}return(h,t)=>(v(),d("div",C,[e("div",D,[e("h1",null,"🎯 "+s(c(o).t("app")),1),t[3]||(t[3]=e("p",{class:"muted",style:{"margin-top":"-8px"}},"Sales training simulator",-1)),e("label",null,s(c(o).t("username")),1),y(e("input",{"onUpdate:modelValue":t[0]||(t[0]=p=>n.value=p),type:"text",autocomplete:"username",onKeyup:w(m,["enter"])},null,544),[[x,n.value]]),e("label",null,s(c(o).t("password")),1),e("div",K,[y(e("input",{"onUpdate:modelValue":t[1]||(t[1]=p=>u.value=p),type:a.value?"text":"password",autocomplete:"current-password",onKeyup:w(m,["enter"])},null,40,E),[[k,u.value]]),e("button",{type:"button",class:"pw-toggle",onClick:t[2]||(t[2]=p=>a.value=!a.value),"aria-label":a.value?"Hide password":"Show password"},s(a.value?"🙈":"👁"),9,L)]),r.value?(v(),d("div",M,s(r.value),1)):S("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:i.value||!n.value||!u.value,onClick:m},[i.value?(v(),d("span",R)):(v(),d("span",U,s(c(o).t("login")),1))],8,N)])]))}},P=b(q,[["__scopeId","data-v-f7904d9f"]]);export{P as default}; import{_ as b,c as d,a as e,t as s,u as c,i as o,w as y,v as x,b as w,d as k,e as S,r as l,f,g as V,h as B,o as v}from"./index-C0Orcg9B.js";const C={class:"login-wrap"},D={class:"card login-card"},K={class:"pw-wrap"},E=["type"],L=["aria-label"],M={key:0,class:"error",role:"alert"},N=["disabled"],R={key:0,class:"spinner"},U={key:1},q={__name:"Login",setup(H){const g=B(),_=V(),n=l(""),u=l(""),a=l(!1),r=l(""),i=l(!1);async function m(){r.value="",i.value=!0;try{await f.login(n.value.trim(),u.value),f.mustSetup?_.push({path:"/setup"}):_.push(g.query.redirect||"/")}catch{r.value=o.t("loginError")}finally{i.value=!1}}return(h,t)=>(v(),d("div",C,[e("div",D,[e("h1",null,"🎯 "+s(c(o).t("app")),1),t[3]||(t[3]=e("p",{class:"muted",style:{"margin-top":"-8px"}},"Sales training simulator",-1)),e("label",null,s(c(o).t("username")),1),y(e("input",{"onUpdate:modelValue":t[0]||(t[0]=p=>n.value=p),type:"text",autocomplete:"username",onKeyup:w(m,["enter"])},null,544),[[x,n.value]]),e("label",null,s(c(o).t("password")),1),e("div",K,[y(e("input",{"onUpdate:modelValue":t[1]||(t[1]=p=>u.value=p),type:a.value?"text":"password",autocomplete:"current-password",onKeyup:w(m,["enter"])},null,40,E),[[k,u.value]]),e("button",{type:"button",class:"pw-toggle",onClick:t[2]||(t[2]=p=>a.value=!a.value),"aria-label":a.value?"Hide password":"Show password"},s(a.value?"🙈":"👁"),9,L)]),r.value?(v(),d("div",M,s(r.value),1)):S("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:i.value||!n.value||!u.value,onClick:m},[i.value?(v(),d("span",R)):(v(),d("span",U,s(c(o).t("login")),1))],8,N)])]))}},P=b(q,[["__scopeId","data-v-f7904d9f"]]);export{P as default};

View File

@@ -0,0 +1 @@
.stat-row[data-v-6767b6b8]{gap:16px}.stat[data-v-6767b6b8]{text-align:center;min-width:100px}.stat span[data-v-6767b6b8]{display:block;font-size:12px;color:var(--muted)}.stat strong[data-v-6767b6b8]{font-size:26px}.stat.won strong[data-v-6767b6b8]{color:var(--green)}.stat.lost strong[data-v-6767b6b8]{color:var(--red)}

View File

@@ -1 +0,0 @@
.stat-row[data-v-b92138a0]{gap:16px}.stat[data-v-b92138a0]{text-align:center;min-width:100px}.stat span[data-v-b92138a0]{display:block;font-size:12px;color:var(--muted)}.stat strong[data-v-b92138a0]{font-size:26px}.stat.won strong[data-v-b92138a0]{color:var(--green)}.stat.lost strong[data-v-b92138a0]{color:var(--red)}

View File

@@ -0,0 +1,6 @@
import{k as x,_ as f,l as k,s as p,c as r,a as t,m as b,u as e,j as B,t as s,i as a,e as v,w as N,y as D,F as S,q as T,r as h,z as c,o as i,A as V}from"./index-C0Orcg9B.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=x("layout-dashboard",[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]]),L={style:{margin:"0"}},M={key:0,class:"row stat-row"},j={class:"card stat"},C={class:"card stat won"},F={class:"card stat lost"},I={class:"card stat"},q={key:1,class:"card"},A={key:2,class:"card empty-state"},E={class:"card",style:{"margin-top":"20px"}},G={style:{"margin-top":"0"}},H={__name:"MyBoard",setup(J){const o=h([]),u=h([]),y=h(!0),m=c(()=>o.value.length),_=c(()=>o.value.filter(n=>n.my_outcome==="won").length),g=c(()=>o.value.filter(n=>n.my_outcome==="lost").length),w=c(()=>o.value.filter(n=>n.my_outcome==="not_tried").length);return k(async()=>{try{o.value=(await p.myBoard()).board||[],u.value=(await p.mySessions()).sessions||[]}finally{y.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",L,[b(e(z),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),B(" "+s(e(a).t("myDashboard")),1)]),l[0]||(l[0]=t("div",{class:"muted",style:{"margin-top":"4px","margin-bottom":"16px"}},"สรุปผลการฝึกของตัวคุณเอง — ดูว่าปิดการขายได้กี่ครั้ง ยังฝึกกับใครบ้าง",-1))]),y.value?v("",!0):(i(),r("div",M,[t("div",j,[t("span",null,s(e(a).t("total")),1),t("strong",null,s(m.value),1)]),t("div",C,[t("span",null,s(e(a).t("won")),1),t("strong",null,s(_.value),1)]),t("div",F,[t("span",null,s(e(a).t("lost")),1),t("strong",null,s(g.value),1)]),t("div",I,[t("span",null,s(e(a).t("notTried")),1),t("strong",null,s(w.value),1)])])),y.value?(i(),r("div",q,[...l[1]||(l[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):o.value.length===0?(i(),r("div",A,[...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)])])):v("",!0),N(t("div",E,[t("h3",G,s(e(a).t("mySessions")),1),(i(!0),r(S,null,T(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:V(["badge",d.outcome])},s(d.outcome==="won"?e(a).t("won"):e(a).t("lost")),3)]))),128))],512),[[D,u.value.length]])]))}},O=f(H,[["__scopeId","data-v-6767b6b8"]]);export{O as default};

View File

@@ -1 +0,0 @@
import{_ as f,k as x,q as v,c as r,a as t,t as s,u as e,i as a,e as _,w as b,x as k,F as B,p as S,r as m,y as u,o as i,z as D}from"./index-BhdfH8-z.js";const M={style:{margin:"0"}},N={key:0,class:"row stat-row"},T={class:"card stat"},z={class:"card stat won"},C={class:"card stat lost"},F={class:"card stat"},V={key:1,class:"card"},j={key:2,class:"card empty-state"},q={class:"card",style:{"margin-top":"20px"}},E={style:{"margin-top":"0"}},G={__name:"MyBoard",setup(I){const o=m([]),c=m([]),p=m(!0),y=u(()=>o.value.length),g=u(()=>o.value.filter(n=>n.my_outcome==="won").length),h=u(()=>o.value.filter(n=>n.my_outcome==="lost").length),w=u(()=>o.value.filter(n=>n.my_outcome==="not_tried").length);return x(async()=>{try{o.value=(await v.myBoard()).board||[],c.value=(await v.mySessions()).sessions||[]}finally{p.value=!1}}),(n,l)=>(i(),r("div",null,[t("div",null,[t("h2",M,"📈 "+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",N,[t("div",T,[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",C,[t("span",null,s(e(a).t("lost")),1),t("strong",null,s(h.value),1)]),t("div",F,[t("span",null,s(e(a).t("notTried")),1),t("strong",null,s(w.value),1)])])),p.value?(i(),r("div",V,[...l[1]||(l[1]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):o.value.length===0?(i(),r("div",j,[...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",q,[t("h3",E,s(e(a).t("mySessions")),1),(i(!0),r(B,null,S(c.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:D(["badge",d.outcome])},s(d.outcome==="won"?e(a).t("won"):e(a).t("lost")),3)]))),128))],512),[[k,c.value.length]])]))}},A=f(G,[["__scopeId","data-v-b92138a0"]]);export{A as default};

View File

@@ -1 +0,0 @@
.grid[data-v-0fa25205]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pcard[data-v-0fa25205]{display:flex;flex-direction:column;min-height:190px}.diff[data-v-0fa25205]{margin:8px 0}.star[data-v-0fa25205]{color:#d8dbe3}.star.on[data-v-0fa25205]{color:#f59e0b}.badge.won[data-v-0fa25205]{background:#dcfce7;color:#166534}.badge.lost[data-v-0fa25205]{background:#fee2e2;color:#991b1b}.badge.not_tried[data-v-0fa25205]{background:#eef2ff;color:#4338ca}.guide[data-v-0fa25205]{background:#eef2ff;border-color:#c7d2fe;margin:12px 0 16px}

View File

@@ -0,0 +1 @@
import{_ as T,l as $,c as r,m as d,n as m,a as t,t as s,u as e,i as n,f as g,j as _,e as w,F as y,q as p,h as L,s as N,x as V,r as b,o as a,S as I,A as h,B as A}from"./index-C0Orcg9B.js";import{T as S}from"./target-qgda8bhm.js";import{A as j}from"./arrow-left-BoUMQ_f_.js";const F={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([]),B=b(!0);async function C(){try{k.value=(await N.listPersonas(c)).personas}finally{B.value=!1}}function z(l){return k.value.filter(i=>i.tier===l)}function P(l){return n.t(l==="A"?"tierA":l==="B"?"tierB":"tierC")}function v(l){return l==="won"?n.t("won"):l==="lost"?n.t("lost"):n.t("notTried")}return $(C),(l,i)=>{const u=V("router-link");return a(),r("div",null,[d(u,{to:"/training",class:"btn-back"},{default:m(()=>[d(e(j),{size:16,"stroke-width":2}),_(" "+s(e(n).t("training")),1)]),_:1}),t("div",F,[t("h2",q,s(e(n).t("personas")),1),t("span",D,s(e(n).t("selectPersona")),1)]),e(g).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(g).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"],f=>t("div",{key:f,style:{margin:"20px 0"}},[t("h4",null,s(P(f)),1),t("div",G,[(a(!0),r(y,null,p(z(f),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(g).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

@@ -0,0 +1 @@
.grid[data-v-46d50c99]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pcard[data-v-46d50c99]{display:flex;flex-direction:column;min-height:190px}.diff[data-v-46d50c99]{margin:8px 0}.star[data-v-46d50c99]{color:#d8dbe3}.star.on[data-v-46d50c99]{color:#f59e0b}.badge.won[data-v-46d50c99]{background:#dcfce7;color:#166534}.badge.lost[data-v-46d50c99]{background:#fee2e2;color:#991b1b}.badge.not_tried[data-v-46d50c99]{background:#eef2ff;color:#4338ca}.guide[data-v-46d50c99]{background:#eef2ff;border-color:#c7d2fe;margin:12px 0 16px}

View File

@@ -1 +0,0 @@
import{_ as N,k as V,c as i,l as k,m as u,a as t,t as s,u as a,i as n,f as y,e as b,F as _,p as f,h as z,q as I,s as L,r as x,o,j as w,z as p,A}from"./index-BhdfH8-z.js";const T={class:"row",style:{"align-items":"center"}},j={style:{margin:"0"}},F={class:"muted",style:{"margin-left":"auto"}},q={key:0,class:"card guide"},D={key:1,class:"row",style:{margin:"12px 0",gap:"10px"}},E={class:"primary"},M={class:"grid"},R={class:"row",style:{"justify-content":"space-between"}},S={class:"diff"},G={class:"muted"},H={class:"muted"},J={class:"muted"},K={class:"muted",style:{"margin-top":"6px"}},O={class:"primary",style:{width:"100%"}},Q={class:"primary",style:{width:"100%"}},U={key:1,class:"muted",style:{"margin-top":"auto","font-size":"12px"}},W={__name:"Personas",setup(X){const d=z().params.gid,g=x([]),B=x(!0);async function C(){try{g.value=(await I.listPersonas(d)).personas}finally{B.value=!1}}function P(l){return g.value.filter(r=>r.tier===l)}function $(l){return n.t(l==="A"?"tierA":l==="B"?"tierB":"tierC")}function h(l){return l==="won"?n.t("won"):l==="lost"?n.t("lost"):n.t("notTried")}return V(C),(l,r)=>{const c=L("router-link");return o(),i("div",null,[k(c,{to:"/training",class:"btn-back"},{default:u(()=>[w("← "+s(a(n).t("training")),1)]),_:1}),t("div",T,[t("h2",j,s(a(n).t("personas")),1),t("span",F,s(a(n).t("selectPersona")),1)]),a(y).isAdmin?b("",!0):(o(),i("div",q,[...r[0]||(r[0]=[t("strong",null,"🎯 วิธีฝึก",-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)])])),a(y).isAdmin?(o(),i("div",D,[k(c,{to:`/admin/groups/${a(d)}/edit`},{default:u(()=>[t("button",E,"🛠 "+s(a(n).t("managePersonas")),1)]),_:1},8,["to"]),r[1]||(r[1]=t("span",{class:"muted"},"Admin: full persona data + edit available here.",-1))])):b("",!0),(o(),i(_,null,f(["A","B","C"],m=>t("div",{key:m,style:{margin:"20px 0"}},[t("h4",null,s($(m)),1),t("div",M,[(o(!0),i(_,null,f(P(m),e=>(o(),i("div",{key:e.id,class:"card pcard lift"},[t("div",R,[t("strong",null,s(e.name),1),t("span",{class:p(["badge",e.my_outcome])},s(h(e.my_outcome)),3)]),t("div",S,[(o(),i(_,null,f(5,v=>t("span",{key:v,class:p(["star",{on:v<=(e.difficulty||1)}])},"★",2)),64)),t("span",G,s(a(n).t("difficulty"))+" "+s(e.difficulty||1)+"/5",1)]),t("div",H,[w(s(e.profession)+" · "+s(e.age_group)+" · "+s(e.location),1),r[2]||(r[2]=t("br",null,null,-1)),t("span",{class:p(["badge",e.channel])},s(e.channel),3),t("span",J," · "+s(e.initiation_mode==="seller"?a(n).t("sellerInitiated"):a(n).t("customerInitiated")),1)]),t("div",K,s(e.product_context),1),a(y).isAdmin?(o(),A(c,{key:0,to:`/groups/${a(d)}/chat/${e.id}`,style:{"margin-top":"auto"}},{default:u(()=>[t("button",O,s(a(n).t("chat")),1)]),_:1},8,["to"])):(o(),i(_,{key:1},[e.my_outcome==="not_tried"?(o(),A(c,{key:0,to:`/groups/${a(d)}/chat/${e.id}`,style:{"margin-top":"auto"}},{default:u(()=>[t("button",Q,s(a(n).t("chat")),1)]),_:1},8,["to"])):(o(),i("div",U,"✓ "+s(a(n).t("trained"))+" ("+s(h(e.my_outcome))+")",1))],64))]))),128))])])),64))])}}},tt=N(W,[["__scopeId","data-v-0fa25205"]]);export{tt as default};

View File

@@ -0,0 +1 @@
.active[data-v-4a5a3af4]{border-color:var(--accent);color:var(--accent);font-weight:600}.ok[data-v-4a5a3af4]{color:var(--green);font-size:13px;margin-top:10px}.error[data-v-4a5a3af4]{margin-top:10px}

View File

@@ -0,0 +1,16 @@
import{k as z,_ as T,r as c,f as i,c as h,a as e,m as k,u as a,S as $,j as v,t as o,i as s,w as x,v as b,e as V,A as S,s as U,o as w}from"./index-C0Orcg9B.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=z("globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]]);/**
* @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 D=z("lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/**
* @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=z("user",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]),I={class:"card"},A={style:{"margin-top":"0"}},G={class:"muted"},H=["value"],O={class:"row",style:{gap:"10px","margin-top":"16px"}},q=["disabled"],F={key:0,class:"spinner"},J={class:"card",style:{"margin-top":"16px"}},K={style:{"margin-top":"0"}},Q={class:"row",style:{gap:"10px","margin-top":"16px"}},R=["disabled"],W={key:0,class:"spinner"},X={class:"card",style:{"margin-top":"16px"}},Y={style:{"margin-top":"0"}},Z={class:"row"},ee={key:0,class:"error",role:"alert"},ae={key:1,class:"ok",role:"status"},se={__name:"Settings",setup(te){var C,M;const y=c(((C=i.user)==null?void 0:C.name)||""),p=c(((M=i.user)==null?void 0:M.email)||""),u=c(""),_=c(""),r=c(""),m=c(""),g=c(!1),f=c(!1);async function B(){var d,t;r.value="",m.value="",g.value=!0;try{const n={};if(y.value&&y.value!==((d=i.user)==null?void 0:d.name)&&(n.name=y.value),p.value&&p.value!==((t=i.user)==null?void 0:t.email)&&(n.email=p.value),Object.keys(n).length){const l=await U.updateProfile(n);i.user=l.user,m.value=s.t("saved")}}catch(n){r.value=n.message}finally{g.value=!1}}async function N(){var d,t,n;if(r.value="",m.value="",u.value.length<4){r.value=s.t("passwordTooShort");return}if(u.value!==_.value){r.value=s.t("passwordMismatch");return}f.value=!0;try{const l=((d=i.user)==null?void 0:d.username)||((t=i.user)==null?void 0:t.id)||"",L=await U.setup({username:l,email:p.value||((n=i.user)==null?void 0:n.email),password:u.value});i.user=L.user,u.value="",_.value="",m.value=s.t("saved")}catch(l){r.value=l.message}finally{f.value=!1}}function P(d){s.set(d)}return(d,t)=>{var n;return w(),h("div",null,[e("h2",null,[k(a($),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),v(" "+o(a(s).t("settings")),1)]),e("div",I,[e("h3",A,[k(a(E),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),v(" "+o(a(s).t("profile")),1)]),e("label",null,[v(o(a(s).t("username"))+" ",1),e("span",G,"("+o(a(s).t("readonly"))+")",1)]),e("input",{value:((n=a(i).user)==null?void 0:n.username)||"",disabled:""},null,8,H),e("label",null,o(a(s).t("name")),1),x(e("input",{"onUpdate:modelValue":t[0]||(t[0]=l=>y.value=l),type:"text"},null,512),[[b,y.value]]),e("label",null,o(a(s).t("email")),1),x(e("input",{"onUpdate:modelValue":t[1]||(t[1]=l=>p.value=l),type:"email"},null,512),[[b,p.value]]),e("div",O,[e("button",{class:"primary",disabled:g.value,onClick:B},[g.value?(w(),h("span",F)):V("",!0),v(o(a(s).t("saveProfile")),1)],8,q)])]),e("div",J,[e("h3",K,[k(a(D),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),v(" "+o(a(s).t("changePassword")),1)]),e("label",null,o(a(s).t("newPassword")),1),x(e("input",{"onUpdate:modelValue":t[2]||(t[2]=l=>u.value=l),type:"password",autocomplete:"new-password"},null,512),[[b,u.value]]),e("label",null,o(a(s).t("confirmPassword")),1),x(e("input",{"onUpdate:modelValue":t[3]||(t[3]=l=>_.value=l),type:"password",autocomplete:"new-password"},null,512),[[b,_.value]]),e("div",Q,[e("button",{class:"primary",disabled:f.value||!u.value||u.value!==_.value,onClick:N},[f.value?(w(),h("span",W)):V("",!0),v(o(a(s).t("save")),1)],8,R)])]),e("div",X,[e("h3",Y,[k(a(j),{size:18,"stroke-width":1.8,style:{"vertical-align":"-3px"}}),v(" "+o(a(s).t("language")),1)]),e("div",Z,[e("button",{class:S({active:a(s).locale==="th"}),onClick:t[4]||(t[4]=l=>P("th"))},"ไทย",2),e("button",{class:S({active:a(s).locale==="en"}),onClick:t[5]||(t[5]=l=>P("en"))},"English",2)])]),r.value?(w(),h("div",ee,o(r.value),1)):V("",!0),m.value?(w(),h("div",ae,"✅ "+o(m.value),1)):V("",!0)])}}},oe=T(se,[["__scopeId","data-v-4a5a3af4"]]);export{oe as default};

View File

@@ -1 +0,0 @@
import{_ as j,r as v,f as u,c as y,a as e,t as o,u as l,i as s,j as x,w as h,v as b,e as k,z as B,q as S,o as f}from"./index-BhdfH8-z.js";const z={class:"card"},D={style:{"margin-top":"0"}},E={class:"muted"},M=["value"],$={class:"row",style:{gap:"10px","margin-top":"16px"}},q=["disabled"],I={key:0,class:"spinner"},L={class:"card",style:{"margin-top":"16px"}},O={style:{"margin-top":"0"}},A={class:"row",style:{gap:"10px","margin-top":"16px"}},F=["disabled"],G={key:0,class:"spinner"},H={class:"card",style:{"margin-top":"16px"}},J={style:{"margin-top":"0"}},K={class:"row"},Q={key:0,class:"error",role:"alert"},R={key:1,class:"ok",role:"status"},W={__name:"Settings",setup(X){var V,C;const m=v(((V=u.user)==null?void 0:V.name)||""),c=v(((C=u.user)==null?void 0:C.email)||""),i=v(""),_=v(""),r=v(""),p=v(""),w=v(!1),g=v(!1);async function U(){var d,a;r.value="",p.value="",w.value=!0;try{const n={};if(m.value&&m.value!==((d=u.user)==null?void 0:d.name)&&(n.name=m.value),c.value&&c.value!==((a=u.user)==null?void 0:a.email)&&(n.email=c.value),Object.keys(n).length){const t=await S.updateProfile(n);u.user=t.user,p.value=s.t("saved")}}catch(n){r.value=n.message}finally{w.value=!1}}async function N(){var d,a,n;if(r.value="",p.value="",i.value.length<4){r.value=s.t("passwordTooShort");return}if(i.value!==_.value){r.value=s.t("passwordMismatch");return}g.value=!0;try{const t=((d=u.user)==null?void 0:d.username)||((a=u.user)==null?void 0:a.id)||"",T=await S.setup({username:t,email:c.value||((n=u.user)==null?void 0:n.email),password:i.value});u.user=T.user,i.value="",_.value="",p.value=s.t("saved")}catch(t){r.value=t.message}finally{g.value=!1}}function P(d){s.set(d)}return(d,a)=>{var n;return f(),y("div",null,[e("h2",null,"⚙️ "+o(l(s).t("settings")),1),e("div",z,[e("h3",D,"👤 "+o(l(s).t("profile")),1),e("label",null,[x(o(l(s).t("username"))+" ",1),e("span",E,"("+o(l(s).t("readonly"))+")",1)]),e("input",{value:((n=l(u).user)==null?void 0:n.username)||"",disabled:""},null,8,M),e("label",null,o(l(s).t("name")),1),h(e("input",{"onUpdate:modelValue":a[0]||(a[0]=t=>m.value=t),type:"text"},null,512),[[b,m.value]]),e("label",null,o(l(s).t("email")),1),h(e("input",{"onUpdate:modelValue":a[1]||(a[1]=t=>c.value=t),type:"email"},null,512),[[b,c.value]]),e("div",$,[e("button",{class:"primary",disabled:w.value,onClick:U},[w.value?(f(),y("span",I)):k("",!0),x(o(l(s).t("saveProfile")),1)],8,q)])]),e("div",L,[e("h3",O,"🔒 "+o(l(s).t("changePassword")),1),e("label",null,o(l(s).t("newPassword")),1),h(e("input",{"onUpdate:modelValue":a[2]||(a[2]=t=>i.value=t),type:"password",autocomplete:"new-password"},null,512),[[b,i.value]]),e("label",null,o(l(s).t("confirmPassword")),1),h(e("input",{"onUpdate:modelValue":a[3]||(a[3]=t=>_.value=t),type:"password",autocomplete:"new-password"},null,512),[[b,_.value]]),e("div",A,[e("button",{class:"primary",disabled:g.value||!i.value||i.value!==_.value,onClick:N},[g.value?(f(),y("span",G)):k("",!0),x(o(l(s).t("save")),1)],8,F)])]),e("div",H,[e("h3",J,"🌐 "+o(l(s).t("language")),1),e("div",K,[e("button",{class:B({active:l(s).locale==="th"}),onClick:a[4]||(a[4]=t=>P("th"))},"ไทย",2),e("button",{class:B({active:l(s).locale==="en"}),onClick:a[5]||(a[5]=t=>P("en"))},"English",2)])]),r.value?(f(),y("div",Q,o(r.value),1)):k("",!0),p.value?(f(),y("div",R,"✅ "+o(p.value),1)):k("",!0)])}}},Z=j(W,[["__scopeId","data-v-2dca72e5"]]);export{Z as default};

View File

@@ -1 +0,0 @@
.active[data-v-2dca72e5]{border-color:var(--accent);color:var(--accent);font-weight:600}.ok[data-v-2dca72e5]{color:var(--green);font-size:13px;margin-top:10px}.error[data-v-2dca72e5]{margin-top:10px}

View File

@@ -1 +1 @@
import{_ as x,c as v,a as e,t,u as s,i as a,j as S,f as _,w,v as f,b as y,e as V,r,g as K,o as m}from"./index-BhdfH8-z.js";const T={class:"setup-wrap"},B={class:"card setup-card"},N={class:"muted"},U={key:0,class:"error",role:"alert"},C=["disabled"],D={key:0,class:"spinner"},M={key:1},P={__name:"Setup",setup(j){const k=K(),i=r(""),l=r(""),p=r(""),o=r(""),d=r(!1);async function c(){if(o.value="",l.value.length<4){o.value=a.t("passwordTooShort");return}if(l.value!==p.value){o.value=a.t("passwordMismatch");return}d.value=!0;try{await _.finishSetup(i.value.trim(),l.value),k.push("/")}catch(h){o.value=h.message}finally{d.value=!1}}return(h,u)=>{var b,g;return m(),v("div",T,[e("div",B,[e("h1",null,"🔐 "+t(s(a).t("setupTitle")),1),e("p",N,[S(t(s(a).t("setupSubtitle"))+" ",1),e("strong",null,t(((b=s(_).user)==null?void 0:b.name)||((g=s(_).user)==null?void 0:g.username)),1)]),e("label",null,t(s(a).t("email")),1),w(e("input",{"onUpdate:modelValue":u[0]||(u[0]=n=>i.value=n),type:"email",autocomplete:"email",onKeyup:y(c,["enter"])},null,544),[[f,i.value]]),e("label",null,t(s(a).t("newPassword")),1),w(e("input",{"onUpdate:modelValue":u[1]||(u[1]=n=>l.value=n),type:"password",autocomplete:"new-password",onKeyup:y(c,["enter"])},null,544),[[f,l.value]]),e("label",null,t(s(a).t("confirmPassword")),1),w(e("input",{"onUpdate:modelValue":u[2]||(u[2]=n=>p.value=n),type:"password",autocomplete:"new-password",onKeyup:y(c,["enter"])},null,544),[[f,p.value]]),o.value?(m(),v("div",U,t(o.value),1)):V("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!l.value||l.value!==p.value,onClick:c},[d.value?(m(),v("span",D)):(m(),v("span",M,t(s(a).t("save")),1))],8,C)])])}}},I=x(P,[["__scopeId","data-v-f190e120"]]);export{I as default}; import{_ as x,c as v,a as e,t,u as s,i as a,j as S,f as _,w,v as f,b as y,e as V,r,g as K,o as m}from"./index-C0Orcg9B.js";const T={class:"setup-wrap"},B={class:"card setup-card"},N={class:"muted"},U={key:0,class:"error",role:"alert"},C=["disabled"],D={key:0,class:"spinner"},M={key:1},P={__name:"Setup",setup(j){const k=K(),i=r(""),l=r(""),p=r(""),o=r(""),d=r(!1);async function c(){if(o.value="",l.value.length<4){o.value=a.t("passwordTooShort");return}if(l.value!==p.value){o.value=a.t("passwordMismatch");return}d.value=!0;try{await _.finishSetup(i.value.trim(),l.value),k.push("/")}catch(h){o.value=h.message}finally{d.value=!1}}return(h,u)=>{var b,g;return m(),v("div",T,[e("div",B,[e("h1",null,"🔐 "+t(s(a).t("setupTitle")),1),e("p",N,[S(t(s(a).t("setupSubtitle"))+" ",1),e("strong",null,t(((b=s(_).user)==null?void 0:b.name)||((g=s(_).user)==null?void 0:g.username)),1)]),e("label",null,t(s(a).t("email")),1),w(e("input",{"onUpdate:modelValue":u[0]||(u[0]=n=>i.value=n),type:"email",autocomplete:"email",onKeyup:y(c,["enter"])},null,544),[[f,i.value]]),e("label",null,t(s(a).t("newPassword")),1),w(e("input",{"onUpdate:modelValue":u[1]||(u[1]=n=>l.value=n),type:"password",autocomplete:"new-password",onKeyup:y(c,["enter"])},null,544),[[f,l.value]]),e("label",null,t(s(a).t("confirmPassword")),1),w(e("input",{"onUpdate:modelValue":u[2]||(u[2]=n=>p.value=n),type:"password",autocomplete:"new-password",onKeyup:y(c,["enter"])},null,544),[[f,p.value]]),o.value?(m(),v("div",U,t(o.value),1)):V("",!0),e("button",{class:"primary",style:{width:"100%","margin-top":"16px"},disabled:d.value||!i.value||!l.value||l.value!==p.value,onClick:c},[d.value?(m(),v("span",D)):(m(),v("span",M,t(s(a).t("save")),1))],8,C)])])}}},I=x(P,[["__scopeId","data-v-f190e120"]]);export{I as default};

View File

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

View File

@@ -0,0 +1 @@
import{_ as w,l as b,s as A,f as l,c as r,a as t,m as _,u as s,j as m,t as e,i as o,B as y,n as h,e as f,F as C,q as N,r as g,x as P,o as i,A as v}from"./index-C0Orcg9B.js";import{T}from"./target-qgda8bhm.js";import{P as B}from"./plus-Cw7xc8qj.js";const z={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},V={style:{margin:"0"}},j={style:{"margin-left":"auto"}},F={class:"primary"},L={class:"muted"},S={key:0,class:"card",style:{"min-height":"80px"}},$={key:1,class:"card empty-state"},q={key:0},D={key:1},E={class:"grid"},G={class:"card lift train-card"},I={class:"row",style:{"justify-content":"space-between"}},M={class:"muted",style:{margin:"6px 0 12px"}},H={class:"row",style:{gap:"8px"}},J={class:"muted"},K={class:"primary",style:{width:"100%","margin-top":"12px"}},O={__name:"Training",setup(Q){const c=g([]),u=g(!0);function k(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 A.listGroups()).groups||[];c.value=l.isAdmin?a:a.filter(d=>d.status==="ready")}finally{u.value=!1}}),(a,d)=>{const p=P("router-link");return i(),r("div",null,[t("div",z,[t("h2",V,[_(s(T),{size:22,"stroke-width":1.8,style:{"vertical-align":"-4px"}}),m(" "+e(s(o).t("training")),1)]),t("div",j,[s(l).isAdmin?(i(),y(p,{key:0,to:"/admin/new-group"},{default:h(()=>[t("button",F,[_(s(B),{size:18,"stroke-width":2}),m(" "+e(s(o).t("addProduct")),1)])]),_:1})):f("",!0)])]),t("p",L,e(s(o).t("trainingSubtitle")),1),u.value?(i(),r("div",S,[...d[0]||(d[0]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):c.value.length===0?(i(),r("div",$,[t("strong",null,e(s(o).t("noTraining")),1),s(l).isAdmin?(i(),r("span",q,'Click "'+e(s(o).t("addProduct"))+'" to create a persona group.',1)):(i(),r("span",D,"Ask an admin to create a persona group first."))])):f("",!0),t("div",E,[(i(!0),r(C,null,N(c.value,n=>(i(),y(p,{key:n.id,to:s(l).isAdmin?`/admin/groups/${n.id}/edit`:`/groups/${n.id}/personas`,style:{"text-decoration":"none"}},{default:h(()=>[t("div",G,[t("div",I,[t("strong",null,e(n.title),1),t("span",{class:v(["badge",n.status==="ready"?"ready":"draft"])},e(n.status),3)]),t("div",M,e(k(n)),1),t("div",H,[t("span",{class:v(["badge",n.channel||"line"])},e(n.channel||"line"),3),t("span",J,e(x(n))+" "+e(s(o).t("personas").toLowerCase()),1)]),t("button",K,e(s(l).isAdmin?n.status==="ready"?s(o).t("managePersonas"):s(o).t("analyze"):s(o).t("selectPersona")),1)])]),_:2},1032,["to"]))),128))])])}}},X=w(O,[["__scopeId","data-v-1e5bd92b"]]);export{X as default};

View File

@@ -1 +0,0 @@
import{_ as k,k as x,q as w,f as d,c as r,a as t,t as e,u as s,i as o,A as _,m,e as y,F as b,p as A,r as h,s as C,o as i,z as f}from"./index-BhdfH8-z.js";const B={class:"row",style:{"align-items":"center","margin-bottom":"16px"}},N={style:{margin:"0"}},P={style:{"margin-left":"auto"}},z={class:"primary"},T={class:"muted"},F={key:0,class:"card",style:{"min-height":"80px"}},L={key:1,class:"card empty-state"},S={key:0},V={key:1},$={class:"grid"},j={class:"card lift train-card"},q={class:"row",style:{"justify-content":"space-between"}},D={class:"muted",style:{margin:"6px 0 12px"}},E={class:"row",style:{gap:"8px"}},G={class:"muted"},I={class:"primary",style:{width:"100%","margin-top":"12px"}},M={__name:"Training",setup(H){const c=h([]),u=h(!0);function v(a){return a.sales_kit&&a.sales_kit.productName||a.input&&a.input.product||""}function g(a){return a.personas||a.persona_count||0}return x(async()=>{try{const a=(await w.listGroups()).groups||[];c.value=d.isAdmin?a:a.filter(l=>l.status==="ready")}finally{u.value=!1}}),(a,l)=>{const p=C("router-link");return i(),r("div",null,[t("div",B,[t("h2",N,"🎯 "+e(s(o).t("training")),1),t("div",P,[s(d).isAdmin?(i(),_(p,{key:0,to:"/admin/new-group"},{default:m(()=>[t("button",z," "+e(s(o).t("addProduct")),1)]),_:1})):y("",!0)])]),t("p",T,e(s(o).t("trainingSubtitle")),1),u.value?(i(),r("div",F,[...l[0]||(l[0]=[t("div",{class:"skeleton",style:{height:"50px"}},null,-1)])])):c.value.length===0?(i(),r("div",L,[t("strong",null,e(s(o).t("noTraining")),1),s(d).isAdmin?(i(),r("span",S,'Click "'+e(s(o).t("addProduct"))+'" to create a persona group.',1)):(i(),r("span",V,"Ask an admin to create a persona group first."))])):y("",!0),t("div",$,[(i(!0),r(b,null,A(c.value,n=>(i(),_(p,{key:n.id,to:s(d).isAdmin?`/admin/groups/${n.id}/edit`:`/groups/${n.id}/personas`,style:{"text-decoration":"none"}},{default:m(()=>[t("div",j,[t("div",q,[t("strong",null,e(n.title),1),t("span",{class:f(["badge",n.status==="ready"?"ready":"draft"])},e(n.status),3)]),t("div",D,e(v(n)),1),t("div",E,[t("span",{class:f(["badge",n.channel||"line"])},e(n.channel||"line"),3),t("span",G,e(g(n))+" "+e(s(o).t("personas").toLowerCase()),1)]),t("button",I,e(s(d).isAdmin?n.status==="ready"?s(o).t("managePersonas"):s(o).t("analyze"):s(o).t("selectPersona")),1)])]),_:2},1032,["to"]))),128))])])}}},K=k(M,[["__scopeId","data-v-901c9d3d"]]);export{K as default};

View File

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

View File

@@ -0,0 +1,6 @@
import{k as e}from"./index-C0Orcg9B.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 t=e("arrow-left",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);export{t as A};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

82
frontend/dist/assets/index-C0Orcg9B.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
frontend/dist/assets/plus-Cw7xc8qj.js vendored Normal file
View File

@@ -0,0 +1,6 @@
import{k as e}from"./index-C0Orcg9B.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 a=e("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);export{a as P};

View File

@@ -0,0 +1,6 @@
import{k as c}from"./index-C0Orcg9B.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 e=c("target",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]]);export{e as T};

View File

@@ -0,0 +1,6 @@
import{k as e}from"./index-C0Orcg9B.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 r=e("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]]);export{r as U};

View File

@@ -4,8 +4,8 @@
<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-BhdfH8-z.js"></script> <script type="module" crossorigin src="/assets/index-C0Orcg9B.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-HCrojZU7.css"> <link rel="stylesheet" crossorigin href="/assets/index-Bu7f-IE2.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -8,6 +8,7 @@
"name": "sales-trainer-frontend", "name": "sales-trainer-frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"lucide-vue-next": "^1.0.0",
"vue": "^3.4.0", "vue": "^3.4.0",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
}, },
@@ -1071,6 +1072,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/lucide-vue-next": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-1.0.0.tgz",
"integrity": "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==",
"deprecated": "Package deprecated. Please use @lucide/vue instead.",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.1"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",

View File

@@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"lucide-vue-next": "^1.0.0",
"vue": "^3.4.0", "vue": "^3.4.0",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
}, },

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="app"> <div class="app">
<nav v-if="auth.user" class="topnav"> <nav v-if="auth.user" class="topnav">
<router-link to="/" class="brand">🎯 {{ i18n.t('app') }}</router-link> <router-link to="/" class="brand">{{ i18n.t('app') }}</router-link>
<div class="tabs"> <div class="tabs">
<router-link v-if="auth.isAdmin" to="/" class="tab" :class="{ active: $route.path === '/' }"> <router-link v-if="auth.isAdmin" to="/" class="tab" :class="{ active: $route.path === '/' }">
{{ i18n.t('adminOverview') }} {{ i18n.t('adminOverview') }}
@@ -15,8 +15,10 @@
</div> </div>
<div class="nav-right"> <div class="nav-right">
<button @click="toggleLang" class="lang">{{ i18n.locale === 'th' ? 'EN' : 'TH' }}</button> <button @click="toggleLang" class="lang">{{ i18n.locale === 'th' ? 'EN' : 'TH' }}</button>
<router-link to="/settings" class="icon-btn" title="Settings"></router-link> <router-link to="/settings" class="icon-btn" :title="i18n.t('settings')">
<button @click="logout" class="logout-btn"> {{ i18n.t('logout') }}</button> <SettingsIcon :size="20" :stroke-width="1.8" />
</router-link>
<button @click="logout" class="logout-btn"><LogOut :size="18" :stroke-width="1.8" /> {{ i18n.t('logout') }}</button>
</div> </div>
</nav> </nav>
<main class="main"> <main class="main">
@@ -29,6 +31,7 @@
<script setup> <script setup>
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { Settings as SettingsIcon, LogOut } from 'lucide-vue-next'
import { auth } from './store/auth' import { auth } from './store/auth'
import { i18n } from './i18n' import { i18n } from './i18n'
@@ -75,7 +78,8 @@ onMounted(() => {
.tab.active { background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #fff; } .tab.active { background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #fff; }
.nav-right { display: flex; align-items: center; gap: 8px; } .nav-right { display: flex; align-items: center; gap: 8px; }
.lang, .logout-btn { padding: 8px 12px; } .lang, .logout-btn { padding: 8px 12px; }
.icon-btn { text-decoration: none; font-size: 18px; padding: 6px 10px; border-radius: 8px; } .logout-btn { display: inline-flex; align-items: center; gap: 6px; }
.icon-btn { text-decoration: none; display: inline-flex; align-items: center; padding: 6px 10px; border-radius: 8px; color: var(--ink); }
.icon-btn:hover { background: #f1f3f9; } .icon-btn:hover { background: #f1f3f9; }
.main { max-width: 1080px; margin: 0 auto; padding: 24px; } .main { max-width: 1080px; margin: 0 auto; padding: 24px; }
@media (max-width: 640px) { @media (max-width: 640px) {

View File

@@ -86,12 +86,14 @@ a { color: inherit; }
a:focus { outline: 2px solid var(--accent); outline-offset: 2px; } a:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Comfortable touch density + consistent transitions */ /* Comfortable touch density + consistent transitions */
button { min-height: 44px; transition: transform .15s ease, box-shadow .2s ease, background .2s ease, opacity .2s ease; } button { min-height: 44px; transition: transform .15s ease, box-shadow .2s ease, background .2s ease, opacity .2s ease; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
button:not(:disabled):hover { box-shadow: 0 4px 12px rgba(20,24,40,.1); } button:not(:disabled):hover { box-shadow: 0 4px 12px rgba(20,24,40,.1); }
button:not(:disabled):active { transform: scale(.97); } button:not(:disabled):active { transform: scale(.97); }
button.primary:not(:disabled):hover { box-shadow: 0 6px 18px rgba(79,70,229,.35); } button.primary:not(:disabled):hover { box-shadow: 0 6px 18px rgba(79,70,229,.35); }
button.soft { background: #f1f3f9; border-color: transparent; color: var(--ink); } button.soft { background: #f1f3f9; border-color: transparent; color: var(--ink); }
button.soft:not(:disabled):hover { background: #e6eaf3; } button.soft:not(:disabled):hover { background: #e6eaf3; }
/* Align inline SVG (lucide) icons inside buttons/links */
button svg, .icon-btn svg, .brand svg { vertical-align: -2px; }
input, select, textarea { input, select, textarea {
min-height: 44px; min-height: 44px;

View File

@@ -1,9 +1,9 @@
<template> <template>
<div> <div>
<h2>👥 {{ i18n.t('users') }}</h2> <h2><UsersIcon :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('users') }}</h2>
<div class="card guide"> <div class="card guide">
<strong>📋 เพมผใชงาน</strong> <strong><Info :size="17" :stroke-width="1.8" style="vertical-align:-3px" /> เพมผใชงาน</strong>
<ol style="margin:8px 0 0;padding-left:20px;line-height:1.8"> <ol style="margin:8px 0 0;padding-left:20px;line-height:1.8">
<li>กรอก <strong>อผใช (สำหรบเขาสระบบ)</strong>, , และรหสผานเรมต</li> <li>กรอก <strong>อผใช (สำหรบเขาสระบบ)</strong>, , และรหสผานเรมต</li>
<li>เลอกสทธ: <strong>user</strong> = เขาฝกขาย, <strong>admin</strong> = ดการระบบ</li> <li>เลอกสทธ: <strong>user</strong> = เขาฝกขาย, <strong>admin</strong> = ดการระบบ</li>
@@ -12,7 +12,7 @@
</div> </div>
<div class="card" style="margin-bottom:16px"> <div class="card" style="margin-bottom:16px">
<h4 style="margin-top:0"> {{ i18n.t('create') }} {{ i18n.t('users').toLowerCase() }}</h4> <h4 style="margin-top:0"><UserPlus :size="18" :stroke-width="1.8" style="vertical-align:-3px" /> {{ i18n.t('create') }} {{ i18n.t('users').toLowerCase() }}</h4>
<div class="row"> <div class="row">
<input v-model="form.username" placeholder="ชื่อผู้ใช้ (เข้าสู่ระบบ)" style="flex:1" /> <input v-model="form.username" placeholder="ชื่อผู้ใช้ (เข้าสู่ระบบ)" style="flex:1" />
<input v-model="form.name" placeholder="ชื่อจริง" style="flex:1" /> <input v-model="form.name" placeholder="ชื่อจริง" style="flex:1" />
@@ -41,6 +41,7 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { Users as UsersIcon, UserPlus, Info } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'

View File

@@ -2,12 +2,12 @@
<div> <div>
<div class="row" style="align-items:center;margin-bottom:16px"> <div class="row" style="align-items:center;margin-bottom:16px">
<div> <div>
<h2 style="margin:0">📊 {{ i18n.t('adminOverview') }}</h2> <h2 style="margin:0"><BarChart3 :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('adminOverview') }}</h2>
<div class="muted" style="margin-top:4px">ภาพรวมผลการฝกของทมทงหมด เลอกชวงเวลาเพอดสถ</div> <div class="muted" style="margin-top:4px">ภาพรวมผลการฝกของทมทงหมด เลอกชวงเวลาเพอดสถ</div>
</div> </div>
<div class="row" style="margin-left:auto;gap:10px"> <div class="row" style="margin-left:auto;gap:10px">
<router-link to="/admin/users"><button class="users-btn">👥 {{ i18n.t('users') }}</button></router-link> <router-link to="/admin/users"><button class="users-btn"><UsersIcon :size="18" :stroke-width="2" /> {{ i18n.t('users') }}</button></router-link>
<router-link to="/admin/new-group"><button class="primary"> {{ i18n.t('addProduct') }}</button></router-link> <router-link to="/admin/new-group"><button class="primary"><Plus :size="18" :stroke-width="2" /> {{ i18n.t('addProduct') }}</button></router-link>
</div> </div>
</div> </div>
@@ -66,6 +66,7 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { BarChart3, Users as UsersIcon, Plus } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'
@@ -100,6 +101,6 @@ onMounted(load)
.badge.not_tried { background: #eef2ff; color: #4338ca; } .badge.not_tried { background: #eef2ff; color: #4338ca; }
.bar { background: #eef0f5; border-radius: 999px; height: 10px; overflow: hidden; margin-top: 10px; } .bar { background: #eef0f5; border-radius: 999px; height: 10px; overflow: hidden; margin-top: 10px; }
.bar-fill { background: linear-gradient(90deg, var(--accent), var(--accent-2)); height: 100%; border-radius: 999px; transition: width .4s ease; } .bar-fill { background: linear-gradient(90deg, var(--accent), var(--accent-2)); height: 100%; border-radius: 999px; transition: width .4s ease; }
.users-btn { background: #0ea5e9; border-color: transparent; color: #fff; font-weight: 600; } .users-btn { display: inline-flex; align-items: center; gap: 6px; background: #0ea5e9; border-color: transparent; color: #fff; font-weight: 600; }
.users-btn:not(:disabled):hover { background: #0284c7; box-shadow: 0 6px 16px rgba(14,165,233,.35); } .users-btn:not(:disabled):hover { background: #0284c7; box-shadow: 0 6px 16px rgba(14,165,233,.35); }
</style> </style>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div> <div>
<router-link :to="`/groups/${gid}/personas`" class="btn-back"> {{ i18n.t('personas') }}</router-link> <router-link :to="`/groups/${gid}/personas`" class="btn-back"><ArrowLeft :size="16" :stroke-width="2" /> {{ i18n.t('personas') }}</router-link>
<!-- How-to-train guide --> <!-- How-to-train guide -->
<div class="card guide"> <div class="card guide">
<strong>🎯 {{ i18n.t('chatGuideTitle') }}</strong> <strong><Target :size="17" :stroke-width="1.8" style="vertical-align:-3px" /> {{ i18n.t('chatGuideTitle') }}</strong>
<p style="margin:6px 0 0;line-height:1.7">{{ i18n.t('chatGuideText') }}</p> <p style="margin:6px 0 0;line-height:1.7">{{ i18n.t('chatGuideText') }}</p>
</div> </div>
@@ -49,7 +49,7 @@
<ul><li v-for="(c, i) in debrief.coaching" :key="i">{{ c }}</li></ul> <ul><li v-for="(c, i) in debrief.coaching" :key="i">{{ c }}</li></ul>
</div> </div>
<details> <details>
<summary>🔎 {{ i18n.t('reveal') }}</summary> <summary><Search :size="15" :stroke-width="1.8" style="vertical-align:-2px" /> {{ i18n.t('reveal') }}</summary>
<div class="reveal-grid" v-if="debrief.revealed_persona"> <div class="reveal-grid" v-if="debrief.revealed_persona">
<div v-for="(v, k) in debrief.revealed_persona" :key="k" class="rev"> <div v-for="(v, k) in debrief.revealed_persona" :key="k" class="rev">
<span class="rk">{{ fieldLabel(k) }}</span> <span class="rk">{{ fieldLabel(k) }}</span>
@@ -65,6 +65,7 @@
<script setup> <script setup>
import { onMounted, nextTick, ref } from 'vue' import { onMounted, nextTick, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { Target, ArrowLeft, Search } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'

View File

@@ -1,10 +1,10 @@
<template> <template>
<div> <div>
<router-link to="/training" class="btn-back"> {{ i18n.t('training') }}</router-link> <router-link to="/training" class="btn-back"><ArrowLeft :size="16" :stroke-width="2" /> {{ i18n.t('training') }}</router-link>
<!-- Step guidance --> <!-- Step guidance -->
<div class="card guide"> <div class="card guide">
<strong>📋 สรางกลมบคคลตนแบบ</strong> <strong><ListChecks :size="18" :stroke-width="1.8" style="vertical-align:-3px" /> สรางกลมบคคลตนแบบ</strong>
<ol style="margin:8px 0 0;padding-left:20px;line-height:1.8"> <ol style="margin:8px 0 0;padding-left:20px;line-height:1.8">
<li>กรอก <strong>/รายละเอยดสนคาหรอบรการ</strong> อยากใหมฝกขาย (องดานลาง)</li> <li>กรอก <strong>/รายละเอยดสนคาหรอบรการ</strong> อยากใหมฝกขาย (องดานลาง)</li>
<li>(ไมงค) ระบกลมลกคาเปาหมาย หรออปโหลดไฟลเอกสารสนค .pdf/.md/.txt</li> <li>(ไมงค) ระบกลมลกคาเปาหมาย หรออปโหลดไฟลเอกสารสนค .pdf/.md/.txt</li>
@@ -13,7 +13,7 @@
</div> </div>
<div class="card"> <div class="card">
<h2 style="margin-top:0">📦 {{ i18n.t('addProduct') }}</h2> <h2 style="margin-top:0"><Box :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('addProduct') }}</h2>
<label>{{ i18n.t('product') }} <span class="req">*</span></label> <label>{{ i18n.t('product') }} <span class="req">*</span></label>
<textarea v-model="form.product" rows="3" placeholder="เช่น ระบบ POS สำหรับร้านอาหาร Cloud POS for small restaurants"></textarea> <textarea v-model="form.product" rows="3" placeholder="เช่น ระบบ POS สำหรับร้านอาหาร Cloud POS for small restaurants"></textarea>
@@ -40,12 +40,12 @@
</div> </div>
</div> </div>
<label>📎 ไฟลเอกสารสนค (.pdf/.md/.txt) ใชเปนขอมลใหระบบได</label> <label><Paperclip :size="15" :stroke-width="1.8" style="vertical-align:-2px" /> ไฟลเอกสารสนค (.pdf/.md/.txt) ใชเปนขอมลใหระบบได</label>
<input type="file" multiple accept=".pdf,.md,.txt" @change="onFiles" /> <input type="file" multiple accept=".pdf,.md,.txt" @change="onFiles" />
<div class="error" v-if="error">{{ error }}</div> <div class="error" v-if="error">{{ error }}</div>
<button class="primary" style="margin-top:16px" :disabled="busy || (!form.product && !files.length)" @click="create"> <button class="primary" style="margin-top:16px;display:inline-flex;align-items:center;gap:6px" :disabled="busy || (!form.product && !files.length)" @click="create">
{{ busy ? 'กำลังสร้าง' : ' สร้างกลุ่มบุคคลต้นแบบ' }} <PlusCircle :size="18" :stroke-width="2" /> {{ busy ? 'กำลังสร้าง' : i18n.t('create') }}
</button> </button>
</div> </div>
</div> </div>
@@ -54,6 +54,7 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { Box, ListChecks, Paperclip, PlusCircle, ArrowLeft } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'

View File

@@ -2,10 +2,10 @@
<div> <div>
<router-link to="/training" class="btn-back"> {{ i18n.t('training') }}</router-link> <router-link to="/training" class="btn-back"> {{ i18n.t('training') }}</router-link>
<div class="row" style="align-items:center;margin-bottom:8px"> <div class="row" style="align-items:center;margin-bottom:8px">
<h2 style="margin:0">🛠 {{ i18n.t('managePersonas') }}</h2> <h2 style="margin:0"><Users :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('managePersonas') }}</h2>
<div class="row" style="margin-left:auto;gap:10px"> <div class="row" style="margin-left:auto;gap:10px">
<button @click="analyze" :disabled="busy" class="primary"> <button @click="analyze" :disabled="busy" class="primary">
<span v-if="busy" class="spinner"></span>{{ busy ? 'กำลังสร้าง' : ' สร้าง/วิเคราะห์บุคคลต้นแบบ' }} <span v-if="busy" class="spinner"></span><Sparkles v-else :size="18" :stroke-width="1.8" /> {{ busy ? 'กำลังสร้าง' : i18n.t('analyze') }}
</button> </button>
</div> </div>
</div> </div>
@@ -42,7 +42,7 @@
</div> </div>
<div class="muted">{{ p.profession }} · {{ p.age_group }} · {{ p.location }}</div> <div class="muted">{{ p.profession }} · {{ p.age_group }} · {{ p.location }}</div>
<div class="muted">{{ p.channel }} · {{ p.initiation_mode === 'seller' ? 'เริ่มขายเอง' : 'ลูกค้าทักก่อน' }}</div> <div class="muted">{{ p.channel }} · {{ p.initiation_mode === 'seller' ? 'เริ่มขายเอง' : 'ลูกค้าทักก่อน' }}</div>
<button class="edit-btn" @click="openEdit(p)"> แก้ไขรายละเอียด</button> <button class="edit-btn" @click="openEdit(p)"><Pencil :size="15" :stroke-width="1.8" /> {{ i18n.t('edit') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -59,6 +59,7 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { Sparkles, Users, Pencil } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'
import PersonaForm from '../components/PersonaForm.vue' import PersonaForm from '../components/PersonaForm.vue'
@@ -104,7 +105,7 @@ onMounted(load)
<style scoped> <style scoped>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.pcard { display: flex; flex-direction: column; } .pcard { display: flex; flex-direction: column; }
.edit-btn { margin-top: auto; } .edit-btn { margin-top: auto; display: inline-flex; align-items: center; gap: 6px; }
.star { color: #d8dbe3; } .star { color: #d8dbe3; }
.star.on { color: #f59e0b; } .star.on { color: #f59e0b; }
.badge.tier-a { background: #dcfce7; color: #166534; } .badge.tier-a { background: #dcfce7; color: #166534; }

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div> <div>
<h2 style="margin:0">📈 {{ i18n.t('myDashboard') }}</h2> <h2 style="margin:0"><LayoutDashboard :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('myDashboard') }}</h2>
<div class="muted" style="margin-top:4px;margin-bottom:16px">สรปผลการฝกของตวคณเอง าปดการขายไดคร งฝกกบใครบาง</div> <div class="muted" style="margin-top:4px;margin-bottom:16px">สรปผลการฝกของตวคณเอง าปดการขายไดคร งฝกกบใครบาง</div>
</div> </div>
@@ -32,6 +32,7 @@
<script setup> <script setup>
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { LayoutDashboard } from 'lucide-vue-next'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'

View File

@@ -1,12 +1,12 @@
<template> <template>
<div> <div>
<router-link to="/training" class="btn-back"> {{ i18n.t('training') }}</router-link> <router-link to="/training" class="btn-back"><ArrowLeft :size="16" :stroke-width="2" /> {{ i18n.t('training') }}</router-link>
<div class="row" style="align-items:center"> <div class="row" style="align-items:center">
<h2 style="margin:0">{{ i18n.t('personas') }}</h2> <h2 style="margin:0">{{ i18n.t('personas') }}</h2>
<span class="muted" style="margin-left:auto">{{ i18n.t('selectPersona') }}</span> <span class="muted" style="margin-left:auto">{{ i18n.t('selectPersona') }}</span>
</div> </div>
<div class="card guide" v-if="!auth.isAdmin"> <div class="card guide" v-if="!auth.isAdmin">
<strong>🎯 </strong> <strong><Target :size="17" :stroke-width="1.8" style="vertical-align:-3px" /> </strong>
<ol style="margin:8px 0 0;padding-left:20px;line-height:1.8"> <ol style="margin:8px 0 0;padding-left:20px;line-height:1.8">
<li>เลอกลกคาจำลอง (คคลตนแบบ) คนหนงทอยากฝกดวย</li> <li>เลอกลกคาจำลอง (คคลตนแบบ) คนหนงทอยากฝกดวย</li>
<li>ระด A ายส ระด C ยากส (จากดาว ความยาก)</li> <li>ระด A ายส ระด C ยากส (จากดาว ความยาก)</li>
@@ -15,8 +15,8 @@
</div> </div>
<div class="row" v-if="auth.isAdmin" style="margin:12px 0;gap:10px"> <div class="row" v-if="auth.isAdmin" style="margin:12px 0;gap:10px">
<router-link :to="`/admin/groups/${gid}/edit`"><button class="primary">🛠 {{ i18n.t('managePersonas') }}</button></router-link> <router-link :to="`/admin/groups/${gid}/edit`"><button class="primary"><SettingsIcon :size="18" :stroke-width="1.8" /> {{ i18n.t('managePersonas') }}</button></router-link>
<span class="muted">Admin: full persona data + edit available here.</span> <span class="muted">Admin: ดการรายละเอยดบคคลตนแบบได</span>
</div> </div>
<div v-for="tier in ['A','B','C']" :key="tier" style="margin:20px 0"> <div v-for="tier in ['A','B','C']" :key="tier" style="margin:20px 0">
@@ -59,6 +59,7 @@
<script setup> <script setup>
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { Target, Settings as SettingsIcon, ArrowLeft } 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'

View File

@@ -1,10 +1,10 @@
<template> <template>
<div> <div>
<h2> {{ i18n.t('settings') }}</h2> <h2><SettingsIcon :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('settings') }}</h2>
<!-- Profile --> <!-- Profile -->
<div class="card"> <div class="card">
<h3 style="margin-top:0">👤 {{ i18n.t('profile') }}</h3> <h3 style="margin-top:0"><UserIcon :size="18" :stroke-width="1.8" style="vertical-align:-3px" /> {{ i18n.t('profile') }}</h3>
<label>{{ i18n.t('username') }} <span class="muted">({{ i18n.t('readonly') }})</span></label> <label>{{ i18n.t('username') }} <span class="muted">({{ i18n.t('readonly') }})</span></label>
<input :value="auth.user?.username || ''" disabled /> <input :value="auth.user?.username || ''" disabled />
@@ -23,7 +23,7 @@
<!-- Password --> <!-- Password -->
<div class="card" style="margin-top:16px"> <div class="card" style="margin-top:16px">
<h3 style="margin-top:0">🔒 {{ i18n.t('changePassword') }}</h3> <h3 style="margin-top:0"><Lock :size="18" :stroke-width="1.8" style="vertical-align:-3px" /> {{ i18n.t('changePassword') }}</h3>
<label>{{ i18n.t('newPassword') }}</label> <label>{{ i18n.t('newPassword') }}</label>
<input v-model="password" type="password" autocomplete="new-password" /> <input v-model="password" type="password" autocomplete="new-password" />
<label>{{ i18n.t('confirmPassword') }}</label> <label>{{ i18n.t('confirmPassword') }}</label>
@@ -37,7 +37,7 @@
<!-- Language --> <!-- Language -->
<div class="card" style="margin-top:16px"> <div class="card" style="margin-top:16px">
<h3 style="margin-top:0">🌐 {{ i18n.t('language') }}</h3> <h3 style="margin-top:0"><Globe :size="18" :stroke-width="1.8" style="vertical-align:-3px" /> {{ i18n.t('language') }}</h3>
<div class="row"> <div class="row">
<button :class="{ active: i18n.locale === 'th' }" @click="setLang('th')">ไทย</button> <button :class="{ active: i18n.locale === 'th' }" @click="setLang('th')">ไทย</button>
<button :class="{ active: i18n.locale === 'en' }" @click="setLang('en')">English</button> <button :class="{ active: i18n.locale === 'en' }" @click="setLang('en')">English</button>
@@ -51,6 +51,7 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { Settings as SettingsIcon, User as UserIcon, Lock, Globe } from 'lucide-vue-next'
import { auth } from '../store/auth' import { auth } from '../store/auth'
import { api } from '../api' import { api } from '../api'
import { i18n } from '../i18n' import { i18n } from '../i18n'

View File

@@ -1,10 +1,10 @@
<template> <template>
<div> <div>
<div class="row" style="align-items:center;margin-bottom:16px"> <div class="row" style="align-items:center;margin-bottom:16px">
<h2 style="margin:0">🎯 {{ i18n.t('training') }}</h2> <h2 style="margin:0"><Target :size="22" :stroke-width="1.8" style="vertical-align:-4px" /> {{ i18n.t('training') }}</h2>
<div style="margin-left:auto"> <div style="margin-left:auto">
<router-link v-if="auth.isAdmin" to="/admin/new-group"> <router-link v-if="auth.isAdmin" to="/admin/new-group">
<button class="primary"> {{ i18n.t('addProduct') }}</button> <button class="primary"><Plus :size="18" :stroke-width="2" /> {{ i18n.t('addProduct') }}</button>
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -46,6 +46,7 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { Target, Plus, Settings as SettingsIcon } 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'