From 470fbd4b40b989d85f9aecb081fc9cedb0e73809 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 23 Apr 2026 23:30:21 +0700 Subject: [PATCH] feat: integrate dynamic categories and product data into homepage with retail price attribute support --- app/Http/Controllers/HomepageController.php | 9 +- app/Models/Product.php | 10 +- resources/js/pages/homepage.tsx | 145 ++++++++++++++++---- 3 files changed, 139 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/HomepageController.php b/app/Http/Controllers/HomepageController.php index a7be8d9..94e6a39 100644 --- a/app/Http/Controllers/HomepageController.php +++ b/app/Http/Controllers/HomepageController.php @@ -2,10 +2,17 @@ namespace App\Http\Controllers; +use App\Models\Category; +use App\Models\Product; + class HomepageController extends Controller { public function __invoke() { - return inertia('homepage'); + return inertia('homepage', [ + 'categories' => Category::all(), + 'bestSellers' => Product::with(['categories', 'prices'])->active()->latest()->take(4)->get(), + 'topSellingProducts' => Product::with(['categories', 'prices'])->withCount('orderItems')->active()->orderBy('order_items_count', 'desc')->take(3)->get(), + ]); } } diff --git a/app/Models/Product.php b/app/Models/Product.php index e1db688..c905f5c 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\PriceType; use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Scope; @@ -21,11 +22,18 @@ use Spatie\Sluggable\SlugOptions; #[Guarded(['id'])] -#[Appends(['thumbnail_url', 'images_data'])] +#[Appends(['thumbnail_url', 'images_data', 'retail_price'])] class Product extends Model implements HasMedia { use HasFactory, HasSlug, InteractsWithMedia, LogsActivity, SoftDeletes; + protected function retailPrice(): Attribute + { + return Attribute::make( + get: fn () => $this->prices->where('price_type', PriceType::RETAIL)->first()?->price ?: 0, + ); + } + protected function casts(): array { return [ diff --git a/resources/js/pages/homepage.tsx b/resources/js/pages/homepage.tsx index 1b53b2b..8088293 100644 --- a/resources/js/pages/homepage.tsx +++ b/resources/js/pages/homepage.tsx @@ -1,10 +1,12 @@ import { Link } from '@inertiajs/react'; import { ArrowRight, Facebook, Instagram, Twitter, X, Youtube } from 'lucide-react'; import { useEffect, useState } from 'react'; +import { formatCurrency } from '@/lib/formatters'; -export default function Homepage() { +export default function Homepage({ categories = [], bestSellers = [], topSellingProducts = [] }: { categories: any[], bestSellers: any[], topSellingProducts: any[] }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); + const [selectedProduct, setSelectedProduct] = useState(null); useEffect(() => { const handleScroll = () => { @@ -53,6 +55,16 @@ export default function Homepage() { scroll-behavior: smooth; scroll-padding-top: 54px; } + .custom-scrollbar::-webkit-scrollbar { + width: 2px; + } + .custom-scrollbar::-webkit-scrollbar-track { + background: transparent; + } + .custom-scrollbar::-webkit-scrollbar-thumb { + background: #1e4a6d; + border-radius: 10px; + } `}
{/* Navbar */} @@ -90,13 +102,16 @@ export default function Homepage() { {/* Categories List */}
-
- {['Kenyamanan Terpilih', 'Kemewahan Harian', 'Gaya Berkelanjutan', 'Seri Oxford', 'Seri Flanel'].map((cat) => ( - ))} + {categories.length === 0 && ( +

Belum ada kategori

+ )}
{/* Diagonal decorative lines like in the image */}
@@ -137,15 +152,26 @@ export default function Homepage() {
- {[ - 'https://images.unsplash.com/photo-1718871716580-117417d490f6?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MzJ8fGRyZXNzfGVufDB8fDB8fHww', - 'https://plus.unsplash.com/premium_photo-1676234842565-bc1df0bfd45a?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDh8fHxlbnwwfHx8fHw%3D', - 'https://images.unsplash.com/photo-1718871729636-887b3d2359ae?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDE0fHx8ZW58MHx8fHx8' - ].map((url, i) => ( -
- Shirt Preview + {topSellingProducts.map((product, i) => ( +
setSelectedProduct(product)} + > + {product.name}
))} + {topSellingProducts.length === 0 && ( +
+ {[1,2,3].map(i => ( +
+ ))} +
+ )}
@@ -168,28 +194,38 @@ export default function Homepage() {
- {[ - { name: 'Kenyamanan Terpilih', price: 'Rp 450.000', img: 'https://plus.unsplash.com/premium_photo-1661432689064-89feeaa63025?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDI0fHx8ZW58MHx8fHx8' }, - { name: 'Kemewahan Harian', price: 'Rp 520.000', img: 'https://images.unsplash.com/photo-1718871828963-450573c7b76f?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDI3fHx8ZW58MHx8fHx8' }, - { name: 'Seri Oxford', price: 'Rp 425.000', img: 'https://images.unsplash.com/photo-1583333001978-8c57d752ce5b?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDM0fHx8ZW58MHx8fHx8' }, - { name: 'Gaya Berkelanjutan', price: 'Rp 380.000', img: 'https://images.unsplash.com/photo-1594179056536-dc8714fc8fdf?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDU3fHx8ZW58MHx8fHx8' } - ].map((item, idx) => ( -
-
+ {bestSellers.map((item, idx) => ( +
+
setSelectedProduct(item)} + > {item.name}
+
+ {item.categories?.map((c: any) => ( + + {c.name} + + ))} +

- {item.name} + {item.name}

-

{item.price}

+

{formatCurrency(item.retail_price)}

))} + {bestSellers.length === 0 && ( +
+ Produk belum tersedia +
+ )}
@@ -331,6 +367,69 @@ export default function Homepage() {
)} + + {/* Product Detail Modal */} +
+
setSelectedProduct(null)}>
+
+ + +
+ {/* Product Images */} +
+
+ {selectedProduct?.name} +
+ {/* Image Gallery if exists */} + {selectedProduct?.images_data?.length > 0 && ( +
+ {selectedProduct.images_data.map((img: any, i: number) => ( +
+ detail +
+ ))} +
+ )} +
+ + {/* Product Info */} +
+
+ {selectedProduct?.categories?.map((c: any) => ( + + {c.name} + + ))} +
+

{selectedProduct?.name}

+

{formatCurrency(selectedProduct?.retail_price || 0)}

+ +
+

Deskripsi

+

+ {selectedProduct?.description || 'Tidak ada deskripsi untuk produk ini.'} +

+
+ +
+ +
+
+
+
+
);