lots of design improvements
This commit is contained in:
@@ -1,168 +1,210 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="mb-4">Product Management</h1>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Product Management</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Review and manage submitted products</p>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{{ session('success') }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
<!-- Status Filter -->
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 mb-6">
|
||||
<div class="px-6 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900 mb-4">Filter by Status</h2>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="{{ route('products.manage') }}"
|
||||
class="inline-flex items-center px-4 py-2 rounded-md text-sm font-medium {{ !request('status') || request('status') == 'pending' ? 'bg-yellow-100 text-yellow-800 ring-2 ring-yellow-600' : 'bg-white text-gray-700 ring-1 ring-gray-300 hover:bg-gray-50' }}">
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Pending
|
||||
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ !request('status') || request('status') == 'pending' ? 'bg-yellow-200 text-yellow-800' : 'bg-gray-200 text-gray-700' }}">
|
||||
{{ $statusCounts['pending'] }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Status Filter -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h5 class="mb-3">Filter by Status</h5>
|
||||
<a href="{{ route('products.manage') }}"
|
||||
class="btn btn-sm {{ !request('status') || request('status') == 'pending' ? 'btn-warning' : 'btn-outline-warning' }}">
|
||||
Pending ({{ $statusCounts['pending'] }})
|
||||
</a>
|
||||
<a href="{{ route('products.manage', ['status' => 'approved']) }}"
|
||||
class="btn btn-sm {{ request('status') == 'approved' ? 'btn-success' : 'btn-outline-success' }}">
|
||||
Approved ({{ $statusCounts['approved'] }})
|
||||
</a>
|
||||
<a href="{{ route('products.manage', ['status' => 'rejected']) }}"
|
||||
class="btn btn-sm {{ request('status') == 'rejected' ? 'btn-danger' : 'btn-outline-danger' }}">
|
||||
Rejected ({{ $statusCounts['rejected'] }})
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ route('products.manage', ['status' => 'approved']) }}"
|
||||
class="inline-flex items-center px-4 py-2 rounded-md text-sm font-medium {{ request('status') == 'approved' ? 'bg-green-100 text-green-800 ring-2 ring-green-600' : 'bg-white text-gray-700 ring-1 ring-gray-300 hover:bg-gray-50' }}">
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Approved
|
||||
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ request('status') == 'approved' ? 'bg-green-200 text-green-800' : 'bg-gray-200 text-gray-700' }}">
|
||||
{{ $statusCounts['approved'] }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Products Table -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@if($products->count() > 0)
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Thumbnail</th>
|
||||
<th>Title</th>
|
||||
<th>Category</th>
|
||||
<th>Submitted By</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($products as $product)
|
||||
<tr>
|
||||
<td>
|
||||
@if($product->thumbnail_url)
|
||||
<img src="{{ $product->thumbnail_url }}"
|
||||
alt="{{ $product->title }}"
|
||||
style="width: 60px; height: 60px; object-fit: cover;"
|
||||
class="rounded">
|
||||
@else
|
||||
<div class="bg-light d-flex align-items-center justify-content-center rounded"
|
||||
style="width: 60px; height: 60px;">
|
||||
<span class="text-muted small">No img</span>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('products.show', $product) }}" class="fw-bold text-decoration-none">
|
||||
{{ $product->title }}
|
||||
</a>
|
||||
<br>
|
||||
<small class="text-muted">{{ Str::limit($product->description, 60) }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ $product->category->name }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('profile.show', $product->user) }}">
|
||||
{{ $product->user->name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@if($product->isPending())
|
||||
<span class="badge bg-warning text-dark">Pending</span>
|
||||
@elseif($product->isApproved())
|
||||
<span class="badge bg-success">Approved</span>
|
||||
@elseif($product->isRejected())
|
||||
<span class="badge bg-danger">Rejected</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<small>{{ $product->created_at->format('M j, Y') }}</small>
|
||||
<br>
|
||||
<small class="text-muted">{{ $product->created_at->diffForHumans() }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
@if($product->isPending() || $product->isRejected())
|
||||
<form action="{{ route('products.approve', $product) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-success btn-sm" title="Approve">
|
||||
✓
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($product->isPending() || $product->isApproved())
|
||||
<form action="{{ route('products.reject', $product) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-sm"
|
||||
title="Reject"
|
||||
onclick="return confirm('Are you sure you want to reject this product?');">
|
||||
✗
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<a href="{{ route('products.show', $product) }}" class="btn btn-info btn-sm" title="View">
|
||||
👁
|
||||
</a>
|
||||
|
||||
@can('edit-product', $product)
|
||||
<a href="{{ route('products.edit', $product) }}" class="btn btn-primary btn-sm" title="Edit">
|
||||
✎
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
@can('delete-product', $product)
|
||||
<form action="{{ route('products.destroy', $product) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-sm"
|
||||
title="Delete"
|
||||
onclick="return confirm('Are you sure you want to delete this product permanently?');">
|
||||
🗑
|
||||
</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
{{ $products->appends(['status' => request('status')])->links() }}
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-info">
|
||||
No products found with status: {{ request('status', 'pending') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<a href="{{ route('products.manage', ['status' => 'rejected']) }}"
|
||||
class="inline-flex items-center px-4 py-2 rounded-md text-sm font-medium {{ request('status') == 'rejected' ? 'bg-red-100 text-red-800 ring-2 ring-red-600' : 'bg-white text-gray-700 ring-1 ring-gray-300 hover:bg-gray-50' }}">
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Rejected
|
||||
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ request('status') == 'rejected' ? 'bg-red-200 text-red-800' : 'bg-gray-200 text-gray-700' }}">
|
||||
{{ $statusCounts['rejected'] }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products List -->
|
||||
@if($products->count() > 0)
|
||||
<div class="space-y-4">
|
||||
@foreach($products as $product)
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-shadow">
|
||||
<div class="p-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Thumbnail -->
|
||||
<div class="flex-shrink-0">
|
||||
@if($product->thumbnail_url)
|
||||
<img src="{{ $product->thumbnail_url }}"
|
||||
alt="{{ $product->title }}"
|
||||
class="w-20 h-20 rounded-lg object-cover border border-gray-200">
|
||||
@else
|
||||
<div class="w-20 h-20 rounded-lg bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center border border-gray-200">
|
||||
<svg class="h-8 w-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<a href="{{ route('products.show', $product) }}" class="hover:text-primary-600">
|
||||
{{ $product->title }}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 line-clamp-2">{{ $product->description }}</p>
|
||||
|
||||
<div class="mt-3 flex flex-wrap items-center gap-4 text-sm">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md bg-primary-100 text-primary-800 font-medium">
|
||||
{{ $product->category->name }}
|
||||
</span>
|
||||
|
||||
<span class="inline-flex items-center text-gray-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
<a href="{{ route('profile.show', $product->user) }}" class="hover:text-primary-600">
|
||||
{{ $product->user->name }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span class="inline-flex items-center text-gray-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{{ $product->created_at->format('M j, Y') }}
|
||||
</span>
|
||||
|
||||
<span class="text-gray-400">•</span>
|
||||
<span class="text-gray-500">{{ $product->created_at->diffForHumans() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Badge -->
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
@if($product->isPending())
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-yellow-100 text-yellow-800">
|
||||
Pending
|
||||
</span>
|
||||
@elseif($product->isApproved())
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-green-100 text-green-800">
|
||||
Approved
|
||||
</span>
|
||||
@elseif($product->isRejected())
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-red-100 text-red-800">
|
||||
Rejected
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
@if($product->isPending() || $product->isRejected())
|
||||
<form action="{{ route('products.approve', $product) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Approve
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($product->isPending() || $product->isApproved())
|
||||
<form action="{{ route('products.reject', $product) }}" method="POST" onsubmit="return confirm('Are you sure you want to reject this product?');">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
Reject
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<a href="{{ route('products.show', $product) }}" class="inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||
</svg>
|
||||
View
|
||||
</a>
|
||||
|
||||
@can('edit-product', $product)
|
||||
<a href="{{ route('products.edit', $product) }}" class="inline-flex items-center px-3 py-1.5 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
Edit
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
@can('delete-product', $product)
|
||||
<form action="{{ route('products.destroy', $product) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this product permanently?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center px-3 py-1.5 border border-red-300 text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if($products->hasPages())
|
||||
<div class="mt-6">
|
||||
{{ $products->appends(['status' => request('status')])->links() }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="text-center py-12 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No products found</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
No products with status: <strong>{{ request('status', 'pending') }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user