Undangan/proyek-frontend/app/components/template-page/TemplateGrid.vue
2025-10-23 13:58:50 +07:00

218 lines
7.3 KiB
Vue

<template>
<div class="flex flex-col min-h-screen">
<!-- Main Content -->
<main class="flex-1">
<div class="max-w-7xl mx-auto px-4 py-8">
<!-- Header & Back Button -->
<div class="mb-8">
<button @click="$emit('back')"
class="text-blue-600 hover:text-blue-800 font-semibold inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" 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-center text-gray-800">
Template {{ category }}
</h1>
<p class="mt-2 text-center text-gray-500">
Pilih template terbaik untuk kategori {{ category }}.
</p>
</div>
<!-- Loading & Error -->
<div v-if="isLoading" class="mt-12 text-center">
Memuat template...
</div>
<div v-else-if="error" class="mt-12 text-center text-red-500">
{{ error }}
</div>
<!-- Grid Template -->
<div v-else-if="templates.length > 0" class="mt-12 grid gap-8 max-w-[1100px] mx-auto grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 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 ? (tpl.foto.startsWith('http') ? tpl.foto : `http://localhost:8000${tpl.foto}`) : '/default.jpg'"
:alt="tpl.nama" class="w-full h-48 object-cover" />
<!-- Body -->
<div class="p-5 text-center">
<h4 class="text-xl font-bold text-gray-800 mb-2">{{ tpl.nama }}</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 > 0" 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" fill="currentColor"
viewBox="0 0 20 20">
<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" />
</svg>
{{ f.deskripsi }}
</li>
</ul>
</div>
</transition>
</div>
<!-- Buttons -->
<div class="flex items-center gap-3 mt-6">
<button @click="$router.push(`/preview/${tpl.id}`)"
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">
Preview
</button>
<NuxtLink :to="`/form/${tpl.kategori}/${tpl.paket.toLowerCase()}`"
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="mt-12 text-center text-gray-500">
Belum ada template untuk kategori ini.
</div>
</div>
</main>
</div>
</template>
<script setup>
import { ref, watch, onMounted } 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
}
// Hardcode fitur per 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, index) => {
const paketKey = tpl.paket
? tpl.paket.charAt(0).toUpperCase() + tpl.paket.slice(1).toLowerCase()
: 'Starter'
return {
id: tpl.id,
nama: tpl.nama_template,
nama_template: tpl.nama_template,
harga: tpl.harga ?? 0,
foto: tpl.foto ?? '/default.jpg',
kategori: tpl.kategori.nama,
paket: paketKey,
fiturs: (fiturPerPaket[paketKey] || []).map((f, i) => ({
id: i + 1,
deskripsi: f
})),
slug: tpl.slug,
preview_link: tpl.preview_link ?? null
}
})
} 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>