The Vue SPA is now built locally and its frontend/dist/ committed (force-added). Dockerfile is a single-stage runtime: copy prebuilt dist + python:3.11 + Flask. No node/npm inside the image -> deterministic, no npm/esbuild postinstall flakiness. To update UI: edit src, npm run build, commit frontend/dist/.
2 lines
4.6 KiB
JavaScript
2 lines
4.6 KiB
JavaScript
import{k as T,c as o,a as s,t as e,u as i,i as r,f as V,l as A,m as B,e as p,w,v as k,F as y,n as C,p as F,q as S,r as c,o as n}from"./index-B1lw4PRH.js";const W={class:"row",style:{"margin-bottom":"16px","align-items":"center"}},q={style:{margin:"0"}},M={class:"muted",style:{margin:"4px 0 0","font-size":"13px"}},N={key:0,style:{"margin-left":"auto"}},P={class:"primary"},U={class:"filter-bar"},z={key:0,class:"card",style:{"min-height":"120px"}},j={class:"stat-grid",style:{"margin-bottom":"20px"}},E={class:"stat-card"},H={class:"stat-value"},Q={class:"stat-card"},R={class:"stat-value",style:{color:"var(--green)"}},$={class:"stat-card"},G={class:"stat-value",style:{color:"var(--red)"}},I={class:"stat-card"},J={class:"stat-value"},K={class:"stat-card"},O={class:"stat-value"},X={class:"stat-card"},Y={class:"stat-value",style:{color:"var(--accent)"}},Z={class:"card",style:{padding:"8px 20px"}},ss={style:{width:"100%","border-collapse":"collapse"}},ts={style:{padding:"8px 0","font-weight":"600"}},es={style:{color:"var(--green)"}},as={style:{color:"var(--red)"}},ls={key:0},os={colspan:"5",class:"muted",style:{padding:"12px 0"}},ns={class:"row",style:{"justify-content":"space-between","align-items":"center"}},is={class:"row",style:{gap:"6px"}},rs={class:"badge won"},ds={class:"badge lost"},us={class:"badge not_tried"},vs={key:0,class:"card empty-state"},ps={__name:"Dashboard",setup(cs){const l=c({overall:{total_sessions:0,wins:0,losses:0,close_rate:0,avg_score:0},trainee_count:0,hardest_personas:[],per_user:[]}),d=c(""),u=c(""),_=c(!0);async function m(){_.value=!0;try{const v=new URLSearchParams;d.value&&v.set("from",d.value),u.value&&v.set("to",u.value);const t=v.toString();l.value=await F.analyticsWithQuery(t?`?${t}`:"")}finally{_.value=!1}}return T(m),(v,t)=>{var g,h,x,b,f;const D=S("router-link");return n(),o("div",null,[s("div",W,[s("div",null,[s("h2",q,e(i(r).t("tabAdminDash")),1),s("p",M,e(i(r).t("analytics")),1)]),i(V).isAdmin?(n(),o("div",N,[A(D,{to:"/admin/new-group"},{default:B(()=>[s("button",P,"+ "+e(i(r).t("groupBuilder")),1)]),_:1})])):p("",!0)]),s("div",U,[t[2]||(t[2]=s("label",{style:{margin:"0"}},"From",-1)),w(s("input",{type:"date","onUpdate:modelValue":t[0]||(t[0]=a=>d.value=a)},null,512),[[k,d.value]]),t[3]||(t[3]=s("label",{style:{margin:"0"}},"To",-1)),w(s("input",{type:"date","onUpdate:modelValue":t[1]||(t[1]=a=>u.value=a)},null,512),[[k,u.value]]),s("button",{class:"primary",onClick:m,style:{"min-height":"44px"}},"Apply")]),_.value?(n(),o("div",z,[...t[4]||(t[4]=[s("div",{class:"skeleton",style:{height:"60px"}},null,-1),s("div",{class:"skeleton",style:{height:"60px","margin-top":"10px"}},null,-1)])])):(n(),o(y,{key:1},[s("div",j,[s("div",E,[t[5]||(t[5]=s("div",{class:"stat-label"},"Sessions",-1)),s("div",H,e(((g=l.value.overall)==null?void 0:g.total_sessions)||0),1)]),s("div",Q,[t[6]||(t[6]=s("div",{class:"stat-label"},"Wins",-1)),s("div",R,e(((h=l.value.overall)==null?void 0:h.wins)||0),1)]),s("div",$,[t[7]||(t[7]=s("div",{class:"stat-label"},"Losses",-1)),s("div",G,e(((x=l.value.overall)==null?void 0:x.losses)||0),1)]),s("div",I,[t[8]||(t[8]=s("div",{class:"stat-label"},"Close rate",-1)),s("div",J,e((b=l.value.overall)==null?void 0:b.close_rate)+"%",1)]),s("div",K,[t[9]||(t[9]=s("div",{class:"stat-label"},"Avg score",-1)),s("div",O,e((f=l.value.overall)==null?void 0:f.avg_score),1)]),s("div",X,[t[10]||(t[10]=s("div",{class:"stat-label"},"Trainees",-1)),s("div",Y,e(l.value.trainee_count||0),1)])]),t[12]||(t[12]=s("h3",{style:{margin:"20px 0 12px"}},"Per trainee",-1)),s("div",Z,[s("table",ss,[t[11]||(t[11]=s("thead",null,[s("tr",{class:"muted",style:{"font-size":"12px","text-align":"left"}},[s("th",{style:{padding:"8px 0"}},"Trainee"),s("th",null,"Played"),s("th",null,"Won"),s("th",null,"Lost"),s("th",null,"Close %")])],-1)),s("tbody",null,[(n(!0),o(y,null,C(l.value.per_user||[],a=>(n(),o("tr",{key:a.user_id,style:{"border-top":"1px solid var(--border)"}},[s("td",ts,e(a.name),1),s("td",null,e(a.sessions),1),s("td",es,e(a.wins),1),s("td",as,e(a.losses),1),s("td",null,e(a.sessions?Math.round(a.wins/a.sessions*100):0)+"%",1)]))),128)),(l.value.per_user||[]).length?p("",!0):(n(),o("tr",ls,[s("td",os,e(i(r).t("noData")),1)]))])])]),t[13]||(t[13]=s("h3",{style:{margin:"20px 0 12px"}},"Hardest personas",-1)),(n(!0),o(y,null,C(l.value.hardest_personas||[],(a,L)=>(n(),o("div",{class:"card",key:L,style:{"margin-bottom":"8px"}},[s("div",ns,[s("strong",null,e(a.persona_name),1),s("div",is,[s("span",rs,e(a.wins)+"W",1),s("span",ds,e(a.losses)+"L",1),s("span",us,"avg "+e(a.avg_score),1)])])]))),128)),(l.value.hardest_personas||[]).length?p("",!0):(n(),o("div",vs,[s("strong",null,e(i(r).t("noData")),1)]))],64))])}}};export{ps as default};
|