Files
sales-trainer/frontend/src/views/Analytics.vue
Macky 88892be1d9 i18n(th): professional, natural Thai; make Users button prominent
- Rewrite TH translations to professional/natural Thai (e.g. app 'ระบบฝึกทักษะการขาย'
  instead of 'ตัวฝึกขาย', 'บุคคลต้นแบบ' for persona, tiers 'พร้อมตัดสินใจซื้อ',
  'จุดอ่อนที่ควรฝึกเพิ่มเติม', 'ปิดการขายได้/ไม่ได้' for won/lost, etc.)
- Users button in admin overview now solid sky-blue (was faint .soft gray) so it's visible.
2026-08-08 08:01:08 +07:00

103 lines
4.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div class="row" style="align-items:center;margin-bottom:16px">
<h2 style="margin:0">📊 {{ i18n.t('adminOverview') }}</h2>
<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/new-group"><button class="primary"> {{ i18n.t('addProduct') }}</button></router-link>
</div>
</div>
<!-- Date filter -->
<div class="card filter-bar">
<span class="muted">🗓 {{ i18n.t('dateRange') }}</span>
<input type="date" v-model="from" />
<span></span>
<input type="date" v-model="to" />
<button class="primary" @click="apply" :disabled="loading"> {{ i18n.t('apply') }}</button>
<button @click="clear" :disabled="loading">{{ i18n.t('clear') }}</button>
</div>
<!-- Headline metrics -->
<div class="row" style="gap:16px;margin:16px 0">
<div class="card stat"><div>Sessions</div><strong>{{ a.overall.total_sessions }}</strong></div>
<div class="card stat"><div>Wins</div><strong style="color:var(--green)">{{ a.overall.wins }}</strong></div>
<div class="card stat"><div>Losses</div><strong style="color:var(--red)">{{ a.overall.losses }}</strong></div>
<div class="card stat"><div>Avg score</div><strong>{{ a.overall.avg_score }}</strong></div>
<div class="card stat"><div>Trainees</div><strong>{{ a.trainee_count }}</strong></div>
</div>
<!-- Close rate with progress bar -->
<div class="card">
<div class="row" style="justify-content:space-between;align-items:center">
<strong>{{ i18n.t('closeRate') }}</strong>
<strong>{{ a.overall.close_rate }}%</strong>
</div>
<div class="bar"><div class="bar-fill" :style="{ width: (a.overall.close_rate || 0) + '%' }"></div></div>
<div class="muted" style="font-size:12px;margin-top:6px">
{{ a.overall.wins }} {{ i18n.t('won').toLowerCase() }} / {{ a.overall.total_sessions }} sessions
</div>
</div>
<!-- Hardest personas -->
<h3 style="margin-top:20px">{{ i18n.t('hardestPersonas') }}</h3>
<div v-if="a.hardest_personas.length === 0" class="card empty-state">
<strong>No data</strong><span>No sessions in this date range.</span>
</div>
<div class="grid">
<div v-for="(p, i) in a.hardest_personas" :key="i" class="card lift tier-card">
<div class="row" style="justify-content:space-between">
<strong>{{ p.persona_name }}</strong>
<span class="muted">#{{ i + 1 }}</span>
</div>
<div class="row" style="margin-top:10px;gap:8px">
<span class="badge won">{{ p.wins }}W</span>
<span class="badge lost">{{ p.losses }}L</span>
<span class="badge not_tried">{{ p.plays }} plays</span>
</div>
<div class="muted" style="margin-top:8px">{{ i18n.t('score') }}: {{ p.avg_score }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { api } from '../api'
import { i18n } from '../i18n'
const a = ref({ overall: { total_sessions: 0, wins: 0, losses: 0, close_rate: 0, avg_score: 0 }, trainee_count: 0, hardest_personas: [] })
const from = ref('')
const to = ref('')
const loading = ref(false)
async function load() {
loading.value = true
try {
a.value = await api.analytics({ from: from.value, to: to.value })
} finally {
loading.value = false
}
}
function apply() { load() }
function clear() { from.value = ''; to.value = ''; load() }
onMounted(load)
</script>
<style scoped>
.stat { text-align: center; min-width: 110px; }
.stat div { color: var(--muted); font-size: 12px; }
.stat strong { font-size: 20px; }
.filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.filter-bar input[type=date] { width: auto; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.tier-card { display: flex; flex-direction: column; }
.badge.won { background: #dcfce7; color: #166534; }
.badge.lost { background: #fee2e2; color: #991b1b; }
.badge.not_tried { background: #eef2ff; color: #4338ca; }
.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; }
.users-btn { 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); }
</style>