eCourt-Doc-Edit/web/app/pages/templates/index.vue

163 lines
7 KiB
Vue
Raw Normal View History

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>