<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Category;
use App\Models\Product;
use App\Models\ProductImage;
use App\Models\ProductVariation;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ProductController extends Controller
{
public function index(Request $request)
{
$query = Product::with('category')->latest();
if ($request->filled('search')) {
$query->where('name', 'like', '%' . $request->search . '%');
}
if ($request->filled('category')) {
$query->where('category_id', $request->category);
}
$products = $query->paginate(20)->withQueryString();
$categories = Category::where('status', true)->get();
return view('admin.products.index', compact('products', 'categories'));
}
public function create()
{
$categories = Category::where('status', true)->orderBy('name')->get();
$tags = Tag::orderBy('name')->get();
return view('admin.products.create', compact('categories', 'tags'));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'nullable|string|unique:products,slug',
'category_id' => 'nullable|exists:categories,id',
'short_description' => 'nullable|string',
'description' => 'nullable|string',
'regular_price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'offer_text' => 'nullable|string|max:255',
'shipping_inside_dhaka' => 'nullable|integer|min:0',
'shipping_outside_dhaka' => 'nullable|integer|min:0',
'stock' => 'required|integer|min:0',
'status' => 'boolean',
'featured' => 'boolean',
'thumbnail' => 'nullable|image|max:4096',
'images.*' => 'nullable|image|max:4096',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
$data['slug'] = $data['slug'] ?? Str::slug($data['name']);
$data['status'] = $request->boolean('status', true);
$data['featured'] = $request->boolean('featured', false);
if ($request->hasFile('thumbnail')) {
$data['thumbnail'] = $request->file('thumbnail')->store('products', 'public');
}
$product = Product::create($data);
if ($request->has('tags')) {
$product->tags()->sync($request->tags);
}
if ($request->hasFile('images')) {
foreach ($request->file('images') as $i => $img) {
$path = $img->store('products/gallery', 'public');
ProductImage::create(['product_id' => $product->id, 'image' => $path, 'sort_order' => $i]);
}
}
// Handle variations
if ($request->filled('variation_types')) {
$this->saveVariations($product, $request);
}
return redirect()->route('admin.products.index')->with('success', 'Product created successfully.');
}
public function edit(Product $product)
{
$product->load('images', 'variations', 'tags');
$categories = Category::where('status', true)->orderBy('name')->get();
$tags = Tag::orderBy('name')->get();
return view('admin.products.edit', compact('product', 'categories', 'tags'));
}
public function update(Request $request, Product $product)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'nullable|string|unique:products,slug,' . $product->id,
'category_id' => 'nullable|exists:categories,id',
'short_description' => 'nullable|string',
'description' => 'nullable|string',
'regular_price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'offer_text' => 'nullable|string|max:255',
'shipping_inside_dhaka' => 'nullable|integer|min:0',
'shipping_outside_dhaka' => 'nullable|integer|min:0',
'stock' => 'required|integer|min:0',
'status' => 'boolean',
'featured' => 'boolean',
'thumbnail' => 'nullable|image|max:4096',
'images.*' => 'nullable|image|max:4096',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
$data['slug'] = $data['slug'] ?? Str::slug($data['name']);
$data['status'] = $request->boolean('status', true);
$data['featured'] = $request->boolean('featured', false);
if ($request->hasFile('thumbnail')) {
$data['thumbnail'] = $request->file('thumbnail')->store('products', 'public');
}
$product->update($data);
$product->tags()->sync($request->tags ?? []);
if ($request->hasFile('images')) {
foreach ($request->file('images') as $i => $img) {
$path = $img->store('products/gallery', 'public');
ProductImage::create([
'product_id' => $product->id,
'image' => $path,
'sort_order' => $product->images()->max('sort_order') + $i + 1,
]);
}
}
if ($request->filled('variation_types')) {
$this->saveVariations($product, $request);
}
return redirect()->route('admin.products.edit', $product)->with('success', 'Product updated successfully.');
}
public function destroy(Product $product)
{
$product->delete();
return redirect()->route('admin.products.index')->with('success', 'Product deleted.');
}
public function deleteImage(ProductImage $image)
{
$image->delete();
return back()->with('success', 'Image removed.');
}
public function deleteVariation(ProductVariation $variation)
{
$variation->delete();
return back()->with('success', 'Variation removed.');
}
private function saveVariations(Product $product, Request $request): void
{
$types = $request->input('variation_types', []);
$values = $request->input('variation_values', []);
$prices = $request->input('variation_prices', []);
$stocks = $request->input('variation_stocks', []);
foreach ($types as $i => $type) {
if (empty($type) || empty($values[$i])) {
continue;
}
ProductVariation::create([
'product_id' => $product->id,
'type' => $type,
'value' => $values[$i],
'price_adjustment' => $prices[$i] ?? 0,
'stock' => $stocks[$i] ?? 0,
]);
}
}
}