Undangan/proyek-frontend/app/components/template-page/TemplateGrid.vue
2025-10-09 15:02:52 +07:00

223 lines
7.1 KiB
Vue

<template>
<div class="flex flex-col min-h-screen">
<!-- Header & Back Button -->
<div class="flex items-center mb-8">
<button @click="$emit('back')"
class="text-blue-600 hover:text-blue-800 font-semibold inline-flex items-center mr-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
Kembali
</button>
<h1 class="text-3xl md:text-4xl font-bold text-gray-800">
Template {{ category }}
</h1>
</div>
<!-- Loading & Error -->
<div v-if="isLoading" class="text-center py-10">
<p>Memuat template...</p>
</div>
<div v-else-if="error" class="text-center py-10 text-red-600">
<p>{{ error }}</p>
</div>
<!-- Grid Template -->
<div v-else-if="templates.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 items-start">
<div v-for="tpl in templates" :key="tpl.id"
class="bg-white border rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
<!-- Gambar -->
<img :src="tpl.foto" :alt="tpl.nama_template" class="w-full h-48 object-cover"
@error="(e) => e.target.src = '/logo2.png'" />
<!-- Body -->
<div class="p-5 text-center">
<h4 class="text-xl font-bold text-gray-800 mb-2">{{ tpl.nama_template }}</h4>
<p class="text-green-600 font-semibold text-xl mb-1">
Rp {{ Number(tpl.harga ?? 0).toLocaleString('id-ID') }}
</p>
<p class="text-gray-500 mb-4 font-medium">Paket: {{ tpl.paket }}</p>
<!-- Dropdown Fitur -->
<div v-if="tpl.fiturs && tpl.fiturs.length" class="relative mb-4">
<button @click="toggleDropdown(tpl.id)"
class="w-full bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 inline-flex justify-between items-center">
<span class="mx-auto text-gray-700 font-semibold">FITUR YANG TERSEDIA</span>
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
<transition name="fade">
<div v-if="openDropdownId === tpl.id" class="mt-4">
<ul
class="space-y-2 text-gray-600 text-left max-h-60 overflow-y-auto px-3 py-2 border border-gray-200 rounded-md shadow-inner bg-gray-50">
<li v-for="f in tpl.fiturs" :key="f.id" class="flex items-center">
<svg class="h-4 w-4 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ f.deskripsi }}
</li>
</ul>
</div>
</transition>
</div>
<!-- Buttons -->
<div class="flex items-center gap-3 mt-6">
<a :href="tpl.preview_link || '#'"
class="w-full bg-white border border-gray-300 text-gray-800 font-semibold py-2 px-4 rounded-lg hover:bg-gray-100 transition-colors text-center">
Preview
</a>
<NuxtLink :to="`${tpl.formPath}?template_id=${tpl.id}`"
class="w-full bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors text-center">
Order
</NuxtLink>
</div>
</div>
</div>
</div>
<div v-else class="text-center py-10 text-gray-500">
<p>Belum ada template untuk kategori ini.</p>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, computed } from 'vue'
const props = defineProps({
category: { type: String, required: true },
id_category: { type: [Number, String], required: true },
})
defineEmits(['back'])
const templates = ref([])
const isLoading = ref(true)
const error = ref(null)
const openDropdownId = ref(null)
const toggleDropdown = (templateId) => {
openDropdownId.value = openDropdownId.value === templateId ? null : templateId
}
// Mapping form untuk tiap template
const formMapping = {
'Undangan Pernikahan Premium': '/form/pernikahan/b',
'Undangan Minimalis': '/form/pernikahan/a',
'Undangan Ulang Tahun Premium': '/form/ulang-tahun/a',
'Undangan Khitan Premium': '/form/khitan/a',
}
// Hardcode fitur per paket
// Mapping paket -> fitur (pastikan key sesuai format paket)
const fiturPerPaket = {
Starter: [
'1x Acara',
'Masa Aktif 3 Bulan',
'Nama Tamu Personal',
'Maks. 100 Tamu',
'Request Musik'
],
Basic: [
'1x Acara',
'6 Galeri Foto',
'Hitung Mundur Waktu Acara',
'Buku Tamu + Data Kehadiran',
'Masa Aktif 6 Bulan',
'Nama Tamu Personal',
'Maks. 200 Tamu',
'Request Musik'
],
Premium: [
'Maksimal 3x Acara (Akad, Resepsi, Syukuran)',
'Unlimited Galeri Foto',
'Timeline Story',
'Google Maps',
'Reminder Google Calendar',
'Link Instagram Live Streaming',
'Amplop Digital',
'Placement Video Cinematic',
'Bonus Undangan Image Post Story',
'Masa Aktif 12 Bulan',
'Nama Tamu Personal Unlimited Tamu',
'Request Musik'
]
}
// Fetch templates dari API
const fetchTemplates = async (categoryId) => {
isLoading.value = true
error.value = null
try {
const res = await $fetch(`/api/templates/category/${categoryId}`, {
baseURL: 'http://localhost:8000'
})
templates.value = res.map(tpl => {
// Pastikan nama paket konsisten: 'Starter', 'Basic', 'Premium'
const paketKey = tpl.paket ? tpl.paket.charAt(0).toUpperCase() + tpl.paket.slice(1).toLowerCase() : 'Starter'
return {
id: tpl.id,
nama_template: tpl.nama_template,
harga: tpl.harga,
kategori: tpl.kategori,
foto: tpl.foto ?? '/logo2.png',
paket: paketKey,
fiturs: (fiturPerPaket[paketKey] || []).map((f, i) => ({
id: i + 1,
deskripsi: f
})),
preview_link: tpl.preview_link ?? null,
formPath: formMapping[tpl.nama_template] || '/form/lainny'
}
})
} catch (err) {
console.error(err)
error.value = 'Gagal memuat template.'
templates.value = []
} finally {
isLoading.value = false
}
}
onMounted(() => fetchTemplates(props.id_category))
watch(() => props.id_category, (newId) => {
if (newId) fetchTemplates(newId)
})
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(-5px);
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
transform: translateY(0);
}
</style>