fix: update public tour functionality

This commit is contained in:
2026-08-20 08:20:29 +03:30
parent 99e7d4ee95
commit 854324813e
3 changed files with 617 additions and 33 deletions
+194 -26
View File
@@ -4,9 +4,9 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\TourStoreRequest; use App\Http\Requests\TourStoreRequest;
use App\Models\Flight;
use App\Models\Tour; use App\Models\Tour;
use App\Models\TravelType; use App\Models\TravelType;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@@ -14,18 +14,37 @@ use Illuminate\Support\Str;
class TourController extends Controller class TourController extends Controller
{ {
/**
* لیست تورها
*/
public function index() public function index()
{ {
$tours = Tour::latest()->paginate(10); // همه تورها نمایش داده می‌شوند
return view('admin.tours.index', compact('tours')); // اگر فقط تورهای تایید شده را می‌خواهی:
// ->where('status', 2)
$tours = Tour::latest()->paginate(20);
// آخرین پروازهای تایید شده
$flights = Flight::where('status', 2)
->latest()
->paginate(3);
return view('admin.tours.index', compact('tours', 'flights'));
} }
/**
* فرم ایجاد تور
*/
public function create() public function create()
{ {
$travelTypes = TravelType::all(); $travelTypes = TravelType::all();
return view('admin.tours.create', compact('travelTypes')); return view('admin.tours.create', compact('travelTypes'));
} }
/**
* ذخیره تور جدید
*/
public function store(TourStoreRequest $request) public function store(TourStoreRequest $request)
{ {
DB::beginTransaction(); DB::beginTransaction();
@@ -33,89 +52,238 @@ class TourController extends Controller
try { try {
$data = $request->validated(); $data = $request->validated();
// ساخت slug اگر خالی بود // ساخت slug در صورت خالی بودن
if (empty($data['slug'])) { if (empty($data['slug'])) {
$data['slug'] = Str::slug($data['title']); $data['slug'] = Str::slug($data['title']);
} }
/*
* وضعیت تور
*
* 1 = در انتظار
* 2 = تایید شده
*
* اگر می‌خواهی تور بلافاصله در لیست نمایش داده شود،
* مقدار 2 قرار می‌دهیم.
*/
$data['status'] = 2;
// ایجاد تور
$tour = Tour::create($data); $tour = Tour::create($data);
// آپلود تصویر
if ($request->hasFile('image')) { if ($request->hasFile('image')) {
$image = $request->file('image');
$newFileName = time() . '.tour-image' . rand(11111, 99999) . '.' . $image->extension();
$path = $image->storeAs('tours', $newFileName, 'public');
$tour->update(['image' => $path]); $image = $request->file('image');
$newFileName =
time()
. '.tour-image'
. rand(11111, 99999)
. '.'
. $image->extension();
$path = $image->storeAs(
'tours',
$newFileName,
'public'
);
$tour->update([
'image' => $path
]);
} }
DB::commit(); DB::commit();
return redirect()->route('admin.tours.index')->with('success', 'تور با موفقیت ایجاد شد.');
return redirect()
->route('admin.tours.index')
->with('success', 'تور با موفقیت ایجاد شد.');
} catch (\Exception $exception) { } catch (\Exception $exception) {
Log::error($exception);
DB::rollBack(); DB::rollBack();
return back()->withInput()->withErrors(['general' => 'خطایی در ثبت رخ داد.']);
Log::error($exception);
return back()
->withInput()
->withErrors([
'general' => 'خطایی در ثبت تور رخ داد.'
]);
} }
} }
/**
* نمایش جزئیات تور
*/
public function show(Tour $tour) public function show(Tour $tour)
{ {
return view('admin.tours.show', compact('tour')); return view('admin.tours.show', compact('tour'));
} }
/**
* فرم ویرایش تور
*/
public function edit(Tour $tour) public function edit(Tour $tour)
{ {
$travelTypes = TravelType::all(); $travelTypes = TravelType::all();
return view('admin.tours.edit', compact('tour', 'travelTypes'));
return view(
'admin.tours.edit',
compact('tour', 'travelTypes')
);
} }
public function update(TourStoreRequest $request, Tour $tour) /**
{ * بروزرسانی تور
*/
public function update(
TourStoreRequest $request,
Tour $tour
) {
DB::beginTransaction(); DB::beginTransaction();
try { try {
$data = $request->validated(); $data = $request->validated();
// ساخت slug
if (empty($data['slug'])) { if (empty($data['slug'])) {
$data['slug'] = Str::slug($data['title']); $data['slug'] = Str::slug($data['title']);
} }
/*
* اگر status از فرم ارسال نشده باشد،
* وضعیت فعلی تور حفظ شود.
*/
if (!isset($data['status'])) {
$data['status'] = $tour->status ?? 2;
}
// اگر تصویر جدید آپلود شده
if ($request->hasFile('image')) { if ($request->hasFile('image')) {
// حذف عکس قبلی از حافظه
if ($tour->image && Storage::disk('public')->exists($tour->image)) { // حذف تصویر قبلی
if (
$tour->image &&
Storage::disk('public')->exists($tour->image)
) {
Storage::disk('public')->delete($tour->image); Storage::disk('public')->delete($tour->image);
} }
$image = $request->file('image'); $image = $request->file('image');
$newFileName = time() . '.tour-image' . rand(11111, 99999) . '.' . $image->extension();
$path = $image->storeAs('tours', $newFileName, 'public'); $newFileName =
time()
. '.tour-image'
. rand(11111, 99999)
. '.'
. $image->extension();
$path = $image->storeAs(
'tours',
$newFileName,
'public'
);
$data['image'] = $path; $data['image'] = $path;
} }
// بروزرسانی
$tour->update($data); $tour->update($data);
DB::commit(); DB::commit();
return redirect()->route('admin.tours.index')->with('success', 'تور با موفقیت به‌روزرسانی شد.');
return redirect()
->route('admin.tours.index')
->with(
'success',
'تور با موفقیت به‌روزرسانی شد.'
);
} catch (\Exception $exception) { } catch (\Exception $exception) {
Log::error($exception);
DB::rollBack(); DB::rollBack();
return back()->withInput()->withErrors(['general' => 'خطایی در به‌روزرسانی رخ داد.']);
Log::error($exception);
return back()
->withInput()
->withErrors([
'general' =>
'خطایی در به‌روزرسانی تور رخ داد.'
]);
} }
} }
/**
* حذف تصویر تور
*/
public function removeImage(Tour $tour) public function removeImage(Tour $tour)
{ {
// حذف تصویر تکی (اختیاری — می‌توانی منطق دلخواه بگذاری) try {
return back()->with('success', 'عملیات حذف تصویر انجام شد.');
if (
$tour->image &&
Storage::disk('public')->exists($tour->image)
) {
Storage::disk('public')->delete($tour->image);
}
$tour->update([
'image' => null
]);
return back()->with(
'success',
'تصویر تور با موفقیت حذف شد.'
);
} catch (\Exception $exception) {
Log::error($exception);
return back()->withErrors([
'general' =>
'حذف تصویر با خطا مواجه شد.'
]);
}
} }
/**
* حذف تور
*/
public function destroy(Tour $tour) public function destroy(Tour $tour)
{ {
$tour->delete(); try {
return redirect()->route('admin.tours.index') // حذف تصویر
->with('success', 'تور با موفقیت حذف شد.'); if (
$tour->image &&
Storage::disk('public')->exists($tour->image)
) {
Storage::disk('public')->delete($tour->image);
}
// حذف تور
$tour->delete();
return redirect()
->route('admin.tours.index')
->with(
'success',
'تور با موفقیت حذف شد.'
);
} catch (\Exception $exception) {
Log::error($exception);
return back()->withErrors([
'general' =>
'حذف تور با خطا مواجه شد.'
]);
}
} }
} }
+77 -1
View File
@@ -3,11 +3,65 @@
@section('title', 'لیست تورها') @section('title', 'لیست تورها')
@section('content') @section('content')
{{-- استایل دکمه علاقهمندی (مناسب برای کارتها) --}}
<style>
.travel-favorite-form {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
margin: 0;
}
.travel-favorite-btn {
width: 42px;
height: 42px;
border-radius: 50%;
border: 1px solid #e0245e;
background: rgba(255, 255, 255, .95);
color: #e0245e;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: all .2s ease;
}
.travel-favorite-btn:hover,
.travel-favorite-btn.active {
background: #e0245e;
color: #fff;
}
</style>
<h1>تورهای ما</h1> <h1>تورهای ما</h1>
<div class="row"> <div class="row">
@forelse($tours as $tour) @forelse($tours as $tour)
<div class="col-md-4"> @php
$isFavTour = in_array($tour->id, $favoriteTourIds ?? []);
@endphp
<div class="col-md-4 position-relative">
<x-tour :tour="$tour"/> <x-tour :tour="$tour"/>
{{-- دکمه علاقهمندی --}}
@auth
<form action="{{ route('favorites.toggle') }}" method="POST" class="travel-favorite-form">
@csrf
<input type="hidden" name="favoritable_type" value="{{ \App\Models\Tour::class }}">
<input type="hidden" name="favoritable_id" value="{{ $tour->id }}">
<button type="submit" class="travel-favorite-btn {{ $isFavTour ? 'active' : '' }}">
<i class="fa {{ $isFavTour ? 'fa-heart' : 'far fa-heart' }}"></i>
</button>
</form>
@else
<a href="{{ route('login') }}" class="travel-favorite-btn">
<i class="far fa-heart"></i>
</a>
@endauth
</div> </div>
@empty @empty
<p>تور فعالی یافت نشد.</p> <p>تور فعالی یافت نشد.</p>
@@ -15,4 +69,26 @@
</div> </div>
{{ $tours->links() }} {{ $tours->links() }}
@endsection @endsection
{{--@extends('layouts.app')--}}
{{--@section('title', 'لیست تورها')--}}
{{--@section('content')--}}
{{-- <h1>تورهای ما</h1>--}}
{{-- <div class="row">--}}
{{-- @forelse($tours as $tour)--}}
{{-- <div class="col-md-4">--}}
{{-- <x-tour :tour="$tour"/>--}}
{{-- </div>--}}
{{-- @empty--}}
{{-- <p>تور فعالی یافت نشد.</p>--}}
{{-- @endforelse--}}
{{-- </div>--}}
{{-- {{ $tours->links() }}--}}
{{--@endsection--}}
+346 -6
View File
@@ -1,7 +1,31 @@
@extends('layouts.app') @extends('layouts.app')
@section('title', $tour->title)
@section('content') @section('content')
<style>
.travel-favorite-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: transparent;
border: 1px solid #e0245e;
color: #e0245e;
border-radius: 25px;
padding: 8px 20px;
font-size: .9rem;
cursor: pointer;
transition: all .2s ease;
}
.travel-favorite-btn:hover,
.travel-favorite-btn.active {
background: #e0245e;
color: #fff;
}
</style>
<div class="tour-details-page"> <div class="tour-details-page">
<div class="container"> <div class="container">
@@ -37,6 +61,25 @@
{{ $tour->title }} {{ $tour->title }}
</h1> </h1>
{{-- دکمه علاقهمندی --}}
@auth
<form action="{{ route('favorites.toggle') }}" method="POST" class="mb-3">
@csrf
<input type="hidden" name="favoritable_type" value="{{ \App\Models\Tour::class }}">
<input type="hidden" name="favoritable_id" value="{{ $tour->id }}">
<button type="submit" class="travel-favorite-btn {{ $isFav ? 'active' : '' }}">
<i class="fa {{ $isFav ? 'fa-heart' : 'far fa-heart' }}"></i>
<span>{{ $isFav ? 'در علاقهمندیها' : 'افزودن به علاقهمندی' }}</span>
</button>
</form>
@else
<a href="{{ route('login') }}" class="travel-favorite-btn">
<i class="far fa-heart"></i>
<span>افزودن به علاقهمندی</span>
</a>
@endauth
{{-- اطلاعات اصلی --}} {{-- اطلاعات اصلی --}}
<div class="tour-details-info"> <div class="tour-details-info">
@@ -293,6 +336,303 @@
@endsection @endsection
{{--@extends('layouts.app')--}}
{{--@section('content')--}}
{{-- <div class="tour-details-page">--}}
{{-- <div class="container">--}}
{{-- <div class="tour-details-card">--}}
{{-- --}}{{-- تصویر تور --}}
{{-- <div class="tour-details-image">--}}
{{-- @if($tour->image)--}}
{{-- <img--}}
{{-- src="{{ asset('storage/' . $tour->image) }}"--}}
{{-- alt="{{ $tour->title }}"--}}
{{-- >--}}
{{-- @else--}}
{{-- <div class="tour-no-image">--}}
{{-- تصویر برای این تور ثبت نشده است--}}
{{-- </div>--}}
{{-- @endif--}}
{{-- </div>--}}
{{-- --}}{{-- محتوای تور --}}
{{-- <div class="tour-details-content">--}}
{{-- --}}{{-- عنوان --}}
{{-- <h1 class="tour-details-title">--}}
{{-- {{ $tour->title }}--}}
{{-- </h1>--}}
{{-- --}}{{-- اطلاعات اصلی --}}
{{-- <div class="tour-details-info">--}}
{{-- --}}{{-- نوع سفر --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- نوع سفر--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $tour->travelType->title ?? 'نامشخص' }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- تاریخ شروع --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- تاریخ حرکت--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $tour->start_date--}}
{{-- ? \Carbon\Carbon::parse($tour->start_date)->format('Y/m/d')--}}
{{-- : 'نامشخص'--}}
{{-- }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- تاریخ پایان --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- تاریخ پایان--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $tour->end_date--}}
{{-- ? \Carbon\Carbon::parse($tour->end_date)->format('Y/m/d')--}}
{{-- : 'نامشخص'--}}
{{-- }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- مدت سفر --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- مدت سفر--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $tour->duration ?? 'نامشخص' }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- قیمت --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- قیمت--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ number_format($tour->price) }}--}}
{{-- تومان--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- ظرفیت --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- ظرفیت--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $tour->capacity ?? 'نامحدود' }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- --}}{{-- تعداد بازدید --}}
{{-- <div class="tour-info-box">--}}
{{-- <span class="tour-info-label">--}}
{{-- تعداد بازدید--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ number_format($tour->views_count) }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- --}}{{-- توضیحات --}}
{{-- <div class="tour-description">--}}
{{-- <h2>--}}
{{-- توضیحات سفر--}}
{{-- </h2>--}}
{{-- @if($tour->description)--}}
{{-- <p>--}}
{{-- {{ $tour->description }}--}}
{{-- </p>--}}
{{-- @else--}}
{{-- <p>--}}
{{-- توضیحی برای این تور ثبت نشده است.--}}
{{-- </p>--}}
{{-- @endif--}}
{{-- </div>--}}
{{-- --}}{{-- پروازهای تور --}}
{{-- @if($tour->flights && $tour->flights->count())--}}
{{-- <div class="tour-flights">--}}
{{-- <h2>--}}
{{-- پروازهای این تور--}}
{{-- </h2>--}}
{{-- @foreach($tour->flights as $flight)--}}
{{-- <div class="flight-item">--}}
{{-- <div>--}}
{{-- <span>--}}
{{-- مبدأ--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $flight->origin ?? '-' }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- <div>--}}
{{-- <span>--}}
{{-- مقصد--}}
{{-- </span>--}}
{{-- <strong>--}}
{{-- {{ $flight->destination ?? '-' }}--}}
{{-- </strong>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- @endforeach--}}
{{-- </div>--}}
{{-- @endif--}}
{{-- --}}{{-- تورهای مرتبط --}}
{{-- @if($relatedTours->count())--}}
{{-- <div class="related-tours">--}}
{{-- <h2>--}}
{{-- تورهای مرتبط--}}
{{-- </h2>--}}
{{-- <div class="related-tours-grid">--}}
{{-- @foreach($relatedTours as $relatedTour)--}}
{{-- <a--}}
{{-- href="{{ route('tours.show', $relatedTour) }}"--}}
{{-- class="related-tour-card"--}}
{{-- >--}}
{{-- @if($relatedTour->image)--}}
{{-- <img--}}
{{-- src="{{ asset('storage/' . $relatedTour->image) }}"--}}
{{-- alt="{{ $relatedTour->title }}"--}}
{{-- >--}}
{{-- @endif--}}
{{-- <div>--}}
{{-- <h3>--}}
{{-- {{ $relatedTour->title }}--}}
{{-- </h3>--}}
{{-- <span>--}}
{{-- @if($relatedTour->start_date)--}}
{{-- {{ \Carbon\Carbon::parse($relatedTour->start_date)->format('Y/m/d') }}--}}
{{-- @endif--}}
{{-- </span>--}}
{{-- </div>--}}
{{-- </a>--}}
{{-- @endforeach--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- @endif--}}
{{-- --}}{{-- دکمه بازگشت --}}
{{-- <div class="tour-back">--}}
{{-- <a--}}
{{-- href="{{ route('tours.index') }}"--}}
{{-- class="tour-back-button"--}}
{{-- >--}}
{{-- بازگشت به لیست تورها--}}
{{-- </a>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- </div>--}}
{{--@endsection--}}
{{-- resources/views/tours/show.blade.php --}} {{-- resources/views/tours/show.blade.php --}}
{{--@extends('layouts.app')--}} {{--@extends('layouts.app')--}}
@@ -301,7 +641,7 @@
{{--@section('content')--}} {{--@section('content')--}}
{{-- <div class="container py-5 mt-4">--}} {{-- <div class="container py-5 mt-4">--}}
{{-- --}}{{-- اطلاعات تور --}} {{-- --}}{{----}}{{-- اطلاعات تور --}}
{{-- <div class="card mb-4 shadow-sm">--}} {{-- <div class="card mb-4 shadow-sm">--}}
{{-- <div class="card-body">--}} {{-- <div class="card-body">--}}
{{-- <h2 class="card-title">{{ $tour->name }}</h2>--}} {{-- <h2 class="card-title">{{ $tour->name }}</h2>--}}
@@ -325,7 +665,7 @@
{{-- </div>--}} {{-- </div>--}}
{{-- </section>--}} {{-- </section>--}}
{{-- --}}{{-- ===== فرم رزرو تور ===== --}} {{-- --}}{{----}}{{-- ===== فرم رزرو تور ===== --}}
{{-- @auth--}} {{-- @auth--}}
{{-- <section class="pb-5">--}} {{-- <section class="pb-5">--}}
{{-- <div class="container">--}} {{-- <div class="container">--}}
@@ -351,7 +691,7 @@
{{-- </div>--}} {{-- </div>--}}
{{-- </section>--}} {{-- </section>--}}
{{-- @else--}} {{-- @else--}}
{{-- --}}{{-- کاربر لاگین نیست => دکمه ورود به روت login --}} {{-- --}}{{----}}{{-- کاربر لاگین نیست => دکمه ورود به روت login --}}
{{-- <div class="alert alert-warning text-center">--}} {{-- <div class="alert alert-warning text-center">--}}
{{-- برای رزرو تور ابتدا <a href="{{ route('login') }}">وارد شوید</a>.--}} {{-- برای رزرو تور ابتدا <a href="{{ route('login') }}">وارد شوید</a>.--}}
{{-- </div>--}} {{-- </div>--}}
@@ -365,7 +705,7 @@
{{--@section('content')--}} {{--@section('content')--}}
{{-- <h1>{{ $tour->title }}</h1>--}} {{-- <h1>{{ $tour->title }}</h1>--}}
{{-- --}}{{----}}{{-- گالری تصاویر (اسلایدر) --}} {{-- --}}{{----}}{{----}}{{----}}{{-- گالری تصاویر (اسلایدر) --}}
{{-- <div id="tourGallery" class="carousel slide" data-bs-ride="carousel">--}} {{-- <div id="tourGallery" class="carousel slide" data-bs-ride="carousel">--}}
{{-- @foreach ($tour->images as $image)--}} {{-- @foreach ($tour->images as $image)--}}
{{-- <div class="carousel-item @if($loop->first) active @endif">--}} {{-- <div class="carousel-item @if($loop->first) active @endif">--}}
@@ -377,13 +717,13 @@
{{-- <p>{{ $tour->description }}</p>--}} {{-- <p>{{ $tour->description }}</p>--}}
{{-- <p>تاریخ شروع: {{ $tour->start_date?->format('Y/m/d') }}</p>--}} {{-- <p>تاریخ شروع: {{ $tour->start_date?->format('Y/m/d') }}</p>--}}
{{-- --}}{{----}}{{-- پروازهای این تور --}} {{-- --}}{{----}}{{----}}{{----}}{{-- پروازهای این تور --}}
{{-- <h3>پروازهای همراه</h3>--}} {{-- <h3>پروازهای همراه</h3>--}}
{{-- @foreach ($tour->flights as $flight)--}} {{-- @foreach ($tour->flights as $flight)--}}
{{-- <x-flight :flight="$flight"/>--}} {{-- <x-flight :flight="$flight"/>--}}
{{-- @endforeach--}} {{-- @endforeach--}}
{{-- --}}{{----}}{{-- نظرات --}} {{-- --}}{{----}}{{----}}{{----}}{{-- نظرات --}}
{{-- <h3>نظرات ({{ $tour->comments->count() }})</h3>--}} {{-- <h3>نظرات ({{ $tour->comments->count() }})</h3>--}}
{{-- @foreach ($tour->comments as $comment)--}} {{-- @foreach ($tour->comments as $comment)--}}
{{-- <x-comment :comment="$comment"/>--}} {{-- <x-comment :comment="$comment"/>--}}