Merge branch 'feature/admin-panel'

This commit is contained in:
kusowl 2026-01-29 09:48:25 +05:30
commit a8be44553e
8 changed files with 114 additions and 62 deletions

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Deal;
use Illuminate\Support\Facades\Log;
class DealController extends Controller
{
public function index()
{
return view('dashboards.admin.deals.index')
->with('deals', $this->deals());
}
public function approve(Deal $deal)
{
try {
\DB::transaction(function () use ($deal) {
$deal->active = true;
$deal->save();
});
return back()->with('success', 'Deal activated successfully.');
} catch (\Throwable $e) {
Log::error('Deal activation Failed: ', [$e->getMessage(), $e->getTrace()]);
return back()->with('error', 'Something went wrong.');
}
}
public function reject(Deal $deal){
try {
$deal->delete();
return back()->with('success', 'Deal deleted successfully.');
} catch (\Throwable $e) {
Log::error('Deal deletion Failed: ', [$e->getMessage(), $e->getTrace()]);
return back()->with('error', 'Something went wrong.');
}
}
private function deals()
{
return Deal::where('active', false)->get();
}
}

View File

@ -31,6 +31,7 @@ public function __invoke(
protected function deals(FormRequest $request, Builder $query, AddRecentSearchAction $action): LengthAwarePaginator protected function deals(FormRequest $request, Builder $query, AddRecentSearchAction $action): LengthAwarePaginator
{ {
$query->tap(fn ($q) => (new Deal)->withActiveDeals($q));
// Add a search query // Add a search query
if ($request->has('search') && $request->get('search') !== null) { if ($request->has('search') && $request->get('search') !== null) {
$query->tap(fn ($q) => (new Deal)->search($q, $request->search)); $query->tap(fn ($q) => (new Deal)->search($q, $request->search));

View File

@ -25,8 +25,7 @@ public function builder(): Builder
->with('type'); ->with('type');
}, },
]) ])
// Select only admin-approved deals
->tap(fn ($q) => (new Deal)->withActiveDeals($q))
// Check if the current user interacted with the deal // Check if the current user interacted with the deal
->tap(fn ($q) => (new Deal)->withCurrentUserInteractions($q)) ->tap(fn ($q) => (new Deal)->withCurrentUserInteractions($q))
->tap(fn ($q) => (new Deal)->withLikePerDeal($q)) ->tap(fn ($q) => (new Deal)->withLikePerDeal($q))

View File

@ -0,0 +1,12 @@
import {showDealModal} from "./deal-view-modal.js";
const table = document.querySelector('.ui-table')
if (table) {
const btns = table.querySelectorAll('.view-deal-btn');
btns.forEach(btn => {
btn.addEventListener('click', async () => {
let dealId = btn.dataset.dealId;
await showDealModal(dealId)
})
})
}

View File

@ -37,6 +37,11 @@ class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full overflow
<x-heroicon-o-exclamation-triangle 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>
<x-dashboard.broker.sidebar.item :link="route('admin.deals.index')">
<x-heroicon-o-fire class="w-5 min-w-5"/>
<p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Deals</p>
</x-dashboard.broker.sidebar.item>
</div> </div>
</div> </div>

View File

@ -1,78 +1,53 @@
@php use App\Enums\ReportStatus; @endphp @php use App\Enums\ReportStatus; @endphp
<x-dashboard.admin.layout title="Deals"> <x-dashboard.admin.layout title="Manage Deals">
<x-slot:heading> <x-slot:heading>
<x-dashboard.page-heading <x-dashboard.page-heading
title="Manage Customers" title="Manage Deals"
description="Edit, Delete and Login as Customer" description="Approve or reject deals"
/> />
</x-slot:heading> </x-slot:heading>
<div class="flex items-center justify-center px-4 pb-4 pt-0 md:px-8 md:pb-8"> <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"> <x-dashboard.card class="w-full">
<h3 class="text-md font-bold mb-2">Reported deals</h3> <h3 class="text-md font-bold mb-2">Approve deals</h3>
<h4 class="text-sm font-medium mb-4 text-accent-600">Total Reports: {{$reports->count()}}</h4>
<x-ui.table> <x-ui.table>
<x-ui.table.head> <x-ui.table.head>
<th>Type</th> <th>Title</th>
<th>Description</th> <th>Description</th>
<th>Customer</th> <th>Link</th>
<th>Status</th> <th>Category</th>
<th class="flex w-full justify-center items-center"> <th>Broker</th>
Deal
<x-heroicon-o-arrow-top-right-on-square class="w-4 ml-1"/>
</th>
</x-ui.table.head> </x-ui.table.head>
@forelse($reports as $report) @forelse($deals as $deal)
<x-ui.table.row> <x-ui.table.row>
<td class="text-sm font-medium px-4 text-center">{{ucfirst($report->type->value)}}</td> <td class="text-sm font-medium px-4 text-center truncate max-w-20">{{ucfirst($deal->title)}}</td>
<td class="text-sm text-accent-600 px-4 text-center max-w-40">{{$report->description}}</td> <td class="text-sm text-accent-600 px-4 text-center truncate max-w-60">{{$deal->description}}</td>
<td class="text-sm px-4 text-center">{{$report->user->name}}</td> <td class="text-sm px-4 text-center">{{$deal->link}}</td>
<td class="text-center px-4"> <td class="text-sm px-4 text-center">{{$deal->category->name}}</td>
<x-ui.badge <td class="text-sm px-4 text-center">{{$deal->broker->name}}</td>
: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> <x-slot:actions>
<div class="flex items-center justify-center space-x-2 py-1 px-2"> <div class="flex items-center justify-center space-x-2 py-1 px-2">
@if($report->status !== ReportStatus::Resolved) <form action="{{route('admin.deals.approve', $deal)}}"
<form action="{{route('admin.reports.resolve', $report)}}" onsubmit="return confirm('Are you sure to activate the deal ?')" method="post"
onsubmit="return confirm('Are you sure to mark this resolve ?')" method="post"
class=" h-full items-center flex justify-center"> class=" h-full items-center flex justify-center">
@csrf @csrf
<x-ui.button-sm tooltip="Mark resolved" variant="green"> <x-ui.button-sm tooltip="Approve deal" variant="green">
<x-heroicon-o-check class="w-4"/> <x-heroicon-o-check class="w-4"/>
</x-ui.button-sm> </x-ui.button-sm>
</form> </form>
@endif
@if($report->status !== ReportStatus::Rejected) <form action="{{route('admin.deals.reject', $deal)}}"
<form action="{{route('admin.reports.reject', $report)}}" onsubmit="return confirm('Are you sure to delete the deal ?')" method="post"
onsubmit="return confirm('Are you sure to reject this ?')" method="post"
class=" h-full items-center flex justify-center"> class=" h-full items-center flex justify-center">
@csrf @csrf
<x-ui.button-sm tooltip="Reject" variant="red"> <x-ui.button-sm tooltip="Delete deal" variant="red">
<x-heroicon-o-x-mark class="w-4"/> <x-heroicon-o-x-mark class="w-4"/>
</x-ui.button-sm> </x-ui.button-sm>
</form> </form>
@endif
<form action="{{route('admin.reports.remove-content', $report)}}" <x-ui.button-sm data-deal-id="{{$deal->id}}" class="view-deal-btn" tooltip="View Content" variant="ghost">
onsubmit="return confirm('Are you sure to remove the deal ?')" method="post" <x-heroicon-o-eye class="w-4"/>
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> </x-ui.button-sm>
</form>
</div> </div>
</x-slot:actions> </x-slot:actions>
</x-ui.table.row> </x-ui.table.row>
@ -84,4 +59,6 @@ class=" h-full items-center flex justify-center">
</x-ui.table> </x-ui.table>
</x-dashboard.card> </x-dashboard.card>
</div> </div>
<x-dashboard.user.deal-modal />
@vite('resources/js/admin-deals.js')
</x-dashboard.admin.layout> </x-dashboard.admin.layout>

View File

@ -5,10 +5,14 @@
use App\Queries\ExplorePageDealsQuery; use App\Queries\ExplorePageDealsQuery;
Route::get('/deals/{deal}', function (Deal $deal) { Route::get('/deals/{deal}', function (Deal $deal) {
$query = (new ExplorePageDealsQuery)->builder();
if (! Auth::user()->isAdmin()) {
$query // Select only admin-approved deals
->tap(fn ($q) => (new Deal)->withActiveDeals($q));
}
$query->where('id', $deal->id);
return new DealResource( return new DealResource(
(new ExplorePageDealsQuery) $query->first()
->builder()
->where('id', $deal->id)
->first()
); );
}); });

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\DealController;
use App\Http\Controllers\Admin\ReportController; use App\Http\Controllers\Admin\ReportController;
use App\Http\Middleware\HasRole; use App\Http\Middleware\HasRole;
@ -22,4 +23,8 @@
Route::post('/reports/reject/{report}', [ReportController::class, 'reject'])->name('reports.reject'); Route::post('/reports/reject/{report}', [ReportController::class, 'reject'])->name('reports.reject');
Route::post('/reports/remove/{report}', Route::post('/reports/remove/{report}',
[ReportController::class, 'removeContent'])->name('reports.remove-content'); [ReportController::class, 'removeContent'])->name('reports.remove-content');
Route::get('/deals', [DealController::class, 'index'])->name('deals.index');
Route::post('/deals/approve/{deal}', [DealController::class, 'approve'])->name('deals.approve');
Route::post('/deals/reject/{deal}', [DealController::class, 'reject'])->name('deals.reject');
}); });