ปรับ look and feel
This commit is contained in:
parent
0148f48204
commit
b47667f918
15 changed files with 915 additions and 523 deletions
|
|
@ -23,30 +23,19 @@ onMounted(async () => {
|
|||
<template>
|
||||
<section class="mx-auto max-w-xl space-y-4 py-10 text-center">
|
||||
<template v-if="busy">
|
||||
<div class="flex items-center justify-center gap-3 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div class="flex items-center justify-center gap-3 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
{{ message }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<AlertBox type="error">{{ message }}</AlertBox>
|
||||
<div class="flex justify-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700"
|
||||
@click="login()"
|
||||
>
|
||||
<div class="flex flex-col-reverse justify-center gap-2 sm:flex-row">
|
||||
<NuxtLink to="/" class="btn btn-secondary w-full sm:w-auto">กลับหน้าหลัก</NuxtLink>
|
||||
<button type="button" class="btn btn-primary w-full sm:w-auto" @click="login()">
|
||||
เข้าสู่ระบบใหม่
|
||||
</button>
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="rounded-md border border-slate-300 px-4 py-2 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
>
|
||||
กลับหน้าหลัก
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -71,10 +71,8 @@ onMounted(load)
|
|||
<template>
|
||||
<AuthGate>
|
||||
<section class="space-y-8">
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลด…
|
||||
</div>
|
||||
|
||||
|
|
@ -85,16 +83,13 @@ onMounted(load)
|
|||
|
||||
<template v-else-if="detail">
|
||||
<!-- หัวเรื่อง + การกระทำ -->
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold break-all">{{ detail.fileName }}</h1>
|
||||
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
|
||||
<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 break-all">{{ detail.fileName }}</h1>
|
||||
<p class="mt-1 text-sm text-muted">
|
||||
<template v-if="detail.templateId">
|
||||
จาก template
|
||||
<NuxtLink
|
||||
:to="`/templates/${detail.templateId}/documents`"
|
||||
class="font-medium text-sky-700 hover:underline dark:text-sky-400"
|
||||
>
|
||||
<NuxtLink :to="`/templates/${detail.templateId}/documents`" class="link">
|
||||
{{ detail.templateName }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
|
@ -103,18 +98,18 @@ onMounted(load)
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end gap-1">
|
||||
<div class="flex shrink-0 flex-col items-start gap-1 sm:items-end">
|
||||
<button
|
||||
v-if="detail.templateId"
|
||||
type="button"
|
||||
:disabled="!detail.templateIsActive"
|
||||
:title="detail.templateIsActive ? '' : 'template นี้ถูก deactivate แล้ว — สร้างเอกสารใหม่ไม่ได้'"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="btn btn-primary w-full sm:w-auto"
|
||||
@click="regenerate"
|
||||
>
|
||||
⟳ สร้างเอกสารใหม่จากข้อมูลนี้
|
||||
</button>
|
||||
<p v-if="detail.templateId && !detail.templateIsActive" class="text-xs text-slate-500 dark:text-slate-400">
|
||||
<p v-if="detail.templateId && !detail.templateIsActive" class="text-xs text-muted">
|
||||
template ถูก deactivate — เอกสารฉบับนี้ยังใช้ได้ แต่สร้างใหม่ไม่ได้
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -122,109 +117,171 @@ onMounted(load)
|
|||
|
||||
<!-- ค่าล่าสุดของทุก field -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-lg font-semibold">ค่า fields ปัจจุบัน</h2>
|
||||
<h2 class="text-section-title">ค่า fields ปัจจุบัน</h2>
|
||||
|
||||
<div
|
||||
v-if="detail.fields.length === 0"
|
||||
class="rounded-md border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
<div v-if="detail.fields.length === 0" class="empty-state">
|
||||
เอกสารนี้ไม่มี field values (ไม่ได้สร้างจาก template หรือ template ไม่มี fields)
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">field</th>
|
||||
<th class="px-4 py-3">ค่า</th>
|
||||
<th class="px-4 py-3">type</th>
|
||||
<th class="px-4 py-3">อัปเดตเมื่อ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
<tr v-for="f in detail.fields" :key="f.key">
|
||||
<td class="px-4 py-3">
|
||||
{{ f.label ?? f.key }}
|
||||
<span v-if="!f.label" class="ml-1 rounded bg-amber-100 px-1.5 py-0.5 text-xs text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
||||
ไม่มีในนิยามปัจจุบัน
|
||||
</span>
|
||||
<span class="block font-mono text-xs text-slate-400">{{ f.key }}</span>
|
||||
</td>
|
||||
<td class="max-w-72 px-4 py-3 break-words">{{ f.value || '—' }}</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">{{ f.type ?? '—' }}</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">
|
||||
{{ f.updatedAt ? formatDateTime(f.updatedAt) : 'ยังไม่เคยมีค่า' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-else>
|
||||
<!-- มือถือ: card list -->
|
||||
<ul class="space-y-3 md:hidden">
|
||||
<li v-for="f in detail.fields" :key="f.key" class="card space-y-1 p-4">
|
||||
<p class="text-sm font-medium">
|
||||
{{ f.label ?? f.key }}
|
||||
<span v-if="!f.label" class="badge badge-accent ml-1">ไม่มีในนิยามปัจจุบัน</span>
|
||||
</p>
|
||||
<p class="font-mono text-xs text-muted">{{ f.key }}</p>
|
||||
<p class="break-words text-sm">{{ f.value || '—' }}</p>
|
||||
<p class="text-xs text-muted">
|
||||
{{ f.type ?? '—' }} ·
|
||||
{{ f.updatedAt ? formatDateTime(f.updatedAt) : 'ยังไม่เคยมีค่า' }}
|
||||
</p>
|
||||
</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">field</th>
|
||||
<th class="px-4 py-3">ค่า</th>
|
||||
<th class="px-4 py-3">type</th>
|
||||
<th class="px-4 py-3">อัปเดตเมื่อ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-line">
|
||||
<tr v-for="f in detail.fields" :key="f.key">
|
||||
<td class="px-4 py-3">
|
||||
{{ f.label ?? f.key }}
|
||||
<span v-if="!f.label" class="badge badge-accent ml-1">ไม่มีในนิยามปัจจุบัน</span>
|
||||
<span class="block font-mono text-xs text-muted">{{ f.key }}</span>
|
||||
</td>
|
||||
<td class="max-w-72 px-4 py-3 break-words">{{ f.value || '—' }}</td>
|
||||
<td class="px-4 py-3 text-muted">{{ f.type ?? '—' }}</td>
|
||||
<td class="px-4 py-3 text-muted">
|
||||
{{ f.updatedAt ? formatDateTime(f.updatedAt) : 'ยังไม่เคยมีค่า' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- version history -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-lg font-semibold">Version history ({{ versions.length }})</h2>
|
||||
<h2 class="text-section-title">Version history ({{ versions.length }})</h2>
|
||||
|
||||
<div
|
||||
v-if="versions.length === 0"
|
||||
class="rounded-md border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
ยังไม่มี version
|
||||
</div>
|
||||
<div v-if="versions.length === 0" class="empty-state">ยังไม่มี version</div>
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">version</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-slate-100 dark:divide-slate-700">
|
||||
<template v-for="v in versions" :key="v.versionId">
|
||||
<tr class="cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700/40" @click="expandedVersionId = expandedVersionId === v.versionId ? null : v.versionId">
|
||||
<td class="px-4 py-3 font-medium">v{{ v.versionNumber }}</td>
|
||||
<td class="px-4 py-3">{{ formatDateTime(v.createdAt) }}</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-slate-500 dark:text-slate-400">{{ v.createdByUserId.slice(0, 8) }}…</td>
|
||||
<td class="px-4 py-3 text-right whitespace-nowrap">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="downloadingVersionId === v.versionId"
|
||||
class="rounded-md border border-slate-300 px-2.5 py-1 text-xs hover:bg-slate-100 disabled:opacity-50 dark:border-slate-600 dark:hover:bg-slate-700"
|
||||
@click.stop="download(v)"
|
||||
>
|
||||
{{ downloadingVersionId === v.versionId ? '…' : '⬇ ดาวน์โหลด' }}
|
||||
</button>
|
||||
<span class="ml-2 text-xs text-slate-400">{{ expandedVersionId === v.versionId ? '▲ ซ่อน fields' : '▼ ดู fields' }}</span>
|
||||
</td>
|
||||
<template v-else>
|
||||
<!-- มือถือ: card ต่อ version (กดขยายดู fields ได้เหมือนแถวตาราง) -->
|
||||
<ul class="space-y-3 md:hidden">
|
||||
<li v-for="v in versions" :key="v.versionId" class="card p-4">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium">v{{ v.versionNumber }}</p>
|
||||
<p class="mt-1 text-xs text-muted">{{ formatDateTime(v.createdAt) }}</p>
|
||||
<p class="mt-0.5 font-mono text-xs text-muted">{{ v.createdByUserId.slice(0, 8) }}…</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-col items-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="downloadingVersionId === v.versionId"
|
||||
class="btn btn-secondary btn-xs"
|
||||
@click.stop="download(v)"
|
||||
>
|
||||
{{ downloadingVersionId === v.versionId ? '…' : '⬇ ดาวน์โหลด' }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-muted"
|
||||
@click="expandedVersionId = expandedVersionId === v.versionId ? null : v.versionId"
|
||||
>
|
||||
{{ expandedVersionId === v.versionId ? '▲ ซ่อน fields' : '▼ ดู fields' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expandedVersionId === v.versionId" class="mt-3 rounded-card bg-surface-muted p-3">
|
||||
<p class="mb-2 text-xs font-medium text-muted">ค่า fields ของ v{{ v.versionNumber }}</p>
|
||||
<dl class="space-y-1.5 text-sm">
|
||||
<div v-for="e in snapshotOf(v)" :key="e.key" class="flex gap-2">
|
||||
<dt class="w-36 shrink-0 truncate text-muted" :title="e.key">
|
||||
{{ fieldLabel(e.key) }}
|
||||
</dt>
|
||||
<dd class="min-w-0 break-words">
|
||||
{{ e.value || '—' }}
|
||||
<span v-if="e.missing" class="badge badge-accent ml-1">หายจากเอกสาร</span>
|
||||
</dd>
|
||||
</div>
|
||||
<p v-if="snapshotOf(v).length === 0" class="text-sm text-muted">
|
||||
version นี้ไม่มี snapshot ของ fields
|
||||
</p>
|
||||
</dl>
|
||||
</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">version</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>
|
||||
<tr v-if="expandedVersionId === v.versionId">
|
||||
<td colspan="4" class="bg-slate-50 px-4 py-4 dark:bg-slate-900">
|
||||
<p class="mb-2 text-xs font-medium text-slate-500 dark:text-slate-400">ค่า fields ของ v{{ v.versionNumber }}</p>
|
||||
<dl class="grid gap-x-6 gap-y-1.5 text-sm sm:grid-cols-2">
|
||||
<div v-for="e in snapshotOf(v)" :key="e.key" class="flex gap-2">
|
||||
<dt class="w-44 shrink-0 truncate text-slate-500 dark:text-slate-400" :title="e.key">
|
||||
{{ fieldLabel(e.key) }}
|
||||
</dt>
|
||||
<dd class="break-words">
|
||||
{{ e.value || '—' }}
|
||||
<span v-if="e.missing" class="ml-1 rounded bg-amber-100 px-1.5 py-0.5 text-xs text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
||||
หายจากเอกสาร
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<p v-if="snapshotOf(v).length === 0" class="text-sm text-slate-500 dark:text-slate-400">
|
||||
version นี้ไม่มี snapshot ของ fields
|
||||
</p>
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-line">
|
||||
<template v-for="v in versions" :key="v.versionId">
|
||||
<tr
|
||||
class="cursor-pointer hover:bg-surface-muted/60"
|
||||
@click="expandedVersionId = expandedVersionId === v.versionId ? null : v.versionId"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium">v{{ v.versionNumber }}</td>
|
||||
<td class="px-4 py-3">{{ formatDateTime(v.createdAt) }}</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-muted">{{ v.createdByUserId.slice(0, 8) }}…</td>
|
||||
<td class="px-4 py-3 text-right whitespace-nowrap">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="downloadingVersionId === v.versionId"
|
||||
class="btn btn-secondary btn-xs"
|
||||
@click.stop="download(v)"
|
||||
>
|
||||
{{ downloadingVersionId === v.versionId ? '…' : '⬇ ดาวน์โหลด' }}
|
||||
</button>
|
||||
<span class="ml-2 text-xs text-muted">
|
||||
{{ expandedVersionId === v.versionId ? '▲ ซ่อน fields' : '▼ ดู fields' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="expandedVersionId === v.versionId">
|
||||
<td colspan="4" class="bg-surface-muted px-4 py-4">
|
||||
<p class="mb-2 text-xs font-medium text-muted">ค่า fields ของ v{{ v.versionNumber }}</p>
|
||||
<dl class="grid gap-x-6 gap-y-1.5 text-sm sm:grid-cols-2">
|
||||
<div v-for="e in snapshotOf(v)" :key="e.key" class="flex gap-2">
|
||||
<dt class="w-44 shrink-0 truncate text-muted" :title="e.key">
|
||||
{{ fieldLabel(e.key) }}
|
||||
</dt>
|
||||
<dd class="break-words">
|
||||
{{ e.value || '—' }}
|
||||
<span v-if="e.missing" class="badge badge-accent ml-1">หายจากเอกสาร</span>
|
||||
</dd>
|
||||
</div>
|
||||
<p v-if="snapshotOf(v).length === 0" class="text-sm text-muted">
|
||||
version นี้ไม่มี snapshot ของ fields
|
||||
</p>
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<AlertBox v-if="downloadError" type="error">{{ downloadError }}</AlertBox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -121,10 +121,8 @@ onMounted(load)
|
|||
<template>
|
||||
<AuthGate>
|
||||
<section class="mx-auto max-w-2xl space-y-6">
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลด template…
|
||||
</div>
|
||||
|
||||
|
|
@ -140,48 +138,39 @@ onMounted(load)
|
|||
สร้างเอกสารสำเร็จ — บันทึกเป็น <strong>version {{ result.versionNumber }}</strong> แล้ว
|
||||
</AlertBox>
|
||||
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-6 dark:border-slate-700 dark:bg-slate-800">
|
||||
<h1 class="text-xl font-bold">{{ detail.name }}</h1>
|
||||
<div class="card p-6">
|
||||
<h1 class="text-page-title">{{ detail.name }}</h1>
|
||||
<dl class="mt-4 space-y-2 text-sm">
|
||||
<div class="flex gap-2">
|
||||
<dt class="w-32 shrink-0 text-slate-500 dark:text-slate-400">document id</dt>
|
||||
<dt class="w-32 shrink-0 text-muted">document id</dt>
|
||||
<dd class="break-all font-mono text-xs leading-6">{{ result.documentId }}</dd>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<dt class="w-32 shrink-0 text-slate-500 dark:text-slate-400">version</dt>
|
||||
<dt class="w-32 shrink-0 text-muted">version</dt>
|
||||
<dd>{{ result.versionNumber }} (ต้นทางของ version history)</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div class="mt-5 flex flex-wrap items-center gap-3">
|
||||
<div class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:flex-wrap">
|
||||
<NuxtLink to="/" class="btn btn-secondary w-full sm:w-auto">กลับหน้าหลัก</NuxtLink>
|
||||
<button type="button" class="btn btn-secondary w-full sm:w-auto" @click="resetForm">
|
||||
สร้างอีกฉบับ
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="downloading"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:opacity-50"
|
||||
class="btn btn-primary w-full sm:w-auto"
|
||||
@click="download"
|
||||
>
|
||||
{{ downloading ? 'กำลังเตรียมไฟล์…' : '⬇ ดาวน์โหลด .docx' }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-slate-300 px-4 py-2 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
@click="resetForm"
|
||||
>
|
||||
สร้างอีกฉบับ
|
||||
</button>
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="rounded-md border border-slate-300 px-4 py-2 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
>
|
||||
กลับหน้าหลัก
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<AlertBox v-if="downloadError" type="error" class="mt-4">{{ downloadError }}</AlertBox>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border border-sky-200 bg-sky-50 p-4 text-sm dark:border-sky-800 dark:bg-sky-950">
|
||||
<p class="font-medium text-sky-800 dark:text-sky-300">ขั้นต่อไป</p>
|
||||
<ol class="mt-2 list-decimal space-y-1 pl-5 text-sky-800 dark:text-sky-200">
|
||||
<div class="panel-info">
|
||||
<p class="font-medium text-primary">ขั้นต่อไป</p>
|
||||
<ol class="mt-2 list-decimal space-y-1 pl-5">
|
||||
<li>เปิดไฟล์ที่ดาวน์โหลดใน Word (Desktop หรือ Online)</li>
|
||||
<li>
|
||||
เปิด task pane ของ add-in <strong>e-Court Documents Versioning</strong> — ระบบจะตรวจพบว่าเป็น
|
||||
|
|
@ -195,8 +184,8 @@ onMounted(load)
|
|||
<!-- ฟอร์มกรอกค่า -->
|
||||
<form v-else class="space-y-5" @submit.prevent="submit" novalidate>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">{{ detail.name }}</h1>
|
||||
<p v-if="detail.description" class="mt-1 text-sm text-slate-600 dark:text-slate-300">
|
||||
<h1 class="text-page-title">{{ detail.name }}</h1>
|
||||
<p v-if="detail.description" class="mt-1 text-sm text-muted">
|
||||
{{ detail.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -214,10 +203,10 @@ onMounted(load)
|
|||
v-for="f in sortedFields"
|
||||
:key="f.key"
|
||||
>
|
||||
<label :for="`field-${f.key}`" class="mb-1 block text-sm font-medium">
|
||||
<label :for="`field-${f.key}`" class="label">
|
||||
{{ f.label }}
|
||||
<span v-if="f.isRequired" class="text-red-500">*</span>
|
||||
<span class="ml-1 font-mono text-xs font-normal text-slate-400">{{ f.key }}</span>
|
||||
<span v-if="f.isRequired" class="text-error-strong">*</span>
|
||||
<span class="ml-1 font-mono text-xs font-normal text-muted">{{ f.key }}</span>
|
||||
</label>
|
||||
|
||||
<input
|
||||
|
|
@ -225,31 +214,28 @@ onMounted(load)
|
|||
v-model="values[f.key]"
|
||||
:type="f.type === 'number' ? 'number' : f.type === 'date' ? 'date' : 'text'"
|
||||
:step="f.type === 'number' ? 'any' : undefined"
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
:class="issues[f.key] ? 'border-red-400 dark:border-red-500' : ''"
|
||||
class="input"
|
||||
:class="issues[f.key] ? 'border-error' : ''"
|
||||
/>
|
||||
<p v-if="issues[f.key]" class="mt-1 text-xs text-red-600 dark:text-red-400">{{ issues[f.key] }}</p>
|
||||
<p v-if="issues[f.key]" class="mt-1 text-xs text-error-strong">{{ issues[f.key] }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="sortedFields.length === 0"
|
||||
class="rounded-md border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
<div v-if="sortedFields.length === 0" class="empty-state">
|
||||
template นี้ไม่มี field definitions —
|
||||
<NuxtLink :to="`/templates/${detail.id}/edit`" class="font-medium text-sky-600 hover:underline dark:text-sky-400">
|
||||
ตรวจสอบที่หน้าจัดการ template
|
||||
</NuxtLink>
|
||||
<NuxtLink :to="`/templates/${detail.id}/edit`" class="link">ตรวจสอบที่หน้าจัดการ template</NuxtLink>
|
||||
</div>
|
||||
|
||||
<AlertBox v-if="submitError" type="error">{{ submitError }}</AlertBox>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="submitting || sortedFields.length === 0 || !detail.isActive"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:opacity-50"
|
||||
>
|
||||
{{ submitting ? 'กำลังสร้างเอกสาร…' : 'สร้างเอกสาร (version 1)' }}
|
||||
</button>
|
||||
<div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="submitting || sortedFields.length === 0 || !detail.isActive"
|
||||
class="btn btn-primary w-full sm:w-auto"
|
||||
>
|
||||
{{ submitting ? 'กำลังสร้างเอกสาร…' : 'สร้างเอกสาร (version 1)' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -21,60 +21,46 @@ onMounted(async () => {
|
|||
|
||||
<template>
|
||||
<AuthGate>
|
||||
<section class="space-y-5">
|
||||
<section class="space-y-6 sm:space-y-8">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">สร้างเอกสารจาก template</h1>
|
||||
<p class="mt-1 text-sm text-slate-600 dark:text-slate-300">
|
||||
<div class="h-1 w-10 rounded-pill bg-accent" aria-hidden="true" />
|
||||
<h1 class="mt-3 text-display">สร้างเอกสารจาก template</h1>
|
||||
<p class="mt-2 text-body-lg text-muted">
|
||||
เลือก template กรอกค่า fields แล้วดาวน์โหลด .docx ที่เป็น <strong>version 1</strong> พร้อม metadata
|
||||
ไว้เปิดต่อใน Word พร้อม add-in
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลดรายการ template…
|
||||
</div>
|
||||
|
||||
<AlertBox v-else-if="error" type="error">{{ error }}</AlertBox>
|
||||
|
||||
<div
|
||||
v-else-if="templates.length === 0"
|
||||
class="rounded-lg border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
<div v-else-if="templates.length === 0" class="empty-state">
|
||||
ยังไม่มี template — ไปที่
|
||||
<NuxtLink to="/templates/new" class="font-medium text-sky-600 hover:underline dark:text-sky-400">
|
||||
สร้าง template ตัวแรก
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/templates/new" class="link">สร้าง template ตัวแรก</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid gap-4 sm:grid-cols-2">
|
||||
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
v-for="t in templates"
|
||||
:key="t.id"
|
||||
class="flex flex-col rounded-lg border border-slate-200 bg-white p-5 transition hover:border-sky-400 hover:shadow-sm dark:border-slate-700 dark:bg-slate-800 dark:hover:border-sky-500"
|
||||
class="card flex flex-col p-5 transition hover:border-primary/50 hover:shadow-soft-lg"
|
||||
>
|
||||
<NuxtLink :to="`/documents/create/${t.id}`" class="flex-1">
|
||||
<h2 class="font-semibold">{{ t.name }}</h2>
|
||||
<p class="mt-1 line-clamp-2 min-h-10 text-sm text-slate-600 dark:text-slate-300">
|
||||
<p class="mt-1 line-clamp-2 min-h-10 text-sm text-muted">
|
||||
{{ t.description ?? '—' }}
|
||||
</p>
|
||||
<p class="mt-3 text-xs text-slate-500 dark:text-slate-400">
|
||||
<p class="mt-3 text-xs text-muted">
|
||||
{{ t.fieldCount }} fields · อัปเดต {{ formatDateTime(t.updatedAt) }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
<div class="mt-3 flex gap-4 border-t border-slate-100 pt-3 text-xs dark:border-slate-700">
|
||||
<NuxtLink
|
||||
:to="`/documents/create/${t.id}`"
|
||||
class="font-medium text-sky-600 hover:underline dark:text-sky-400"
|
||||
>
|
||||
สร้างเอกสาร →
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:to="`/templates/${t.id}/documents`"
|
||||
class="text-slate-500 hover:text-sky-600 hover:underline dark:text-slate-400 dark:hover:text-sky-400"
|
||||
>
|
||||
<div class="mt-auto flex gap-4 border-t border-line pt-3 text-xs">
|
||||
<NuxtLink :to="`/documents/create/${t.id}`" class="link">สร้างเอกสาร →</NuxtLink>
|
||||
<NuxtLink :to="`/templates/${t.id}/documents`" class="text-muted hover:text-primary hover:underline">
|
||||
ดูเอกสารที่สร้างแล้ว
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,11 +35,9 @@ onMounted(load)
|
|||
|
||||
<template>
|
||||
<AuthGate>
|
||||
<section class="space-y-5">
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<section class="space-y-6">
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลด…
|
||||
</div>
|
||||
|
||||
|
|
@ -49,25 +47,22 @@ onMounted(load)
|
|||
</AlertBox>
|
||||
|
||||
<template v-else-if="template">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">เอกสารที่สร้างจาก template</p>
|
||||
<h1 class="text-2xl font-bold">{{ template.name }}</h1>
|
||||
<p v-if="template.description" class="mt-1 text-sm text-slate-600 dark:text-slate-300">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs text-muted">เอกสารที่สร้างจาก template</p>
|
||||
<h1 class="mt-1 text-page-title">{{ template.name }}</h1>
|
||||
<p v-if="template.description" class="mt-1 text-sm text-muted">
|
||||
{{ template.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<NuxtLink
|
||||
:to="`/templates/${template.id}/edit`"
|
||||
class="rounded-md border border-slate-300 px-3 py-1.5 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
>
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<NuxtLink :to="`/templates/${template.id}/edit`" class="btn btn-secondary btn-sm">
|
||||
แก้ไข template
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="template.isActive"
|
||||
:to="`/documents/create/${template.id}`"
|
||||
class="rounded-md bg-sky-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-sky-700"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
+ สร้างเอกสารใหม่
|
||||
</NuxtLink>
|
||||
|
|
@ -78,62 +73,66 @@ onMounted(load)
|
|||
template นี้ถูก deactivate แล้ว — สร้างเอกสารใหม่ไม่ได้ แต่เอกสารที่สร้างไว้แล้วยังใช้งานได้ตามปกติ
|
||||
</AlertBox>
|
||||
|
||||
<div
|
||||
v-if="documents.length === 0"
|
||||
class="rounded-lg border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
<div v-if="documents.length === 0" class="empty-state">
|
||||
ยังไม่มีเอกสารจาก template นี้<template v-if="template.isActive">
|
||||
—
|
||||
<NuxtLink
|
||||
:to="`/documents/create/${template.id}`"
|
||||
class="font-medium text-sky-600 hover:underline dark:text-sky-400"
|
||||
>
|
||||
สร้างเอกสารแรก
|
||||
</NuxtLink></template
|
||||
<NuxtLink :to="`/documents/create/${template.id}`" class="link">สร้างเอกสารแรก</NuxtLink></template
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">ไฟล์เอกสาร</th>
|
||||
<th class="px-4 py-3">สร้างเมื่อ</th>
|
||||
<th class="px-4 py-3 text-center">versions</th>
|
||||
<th class="px-4 py-3">กิจกรรมล่าสุด</th>
|
||||
<th class="px-4 py-3 text-right">การกระทำ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
<tr v-for="d in documents" :key="d.documentId">
|
||||
<td class="max-w-72 px-4 py-3">
|
||||
<NuxtLink
|
||||
:to="`/documents/${d.documentId}`"
|
||||
class="font-medium text-sky-700 hover:underline dark:text-sky-400"
|
||||
>
|
||||
{{ d.fileName }}
|
||||
</NuxtLink>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">{{ formatDateTime(d.createdAt) }}</td>
|
||||
<td class="px-4 py-3 text-center">{{ d.versionCount }}</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">
|
||||
<template v-if="d.latestVersionAt">
|
||||
v{{ d.latestVersionNumber }} · {{ formatDateTime(d.latestVersionAt) }}
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<NuxtLink
|
||||
:to="`/documents/${d.documentId}`"
|
||||
class="rounded-md border border-slate-300 px-2.5 py-1 text-xs hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-700"
|
||||
>
|
||||
ดูรายละเอียด
|
||||
</NuxtLink>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-else>
|
||||
<!-- มือถือ: card list -->
|
||||
<ul class="space-y-3 md:hidden">
|
||||
<li v-for="d in documents" :key="d.documentId" class="card space-y-2 p-4">
|
||||
<NuxtLink :to="`/documents/${d.documentId}`" class="link break-words font-semibold">
|
||||
{{ d.fileName }}
|
||||
</NuxtLink>
|
||||
<p class="text-xs text-muted">สร้าง {{ formatDateTime(d.createdAt) }} · {{ d.versionCount }} versions</p>
|
||||
<p class="text-xs text-muted">
|
||||
<template v-if="d.latestVersionAt">
|
||||
ล่าสุด v{{ d.latestVersionNumber }} · {{ formatDateTime(d.latestVersionAt) }}
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</p>
|
||||
<NuxtLink :to="`/documents/${d.documentId}`" class="btn btn-secondary btn-xs">ดูรายละเอียด</NuxtLink>
|
||||
</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">versions</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="d in documents" :key="d.documentId">
|
||||
<td class="max-w-72 px-4 py-3">
|
||||
<NuxtLink :to="`/documents/${d.documentId}`" class="link">{{ d.fileName }}</NuxtLink>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-muted">{{ formatDateTime(d.createdAt) }}</td>
|
||||
<td class="px-4 py-3 text-center">{{ d.versionCount }}</td>
|
||||
<td class="px-4 py-3 text-muted">
|
||||
<template v-if="d.latestVersionAt">
|
||||
v{{ d.latestVersionNumber }} · {{ formatDateTime(d.latestVersionAt) }}
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<NuxtLink :to="`/documents/${d.documentId}`" class="btn btn-secondary btn-xs">
|
||||
ดูรายละเอียด
|
||||
</NuxtLink>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
</AuthGate>
|
||||
|
|
|
|||
|
|
@ -134,83 +134,99 @@ onMounted(load)
|
|||
<template>
|
||||
<AuthGate>
|
||||
<section class="mx-auto max-w-4xl space-y-8">
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลด…
|
||||
</div>
|
||||
|
||||
<AlertBox v-else-if="loadError" type="error">{{ loadError }}</AlertBox>
|
||||
|
||||
<template v-else-if="detail">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">{{ detail.name }}</h1>
|
||||
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||
<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">{{ detail.name }}</h1>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
สร้าง {{ formatDateTime(detail.createdAt) }} · อัปเดต {{ formatDateTime(detail.updatedAt) }}
|
||||
</p>
|
||||
</div>
|
||||
<NuxtLink
|
||||
to="/templates"
|
||||
class="rounded-md border border-slate-300 px-3 py-1.5 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
>
|
||||
← รายการ template
|
||||
</NuxtLink>
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<NuxtLink to="/templates" class="btn btn-secondary btn-sm">← รายการ template</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1) metadata -->
|
||||
<form class="space-y-4" @submit.prevent="save">
|
||||
<h2 class="text-lg font-semibold">ข้อมูล template</h2>
|
||||
<form class="max-w-xl space-y-4" @submit.prevent="save">
|
||||
<h2 class="text-section-title">ข้อมูล template</h2>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label for="edit-name" class="mb-1 block text-sm font-medium">ชื่อ</label>
|
||||
<input
|
||||
id="edit-name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
required
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
/>
|
||||
<label for="edit-name" class="label">ชื่อ</label>
|
||||
<input id="edit-name" v-model="name" type="text" required class="input" />
|
||||
</div>
|
||||
<div class="flex items-end gap-2 pb-1">
|
||||
<input
|
||||
id="edit-active"
|
||||
v-model="isActive"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-slate-300 dark:border-slate-600 dark:bg-slate-800"
|
||||
/>
|
||||
<input id="edit-active" v-model="isActive" type="checkbox" class="h-4 w-4 accent-primary" />
|
||||
<label for="edit-active" class="text-sm">
|
||||
active
|
||||
<span class="text-xs text-slate-500 dark:text-slate-400">(เอาออก = สร้างเอกสารใหม่ไม่ได้ แต่เอกสารเดิมยังใช้ได้)</span>
|
||||
<span class="text-xs text-muted">(เอาออก = สร้างเอกสารใหม่ไม่ได้ แต่เอกสารเดิมยังใช้ได้)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-desc" class="mb-1 block text-sm font-medium">คำอธิบาย</label>
|
||||
<textarea
|
||||
id="edit-desc"
|
||||
v-model="description"
|
||||
rows="2"
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
/>
|
||||
<label for="edit-desc" class="label">คำอธิบาย</label>
|
||||
<textarea id="edit-desc" v-model="description" rows="2" class="input" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 2) field definitions -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">Field definitions</h2>
|
||||
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||
<h2 class="text-section-title">Field definitions</h2>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
<span class="font-mono">key</span> มาจาก content control tag ในไฟล์ — เพิ่ม/ลบ key ต้องแทนที่ไฟล์ด้านล่าง
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800">
|
||||
<!-- มือถือ: card ต่อ field (v-model ชุดเดียวกับตาราง) -->
|
||||
<ul v-if="fields.length" class="space-y-3 md:hidden">
|
||||
<li v-for="f in fields" :key="f.key" class="card space-y-3 p-4">
|
||||
<p class="font-mono text-xs text-muted">{{ f.key }}</p>
|
||||
<div>
|
||||
<label :for="`m-label-${f.key}`" class="label">label</label>
|
||||
<input :id="`m-label-${f.key}`" v-model="f.label" type="text" :aria-label="`label ของ ${f.key}`" class="input" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label :for="`m-type-${f.key}`" class="label">type</label>
|
||||
<select :id="`m-type-${f.key}`" v-model="f.type" :aria-label="`type ของ ${f.key}`" class="input">
|
||||
<option v-for="t in FIELD_TYPES" :key="t" :value="t">{{ t }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label :for="`m-order-${f.key}`" class="label">ลำดับ</label>
|
||||
<input
|
||||
:id="`m-order-${f.key}`"
|
||||
v-model.number="f.sortOrder"
|
||||
type="number"
|
||||
:aria-label="`ลำดับของ ${f.key}`"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input v-model="f.isRequired" type="checkbox" :aria-label="`required ของ ${f.key}`" class="h-4 w-4 accent-primary" />
|
||||
required
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<AlertBox v-else-if="fields.length === 0" type="info">
|
||||
ไฟล์นี้ไม่มี content control เลย — แทนที่ไฟล์ด้วยไฟล์ที่มี content controls
|
||||
</AlertBox>
|
||||
|
||||
<!-- desktop: ตารางแก้ค่า -->
|
||||
<div v-if="fields.length" class="table-card hidden md:block">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<thead class="table-head">
|
||||
<tr>
|
||||
<th class="px-4 py-3">key</th>
|
||||
<th class="px-4 py-3">label</th>
|
||||
|
|
@ -219,7 +235,7 @@ onMounted(load)
|
|||
<th class="px-4 py-3">ลำดับ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
<tbody class="divide-y divide-line">
|
||||
<tr v-for="(f, i) in fields" :key="f.key">
|
||||
<td class="px-4 py-2 font-mono text-xs">{{ f.key }}</td>
|
||||
<td class="px-4 py-2">
|
||||
|
|
@ -227,15 +243,11 @@ onMounted(load)
|
|||
v-model="f.label"
|
||||
type="text"
|
||||
:aria-label="`label ของ ${f.key}`"
|
||||
class="w-full min-w-40 rounded-md border border-slate-300 bg-white px-2 py-1 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
class="input min-w-40 px-2 py-1"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<select
|
||||
v-model="f.type"
|
||||
:aria-label="`type ของ ${f.key}`"
|
||||
class="rounded-md border border-slate-300 bg-white px-2 py-1 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
>
|
||||
<select v-model="f.type" :aria-label="`type ของ ${f.key}`" class="input px-2 py-1">
|
||||
<option v-for="t in FIELD_TYPES" :key="t" :value="t">{{ t }}</option>
|
||||
</select>
|
||||
</td>
|
||||
|
|
@ -244,7 +256,7 @@ onMounted(load)
|
|||
v-model="f.isRequired"
|
||||
type="checkbox"
|
||||
:aria-label="`required ของ ${f.key}`"
|
||||
class="h-4 w-4 rounded border-slate-300 dark:border-slate-600 dark:bg-slate-800"
|
||||
class="h-4 w-4 accent-primary"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
|
|
@ -252,29 +264,19 @@ onMounted(load)
|
|||
v-model.number="f.sortOrder"
|
||||
type="number"
|
||||
:aria-label="`ลำดับของ ${f.key}`"
|
||||
class="w-20 rounded-md border border-slate-300 bg-white px-2 py-1 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
class="input w-20 px-2 py-1"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="fields.length === 0">
|
||||
<td colspan="5" class="px-4 py-6 text-center text-sm text-slate-500 dark:text-slate-400">
|
||||
ไฟล์นี้ไม่มี content control เลย — แทนที่ไฟล์ด้วยไฟล์ที่มี content controls
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="saving"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:opacity-50"
|
||||
@click="save"
|
||||
>
|
||||
<button type="button" :disabled="saving" class="btn btn-primary" @click="save">
|
||||
{{ saving ? 'กำลังบันทึก…' : 'บันทึกทั้งหมด' }}
|
||||
</button>
|
||||
<span v-if="savedAt && !saveError" class="text-xs text-emerald-600 dark:text-emerald-400">
|
||||
<span v-if="savedAt && !saveError" class="text-xs text-success-strong">
|
||||
บันทึกแล้วเมื่อ {{ savedAt }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -282,27 +284,27 @@ onMounted(load)
|
|||
</div>
|
||||
|
||||
<!-- 3) replace file -->
|
||||
<div class="space-y-3 border-t border-slate-200 pt-6 dark:border-slate-700">
|
||||
<div class="space-y-3 border-t border-line pt-6">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">แทนที่ไฟล์ template</h2>
|
||||
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||
<h2 class="text-section-title">แทนที่ไฟล์ template</h2>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
ระบบจะ detect tags ใหม่จากไฟล์ — field definition ของ tag ที่หายไปจะถูกลบ, tag ใหม่จะเพิ่มแบบ default
|
||||
(เอกสารที่สร้างไปแล้วไม่กระทบ)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
||||
<input
|
||||
ref="replaceInput"
|
||||
type="file"
|
||||
accept=".docx"
|
||||
class="rounded-md border border-slate-300 bg-white px-3 py-2 text-sm file:mr-3 file:rounded file:border-0 file:bg-slate-100 file:px-3 file:py-1 file:text-sm dark:border-slate-600 dark:bg-slate-800 dark:file:bg-slate-700"
|
||||
class="input sm:max-w-md file:mr-3 file:rounded-pill file:border-0 file:bg-surface-muted file:px-3 file:py-1 file:text-sm file:font-medium"
|
||||
@change="onReplaceFileChange"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="replacing || !replaceFile"
|
||||
class="rounded-md border border-slate-300 px-4 py-2 text-sm hover:bg-slate-100 disabled:opacity-50 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
class="btn btn-secondary w-full sm:w-auto"
|
||||
@click="submitReplaceFile"
|
||||
>
|
||||
{{ replacing ? 'กำลังอัปโหลด…' : 'อัปโหลดไฟล์ใหม่' }}
|
||||
|
|
|
|||
|
|
@ -52,120 +52,109 @@ onMounted(load)
|
|||
|
||||
<template>
|
||||
<AuthGate>
|
||||
<section class="space-y-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="text-2xl font-bold">จัดการ template</h1>
|
||||
<NuxtLink
|
||||
to="/templates/new"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700"
|
||||
>
|
||||
+ สร้าง template ใหม่
|
||||
</NuxtLink>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-slate-600 dark:text-slate-300">
|
||||
<input
|
||||
v-model="showInactive"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-slate-300 dark:border-slate-600 dark:bg-slate-800"
|
||||
/>
|
||||
<label class="flex items-center gap-2 text-sm text-muted">
|
||||
<input v-model="showInactive" type="checkbox" class="h-4 w-4 accent-primary" />
|
||||
แสดง template ที่ deactivate ไว้ด้วย
|
||||
</label>
|
||||
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-slate-500 dark:text-slate-400">
|
||||
<span
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-slate-600 dark:border-slate-600 dark:border-t-slate-300"
|
||||
/>
|
||||
<div v-if="loading" class="flex items-center gap-3 py-10 text-sm text-muted">
|
||||
<span class="spinner" />
|
||||
กำลังโหลด…
|
||||
</div>
|
||||
|
||||
<AlertBox v-else-if="error" type="error">{{ error }}</AlertBox>
|
||||
|
||||
<div
|
||||
v-else-if="templates.length === 0"
|
||||
class="rounded-lg border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400"
|
||||
>
|
||||
<div v-else-if="templates.length === 0" class="empty-state">
|
||||
ยังไม่มี template{{ showInactive ? ' เลย' : 'ที่ active' }} —
|
||||
<NuxtLink to="/templates/new" class="font-medium text-sky-600 hover:underline dark:text-sky-400">
|
||||
สร้าง template แรก
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/templates/new" class="link">สร้าง template แรก</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800"
|
||||
>
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<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-slate-100 dark:divide-slate-700">
|
||||
<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="text-sky-700 hover:underline dark:text-sky-400"
|
||||
>
|
||||
{{ t.name }}
|
||||
</NuxtLink>
|
||||
</td>
|
||||
<td class="max-w-60 px-4 py-3 text-slate-600 dark:text-slate-300">{{ 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="rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300"
|
||||
>
|
||||
active
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="rounded-full bg-slate-200 px-2 py-0.5 text-xs font-medium text-slate-600 dark:bg-slate-700 dark:text-slate-300"
|
||||
>
|
||||
deactivated
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">{{ formatDateTime(t.updatedAt) }}</td>
|
||||
<td class="px-4 py-3 text-right whitespace-nowrap">
|
||||
<NuxtLink
|
||||
:to="`/templates/${t.id}/documents`"
|
||||
class="rounded-md border border-slate-300 px-2.5 py-1 text-xs hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-700"
|
||||
>
|
||||
เอกสาร
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:to="`/templates/${t.id}/edit`"
|
||||
class="ml-2 rounded-md border border-slate-300 px-2.5 py-1 text-xs hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-700"
|
||||
>
|
||||
แก้ไข
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="busyId === t.id"
|
||||
class="ml-2 rounded-md border px-2.5 py-1 text-xs disabled:opacity-50"
|
||||
:class="
|
||||
t.isActive
|
||||
? 'border-red-300 text-red-600 hover:bg-red-50 dark:border-red-800 dark:text-red-400 dark:hover:bg-red-950'
|
||||
: 'border-emerald-300 text-emerald-600 hover:bg-emerald-50 dark:border-emerald-800 dark:text-emerald-400 dark:hover:bg-emerald-950'
|
||||
"
|
||||
@click="toggleActive(t)"
|
||||
>
|
||||
{{ busyId === t.id ? '…' : t.isActive ? 'deactivate' : 'activate' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||
<!-- 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>
|
||||
|
||||
<p class="text-xs text-muted">
|
||||
การ deactivate เป็น soft-delete — เอกสารที่สร้างจาก template นั้นไปแล้วยังใช้งานต่อได้ตามปกติ
|
||||
</p>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ function resetForm() {
|
|||
|
||||
<template>
|
||||
<AuthGate>
|
||||
<section class="mx-auto max-w-2xl space-y-5">
|
||||
<h1 class="text-2xl font-bold">สร้าง template ใหม่</h1>
|
||||
<section class="mx-auto max-w-xl space-y-6">
|
||||
<h1 class="text-page-title">สร้าง template ใหม่</h1>
|
||||
|
||||
<!-- ผลลัพธ์หลังอัปโหลดสำเร็จ: field list ที่ detect ได้จากไฟล์ -->
|
||||
<template v-if="created">
|
||||
|
|
@ -70,16 +70,26 @@ function resetForm() {
|
|||
อัปโหลดสำเร็จ — ตรวจพบ <strong>{{ created.fields.length }}</strong> fields จากไฟล์
|
||||
</AlertBox>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800">
|
||||
<!-- มือถือ: card list -->
|
||||
<ul class="space-y-3 md:hidden">
|
||||
<li v-for="f in created.fields" :key="f.key" class="card space-y-1 p-4">
|
||||
<p class="font-mono text-xs text-muted">{{ f.key }}</p>
|
||||
<p class="text-sm font-medium">{{ f.label }}</p>
|
||||
<p class="text-xs text-muted">type: {{ f.type }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- desktop: ตาราง -->
|
||||
<div class="table-card hidden md:block">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-slate-200 text-left text-xs uppercase text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<thead class="table-head">
|
||||
<tr>
|
||||
<th class="px-4 py-3">key (SDT tag)</th>
|
||||
<th class="px-4 py-3">label เริ่มต้น</th>
|
||||
<th class="px-4 py-3">type เริ่มต้น</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
<tbody class="divide-y divide-line">
|
||||
<tr v-for="f in created.fields" :key="f.key">
|
||||
<td class="px-4 py-2 font-mono text-xs">{{ f.key }}</td>
|
||||
<td class="px-4 py-2">{{ f.label }}</td>
|
||||
|
|
@ -89,20 +99,13 @@ function resetForm() {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<NuxtLink
|
||||
:to="`/templates/${created.id}/edit`"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700"
|
||||
>
|
||||
ตั้งค่า label/type/required ต่อ →
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-slate-300 px-4 py-2 text-sm hover:bg-slate-100 dark:border-slate-600 dark:hover:bg-slate-800"
|
||||
@click="resetForm"
|
||||
>
|
||||
<div class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<button type="button" class="btn btn-secondary w-full sm:w-auto" @click="resetForm">
|
||||
อัปโหลดอีกไฟล์
|
||||
</button>
|
||||
<NuxtLink :to="`/templates/${created.id}/edit`" class="btn btn-primary w-full sm:w-auto">
|
||||
ตั้งค่า label/type/required ต่อ →
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -110,40 +113,40 @@ function resetForm() {
|
|||
<AlertBox v-if="error" type="error">{{ error }}</AlertBox>
|
||||
|
||||
<div>
|
||||
<label for="tpl-name" class="mb-1 block text-sm font-medium">ชื่อ template <span class="text-red-500">*</span></label>
|
||||
<label for="tpl-name" class="label">ชื่อ template <span class="text-error-strong">*</span></label>
|
||||
<input
|
||||
id="tpl-name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
required
|
||||
placeholder="เช่น สัญญาจ้างงาน"
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="tpl-desc" class="mb-1 block text-sm font-medium">คำอธิบาย</label>
|
||||
<label for="tpl-desc" class="label">คำอธิบาย</label>
|
||||
<textarea
|
||||
id="tpl-desc"
|
||||
v-model="description"
|
||||
rows="2"
|
||||
placeholder="คำอธิบายสั้นๆ ว่าเอกสารนี้ใช้ทำอะไร"
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="tpl-file" class="mb-1 block text-sm font-medium">ไฟล์ template (.docx) <span class="text-red-500">*</span></label>
|
||||
<label for="tpl-file" class="label">ไฟล์ template (.docx) <span class="text-error-strong">*</span></label>
|
||||
<input
|
||||
id="tpl-file"
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".docx"
|
||||
required
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm file:mr-3 file:rounded file:border-0 file:bg-slate-100 file:px-3 file:py-1 file:text-sm dark:border-slate-600 dark:bg-slate-800 dark:file:bg-slate-700"
|
||||
class="input file:mr-3 file:rounded-pill file:border-0 file:bg-surface-muted file:px-3 file:py-1 file:text-sm file:font-medium"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
สร้างไฟล์ด้วย Word แล้วใส่
|
||||
<strong>Content Controls</strong> (Developer → Rich Text/Plain Text Control) โดยตั้งค่า
|
||||
<span class="font-mono">Tag</span> ของแต่ละ control เป็น field key — ระบบจะ detect tags
|
||||
|
|
@ -151,13 +154,11 @@ function resetForm() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="submitting"
|
||||
class="rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:opacity-50"
|
||||
>
|
||||
{{ submitting ? 'กำลังอัปโหลด…' : 'อัปโหลด template' }}
|
||||
</button>
|
||||
<div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
|
||||
<button type="submit" :disabled="submitting" class="btn btn-primary w-full sm:w-auto">
|
||||
{{ submitting ? 'กำลังอัปโหลด…' : 'อัปโหลด template' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</AuthGate>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue