Updated dashboard

This commit is contained in:
m.razaghi
2026-08-03 17:26:15 +03:30
parent 6e68ed45e4
commit aad799d29f
3 changed files with 287 additions and 0 deletions

View 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'
));
}
}

View 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

View File

@@ -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');
});