Updated dashboard
This commit is contained in:
59
app/Http/Controllers/Admin/DashboardController.php
Normal file
59
app/Http/Controllers/Admin/DashboardController.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\Suggestion;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\UserSubscriber;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// 1. Calculate General Statistics
|
||||
$totalUsers = User::count();
|
||||
|
||||
// Using your UserSubscriber model, checking the is_free column and expired_at date
|
||||
$paidSubscribers = UserSubscriber::where('expired_at', '>=', now())
|
||||
->where('is_free', false)
|
||||
->distinct('user_id')
|
||||
->count('user_id');
|
||||
|
||||
$freeSubscribers = UserSubscriber::where('expired_at', '>=', now())
|
||||
->where('is_free', true)
|
||||
->distinct('user_id')
|
||||
->count('user_id');
|
||||
|
||||
// 2. Fetch Recent Data (Limit to 5)
|
||||
$recentSuggestions = Suggestion::with('user')->latest()->take(5)->get();
|
||||
$recentTickets = Ticket::with('user')->latest('updated_at')->take(5)->get();
|
||||
|
||||
// 3. Prepare Chart Data (Last 7 Days)
|
||||
$chartDates = [];
|
||||
$newUsersData = [];
|
||||
$paidSubsData = [];
|
||||
$freeSubsData = [];
|
||||
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$date = now()->subDays($i);
|
||||
$chartDates[] = $date->format('Y-m-d'); // Example: "2023-10-25"
|
||||
$dbDate = $date->format('Y-m-d');
|
||||
|
||||
// Line Chart Data: New Users
|
||||
$newUsersData[] = User::whereDate('created_at', $dbDate)->count();
|
||||
|
||||
// Column Chart Data: Subscriptions
|
||||
$paidSubsData[] = UserSubscriber::whereDate('created_at', $dbDate)->where('is_free', false)->count();
|
||||
$freeSubsData[] = UserSubscriber::whereDate('created_at', $dbDate)->where('is_free', true)->count();
|
||||
}
|
||||
|
||||
return view('admin.dashboard.index', compact(
|
||||
'totalUsers', 'paidSubscribers', 'freeSubscribers',
|
||||
'recentSuggestions', 'recentTickets',
|
||||
'chartDates', 'newUsersData', 'paidSubsData', 'freeSubsData'
|
||||
));
|
||||
}
|
||||
}
|
||||
224
resources/views/admin/dashboard/index.blade.php
Normal file
224
resources/views/admin/dashboard/index.blade.php
Normal file
@@ -0,0 +1,224 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('content')
|
||||
|
||||
<!-- 1. Statistics Cards -->
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card mini-stats-wid text-white bg-primary">
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1">
|
||||
<p class="text-white-50 font-weight-medium mb-2">کل کاربران</p>
|
||||
<h4 class="text-white mb-0">{{ number_format($totalUsers) }}</h4>
|
||||
</div>
|
||||
<div class="mini-stat-icon avatar-sm rounded-circle bg-white align-self-center">
|
||||
<span class="avatar-title bg-white text-primary"><i class="mdi mdi-account-group font-size-24"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card mini-stats-wid text-white bg-success">
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1">
|
||||
<p class="text-white-50 font-weight-medium mb-2">اشتراکهای فعال (پولی)</p>
|
||||
<h4 class="text-white mb-0">{{ number_format($paidSubscribers) }}</h4>
|
||||
</div>
|
||||
<div class="mini-stat-icon avatar-sm rounded-circle bg-white align-self-center">
|
||||
<span class="avatar-title bg-white text-success"><i class="mdi mdi-cash-multiple font-size-24"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card mini-stats-wid text-white bg-info">
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1">
|
||||
<p class="text-white-50 font-weight-medium mb-2">اشتراکهای فعال (رایگان)</p>
|
||||
<h4 class="text-white mb-0">{{ number_format($freeSubscribers) }}</h4>
|
||||
</div>
|
||||
<div class="mini-stat-icon avatar-sm rounded-circle bg-white align-self-center">
|
||||
<span class="avatar-title bg-white text-info"><i class="mdi mdi-gift font-size-24"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Charts Section -->
|
||||
<div class="row mb-4">
|
||||
<!-- Line Chart: New Users -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title mb-4">ثبتنام کاربران جدید (۷ روز گذشته)</h4>
|
||||
<div id="users-line-chart" style="height: 350px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Column Chart: Subscriptions -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title mb-4">اشتراکهای ثبت شده (۷ روز گذشته)</h4>
|
||||
<div id="subs-column-chart" style="height: 350px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Recent Tables Section -->
|
||||
<div class="row">
|
||||
<!-- Recent Tickets -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title mb-4">آخرین تیکتها</h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-centered table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>کاربر</th>
|
||||
<th>موضوع</th>
|
||||
<th>وضعیت</th>
|
||||
<th>مشاهده</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($recentTickets as $ticket)
|
||||
<tr>
|
||||
<td>{{ optional($ticket->user)->mobile ?? 'ناشناس' }}</td>
|
||||
<td>{{ Str::limit($ticket->subject, 30) }}</td>
|
||||
<td>
|
||||
@if($ticket->status === 'open' || $ticket->status === 'user_reply')
|
||||
<span class="badge bg-danger">نیاز به پاسخ</span>
|
||||
@else
|
||||
<span class="badge bg-success">پاسخ داده شده</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><a href="{{ route('tickets.show', $ticket->id) }}" class="btn btn-sm btn-primary">مشاهده</a></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="text-center">تیکتی وجود ندارد</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Suggestions -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title mb-4">آخرین پیشنهادات</h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-centered table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>کاربر</th>
|
||||
<th>پیشنهاد</th>
|
||||
<th>تاریخ ثبت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($recentSuggestions as $suggestion)
|
||||
<tr>
|
||||
<td>{{ optional($suggestion->user)->mobile ?? 'ناشناس' }}</td>
|
||||
<td title="{{ $suggestion->text }}">{{ Str::limit($suggestion->text, 40) }}</td>
|
||||
<td dir="ltr">{{ $suggestion->created_at->format('Y/m/d H:i') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="3" class="text-center">پیشنهادی وجود ندارد</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// --- 1. Line Chart: New Users ---
|
||||
var userOptions = {
|
||||
series: [{
|
||||
name: "کاربران جدید",
|
||||
data: {!! json_encode($newUsersData) !!}
|
||||
}],
|
||||
chart: {
|
||||
height: 350,
|
||||
type: 'line',
|
||||
fontFamily: 'Vazirmatn, Tahoma, sans-serif',
|
||||
toolbar: { show: false }
|
||||
},
|
||||
stroke: {
|
||||
width: 3,
|
||||
curve: 'smooth' // Makes the line wavy and modern
|
||||
},
|
||||
colors: ['#007bff'],
|
||||
xaxis: {
|
||||
categories: {!! json_encode($chartDates) !!}
|
||||
},
|
||||
dataLabels: { enabled: false }
|
||||
};
|
||||
var userChart = new ApexCharts(document.querySelector("#users-line-chart"), userOptions);
|
||||
userChart.render();
|
||||
|
||||
|
||||
// --- 2. Column Chart: Subscriptions ---
|
||||
var subsOptions = {
|
||||
series: [{
|
||||
name: 'اشتراک پولی',
|
||||
data: {!! json_encode($paidSubsData) !!}
|
||||
}, {
|
||||
name: 'اشتراک رایگان',
|
||||
data: {!! json_encode($freeSubsData) !!}
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar', // 'bar' is used for column charts in ApexCharts
|
||||
height: 350,
|
||||
fontFamily: 'Vazirmatn, Tahoma, sans-serif',
|
||||
toolbar: { show: false }
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: '50%',
|
||||
borderRadius: 4
|
||||
},
|
||||
},
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { show: true, width: 2, colors: ['transparent'] },
|
||||
colors: ['#28a745', '#17a2b8'], // Green for paid, Info/Blue for free
|
||||
xaxis: {
|
||||
categories: {!! json_encode($chartDates) !!}
|
||||
},
|
||||
fill: { opacity: 1 },
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val + " عدد"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var subsChart = new ApexCharts(document.querySelector("#subs-column-chart"), subsOptions);
|
||||
subsChart.render();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -30,6 +30,7 @@ use App\Http\Controllers\Admin\VolumController;
|
||||
use App\Http\Controllers\api\PayController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Admin\TicketController;
|
||||
use App\Http\Controllers\Admin\DashboardController;
|
||||
|
||||
Route::get('payment/callback', [PayController::class, 'callback']);
|
||||
Route::middleware(['auth', config('jetstream.auth_session')])->group(function () {
|
||||
@@ -65,4 +66,7 @@ Route::middleware(['auth', config('jetstream.auth_session')])->group(function ()
|
||||
Route::get('tickets/{ticket}', [TicketController::class, 'show'])->name('tickets.show');
|
||||
Route::post('tickets/{ticket}/reply', [TicketController::class, 'reply'])->name('tickets.reply');
|
||||
Route::post('tickets/{ticket}/close', [TicketController::class, 'close'])->name('tickets.close');
|
||||
|
||||
|
||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user