diff --git a/app/Http/Controllers/CategoryManagementController.php b/app/Http/Controllers/CategoryManagementController.php new file mode 100644 index 0000000..d048818 --- /dev/null +++ b/app/Http/Controllers/CategoryManagementController.php @@ -0,0 +1,104 @@ +filled('search')) { + $search = $request->search; + $query->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('description', 'like', "%{$search}%"); + }); + } + + $categories = $query->latest()->paginate(20); + + return view('categories.index', compact('categories')); + } + + /** + * Show the form for creating a new category. + */ + public function create() + { + return view('categories.create'); + } + + /** + * Store a newly created category. + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255', 'unique:categories,name'], + 'description' => ['nullable', 'string', 'max:1000'], + ]); + + // Generate unique slug + $validated['slug'] = Category::generateUniqueSlug($validated['name']); + + Category::create($validated); + + return redirect()->route('categories.index') + ->with('success', 'Category created successfully.'); + } + + /** + * Show the form for editing a category. + */ + public function edit(Category $category) + { + return view('categories.edit', compact('category')); + } + + /** + * Update the specified category. + */ + public function update(Request $request, Category $category) + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255', Rule::unique('categories')->ignore($category->id)], + 'description' => ['nullable', 'string', 'max:1000'], + ]); + + // Regenerate slug if name changed + if ($category->name !== $validated['name']) { + $validated['slug'] = Category::generateUniqueSlug($validated['name'], $category->id); + } + + $category->update($validated); + + return redirect()->route('categories.index') + ->with('success', 'Category updated successfully.'); + } + + /** + * Remove the specified category. + */ + public function destroy(Category $category) + { + // Check if category has products + if ($category->products()->count() > 0) { + return redirect()->route('categories.index') + ->with('error', "Cannot delete category '{$category->name}' because it has {$category->products()->count()} products. Please reassign or delete those products first."); + } + + $category->delete(); + + return redirect()->route('categories.index') + ->with('success', 'Category deleted successfully.'); + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php index 0bbfea0..157b80e 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Str; class Category extends Model { @@ -25,4 +26,39 @@ class Category extends Model { return $this->hasMany(Product::class); } + + /** + * Generate a unique slug for the category based on its name. + */ + public static function generateUniqueSlug(string $name, ?int $excludeId = null): string + { + $slug = Str::slug($name); + $originalSlug = $slug; + $count = 1; + + // Keep appending numbers until we find a unique slug + $query = static::where('slug', $slug); + if ($excludeId) { + $query->where('id', '!=', $excludeId); + } + + while ($query->exists()) { + $slug = $originalSlug . '-' . $count; + $count++; + $query = static::where('slug', $slug); + if ($excludeId) { + $query->where('id', '!=', $excludeId); + } + } + + return $slug; + } + + /** + * Get the route key for the model. + */ + public function getRouteKeyName(): string + { + return 'slug'; + } } diff --git a/resources/views/categories/create.blade.php b/resources/views/categories/create.blade.php new file mode 100644 index 0000000..a210afd --- /dev/null +++ b/resources/views/categories/create.blade.php @@ -0,0 +1,91 @@ +@extends('layouts.app') + +@section('content') +
+
+ +
+
+
+

Create Category

+

Add a new product category

+
+ + Back to Categories + +
+
+ + +
+ @csrf + +
+
+ +
+ + + @error('name') +

{{ $message }}

+ @enderror +

+ A unique, descriptive name for the category +

+
+ + +
+ + + @error('description') +

{{ $message }}

+ @enderror +

+ Help users understand what this category is for +

+
+
+
+ + +
+ + Cancel + + +
+
+
+
+@endsection diff --git a/resources/views/categories/edit.blade.php b/resources/views/categories/edit.blade.php new file mode 100644 index 0000000..4cd5015 --- /dev/null +++ b/resources/views/categories/edit.blade.php @@ -0,0 +1,120 @@ +@extends('layouts.app') + +@section('content') +
+
+ +
+
+
+

Edit Category

+

Update category information

+
+ + Back to Categories + +
+
+ + +
+ @csrf + @method('PUT') + +
+
+ +
+ + + @error('name') +

{{ $message }}

+ @enderror +

+ A unique, descriptive name for the category +

+
+ + +
+
+ + + +
+

+ Current slug: {{ $category->slug }} +

+

+ The slug will be automatically regenerated if you change the category name. +

+
+
+
+ + +
+ + + @error('description') +

{{ $message }}

+ @enderror +

+ Help users understand what this category is for +

+
+ + + @if($category->products()->count() > 0) +
+

Category Usage

+

+ This category currently has {{ $category->products()->count() }} + {{ Str::plural('product', $category->products()->count()) }} associated with it. +

+
+ @endif +
+
+ + +
+ + Cancel + + +
+
+
+
+@endsection diff --git a/resources/views/categories/index.blade.php b/resources/views/categories/index.blade.php new file mode 100644 index 0000000..dbc905f --- /dev/null +++ b/resources/views/categories/index.blade.php @@ -0,0 +1,175 @@ +@extends('layouts.app') + +@section('content') +
+
+ +
+
+
+

Category Management

+

Manage all product categories

+
+ + + + + New Category + +
+
+ + + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + + +
+
+
+ +
+
+ + @if(request('search')) + + Clear + + @endif +
+
+
+ + +
+
+ + + + + + + + + + + + @forelse($categories as $category) + + + + + + + + @empty + + + + @endforelse + +
+ Category + + Slug + + Products + + Created + + Actions +
+
+ {{ $category->name }} +
+ @if($category->description) +
+ {{ $category->description }} +
+ @endif +
+ + {{ $category->slug }} + + + + {{ $category->products_count }} {{ Str::plural('product', $category->products_count) }} + + + {{ $category->created_at->format('M d, Y') }} + +
+ + Edit + +
+ @csrf + @method('DELETE') + +
+
+
+
+ + + +

No categories found

+ + Create your first category + +
+
+
+ + + @if($categories->hasPages()) +
+ {{ $categories->links() }} +
+ @endif +
+
+
+@endsection diff --git a/resources/views/components/layout/navbar.blade.php b/resources/views/components/layout/navbar.blade.php index 89d451a..68b9769 100644 --- a/resources/views/components/layout/navbar.blade.php +++ b/resources/views/components/layout/navbar.blade.php @@ -137,7 +137,22 @@ - Manage products +
+ + + + Manage Products +
+
+ +
+ + + + Manage Categories +
@endif @@ -198,8 +213,12 @@ @endif @if(auth()->user()->hasAnyRole(['admin', 'moderator'])) - Manage + class="block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('products.manage') ? 'border-primary-500 text-primary-700 bg-primary-50 dark:bg-primary-900 dark:text-primary-200' : 'border-transparent text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:text-gray-700 dark:hover:text-gray-300' }}"> + Manage Products + + + Manage Categories @endif @endauth diff --git a/routes/web.php b/routes/web.php index 96838d6..fee3efe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Admin\UserManagementController; use App\Http\Controllers\AdminController; use App\Http\Controllers\BookmarkController; +use App\Http\Controllers\CategoryManagementController; use App\Http\Controllers\CommentController; use App\Http\Controllers\ProductController; use App\Http\Controllers\ProfileController; @@ -58,6 +59,9 @@ Route::middleware(['auth', 'moderator'])->group(function () { Route::get('/products-manage', [ProductController::class, 'manage'])->name('products.manage'); Route::post('/products/{product}/approve', [ProductController::class, 'approve'])->name('products.approve'); Route::post('/products/{product}/reject', [ProductController::class, 'reject'])->name('products.reject'); + + // Category Management Routes (Moderators & Admins) + Route::resource('categories', CategoryManagementController::class); }); // Admin Routes