Undangan/proyek-frontend/app/components/template-page/CategorySelection.vue
2025-10-14 11:37:54 +07:00

278 lines
9.5 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">
<!-- Back button -->
<div class="mb-8">
<NuxtLink to="/" 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 ke Beranda
</NuxtLink>
</div>
<!-- Header -->
<h1 class="text-3xl md:text-4xl font-bold text-center text-gray-800">
Pilih Kategori Favoritmu
</h1>
<p class="mt-2 text-center text-gray-500">
Tersedia berbagai desain undangan pernikahan, khitan, ulang tahun, dan lainnya.
</p>
<!-- Loading / Error kategori -->
<div v-if="isLoading" class="mt-12 text-center">Memuat kategori...</div>
<div v-else-if="error" class="mt-12 text-center text-red-500">
Gagal memuat kategori.
</div>
<!-- Kategori Grid -->
<div v-else-if="categories.length > 0" class="mt-12 flex flex-wrap justify-center gap-6">
<div v-for="category in categories" :key="category.id + '-' + category.foto"
@click="onCategoryClick(category)"
class="group cursor-pointer relative overflow-hidden rounded-lg shadow-lg hover:shadow-2xl transition-all duration-300 w-72">
<img v-if="category.foto" :src="`http://localhost:8000${category.foto}`" :alt="category.nama"
class="w-full h-96 object-cover transition-transform duration-300 group-hover:scale-110">
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/40 to-transparent"></div>
<div class="absolute inset-0 flex flex-col justify-center items-start px-4 text-white">
<h3 class="text-xl font-semibold mb-2">
{{ category.nama }}
</h3>
<p class="text-lg font-normal leading-snug whitespace-normal break-words max-w-[90%]">
{{ category.deskripsi }}
</p>
</div>
</div>
</div>
<div v-else class="mt-12 text-center text-gray-500">
Belum ada kategori.
</div>
<!-- Header Templates -->
<div class="mt-20 text-center">
<h2 class="text-2xl md:text-3xl font-bold text-gray-800">
Semua Template yang Ada
</h2>
<p class="mt-2 text-gray-500">
Pilih template terbaik sesuai kebutuhan undanganmu.
</p>
</div>
<!-- Semua template (paket & fitur hardcode per kategori) -->
<div v-if="!isLoadingTemplates" class="mt-12">
<div v-if="templatesWithFeatures.length === 0" class="text-center text-gray-500">
Belum ada template tersedia.
</div>
<div v-else class="grid gap-8 max-w-[1100px] mx-auto grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 items-start">
<div v-for="t in templatesWithFeatures" :key="t.id"
class="bg-white border rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
<!-- Image -->
<img
:src="t.foto ? (t.foto.startsWith('http') ? t.foto : `http://localhost:8000${t.foto}`) : '/default.jpg'"
:alt="t.nama_template" 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">{{ t.nama }}</h4>
<p class="text-green-600 font-semibold text-xl mb-1">
Rp {{ Number(t.harga).toLocaleString('id-ID') }}
</p>
<p class="text-gray-500 mb-4 font-medium">Paket: {{ t.paket }}</p>
<!-- Dropdown fitur -->
<div v-if="t.fiturs && t.fiturs.length > 0" class="relative mb-4">
<button @click="toggleDropdown(t.id)"
class="w-full bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 inline-flex justify-between items-start">
<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 === t.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 t.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/${t.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/${t.formPath}`"
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>
<!-- END Templates -->
</div>
</main>
<!-- Footer -->
<LandingPageFooter class="w-full" />
</div>
</template>
<script setup>
import { ref, computed, onMounted, onActivated } from 'vue'
const emit = defineEmits(['category-selected', 'template-selected'])
const categories = ref([])
const isLoading = ref(true)
const error = ref(null)
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',
}
// state dropdown fitur
const openDropdownId = ref(null)
const toggleDropdown = (templateId) => {
openDropdownId.value = openDropdownId.value === templateId ? null : templateId
}
// Paket & fitur hardcode
const paketData = [
{
paket: 'Starter',
fiturs: [
'1x Acara',
'Masa Aktif 3 Bulan',
'Nama Tamu Personal',
'Maks. 100 Tamu',
'Request Musik'
]
},
{
paket: 'Basic',
fiturs: [
'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'
]
},
{
paket: 'Premium',
fiturs: [
'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 kategori
const fetchCategories = async () => {
isLoading.value = true
error.value = null
try {
const res = await $fetch('http://localhost:8000/api/kategoris')
categories.value = res
} catch (err) {
console.error(err)
error.value = 'Gagal memuat kategori.'
} finally {
isLoading.value = false
}
}
// fetch semua template
const templatesRaw = ref([])
const isLoadingTemplates = ref(true)
const fetchTemplates = async () => {
isLoadingTemplates.value = true
try {
const res = await $fetch('http://localhost:8000/api/templates')
templatesRaw.value = res
} catch (err) {
console.error('Gagal fetch templates', err)
templatesRaw.value = []
} finally {
isLoadingTemplates.value = false
}
}
// mapping template dengan paket & fitur hardcode
const templatesWithFeatures = computed(() =>
(templatesRaw.value || []).map((t, index) => ({
id: t.id,
nama: t.nama_template,
harga: t.harga,
foto: t.foto || '/logo1.png',
kategori: t.kategori,
paket: paketData[index % paketData.length].paket,
fiturs: paketData[index % paketData.length].fiturs.map((f, i) => ({ id: i + 1, deskripsi: f })),
formPath: t.slug
}))
)
onMounted(() => {
fetchCategories()
fetchTemplates()
})
onActivated(() => {
fetchCategories()
fetchTemplates()
})
const onCategoryClick = (category) => {
emit('category-selected', category)
}
const onTemplateClick = (template) => {
emit('template-selected', template)
}
</script>