feat: implement site visit in controller and add visit table

This commit is contained in:
2026-08-20 12:44:04 -07:00
parent 99c60dd9c7
commit f0331152ea
11 changed files with 344 additions and 52 deletions
@@ -6,15 +6,31 @@ use App\Http\Controllers\Controller;
use App\Models\Booking; use App\Models\Booking;
use App\Models\Flight; use App\Models\Flight;
use App\Models\User; use App\Models\User;
use App\Models\Visit;
use Carbon\Carbon;
class DashboardController extends Controller class DashboardController extends Controller
{ {
public function index() public function index()
{ {
$startDate = Carbon::now()->subDays(9)->startOfDay();
$endDate = Carbon::now()->endOfDay();
$flightsCount = Flight::count(); $flightsCount = Flight::count();
$usersCount = User::count(); $usersCount = User::count();
$bookingsCount = Booking::count(); $bookingsCount = Booking::count();
$indexPageCont = Visit::count();
return view('admin.dashboard', compact('flightsCount', 'usersCount', 'bookingsCount')); $visits = Visit::whereBetween('created_at', [$startDate, $endDate])
->selectRaw('DATE(created_at) as visit_date, COUNT(*) as total')
->groupBy('visit_date')
->orderBy('visit_date')
->get();
return view('admin.dashboard', compact('flightsCount', 'usersCount',
'bookingsCount', 'indexPageCont' , 'visits'));
} }
} }
@@ -288,7 +288,6 @@ class FlightController extends Controller
'status' => [ 'status' => [
'required', 'required',
'in:0,1',
], ],
]); ]);
+12 -1
View File
@@ -5,13 +5,24 @@ namespace App\Http\Controllers;
use App\Models\Airport; use App\Models\Airport;
use App\Models\Flight; use App\Models\Flight;
use App\Models\Tour; use App\Models\Tour;
use App\Models\Visit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class IndexController extends Controller class IndexController extends Controller
{ {
public function index() public function index(Request $request)
{ {
if (! $request->session()->has('visited')) {
Visit::create([
'ip_address' => $request->ip(),
'url' => $request->fullUrl(),
'user_agent' => $request->userAgent(),
]);
$request->session()->put('visited', true);
}
$flights = Flight::query() $flights = Flight::query()
->where('status', \App\Enums\FlightStatus::ENABLE) ->where('status', \App\Enums\FlightStatus::ENABLE)
->with([ ->with([
+2 -1
View File
@@ -6,6 +6,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\FlightStatus;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@@ -52,7 +53,7 @@ class Flight extends Model
'price' => 'float', 'price' => 'float',
'capacity' => 'int', 'capacity' => 'int',
'views' => 'int', 'views' => 'int',
'status' => 'int' 'status' => FlightStatus::class
]; ];
protected $fillable = [ protected $fillable = [
+4 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\TourStatus;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -15,7 +16,8 @@ class Tour extends Model
'duration' => 'int', 'duration' => 'int',
'views' => 'int', 'views' => 'int',
'price' => 'int', 'price' => 'int',
'status' => 'int', 'status' => TourStatus::class,
'departure_time'=> 'datetime',
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
]; ];
@@ -30,6 +32,7 @@ class Tour extends Model
'duration', 'duration',
'views', 'views',
'price', 'price',
'departure_time',
'status', 'status',
'created_at', 'created_at',
]; ];
+34
View File
@@ -0,0 +1,34 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class Visit
*
* @property int $id
* @property string|null $ip_address
* @property string|null $url
* @property string|null $user_agent
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*
* @package App\Models
*/
class Visit extends Model
{
protected $table = 'visits';
public static $snakeAttributes = false;
protected $fillable = [
'ip_address',
'url',
'user_agent'
];
}
+92 -10
View File
@@ -1,4 +1,3 @@
```blade
@extends('admin.layouts.app') @extends('admin.layouts.app')
@section('breadcrumb') @section('breadcrumb')
@@ -23,8 +22,47 @@
<div class="row"> <div class="row">
{{-- تعداد پروازها --}} <div class="col-xl-3 col-lg-3 col-md-6 col-sm-12">
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12">
<div class="card custom-card overflow-hidden">
<div class="card-body">
<div class="d-flex gap-3">
<div class="avatar avatar-md bg-info svg-white">
<i class="ri-eye-line fs-24"></i>
</div>
<div class="flex-fill">
<div class="fw-medium fs-13 mb-1 text-dark">
بازدید کل سایت
</div>
<div class="fs-22 fw-semibold mb-1 text-info">
{{ $indexPageCont }}
</div>
<div class="d-flex align-items-center fs-11">
<span class="text-default op-6">
کل بازدیدها
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-3 col-md-6 col-sm-12">
<div class="card custom-card overflow-hidden"> <div class="card custom-card overflow-hidden">
@@ -64,9 +102,7 @@
</div> </div>
<div class="col-xl-3 col-lg-3 col-md-6 col-sm-12">
{{-- تعداد کاربران --}}
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12">
<div class="card custom-card overflow-hidden"> <div class="card custom-card overflow-hidden">
@@ -106,9 +142,7 @@
</div> </div>
<div class="col-xl-3 col-lg-3 col-md-6 col-sm-12">
{{-- تعداد رزروها --}}
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12">
<div class="card custom-card overflow-hidden"> <div class="card custom-card overflow-hidden">
@@ -150,9 +184,57 @@
</div> </div>
<div class="row mt-4">
<div class="col-xl-12">
<div class="card custom-card">
<div class="card-header">
<div class="card-title">
نمودار بازدید سایت (۱۰ روز اخیر)
</div>
</div>
<div class="card-body">
<canvas id="visitsChart" height="90"></canvas>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('visitsChart'), {
type: 'bar',
data: {
labels: @json($visits->map(fn($v) => verta($v->visit_date)->format('m/d'))),
datasets: [{
label: 'تعداد بازدید',
data: @json($visits->pluck('total')),
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderRadius: 6,
maxBarThickness: 40
}]
},
options: {
responsive: true,
scales: {
y: { beginAtZero: true, ticks: { precision: 0 } }
},
plugins: {
legend: { display: false }
}
}
});
</script>
@endsection @endsection
```
@@ -25,7 +25,7 @@
<span class="text-success">{{ $message }}</span> <span class="text-success">{{ $message }}</span>
@enderror @enderror
@error('errors') @error('error')
<span class="text-danger">{{ $message }}</span> <span class="text-danger">{{ $message }}</span>
@enderror @enderror
<div class="row"> <div class="row">
@@ -236,11 +236,11 @@
</td> </td>
<td> <td>
@if(\App\Enums\FlightStatus::ENABLE) @if($flight->status == \App\Enums\FlightStatus::ENABLE)
<span class="badge bg-primary-transparent"> <span class="badge bg-primary-transparent">
فعال فعال
</span> </span>
@elseif(\App\Enums\FlightStatus::DISABLE) @elseif($flight->status == \App\Enums\FlightStatus::DISABLE )
<span class="badge bg-warning-transparent"> <span class="badge bg-warning-transparent">
غیرفعال غیرفعال
</span> </span>
+1 -1
View File
@@ -8,7 +8,7 @@
data-menu-styles="light" data-menu-styles="light"
dir="rtl" dir="rtl"
loader="enable" loader="enable"
data-vertical-style="overlay"> data-vertical-style="default">
<head> <head>
<!-- Meta Data --> <!-- Meta Data -->
<meta charset="UTF-8"> <meta charset="UTF-8">
+109 -14
View File
@@ -1,45 +1,140 @@
<style>
.flight-row {
display: flex;
flex-wrap: wrap;
}
.flight-col {
display: flex;
}
.flight-item {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.flight-img {
height: 200px;
min-height: 200px;
flex-shrink: 0;
overflow: hidden;
}
.flight-img img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.flight-content {
flex: 1;
display: flex;
flex-direction: column;
}
.flight-title {
flex: 1;
}
.flight-title-info {
min-height: 55px;
}
.flight-title-info h4 {
min-height: 48px;
}
.flight-date {
min-height: 25px;
}
.flight-bottom {
margin-top: auto;
}
</style>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="flight-item"> <div class="flight-item">
<div class="flight-img"> <div class="flight-img">
@if(getFileUrl($flight->file_id))
@if($flight?->file_id)
<img <img
src="{{ getFileUrl($flight->file_id) }}" src="{{ getFileUrl($flight->file_id) }}"
alt="{{ $flight->title }}" alt="{{ $flight->title }}"
style="width: 100%; height: 200px; object-fit: cover;"
> >
@else @else
<img src="{{ asset('assets/img/flight/01.jpg') }}" alt <img
style="width: 100%; height: 200px; object-fit: cover;"> src="{{ asset('assets/img/flight/01.jpg') }}"
alt="{{ $flight->title }}"
>
@endif @endif
<a href="{{ route('account.wishlist.toggle', [ <a
href="{{ route('account.wishlist.toggle', [
'type' => 'flight', 'type' => 'flight',
'id' => $flight->id 'id' => $flight->id
]) }}" class="add-wishlist"> ]) }}"
class="add-wishlist"
>
<i class="far fa-heart"></i> <i class="far fa-heart"></i>
</a> </a>
</div> </div>
<div class="flight-content"> <div class="flight-content">
<div class="flight-title"> <div class="flight-title">
<div class="flight-title-info"> <div class="flight-title-info">
<img src="{{ asset('assets/img/flight/airline-1.png') }}" alt>
<h4><a href="#">{{ $flight->departureAirport->city->name }}<i <img
class="far fa-exchange"></i> {{ $flight->arrivalAirport->city->name }} </a></h4> src="{{ asset('assets/img/flight/airline-1.png') }}"
alt
>
<h4>
<a href="#">
{{ $flight->departureAirport?->city?->name }}
<i class="far fa-exchange"></i>
{{ $flight->arrivalAirport?->city?->name }}
</a>
</h4>
</div> </div>
<p class="flight-date"><i
class="far fa-calendar-days"></i>{{ $flight->departure_time->toJalali()->format('Y/m/d H:i') }} <p class="flight-date">
<i class="far fa-calendar-days"></i>
{{ $flight->departure_time->toJalali()->format('Y/m/d H:i') }}
</p> </p>
</div> </div>
<div class="flight-bottom"> <div class="flight-bottom">
<div class="flight-price"> <div class="flight-price">
از <span> {{ number_format($flight->price) }} تومان </span> از
<span>
{{ number_format($flight->price) }} تومان
</span>
</div> </div>
<div class="flight-text-btn"> <div class="flight-text-btn">
<a href="{{ route('flights.show', $flight->id) }}">نمایش جزئیات <i <a href="{{ route('flights.show', $flight->id) }}">
class="fas fa-arrow-left"></i></a> نمایش جزئیات
<i class="fas fa-arrow-left"></i>
</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
+54 -3
View File
@@ -1,13 +1,64 @@
<style>
.tour-item {
height: 100%;
display: flex;
flex-direction: column;
}
.tour-img {
height: 260px;
flex-shrink: 0;
overflow: hidden;
}
.tour-img img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.tour-content {
flex: 1;
display: flex;
flex-direction: column;
}
.tour-title {
min-height: 50px;
}
.tour-content > p {
min-height: 48px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.tour-duration {
min-height: 25px;
}
.tour-bottom {
margin-top: auto;
}
</style>
<div class="col-md-6 col-lg-6"> <div class="col-md-6 col-lg-6">
<div class="tour-item"> <div class="tour-item">
<div class="tour-img"> <div class="tour-img">
@if(getFileUrl($tour?->file?->id))
<img <img
src="{{ $tour->file src="{{ getFileUrl($tour?->file?->id) }}"
? asset('storage/' . $tour->file->path)
: asset('assets/img/tour/01.jpg') }}"
alt="{{ $tour->title }}" alt="{{ $tour->title }}"
style=""
> >
@else
<img src="{{ asset('assets/img/tour/02.jpg') }}" alt
style="">
@endif
<a href="{{ route('account.wishlist.toggle', [ <a href="{{ route('account.wishlist.toggle', [
'type' => 'tour', 'type' => 'tour',