132 lines
5.5 KiB
Vue
132 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
import { formatRupiah } from '@/lib/rupiah';
|
|
import type { Product } from '@/types/product';
|
|
|
|
defineProps<{
|
|
product: Product;
|
|
indexOffset?: number;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
quickView: [product: Product];
|
|
}>();
|
|
|
|
const getProductImage = (product: Product, indexOffset = 0) => {
|
|
for (const variant of product.variants) {
|
|
if (variant.images && variant.images.length > 0) {
|
|
return variant.images[0].url;
|
|
}
|
|
}
|
|
|
|
const firstCat = product.categories?.[0]?.slug || '';
|
|
const fallbacks: Record<string, string[]> = {
|
|
'daster': [
|
|
'https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'setelan-celana': [
|
|
'https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1618244972963-dbee1a7edc95?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'atasan': [
|
|
'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1532244769068-b223ede82d7b?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'bawahan': [
|
|
'https://images.unsplash.com/photo-1583496661160-fb4886b36ca7?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1551163943-3f6a855d1153?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'busui': [
|
|
'https://images.unsplash.com/photo-1509631179647-0177331693ae?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=800&auto=format&fit=crop&q=80'
|
|
]
|
|
};
|
|
|
|
const categoryUrls = fallbacks[firstCat] || [
|
|
'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=800&auto=format&fit=crop&q=80'
|
|
];
|
|
|
|
return categoryUrls[indexOffset % categoryUrls.length];
|
|
};
|
|
|
|
const getProductPriceRange = (product: Product) => {
|
|
const prices = product.variants.flatMap(v =>
|
|
v.prices.filter(p => p.type === 'retail').map(p => p.price)
|
|
);
|
|
|
|
if (prices.length === 0) {
|
|
const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price));
|
|
|
|
if (fallbackPrices.length === 0) {
|
|
return 'Rp 0';
|
|
}
|
|
|
|
const min = Math.min(...fallbackPrices);
|
|
const max = Math.max(...fallbackPrices);
|
|
|
|
return min === max ? `Rp ${formatRupiah(min)}` : `Rp ${formatRupiah(min)} - Rp ${formatRupiah(max)}`;
|
|
}
|
|
|
|
const min = Math.min(...prices);
|
|
const max = Math.max(...prices);
|
|
|
|
return min === max ? `Rp ${formatRupiah(min)}` : `Rp ${formatRupiah(min)} - Rp ${formatRupiah(max)}`;
|
|
};
|
|
|
|
const getTotalStock = (product: Product) => {
|
|
return product.variants.reduce((acc, variant) => acc + variant.stock, 0);
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="group relative flex flex-col space-y-4 cursor-pointer" @click="emit('quickView', product)">
|
|
<!-- Product Image Container -->
|
|
<div
|
|
class="relative aspect-3/4 w-full overflow-hidden rounded-2xl bg-amber-50/50 shadow-md transition-shadow hover">
|
|
<img :src="getProductImage(product, indexOffset ?? 0)" :alt="product.name"
|
|
class="h-full w-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105" />
|
|
<div
|
|
class="absolute inset-0 bg-linear-to-t from-amber-950/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
|
|
<!-- Status Badges -->
|
|
<div class="absolute top-4 left-4 flex flex-col space-y-2">
|
|
<span v-if="getTotalStock(product) === 0"
|
|
class="bg-red-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md">
|
|
Habis
|
|
</span>
|
|
<span v-else-if="getTotalStock(product) < 15"
|
|
class="bg-amber-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md animate-pulse">
|
|
Menipis
|
|
</span>
|
|
<span v-else
|
|
class="bg-amber-600 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md">
|
|
Terbaru
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Details Info -->
|
|
<div class="flex flex-col space-y-2">
|
|
<!-- Category Badges -->
|
|
<div class="flex flex-wrap gap-1.5">
|
|
<span v-for="cat in product.categories" :key="cat.id"
|
|
class="inline-block text-[9px] font-bold uppercase tracking-widest text-amber-700 bg-amber-100/60 px-2 py-0.5 rounded-full">
|
|
{{ cat.name }}
|
|
</span>
|
|
</div>
|
|
|
|
<h4 class="text-base font-semibold text-slate-700 line-clamp-1 group-hover transition-colors">
|
|
{{ product.name }}
|
|
</h4>
|
|
<p class="text-sm font-bold text-amber-600">
|
|
{{ getProductPriceRange(product) }}
|
|
</p>
|
|
<span class="text-[11px] text-slate-400 font-light mt-0.5">
|
|
Ukuran tersedia: {{product.variants.map(v => v.name).join(', ')}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|