git commit "add comments and fix tour comments display"

This commit is contained in:
2026-08-17 13:33:37 +03:30
parent 970786ae94
commit 902384625b
11 changed files with 729 additions and 166 deletions
@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Http\Requests\CommentStoreRequest;
use App\Models\Comment;
use Illuminate\Http\Request;
@@ -18,4 +19,27 @@ class CommentController extends Controller
return view('account.users.comments.index',compact('comments'));
}
public function store(CommentStoreRequest $request)
{
$data = $request->validated();
$data['user_id'] = auth()->id();
$data['status'] = 0;
Comment::create([
'flight_id' => $request->flight_id,
'tour_id' => $request->tour_id,
'comment' => $request->comment,
'rating' => $request->rating,
'user_id' => auth()->id(),
'status' => 0,
]);
return back()->with(
'success',
'نظر شما با موفقیت ثبت شد و پس از بررسی نمایش داده می‌شود.'
);
}
}
+6 -1
View File
@@ -24,7 +24,12 @@ class FlightController extends Controller
{
$flight->increment('views');
return view('flights.show', compact('flight'));
$comments = $flight->comments()
->latest()
->get();
return view('flights.show', compact('flight','comments'));
}
}
+6 -1
View File
@@ -24,7 +24,12 @@ class TourController extends Controller
public function show(Tour $tour)
{
$tour->load('travelType');
$tour->increment('views');
$comments = $tour->comments()
->latest()
->get();
return view('tours.show', compact('tour'));
return view('tours.show', compact('tour','comments'));
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class CommentStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'tour_id' => [
'nullable',
'exists:tours,id',
'required_without:flight_id',
],
'flight_id' => [
'nullable',
'exists:flights,id',
'required_without:tour_id',
],
'rating' => [
'required',
'integer',
'min:1',
'max:5',
],
'comment' => [
'nullable',
'string',
'max:1000',
],
];
}
}
+18 -11
View File
@@ -11,16 +11,17 @@ use Illuminate\Database\Eloquent\Model;
/**
* Class Comment
*
*
* @property int $id
* @property int $user_id
* @property int $tour_id
* @property int $flight_id
* @property int $rating
* @property string|null $comment
* @property int $status
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*
*
* @property Tour $tour
* @property User $user
*
@@ -34,6 +35,7 @@ class Comment extends Model
protected $casts = [
'user_id' => 'int',
'tour_id' => 'int',
'flight_id' => 'int',
'rating' => 'int',
'status' => 'int'
];
@@ -41,18 +43,23 @@ class Comment extends Model
protected $fillable = [
'user_id',
'tour_id',
'flight_id',
'rating',
'comment',
'status'
];
public function tour()
{
return $this->belongsTo(Tour::class);
}
public function tour()
{
return $this->belongsTo(Tour::class, 'tour_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
}
public function flight()
{
return $this->belongsTo(Flight::class, 'flight_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}}
+52 -42
View File
@@ -23,10 +23,10 @@ use Illuminate\Database\Eloquent\Model;
* @property Carbon|null $departure_time
* @property Carbon|null $arrival_time
* @property string|null $flight_class
* @property string|null $image
* @property string $views
* @property int|null $capacity
* @property float|null $price
* @property string|null $image
* @property string|null $views
* @property int|null $status
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
@@ -35,49 +35,59 @@ use Illuminate\Database\Eloquent\Model;
*/
class Flight extends Model
{
protected $table = 'flights';
public static $snakeAttributes = false;
protected $table = 'flights';
public static $snakeAttributes = false;
protected $casts = [
'departure_date' => 'datetime',
'departure_time' => 'datetime',
'arrival_time' => 'datetime',
'capacity' => 'int',
'price' => 'float',
'status' => 'int'
];
protected $casts = [
'departure_date' => 'datetime',
'departure_time' => 'datetime',
'arrival_time' => 'datetime',
'capacity' => 'int',
'price' => 'float',
'status' => 'int'
];
protected $fillable = [
'airline',
'flight_number',
'origin',
'destination',
'departure_date',
'departure_time',
'arrival_time',
'flight_class',
'capacity',
'price',
protected $fillable = [
'airline',
'flight_number',
'origin',
'destination',
'departure_date',
'departure_time',
'arrival_time',
'flight_class',
'capacity',
'price',
'image',
'views',
'status'
];
'status'
];
#[Scope]
public function applySearch(Builder $query, array $filters): void
{
$query
->when($filters['origin'] ?? null, function ($query, $origin) {
$query->where('origin', $origin);
})
->when($filters['destination'] ?? null, function ($query, $destination) {
$query->where('destination', $destination);
})
->when($filters['departure_date'] ?? null, function ($query, $date) {
$query->whereDate('departure_date', $date);
})
->when($filters['passengers'] ?? null, function ($query, $passengers) {
$query->where('capacity', '>=', $passengers);
});
public function travelType()
{
return $this->belongsTo(TravelType::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
#[Scope]
public function applySearch(Builder $query, array $filters): void
{
$query
->when($filters['origin'] ?? null, function ($query, $origin) {
$query->where('origin', $origin);
})
->when($filters['destination'] ?? null, function ($query, $destination) {
$query->where('destination', $destination);
})
->when($filters['departure_date'] ?? null, function ($query, $date) {
$query->whereDate('departure_date', $date);
})
->when($filters['passengers'] ?? null, function ($query, $passengers) {
$query->where('capacity', '>=', $passengers);
});
}
}
}
+2
View File
@@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Model;
* @property string $summary
* @property string $description
* @property string $image
* @property string $views
* @property int $travel_type_id
* @property float $price
* @property Carbon $departure_date
@@ -46,6 +47,7 @@ class Tour extends Model
'summary',
'description',
'image',
'views',
'travel_type_id',
'price',
'departure_date',
+4
View File
@@ -37,4 +37,8 @@ class TravelType extends Model
{
return $this->hasMany(Tour::class, 'travel_type_id');
}
public function flights()
{
return $this->hasMany(Flight::class, 'travel_type_id');
}
}
+477 -111
View File
@@ -1,161 +1,424 @@
<style>
.flight-detail-page {
padding: 70px 0;
background: #f5f7fb;
direction: rtl;
}
.flight-detail-card {
background: #fff;
border-radius: 25px;
overflow: hidden;
display: flex;
gap: 0;
box-shadow: 0 15px 50px rgba(0,0,0,.12);
.flight-detail-page{
padding:80px 0;
background:
linear-gradient(135deg,#eef5ff,#f9fbff);
direction:rtl;
}
.flight-detail-image {
width: 40%;
min-height: 450px;
}
/* کارت اصلی */
.flight-detail-card{
.flight-detail-image img {
width: 100%;
height: 100%;
object-fit: cover;
background:rgba(255,255,255,.85);
backdrop-filter:blur(15px);
border-radius:35px;
overflow:hidden;
display:flex;
box-shadow:
0 25px 70px rgba(0,0,0,.12);
transition:.4s;
}
.flight-detail-content {
width: 60%;
padding: 45px;
.flight-detail-card:hover{
transform:translateY(-8px);
box-shadow:
0 35px 90px rgba(25,118,210,.25);
}
.flight-detail-content h1 {
font-size: 34px;
font-weight: 800;
color: #102a43;
margin: 0;
/* تصویر */
.flight-detail-image{
width:42%;
min-height:600px;
position:relative;
}
.flight-badge-show {
background: #1976d2;
color: white;
padding: 8px 22px;
border-radius: 50px;
font-size: 14px;
.flight-detail-image:after{
content:"";
position:absolute;
inset:0;
background:
linear-gradient(
180deg,
transparent,
rgba(0,0,0,.35)
);
}
.route-box {
margin: 35px 0;
padding: 25px;
border-radius: 20px;
background: #f8fafc;
display: flex;
align-items: center;
justify-content: space-around;
}
.flight-detail-image img{
.route-box div {
text-align: center;
}
.route-box small {
display: block;
color: #78909c;
margin-bottom: 8px;
}
.route-box strong {
font-size: 22px;
color: #102a43;
}
.route-line {
font-size: 30px;
color: #f4b400;
}
.flight-info-grid {
display: grid;
grid-template-columns: repeat(2,1fr);
gap: 20px;
margin-bottom: 35px;
}
.flight-info-grid div {
background: #f8fafc;
padding: 18px;
border-radius: 15px;
}
.flight-info-grid span {
display:block;
color:#78909c;
font-size:13px;
margin-bottom:8px;
}
.flight-info-grid b {
font-size:18px;
color:#102a43;
}
.buy-flight-btn {
display:inline-block;
width:100%;
text-align:center;
background:#1976d2;
height:100%;
object-fit:cover;
}
/* محتوا */
.flight-detail-content{
width:58%;
padding:55px;
}
.flight-detail-content h1{
font-size:38px;
font-weight:900;
color:#102a43;
}
/* badge */
.flight-badge-show{
background:
linear-gradient(135deg,#1976d2,#42a5f5);
color:white;
padding:15px;
border-radius:15px;
text-decoration:none;
font-weight:700;
padding:10px 30px;
border-radius:50px;
font-weight:bold;
box-shadow:
0 10px 25px rgba(25,118,210,.35);
}
/* مسیر */
.route-box{
margin:40px 0;
padding:35px;
border-radius:25px;
background:#fff;
display:flex;
justify-content:space-around;
align-items:center;
box-shadow:
inset 0 0 20px rgba(0,0,0,.04);
}
.route-box small{
color:#90a4ae;
font-size:14px;
}
.route-box strong{
display:block;
margin-top:8px;
font-size:28px;
color:#102a43;
}
.route-line{
font-size:45px;
color:#f4b400;
animation:fly 2s infinite alternate;
}
@keyframes fly{
from{
transform:translateX(-10px);
}
to{
transform:translateX(10px);
}
}
/* اطلاعات */
.flight-info-grid{
display:grid;
grid-template-columns:repeat(2,1fr);
gap:20px;
}
.flight-info-grid div{
background:#fff;
padding:22px;
border-radius:20px;
border:1px solid #edf2f7;
transition:.3s;
}
.buy-flight-btn:hover {
background:#125ca8;
color:white;
.flight-info-grid div:hover{
transform:translateY(-5px);
box-shadow:
0 15px 35px rgba(0,0,0,.08);
}
@media(max-width:768px){
.flight-info-grid span{
color:#78909c;
font-size:14px;
}
.flight-info-grid b,
.flight-info-grid strong{
display:block;
margin-top:10px;
color:#102a43;
font-size:20px;
}
/* فرم کامنت */
.flight-comments{
margin-top:45px;
background:#fff;
padding:35px;
border-radius:30px;
box-shadow:
0 15px 40px rgba(0,0,0,.08);
}
.flight-comments h3{
font-weight:900;
color:#102a43;
margin-bottom:25px;
}
.flight-comments textarea{
border-radius:20px;
border:2px solid #edf2f7;
padding:20px;
resize:none;
transition:.3s;
}
.flight-comments textarea:focus{
border-color:#1976d2;
box-shadow:
0 0 0 5px rgba(25,118,210,.12);
}
.flight-comments button{
width:100%;
padding:16px;
border-radius:20px;
border:none;
font-weight:800;
font-size:18px;
background:
linear-gradient(
135deg,
#1976d2,
#42a5f5
);
box-shadow:
0 15px 30px rgba(25,118,210,.35);
transition:.3s;
}
.flight-comments button:hover{
transform:translateY(-4px);
}
/* موبایل */
@media(max-width:900px){
.flight-detail-card{
flex-direction:column;
}
.flight-detail-image,
.flight-detail-content{
width:100%;
}
.flight-detail-image{
height:280px;
height:300px;
}
.flight-detail-content{
padding:30px;
}
.flight-info-grid{
grid-template-columns:1fr;
}
.route-box{
flex-direction:column;
gap:25px;
}
}
</style>
</style>
<section class="flight-detail-page">
<div class="container">
@@ -221,6 +484,109 @@
<b>{{ number_format($flight->price) }} تومان</b>
</div>
<div>
<span>تعداد مشاهده</span>
<strong>
{{ $flight->views ?? 0 }}
بازدید
</strong>
</div>
</div>
<div class="flight-comments mt-5">
<h3>
ثبت نظر درباره این پرواز
</h3>
<form action="{{ route('users.comments.store') }}" method="POST">
@csrf
<input
type="hidden"
name="flight_id"
value="{{ $flight->id }}"
>
<div class="mb-3">
<label for="rating">
امتیاز
</label>
<select name="rating" id="rating" class="form-control">
<option value="5">★★★★★</option>
<option value="4">★★★★</option>
<option value="3">★★★</option>
<option value="2">★★</option>
<option value="1">★</option>
</select>
</div>
<div class="mb-3">
<label for="comment">
نظر شما
</label>
<textarea
name="comment"
id="comment"
rows="5"
class="form-control"
placeholder="نظر خود را درباره این پرواز بنویسید..."
></textarea>
</div>
<button type="submit" class="btn btn-primary">
ثبت نظر
</button>
</form>
<div class="comments-list mt-5">
<h3 class="mb-4">
نظرات کاربران
</h3>
@forelse($comments as $comment)
<div class="card p-4 mb-3 shadow-sm">
<div class="d-flex justify-content-between">
<strong>
{{ $comment->user->name }}
</strong>
<small>
{{ $comment->created_at->diffForHumans() }}
</small>
</div>
<p class="mt-3 mb-0">
{{ $comment->comment }}
</p>
</div>
@empty
<div class="alert alert-secondary">
هنوز نظری ثبت نشده است.
</div>
@endforelse
</div>
</div>
</div>
+86
View File
@@ -102,6 +102,92 @@
{{ $tour->description }}
</p>
<div>
<span>تعداد مشاهده</span>
<strong>
{{ $tour->views ?? 0 }}
بازدید
</strong>
</div>
</div>
<form action="{{ route('users.comments.store') }}" method="POST">
@csrf
<input
type="hidden"
name="tour_id"
value="{{ $tour->id }}"
>
<div class="mb-3">
<label>
امتیاز
</label>
<select name="rating" class="form-control">
<option value="5">★★★★★</option>
<option value="4">★★★★</option>
<option value="3">★★★</option>
<option value="2">★★</option>
<option value="1">★</option>
</select>
</div>
<div class="mb-3">
<label>
نظر شما
</label>
<textarea
name="comment"
rows="5"
class="form-control"
></textarea>
</div>
<button class="btn btn-primary">
ثبت نظر
</button>
</form>
<div class="comments-list mt-5">
<h3 class="mb-4">
نظرات کاربران
</h3>
@forelse($comments as $comment)
<div class="card p-4 mb-3 shadow-sm">
<div class="d-flex justify-content-between">
<strong>
{{ $comment->user->name }}
</strong>
<small>
{{ $comment->created_at->diffForHumans() }}
</small>
</div>
<p class="mt-3 mb-0">
{{ $comment->comment }}
</p>
</div>
@empty
<div class="alert alert-secondary">
هنوز نظری ثبت نشده است.
</div>
@endforelse
</div>
+1
View File
@@ -33,6 +33,7 @@ Route::prefix('users')->name('users.')->middleware('auth:web')->group(function (
Route::delete('/favorite/{favorite}',[FavoriteController::class,'destroy'])->name('favorite.destroy');
Route::get('/comments',[CommentController::class,'index'])->name('comments.index');
Route::post('/comments',[CommentController::class,'store'])->name('comments.store');
Route::get('/cities/{province}', [CityController::class, 'byProvince'])->name('cities.province');