diff --git a/resources/js/components/card/ProductCard.vue b/resources/js/components/card/ProductCard.vue new file mode 100644 index 0000000..8aaf6f6 --- /dev/null +++ b/resources/js/components/card/ProductCard.vue @@ -0,0 +1,131 @@ + + + diff --git a/resources/js/components/modal/ProductModal.vue b/resources/js/components/modal/ProductModal.vue new file mode 100644 index 0000000..a5abdb3 --- /dev/null +++ b/resources/js/components/modal/ProductModal.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/resources/js/components/ui/carousel/DraggableCarousel.vue b/resources/js/components/ui/carousel/DraggableCarousel.vue new file mode 100644 index 0000000..b5e2d63 --- /dev/null +++ b/resources/js/components/ui/carousel/DraggableCarousel.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/resources/js/pages/Home.vue b/resources/js/pages/Home.vue index d3e6f3c..83e11f3 100644 --- a/resources/js/pages/Home.vue +++ b/resources/js/pages/Home.vue @@ -11,68 +11,18 @@ import { Check, Info, ChevronRight, - Eye, Phone, MapPin, Mail, - Filter, LogIn } from '@lucide/vue'; import { ref, computed } from 'vue'; -import { CatalogSortOption } from '@/constants/catalog-sort-option'; -import RupiahInput from '@/components/form/rupiah-input/RupiahInput.vue'; -import Select from '@/components/ui/select/Select.vue'; -import SelectContent from '@/components/ui/select/SelectContent.vue'; -import SelectItem from '@/components/ui/select/SelectItem.vue'; -import SelectTrigger from '@/components/ui/select/SelectTrigger.vue'; -import SelectValue from '@/components/ui/select/SelectValue.vue'; - -interface Price { - id: number; - variant_id: number; - type: string; - price: number; - price_formatted: string; - type_label: string; -} - -interface MediaItem { - id: number; - url: string; - thumb_url: string; -} - -interface Variant { - id: number; - product_id: number; - name: string; - stock: number; - prices: Price[]; - images: MediaItem[]; -} - -interface Category { - id: number; - name: string; - slug: string; -} - -interface Product { - id: number; - name: string; - slug: string; - description: string | null; - is_active: boolean; - categories: Category[]; - variants: Variant[]; -} - -interface MediaItem { - id: number; - url: string; - thumb_url: string; -} - +import ProductCard from '@/components/card/ProductCard.vue'; +import ProductModal from '@/components/modal/ProductModal.vue'; +import DraggableCarousel from '@/components/ui/carousel/DraggableCarousel.vue'; +import { formatRupiah } from '@/lib/rupiah'; +import admin from '@/routes/admin'; +import type { Product, Variant, Price, Category, MediaItem } from '@/types/product'; interface HomepageData { hero_image_url: string | null; about_image_url: string | null; @@ -97,10 +47,6 @@ const props = defineProps<{ // Search & Catalog Filter State const searchInput = ref(''); const selectedCategory = ref(null); -const minPrice = ref(null); -const maxPrice = ref(null); -const sortBy = ref(CatalogSortOption.DEFAULT); -const showFilters = ref(false); // Hardcoded order steps const orderSteps = [ @@ -122,21 +68,6 @@ const toggleCategory = (slug: string) => { selectedCategory.value = selectedCategory.value === slug ? null : slug; }; -// Helper to get ecer price of a product for filtering/sorting -const getPrimaryEcerPrice = (product: Product): number => { - const ecerPrices = product.variants.flatMap(v => - v.prices.filter(p => p.type === 'ecer').map(p => p.price) - ); - - if (ecerPrices.length > 0) { - return Math.min(...ecerPrices); - } - - const allPrices = product.variants.flatMap(v => v.prices.map(p => p.price)); - - return allPrices.length > 0 ? Math.min(...allPrices) : 0; -}; - // Filtered and Sorted Products const filteredProducts = computed(() => { const result = props.products.filter(product => { @@ -148,23 +79,9 @@ const filteredProducts = computed(() => { const matchesCategory = !selectedCategory.value || product.categories.some(cat => cat.slug === selectedCategory.value); - // Price filter - const price = getPrimaryEcerPrice(product); - const matchesMinPrice = minPrice.value === null || price >= minPrice.value; - const matchesMaxPrice = maxPrice.value === null || price <= maxPrice.value; - - return matchesSearch && matchesCategory && matchesMinPrice && matchesMaxPrice; + return matchesSearch && matchesCategory; }); - // Sorting - if (sortBy.value === CatalogSortOption.PRICE_ASC) { - result.sort((a, b) => getPrimaryEcerPrice(a) - getPrimaryEcerPrice(b)); - } else if (sortBy.value === CatalogSortOption.PRICE_DESC) { - result.sort((a, b) => getPrimaryEcerPrice(b) - getPrimaryEcerPrice(a)); - } else if (sortBy.value === CatalogSortOption.NAME_ASC) { - result.sort((a, b) => a.name.localeCompare(b.name)); - } - return result; }); @@ -209,11 +126,6 @@ const getProductImage = (product: Product, indexOffset = 0) => { return categoryUrls[indexOffset % categoryUrls.length]; }; -// Price Formatting (Indonesian Rupiah) -const formatPrice = (price: number) => { - return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(price); -}; - // Price Range Resolver const getProductPriceRange = (product: Product) => { const prices = product.variants.flatMap(v => @@ -224,23 +136,19 @@ const getProductPriceRange = (product: Product) => { const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price)); if (fallbackPrices.length === 0) { -return 'Rp 0'; -} + return 'Rp 0'; + } const min = Math.min(...fallbackPrices); const max = Math.max(...fallbackPrices); - return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`; + 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 ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`; -}; - -const getTotalStock = (product: Product) => { - return product.variants.reduce((acc, variant) => acc + variant.stock, 0); + return min === max ? `Rp ${formatRupiah(min)}` : `Rp ${formatRupiah(min)} - Rp ${formatRupiah(max)}`; }; const featuredProducts = computed(() => { @@ -304,35 +212,22 @@ const cartCount = computed(() => { // Centered Modal State const activeProduct = ref(null); -const activeVariant = ref(null); -const activePriceType = ref('ecer'); const openQuickView = (product: Product) => { activeProduct.value = product; - activeVariant.value = product.variants[0] || null; - activePriceType.value = 'ecer'; }; const closeQuickView = () => { activeProduct.value = null; - activeVariant.value = null; }; -const selectedPrice = computed(() => { - if (!activeVariant.value) { -return null; -} - - return activeVariant.value.prices.find(p => p.type === activePriceType.value) || activeVariant.value.prices[0] || null; -}); - // Checkout Mock / WhatsApp redirect const isCheckingOut = ref(false); const handleWhatsAppCheckout = () => { if (cart.value.length === 0) { -return; -} + return; + } isCheckingOut.value = true; @@ -343,9 +238,9 @@ return; message += ` - Varian/Ukuran: ${item.variant.name}\n`; message += ` - Jenis Harga: ${item.price.type_label}\n`; message += ` - Jumlah: ${item.quantity} pcs\n`; - message += ` - Subtotal: ${formatPrice(item.price.price * item.quantity)}\n\n`; + message += ` - Subtotal: Rp ${formatRupiah(item.price.price * item.quantity)}\n\n`; }); - message += `*Total Pembayaran: ${formatPrice(cartTotal.value)}*\n\n`; + message += `*Total Pembayaran: Rp ${formatRupiah(cartTotal.value)}*\n\n`; message += `Mohon info untuk detail rekening transfer dan pengirimannya. Terima kasih!`; const formattedPhone = props.contactPhone.replace(/[^0-9]/g, ''); @@ -358,22 +253,17 @@ return; }, 800); }; -const resetPriceFilters = () => { - minPrice.value = null; - maxPrice.value = null; - sortBy.value = CatalogSortOption.DEFAULT; -}; - -