From 9dc27863e27200f9895d7fd1148a72626b85a4e7 Mon Sep 17 00:00:00 2001 From: mina-esn Date: Mon, 17 Aug 2026 16:37:11 +0330 Subject: [PATCH] feat: implement tour listing with sorting and pagination --- app/Http/Controllers/TourController.php | 33 +++ app/Models/Tour.php | 22 ++ app/Providers/AppServiceProvider.php | 3 +- app/helpers/helper.php | 36 +++ composer.json | 5 +- resources/views/tours/index.blade.php | 285 ++++++++++++++++++++++++ resources/views/tours/show.blade.php | 3 + routes/web.php | 7 + 8 files changed, 392 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/TourController.php create mode 100644 resources/views/tours/index.blade.php create mode 100644 resources/views/tours/show.blade.php diff --git a/app/Http/Controllers/TourController.php b/app/Http/Controllers/TourController.php new file mode 100644 index 0000000..b654706 --- /dev/null +++ b/app/Http/Controllers/TourController.php @@ -0,0 +1,33 @@ +query('sort', 'default'); + + $tours = Tour::with([ + 'originCity', + 'city', + 'tourDepartures', + ]) + ->applySort($sort) + ->paginate(3) + ->withQueryString(); + + return view('tours.index', compact('title', 'tours', 'sort')); + } + + public function show(Tour $tour) + { + $title= 'جزئیات تور'; + return view('tours.show', compact('title')); + } +} diff --git a/app/Models/Tour.php b/app/Models/Tour.php index 03f1372..4307fcf 100644 --- a/app/Models/Tour.php +++ b/app/Models/Tour.php @@ -7,6 +7,8 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; @@ -50,6 +52,26 @@ class Tour extends Model 'file_id', 'tour_type' ]; + #[Scope] + protected function applySort(Builder $query, ?string $sort = null): void + { + switch ($sort) { + + case 'lowest': + $query->withMin('tourDepartures', 'price') + ->orderBy('tour_departures_min_price'); + break; + + case 'highest': + $query->withMax('tourDepartures', 'price') + ->orderByDesc('tour_departures_max_price'); + break; + + default: + $query->orderByDesc('id'); + break; + } + } public function flight() { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..86b1719 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Pagination\Paginator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -19,6 +20,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - // + Paginator::useBootstrapFive(); } } diff --git a/app/helpers/helper.php b/app/helpers/helper.php index 880b29d..e1e8858 100644 --- a/app/helpers/helper.php +++ b/app/helpers/helper.php @@ -1,5 +1,8 @@ tourDepartures->first(); + + if (!$departure) { + return 'نامشخص'; + } + + $start = Carbon::parse($departure->start_date); + $end = Carbon::parse($departure->end_date); + + $nights = $start->diffInDays($end); + $days = $nights + 1; + + return "{$days} روز و {$nights} شب"; + } +} +if (!function_exists('tourDateRange')) { + function tourDateRange(Tour $tour): string + { + $departure = $tour->tourDepartures->first(); + + if (!$departure) { + return 'تاریخ نامشخص'; + } + + $start = Verta::instance($departure->start_date); + $end = Verta::instance($departure->end_date); + + return $start->format('Y/m/d') . ' - ' . $end->format('Y/m/d'); + } +} diff --git a/composer.json b/composer.json index da35d55..8c8b634 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,10 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" - } + }, + "files": [ + "app/helpers/helper.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/resources/views/tours/index.blade.php b/resources/views/tours/index.blade.php new file mode 100644 index 0000000..f88111a --- /dev/null +++ b/resources/views/tours/index.blade.php @@ -0,0 +1,285 @@ +@extends('layouts.app') + +@section('content') +
+ + + +
+
+
+ + + +
+
+
+ + +
+
+
+ +
+
+
+
{{ $tours->total() }} نتیجه پیدا شد
+ +
+
+ + +
+
+
+
+
+ @forelse($tours as $tour) +
+
+
+ + +
+
+ +

{{$tour->title}}

+
+ سفر {{ tourDuration($tour) }}
+
{{ tourDateRange($tour) }}
+
+
+ {{number_format($tour->tourDepartures->first()->price)}} تومان +
+ +
+
+
+
+ @empty +
هیچ توری یافت نشد
+ + @endforelse + @if($tours->hasPages()) +
+ +
+ {{ $tours->links() }} +
+ +
+

+ نمایش {{ $tours->firstItem() }} - + {{ $tours->lastItem() }} + از {{ $tours->total() }} تور +

+
+ +
+ @endif + +
+
+
+
+
+ +
+@endsection diff --git a/resources/views/tours/show.blade.php b/resources/views/tours/show.blade.php new file mode 100644 index 0000000..2466e19 --- /dev/null +++ b/resources/views/tours/show.blade.php @@ -0,0 +1,3 @@ +
+ +
diff --git a/routes/web.php b/routes/web.php index a7f57dd..57402db 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ use App\Http\Controllers\FlightController; use App\Http\Controllers\IndexController; +use App\Http\Controllers\TourController; use Illuminate\Support\Facades\Route; Route::get('/' , [IndexController::class , 'index'])->name('index'); @@ -10,3 +11,9 @@ Route::prefix('flights')->controller(FlightController::class)->name('flights.')- Route::get('/' , 'index')->name('index'); Route::get('{flight}','show')->name('show'); }); + +Route::prefix('tours')->controller(TourController::class)->name('tours.')->group(function (){ + Route::get('/' , 'index')->name('index'); + Route::get('{tour}','show')->name('show'); +}); +