2026-08-20 06:20:16 +07:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
/** FR-9 UI — รายการ template ทั้งหมด (admin): ดู/แก้ไข/activate-deactivate */
|
|
|
|
|
|
import type { TemplateListItem } from '~/types/api'
|
|
|
|
|
|
|
|
|
|
|
|
const { apiJson } = useApi()
|
|
|
|
|
|
useHead({ title: 'จัดการ template — e-Court Documents Versioning' })
|
|
|
|
|
|
|
|
|
|
|
|
const templates = ref<TemplateListItem[]>([])
|
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
|
const error = ref<string | null>(null)
|
|
|
|
|
|
const showInactive = ref(false)
|
|
|
|
|
|
const busyId = ref<string | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
error.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const qs = showInactive.value ? '?includeInactive=true' : ''
|
|
|
|
|
|
templates.value = (await apiJson<TemplateListItem[]>(`/api/templates${qs}`)) ?? []
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
error.value = err instanceof Error ? err.message : 'โหลดรายการ template ไม่สำเร็จ'
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** deactivate = DELETE (soft-delete), activate กลับ = PUT { isActive: true } */
|
|
|
|
|
|
async function toggleActive(t: TemplateListItem) {
|
|
|
|
|
|
const verb = t.isActive ? 'deactivate' : 'activate'
|
|
|
|
|
|
if (!window.confirm(`ยืนยัน${verb === 'deactivate' ? 'การ deactivate' : 'การ activate'} template “${t.name}”?`)) return
|
|
|
|
|
|
|
|
|
|
|
|
busyId.value = t.id
|
|
|
|
|
|
error.value = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (t.isActive) await apiJson(`/api/templates/${t.id}`, { method: 'DELETE' })
|
|
|
|
|
|
else await apiJson(`/api/templates/${t.id}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({ isActive: true }),
|
|
|
|
|
|
})
|
|
|
|
|
|
await load()
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
error.value = err instanceof Error ? err.message : `${verb} ไม่สำเร็จ`
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busyId.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(showInactive, load)
|
|
|
|
|
|
onMounted(load)
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<AuthGate>
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<section class="space-y-6">
|
|
|
|
|
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
|
|
|
|
<div class="min-w-0">
|
|
|
|
|
|
<h1 class="text-page-title">จัดการ template</h1>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
|
|
|
|
|
<NuxtLink to="/templates/new" class="btn btn-primary w-full sm:w-auto">+ สร้าง template ใหม่</NuxtLink>
|
|
|
|
|
|
</div>
|
2026-08-20 06:20:16 +07:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<label class="flex items-center gap-2 text-sm text-muted">
|
|
|
|
|
|
<input v-model="showInactive" type="checkbox" class="h-4 w-4 accent-primary" />
|
2026-08-20 06:20:16 +07:00
|
|
|
|
แสดง template ที่ deactivate ไว้ด้วย
|
|
|
|
|
|
</label>
|
|
|
|
|
|
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
|
|
|
|
|
<span class="spinner" />
|
2026-08-20 06:20:16 +07:00
|
|
|
|
กำลังโหลด…
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<AlertBox v-else-if="error" type="error">{{ error }}</AlertBox>
|
|
|
|
|
|
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<div v-else-if="templates.length === 0" class="empty-state">
|
2026-08-20 06:20:16 +07:00
|
|
|
|
ยังไม่มี template{{ showInactive ? ' เลย' : 'ที่ active' }} —
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<NuxtLink to="/templates/new" class="link">สร้าง template แรก</NuxtLink>
|
2026-08-20 06:20:16 +07:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<template v-else>
|
|
|
|
|
|
<!-- มือถือ: card list -->
|
|
|
|
|
|
<ul class="space-y-3 md:hidden">
|
|
|
|
|
|
<li v-for="t in templates" :key="t.id" class="card space-y-3 p-4">
|
|
|
|
|
|
<div class="flex items-start justify-between gap-3">
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/edit`" class="link min-w-0 break-words font-semibold">
|
|
|
|
|
|
{{ t.name }}
|
|
|
|
|
|
</NuxtLink>
|
|
|
|
|
|
<span v-if="t.isActive" class="badge badge-success shrink-0">active</span>
|
|
|
|
|
|
<span v-else class="badge badge-neutral shrink-0">deactivated</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="line-clamp-2 text-sm text-muted">{{ t.description ?? '—' }}</p>
|
|
|
|
|
|
<p class="text-xs text-muted">
|
|
|
|
|
|
{{ t.fieldCount }} fields · อัปเดต {{ formatDateTime(t.updatedAt) }}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/documents`" class="btn btn-secondary btn-xs">เอกสาร</NuxtLink>
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/edit`" class="btn btn-secondary btn-xs">แก้ไข</NuxtLink>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="busyId === t.id"
|
|
|
|
|
|
class="btn btn-xs"
|
|
|
|
|
|
:class="t.isActive ? 'btn-outline-danger' : 'btn-outline-success'"
|
|
|
|
|
|
@click="toggleActive(t)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ busyId === t.id ? '…' : t.isActive ? 'deactivate' : 'activate' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- desktop: ตาราง -->
|
|
|
|
|
|
<div class="table-card hidden md:block">
|
|
|
|
|
|
<table class="w-full text-sm">
|
|
|
|
|
|
<thead class="table-head">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th class="px-4 py-3">ชื่อ</th>
|
|
|
|
|
|
<th class="px-4 py-3">คำอธิบาย</th>
|
|
|
|
|
|
<th class="px-4 py-3 text-center">fields</th>
|
|
|
|
|
|
<th class="px-4 py-3">สถานะ</th>
|
|
|
|
|
|
<th class="px-4 py-3">อัปเดตล่าสุด</th>
|
|
|
|
|
|
<th class="px-4 py-3 text-right">การกระทำ</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody class="divide-y divide-line">
|
|
|
|
|
|
<tr v-for="t in templates" :key="t.id" class="align-top">
|
|
|
|
|
|
<td class="px-4 py-3 font-medium">
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/edit`" class="link">{{ t.name }}</NuxtLink>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="max-w-60 px-4 py-3 text-muted">{{ t.description ?? '—' }}</td>
|
|
|
|
|
|
<td class="px-4 py-3 text-center">{{ t.fieldCount }}</td>
|
|
|
|
|
|
<td class="px-4 py-3">
|
|
|
|
|
|
<span v-if="t.isActive" class="badge badge-success">active</span>
|
|
|
|
|
|
<span v-else class="badge badge-neutral">deactivated</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-4 py-3 text-muted">{{ formatDateTime(t.updatedAt) }}</td>
|
|
|
|
|
|
<td class="whitespace-nowrap px-4 py-3 text-right">
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/documents`" class="btn btn-secondary btn-xs">เอกสาร</NuxtLink>
|
|
|
|
|
|
<NuxtLink :to="`/templates/${t.id}/edit`" class="btn btn-secondary btn-xs ml-2">แก้ไข</NuxtLink>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="busyId === t.id"
|
|
|
|
|
|
class="btn btn-xs ml-2"
|
|
|
|
|
|
:class="t.isActive ? 'btn-outline-danger' : 'btn-outline-success'"
|
|
|
|
|
|
@click="toggleActive(t)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ busyId === t.id ? '…' : t.isActive ? 'deactivate' : 'activate' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2026-08-20 06:20:16 +07:00
|
|
|
|
|
2026-08-20 21:34:45 +07:00
|
|
|
|
<p class="text-xs text-muted">
|
2026-08-20 06:20:16 +07:00
|
|
|
|
การ deactivate เป็น soft-delete — เอกสารที่สร้างจาก template นั้นไปแล้วยังใช้งานต่อได้ตามปกติ
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</AuthGate>
|
|
|
|
|
|
</template>
|