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

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

View File

@@ -86,12 +86,14 @@ a { color: inherit; }
a:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* 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):active { transform: scale(.97); }
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: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 {
min-height: 44px;

View File

@@ -1,9 +1,9 @@
<template>
<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">
<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">
<li>กรอก <strong>อผใช (สำหรบเขาสระบบ)</strong>, , และรหสผานเรมต</li>
<li>เลอกสทธ: <strong>user</strong> = เขาฝกขาย, <strong>admin</strong> = ดการระบบ</li>
@@ -12,7 +12,7 @@
</div>
<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">
<input v-model="form.username" placeholder="ชื่อผู้ใช้ (เข้าสู่ระบบ)" style="flex:1" />
<input v-model="form.name" placeholder="ชื่อจริง" style="flex:1" />
@@ -41,6 +41,7 @@
<script setup>
import { onMounted, ref } from 'vue'
import { Users as UsersIcon, UserPlus, Info } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'

View File

@@ -2,12 +2,12 @@
<div>
<div class="row" style="align-items:center;margin-bottom:16px">
<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>
<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>
<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"><Plus :size="18" :stroke-width="2" /> {{ i18n.t('addProduct') }}</button></router-link>
</div>
</div>
@@ -66,6 +66,7 @@
<script setup>
import { onMounted, ref } from 'vue'
import { BarChart3, Users as UsersIcon, Plus } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'
@@ -100,6 +101,6 @@ onMounted(load)
.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 { 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); }
</style>

View File

@@ -1,10 +1,10 @@
<template>
<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 -->
<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>
</div>
@@ -49,7 +49,7 @@
<ul><li v-for="(c, i) in debrief.coaching" :key="i">{{ c }}</li></ul>
</div>
<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 v-for="(v, k) in debrief.revealed_persona" :key="k" class="rev">
<span class="rk">{{ fieldLabel(k) }}</span>
@@ -65,6 +65,7 @@
<script setup>
import { onMounted, nextTick, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Target, ArrowLeft, Search } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'

View File

@@ -1,10 +1,10 @@
<template>
<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 -->
<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">
<li>กรอก <strong>/รายละเอยดสนคาหรอบรการ</strong> อยากใหมฝกขาย (องดานลาง)</li>
<li>(ไมงค) ระบกลมลกคาเปาหมาย หรออปโหลดไฟลเอกสารสนค .pdf/.md/.txt</li>
@@ -13,7 +13,7 @@
</div>
<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>
<textarea v-model="form.product" rows="3" placeholder="เช่น ระบบ POS สำหรับร้านอาหาร Cloud POS for small restaurants"></textarea>
@@ -40,12 +40,12 @@
</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" />
<div class="error" v-if="error">{{ error }}</div>
<button class="primary" style="margin-top:16px" :disabled="busy || (!form.product && !files.length)" @click="create">
{{ busy ? 'กำลังสร้าง' : ' สร้างกลุ่มบุคคลต้นแบบ' }}
<button class="primary" style="margin-top:16px;display:inline-flex;align-items:center;gap:6px" :disabled="busy || (!form.product && !files.length)" @click="create">
<PlusCircle :size="18" :stroke-width="2" /> {{ busy ? 'กำลังสร้าง' : i18n.t('create') }}
</button>
</div>
</div>
@@ -54,6 +54,7 @@
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Box, ListChecks, Paperclip, PlusCircle, ArrowLeft } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'

View File

@@ -2,10 +2,10 @@
<div>
<router-link to="/training" class="btn-back"> {{ i18n.t('training') }}</router-link>
<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">
<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>
</div>
</div>
@@ -42,7 +42,7 @@
</div>
<div class="muted">{{ p.profession }} · {{ p.age_group }} · {{ p.location }}</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>
@@ -59,6 +59,7 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Sparkles, Users, Pencil } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'
import PersonaForm from '../components/PersonaForm.vue'
@@ -104,7 +105,7 @@ onMounted(load)
<style scoped>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.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.on { color: #f59e0b; }
.badge.tier-a { background: #dcfce7; color: #166534; }

View File

@@ -1,7 +1,7 @@
<template>
<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>
@@ -32,6 +32,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { LayoutDashboard } from 'lucide-vue-next'
import { api } from '../api'
import { i18n } from '../i18n'

View File

@@ -1,12 +1,12 @@
<template>
<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">
<h2 style="margin:0">{{ i18n.t('personas') }}</h2>
<span class="muted" style="margin-left:auto">{{ i18n.t('selectPersona') }}</span>
</div>
<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">
<li>เลอกลกคาจำลอง (คคลตนแบบ) คนหนงทอยากฝกดวย</li>
<li>ระด A ายส ระด C ยากส (จากดาว ความยาก)</li>
@@ -15,8 +15,8 @@
</div>
<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>
<span class="muted">Admin: full persona data + edit available here.</span>
<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: ดการรายละเอยดบคคลตนแบบได</span>
</div>
<div v-for="tier in ['A','B','C']" :key="tier" style="margin:20px 0">
@@ -59,6 +59,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Target, Settings as SettingsIcon, ArrowLeft } from 'lucide-vue-next'
import { api } from '../api'
import { auth } from '../store/auth'
import { i18n } from '../i18n'

View File

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

View File

@@ -1,10 +1,10 @@
<template>
<div>
<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">
<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>
</div>
</div>
@@ -46,6 +46,7 @@
<script setup>
import { onMounted, ref } from 'vue'
import { Target, Plus, Settings as SettingsIcon } from 'lucide-vue-next'
import { api } from '../api'
import { auth } from '../store/auth'
import { i18n } from '../i18n'