feat(training): delete group button (admin only) on Training page
- Backend DELETE /api/groups/<gid> (admin, own-org enforced via _get_owned_group); cascades removal of the group's sessions. GroupStore.delete added. - Training.vue: each group card gets a trash (Trash2 line icon) delete button shown only to admins (auth.isAdmin); confirm dialog; @click.stop so it doesn't navigate. Card restructured so the delete button is not inside the clickable router-link. - test_user_journey: trainee cannot delete (403), admin deletes, group gone (404). All suites pass. Rebuilt dist.
This commit is contained in:
@@ -43,6 +43,7 @@ export const api = {
|
||||
createGroup: (formData) => request('POST', '/api/groups', formData, true),
|
||||
listGroups: () => request('GET', '/api/groups'),
|
||||
getGroup: (id) => request('GET', `/api/groups/${id}`),
|
||||
deleteGroup: (id) => request('DELETE', `/api/groups/${id}`),
|
||||
analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`),
|
||||
listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`),
|
||||
getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`),
|
||||
|
||||
@@ -20,17 +20,30 @@
|
||||
<span v-else>Ask an admin to create a persona group first.</span>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<router-link
|
||||
v-for="g in groups"
|
||||
:key="g.id"
|
||||
:to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`"
|
||||
style="text-decoration:none"
|
||||
>
|
||||
<div class="card lift train-card">
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<div v-for="g in groups" :key="g.id" class="card lift train-card">
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<router-link
|
||||
:to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`"
|
||||
style="text-decoration:none;color:inherit;min-width:0"
|
||||
>
|
||||
<strong>{{ g.title }}</strong>
|
||||
</router-link>
|
||||
<div class="row" style="gap:6px;align-items:center">
|
||||
<span class="badge" :class="g.status === 'ready' ? 'ready' : 'draft'">{{ g.status }}</span>
|
||||
<button
|
||||
v-if="auth.isAdmin"
|
||||
class="del-btn"
|
||||
:title="'ลบกลุ่มนี้ (ติดลบถาวร)'"
|
||||
@click.prevent.stop="removeGroup(g)"
|
||||
>
|
||||
<Trash2 :size="16" :stroke-width="1.8" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
:to="auth.isAdmin ? `/admin/groups/${g.id}/edit` : `/groups/${g.id}/personas`"
|
||||
style="text-decoration:none;color:inherit"
|
||||
>
|
||||
<div class="muted" style="margin:6px 0 12px">{{ productName(g) }}</div>
|
||||
<div class="row" style="gap:8px">
|
||||
<span class="badge" :class="g.channel || 'line'">{{ g.channel || 'line' }}</span>
|
||||
@@ -39,15 +52,15 @@
|
||||
<button class="primary" style="width:100%;margin-top:12px">
|
||||
{{ auth.isAdmin ? (g.status === 'ready' ? i18n.t('managePersonas') : i18n.t('analyze')) : i18n.t('selectPersona') }}
|
||||
</button>
|
||||
</div>
|
||||
</router-link>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Target, Plus, BookOpen } from 'lucide-vue-next'
|
||||
import { Target, Plus, BookOpen, Trash2 } from 'lucide-vue-next'
|
||||
import { api } from '../api'
|
||||
import { auth } from '../store/auth'
|
||||
import { i18n } from '../i18n'
|
||||
@@ -62,6 +75,16 @@ function personaCount(g) {
|
||||
return (g.personas || g.persona_count || 0)
|
||||
}
|
||||
|
||||
async function removeGroup(g) {
|
||||
if (!confirm(`ลบกลุ่ม "${g.title}" และบุคคลต้นแบบทั้งหมด? (ติดลบถาวร)`)) return
|
||||
try {
|
||||
await api.deleteGroup(g.id)
|
||||
groups.value = groups.value.filter((x) => x.id !== g.id)
|
||||
} catch (e) {
|
||||
alert(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const all = (await api.listGroups()).groups || []
|
||||
@@ -81,4 +104,10 @@ onMounted(async () => {
|
||||
button.soft { background: #f1f3f9; border-color: transparent; }
|
||||
.badge.facebook { background: #e0f2fe; color: #0369a1; }
|
||||
.badge.line { background: #dcfce7; color: #15803d; }
|
||||
.del-btn {
|
||||
background: transparent; border: 1px solid var(--border); border-radius: 8px;
|
||||
min-height: 32px; width: 32px; padding: 0; display: inline-flex; align-items: center;
|
||||
justify-content: center; color: var(--muted); cursor: pointer;
|
||||
}
|
||||
.del-btn:hover { background: #fee2e2; border-color: #fecaca; color: var(--red); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user