- Refactor New Quote page to enterprise data-entry layout (2-column, dense) - Add Payment Terms dropdown (COD, NET 15, NET 30, NET 60) - Fix sidebar menu active states and route names - Fix brand filter badge visibility on brands page - Remove company_name references (use business instead) - Polish Promotions page layout - Fix double-click issue on sidebar menu collapse - Make all searches case-insensitive (like -> ilike for PostgreSQL)
204 lines
6.0 KiB
PHP
204 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Brand;
|
|
use App\Models\Product;
|
|
use App\Models\Strain;
|
|
use Illuminate\Http\Request;
|
|
|
|
class MarketplaceController extends Controller
|
|
{
|
|
/**
|
|
* Display marketplace browse page
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
// Start with active products only
|
|
$query = Product::query()
|
|
->with(['brand', 'strain'])
|
|
->active();
|
|
|
|
// Search filter (name, SKU, description)
|
|
if ($search = $request->input('search')) {
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'ilike', "%{$search}%")
|
|
->orWhere('sku', 'ilike', "%{$search}%")
|
|
->orWhere('description', 'ilike', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// Brand filter
|
|
if ($brandId = $request->input('brand_id')) {
|
|
$query->where('brand_id', $brandId);
|
|
}
|
|
|
|
// Strain type filter
|
|
if ($strainType = $request->input('strain_type')) {
|
|
$query->whereHas('strain', function ($q) use ($strainType) {
|
|
$q->where('type', $strainType);
|
|
});
|
|
}
|
|
|
|
// Price range filter
|
|
if ($priceMin = $request->input('price_min')) {
|
|
$query->where('wholesale_price', '>=', $priceMin);
|
|
}
|
|
if ($priceMax = $request->input('price_max')) {
|
|
$query->where('wholesale_price', '<=', $priceMax);
|
|
}
|
|
|
|
// In stock filter
|
|
if ($request->input('in_stock')) {
|
|
$query->where('quantity_on_hand', '>', 0);
|
|
}
|
|
|
|
// Sorting
|
|
$sort = $request->input('sort', 'newest');
|
|
match ($sort) {
|
|
'name_asc' => $query->orderBy('name', 'asc'),
|
|
'name_desc' => $query->orderBy('name', 'desc'),
|
|
'price_asc' => $query->orderBy('wholesale_price', 'asc'),
|
|
'price_desc' => $query->orderBy('wholesale_price', 'desc'),
|
|
'newest' => $query->latest(),
|
|
default => $query->latest(),
|
|
};
|
|
|
|
// Paginate results
|
|
$products = $query->paginate(12)->withQueryString();
|
|
|
|
// Get all active brands for filters
|
|
$brands = Brand::active()->orderBy('name')->get();
|
|
|
|
// Get featured products for carousel (exclude from main results if in first page)
|
|
$featuredProducts = Product::query()
|
|
->with(['brand', 'strain'])
|
|
->featured()
|
|
->inStock()
|
|
->limit(3)
|
|
->get();
|
|
|
|
$business = auth()->user()->businesses->first();
|
|
|
|
return view('buyer.marketplace.index', compact('products', 'brands', 'featuredProducts', 'business'));
|
|
}
|
|
|
|
/**
|
|
* Display all products (redirects to main browse page)
|
|
*/
|
|
public function products()
|
|
{
|
|
return redirect()->route('buyer.browse');
|
|
}
|
|
|
|
/**
|
|
* Display all brands directory
|
|
*/
|
|
public function brands()
|
|
{
|
|
$brands = Brand::query()
|
|
->active()
|
|
->withCount(['products' => function ($query) {
|
|
$query->active();
|
|
}])
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
$business = auth()->user()->businesses->first();
|
|
|
|
return view('buyer.marketplace.brands', compact('brands', 'business'));
|
|
}
|
|
|
|
/**
|
|
* Display products in specific category
|
|
*/
|
|
public function category($category)
|
|
{
|
|
$business = auth()->user()->businesses->first();
|
|
|
|
return view('buyer.marketplace.category', compact('category', 'business'));
|
|
}
|
|
|
|
/**
|
|
* Show individual product (nested under brand)
|
|
*/
|
|
public function showProduct($brandSlug, $productSlug)
|
|
{
|
|
// Find brand by slug
|
|
$brand = Brand::query()
|
|
->where('slug', $brandSlug)
|
|
->active()
|
|
->firstOrFail();
|
|
|
|
// Find product by slug within this brand
|
|
$product = Product::query()
|
|
->with([
|
|
'brand',
|
|
'strain',
|
|
'availableBatches' => function ($query) {
|
|
$query->with(['coaFiles'])
|
|
->orderBy('production_date', 'desc')
|
|
->orderBy('created_at', 'desc');
|
|
},
|
|
])
|
|
->where('brand_id', $brand->id)
|
|
->where(function ($query) use ($productSlug) {
|
|
$query->where('slug', $productSlug);
|
|
// Only try ID lookup if the value is numeric
|
|
if (is_numeric($productSlug)) {
|
|
$query->orWhere('id', $productSlug);
|
|
}
|
|
})
|
|
->active()
|
|
->firstOrFail();
|
|
|
|
// Get related products from same brand
|
|
$relatedProducts = Product::query()
|
|
->with(['brand', 'strain'])
|
|
->where('brand_id', $product->brand_id)
|
|
->where('id', '!=', $product->id)
|
|
->active()
|
|
->inStock()
|
|
->limit(4)
|
|
->get();
|
|
|
|
$business = auth()->user()->businesses->first();
|
|
|
|
return view('buyer.marketplace.product', compact('product', 'relatedProducts', 'brand', 'business'));
|
|
}
|
|
|
|
/**
|
|
* Show individual brand storefront
|
|
*/
|
|
public function showBrand($brandSlug)
|
|
{
|
|
// Find brand by slug
|
|
$brand = Brand::query()
|
|
->where('slug', $brandSlug)
|
|
->active()
|
|
->firstOrFail();
|
|
|
|
// Get featured products from this brand
|
|
$featuredProducts = Product::query()
|
|
->with(['strain'])
|
|
->where('brand_id', $brand->id)
|
|
->featured()
|
|
->inStock()
|
|
->limit(3)
|
|
->get();
|
|
|
|
// Get all products from this brand
|
|
$products = Product::query()
|
|
->with(['strain'])
|
|
->where('brand_id', $brand->id)
|
|
->active()
|
|
->orderBy('is_featured', 'desc')
|
|
->orderBy('name')
|
|
->paginate(20);
|
|
|
|
$business = auth()->user()->businesses->first();
|
|
|
|
return view('buyer.marketplace.brand', compact('brand', 'featuredProducts', 'products', 'business'));
|
|
}
|
|
}
|