feat: implement site visit in controller and add visit table
This commit is contained in:
@@ -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',
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -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([
|
||||||
|
|||||||
@@ -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
@@ -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',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user