feature(manage-reported-deals): admin can approve or reject reports and remove content

This commit is contained in:
kusowl 2026-01-28 16:42:59 +05:30
parent c087126080
commit 298d73791f
8 changed files with 191 additions and 7 deletions

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Enums\ReportStatus;
use App\Http\Controllers\Controller;
use App\Models\Report;
use Illuminate\Support\Facades\DB;
class ReportController extends Controller
{
public function index()
{
$reports = Report::query()
->with(['user:id,name', 'deals:id,title'])
->tap(fn ($q) => (new Report)->orderByStatus($q,
[ReportStatus::Pending, ReportStatus::Rejected, ReportStatus::Resolved]))
->get();
return view('dashboards.admin.reports.index')
->with('reports', $reports);
}
public function resolve(Report $report)
{
try {
$report->status = ReportStatus::Resolved;
$report->save();
return back()->with('success', 'Report resolved successfully.');
} catch (\Throwable $e) {
\Log::error('Error resolving report', [$report->id, $e->getMessage(), $e->getTraceAsString()]);
return back()->with('error', 'Something went wrong.');
}
}
public function reject(Report $report)
{
try {
$report->status = ReportStatus::Rejected;
$report->save();
return back()->with('success', 'Report Rejected successfully.');
} catch (\Throwable $e) {
\Log::error('Error rejecting report', [$report->id, $e->getMessage(), $e->getTraceAsString()]);
return back()->with('error', 'Something went wrong.');
}
}
/**
* Make the attached deal inactive and mark the report resolved.
*/
public function removeContent(Report $report)
{
try {
DB::transaction(function () use ($report) {
$deal = $report->deals()->first();
$deal->active = false;
$report->status = ReportStatus::Resolved;
$deal->save();
$report->save();
});
return back()->with('error', 'Deal has been deactivated.');
} catch (\Throwable $e) {
\Log::error('Error removing content of report', [$report->id, $e->getMessage(), $e->getTraceAsString()]);
return back()->with('error', 'Something went wrong.');
}
}
}

View File

@ -4,6 +4,8 @@
use App\Enums\ReportStatus; use App\Enums\ReportStatus;
use App\Enums\ReportType; use App\Enums\ReportType;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -27,4 +29,15 @@ protected function casts(): array
'status' => ReportStatus::class, 'status' => ReportStatus::class,
]; ];
} }
#[Scope]
public function orderByStatus(Builder $query, array $statusOrder): Builder
{
$values = array_map(fn ($enum) => $enum->value, $statusOrder);
// Create placeholders for each value: FIELD(status, ?, ?, ?)
$placeholders = implode(',', array_fill(0, count($values), '?'));
return $query->orderByRaw("FIELD(status, $placeholders)", $values);
}
} }

View File

@ -33,8 +33,8 @@ class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full overflow
<p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Brokers</p> <p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Brokers</p>
</x-dashboard.broker.sidebar.item> </x-dashboard.broker.sidebar.item>
<x-dashboard.broker.sidebar.item :link="route('broker.deals.index')"> <x-dashboard.broker.sidebar.item :link="route('admin.reports.index')">
<x-heroicon-o-document-text class="w-5 min-w-5"/> <x-heroicon-o-exclamation-triangle class="w-5 min-w-5"/>
<p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Reports</p> <p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Reports</p>
</x-dashboard.broker.sidebar.item> </x-dashboard.broker.sidebar.item>
</div> </div>

View File

@ -1,5 +1,5 @@
@props(['deal_id' => '', 'deal_title' => '', 'isLiked' => false, 'isFavourite' => false, 'isInteractive' => true]) @props(['deal_id' => '', 'deal_title' => '', 'isLiked' => false, 'isFavourite' => false, 'isInteractive' => true])
<div class="action-toolbar" data-deal-title="{{$deal_title}}"> <div class="action-toolbar flex items-center" data-deal-title="{{$deal_title}}">
<x-dashboard.user.action-toolbar.like-button :is-liked="$isLiked" :is-interactive="$isInteractive" /> <x-dashboard.user.action-toolbar.like-button :is-liked="$isLiked" :is-interactive="$isInteractive" />
<x-dashboard.user.action-toolbar.favorite-button :is-favourite="$isFavourite" :is-interactive="$isInteractive" /> <x-dashboard.user.action-toolbar.favorite-button :is-favourite="$isFavourite" :is-interactive="$isInteractive" />
<x-dashboard.user.action-toolbar.report-button :id="$deal_id" :title="$deal_title" /> <x-dashboard.user.action-toolbar.report-button :id="$deal_id" :title="$deal_title" />

View File

@ -16,8 +16,8 @@ class="deal-image h-full w-full object-cover rounded-lg border-none">
<x-ui.button-sm data-is-loading="true" class="w-fit deal-category data-[is-loading=true]:h-6 data-[is-loading=true]:w-15" variant="neutral"/> <x-ui.button-sm data-is-loading="true" class="w-fit deal-category data-[is-loading=true]:h-6 data-[is-loading=true]:w-15" variant="neutral"/>
<p data-is-loading="true" class="deal-title font-bold text-lg data-[is-loading=true]:h-3 "></p> <p data-is-loading="true" class="deal-title font-bold text-lg data-[is-loading=true]:h-3 "></p>
<p data-is-loading="true" class="deal-description text-sm text-accent-600 wrap-break-word"></p> <p data-is-loading="true" class="deal-description text-sm text-accent-600 wrap-break-word"></p>
<div class="flex justify-between"> <div class="flex items-center justify-between">
<div > <div class="flex items-center">
<x-dashboard.user.action-toolbar.like-button data-is-loading="true" :is-interactive="false"/> <x-dashboard.user.action-toolbar.like-button data-is-loading="true" :is-interactive="false"/>
<x-dashboard.user.action-toolbar.favorite-button data-is-loading="true" :is-interactive="false"/> <x-dashboard.user.action-toolbar.favorite-button data-is-loading="true" :is-interactive="false"/>
</div> </div>

View File

@ -2,9 +2,12 @@
@php @php
$variants = [ $variants = [
'neutral' => 'bg-black text-white', 'neutral' => 'bg-black text-white',
'ghost' => 'bg-gray-200 text-gray-800' 'ghost' => 'bg-gray-200 text-gray-800',
'red' => 'bg-red-100 text-red-700',
'green' => 'bg-green-100 text-green-700',
'yellow' => 'bg-yellow-100 text-yellow-700',
]; ];
@endphp @endphp
<span class="text-xs {{$variants[$variant]}} px-3 py-1 rounded-lg"> <span class="text-xs {{$variants[$variant]}} px-3 py-1 rounded-lg">
<p class="font-bold">{{$title}}</p> <span class="font-bold">{{$title}}</span>
</span> </span>

View File

@ -0,0 +1,87 @@
@php use App\Enums\ReportStatus; @endphp
<x-dashboard.admin.layout title="Customers">
<x-slot:heading>
<x-dashboard.page-heading
title="Manage Customers"
description="Edit, Delete and Login as Customer"
/>
</x-slot:heading>
<div class="flex items-center justify-center px-4 pb-4 pt-0 md:px-8 md:pb-8">
<x-dashboard.card class="w-full">
<h3 class="text-md font-bold mb-2">Reported deals</h3>
<h4 class="text-sm font-medium mb-4 text-accent-600">Total Reports: {{$reports->count()}}</h4>
<x-ui.table>
<x-ui.table.head>
<th>Type</th>
<th>Description</th>
<th>Customer</th>
<th>Status</th>
<th class="flex w-full justify-center items-center">
Deal
<x-heroicon-o-arrow-top-right-on-square class="w-4 ml-1"/>
</th>
</x-ui.table.head>
@forelse($reports as $report)
<x-ui.table.row>
<td class="text-sm font-medium px-4 text-center">{{ucfirst($report->type->value)}}</td>
<td class="text-sm text-accent-600 px-4 text-center max-w-40">{{$report->description}}</td>
<td class="text-sm px-4 text-center">{{$report->user->name}}</td>
<td class="text-center px-4">
<x-ui.badge
:title="$report->status->value"
:variant="match ($report->status){
ReportStatus::Resolved => 'green',
ReportStatus::Pending => 'yellow',
ReportStatus::Rejected => 'red'
}"
/>
</td>
<td class="text-sm px-4 text-center truncate max-w-40">
<a target="_blank" class="hover:underline"
href="{{route('explore', ['show' => $report->deals()->first()->id])}}">
{{$report->deals()->first()->title}}
</a>
</td>
<x-slot:actions>
<div class="flex items-center justify-center space-x-2 py-1 px-2">
@if($report->status !== ReportStatus::Resolved)
<form action="{{route('admin.reports.resolve', $report)}}"
onsubmit="return confirm('Are you sure to mark this resolve ?')" method="post"
class=" h-full items-center flex justify-center">
@csrf
<x-ui.button-sm tooltip="Mark resolved" variant="green">
<x-heroicon-o-check class="w-4"/>
</x-ui.button-sm>
</form>
@endif
@if($report->status !== ReportStatus::Rejected)
<form action="{{route('admin.reports.reject', $report)}}"
onsubmit="return confirm('Are you sure to reject this ?')" method="post"
class=" h-full items-center flex justify-center">
@csrf
<x-ui.button-sm tooltip="Reject" variant="red">
<x-heroicon-o-x-mark class="w-4"/>
</x-ui.button-sm>
</form>
@endif
<form action="{{route('admin.reports.remove-content', $report)}}"
onsubmit="return confirm('Are you sure to remove the deal ?')" method="post"
class=" h-full items-center flex justify-center">
@csrf
<x-ui.button-sm tooltip="Remove Content" variant="ghost">
<x-heroicon-o-exclamation-triangle class="w-4"/>
</x-ui.button-sm>
</form>
</div>
</x-slot:actions>
</x-ui.table.row>
@empty
<x-ui.table.row>
<td colspan="2" class="text-center text-sm text-accent-600 py-2">No Customer found</td>
</x-ui.table.row>
@endforelse
</x-ui.table>
</x-dashboard.card>
</div>
</x-dashboard.admin.layout>

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\Admin\AdminDashboardController; use App\Http\Controllers\Admin\AdminDashboardController;
use App\Http\Controllers\Admin\BrokerController; use App\Http\Controllers\Admin\BrokerController;
use App\Http\Controllers\Admin\CustomerController; use App\Http\Controllers\Admin\CustomerController;
use App\Http\Controllers\Admin\ReportController;
use App\Http\Middleware\HasRole; use App\Http\Middleware\HasRole;
Route::prefix('/admin') Route::prefix('/admin')
@ -15,4 +16,10 @@
Route::resource('brokers', BrokerController::class)->except('show', 'create', 'store'); Route::resource('brokers', BrokerController::class)->except('show', 'create', 'store');
Route::post('/brokers/approve/{broker}', [BrokerController::class, 'approve'])->name('brokers.approve'); Route::post('/brokers/approve/{broker}', [BrokerController::class, 'approve'])->name('brokers.approve');
Route::get('/reports', [ReportController::class, 'index'])->name('reports.index');
Route::post('/reports/resolve/{report}', [ReportController::class, 'resolve'])->name('reports.resolve');
Route::post('/reports/reject/{report}', [ReportController::class, 'reject'])->name('reports.reject');
Route::post('/reports/remove/{report}',
[ReportController::class, 'removeContent'])->name('reports.remove-content');
}); });