Refactor News management features: replace author text input with a searchable select component in NewsResource, update NewsPageController to fetch news with authors and recent articles, and enhance the news show view to display author details and comments more effectively.
This commit is contained in:
98
app/Filament/Resources/AuthorResource.php
Normal file
98
app/Filament/Resources/AuthorResource.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\AuthorResource\Pages;
|
||||
use App\Filament\Resources\AuthorResource\RelationManagers;
|
||||
use App\Models\Author;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class AuthorResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Author::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-users';
|
||||
|
||||
protected static ?string $navigationGroup = 'News';
|
||||
|
||||
public static function form(Form $form):
|
||||
Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\Card::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
FileUpload::make('profile_image')
|
||||
->label('Profile Image 400x400')
|
||||
->image()
|
||||
->directory('authors')
|
||||
->nullable(),
|
||||
RichEditor::make('description')
|
||||
->nullable()
|
||||
->columnSpanFull(),
|
||||
])->columns(1),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
ImageColumn::make('profile_image')
|
||||
->square()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListAuthors::route('/'),
|
||||
'create' => Pages\CreateAuthor::route('/create'),
|
||||
'edit' => Pages\EditAuthor::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
12
app/Filament/Resources/AuthorResource/Pages/CreateAuthor.php
Normal file
12
app/Filament/Resources/AuthorResource/Pages/CreateAuthor.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\AuthorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AuthorResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAuthor extends CreateRecord
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/AuthorResource/Pages/EditAuthor.php
Normal file
19
app/Filament/Resources/AuthorResource/Pages/EditAuthor.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\AuthorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AuthorResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAuthor extends EditRecord
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/AuthorResource/Pages/ListAuthors.php
Normal file
19
app/Filament/Resources/AuthorResource/Pages/ListAuthors.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\AuthorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AuthorResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAuthors extends ListRecords
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use Filament\Forms;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -48,9 +49,11 @@ class NewsResource extends Resource
|
||||
->directory('news')
|
||||
->nullable()
|
||||
->columnSpanFull(),
|
||||
TextInput::make('author')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
RichEditor::make('content')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
@@ -75,7 +78,8 @@ class NewsResource extends Resource
|
||||
Tables\Columns\TextColumn::make('slug')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('author')
|
||||
Tables\Columns\TextColumn::make('author.name')
|
||||
->label('Author Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('published_at')
|
||||
|
||||
@@ -9,14 +9,17 @@ class NewsPageController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$allNews = News::all();
|
||||
$allNews = News::query()->with('author')->latest()->get();
|
||||
|
||||
return view('web.pages.news.index', compact('allNews'));
|
||||
}
|
||||
|
||||
public function show(News $news)
|
||||
public function show($news)
|
||||
{
|
||||
return view('web.pages.news.show', compact('news'));
|
||||
$news = News::where('slug', $news)->with('author', 'comments')->first();
|
||||
$recentNews = News::query()->with('author')->latest()->limit(3)->get();
|
||||
|
||||
return view('web.pages.news.show', compact('news', 'recentNews'));
|
||||
}
|
||||
|
||||
public function storeComment(Request $request, News $news)
|
||||
|
||||
23
app/Models/Author.php
Normal file
23
app/Models/Author.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Author extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'profile_image',
|
||||
];
|
||||
|
||||
public function news(): HasMany
|
||||
{
|
||||
return $this->hasMany(News::class);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class News extends Model
|
||||
@@ -16,11 +17,16 @@ class News extends Model
|
||||
'content',
|
||||
'image',
|
||||
'published_at',
|
||||
'author',
|
||||
'author_id',
|
||||
];
|
||||
|
||||
public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(Comment::class);
|
||||
}
|
||||
|
||||
public function author(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Author::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('news', function (Blueprint $table) {
|
||||
$table->longText('author_description')->nullable()->after('author');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('news', function (Blueprint $table) {
|
||||
$table->dropColumn('author_description');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('authors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->longText('description')->nullable();
|
||||
$table->string('profile_image')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('authors');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('news', function (Blueprint $table) {
|
||||
$table->foreignId('author_id')->nullable()->constrained()->onDelete('set null');
|
||||
$table->dropColumn('author');
|
||||
$table->dropColumn('author_description');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('news', function (Blueprint $table) {
|
||||
$table->dropForeign(['author_id']);
|
||||
$table->dropColumn('author_id');
|
||||
$table->string('author')->nullable();
|
||||
$table->string('author_description')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -20,56 +20,71 @@
|
||||
</div>
|
||||
<!-- Breadcrumb Area End -->
|
||||
|
||||
<!-- Blog Single Area Start -->
|
||||
<div class="blog-single__area section-padding">
|
||||
<!-- Blog Details Area Start -->
|
||||
<div class="blog__details section-padding">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-lg-8 lg-mb-50">
|
||||
<div class="blog__single-left">
|
||||
<div class="blog__single-left-image">
|
||||
<img src="/storage/{{ $news->image }}" alt="{{ $news->title }}">
|
||||
<div class="col-lg-8 lg-mb-25">
|
||||
<div class="blog__details-area">
|
||||
<img src="/storage/{{ $news->image }}" alt="image">
|
||||
<h3 class="mt-25 mb-20">{{ $news->title }}</h3>
|
||||
<p>{{ $news->description }}</p>
|
||||
|
||||
<div class="blog__details-area-author">
|
||||
<div class="blog__details-area-author-image">
|
||||
<img src="{{ $news->author->profile_image ?? '/web/assets/img/team/member-1.jpg' }}" alt="avatar">
|
||||
</div>
|
||||
<div class="blog__single-left-content">
|
||||
<div class="blog__single-left-content-meta">
|
||||
<ul>
|
||||
<li><i class="fa-regular fa-calendar"></i> {{ \Carbon\Carbon::parse($news->published_at)->format('d M, Y') }}</li>
|
||||
<li><i class="fa-regular fa-user"></i> {{ $news->author }}</li>
|
||||
</ul>
|
||||
<div class="blog__details-area-author-content">
|
||||
<h5>{{ $news->author->name ?? 'Admin' }}</h5>
|
||||
<p>{{ $news->description ?? 'Super Admin' }}</p>
|
||||
</div>
|
||||
<h3 class="mb-20">{{ $news->title }}</h3>
|
||||
{!! $news->content !!}
|
||||
</div>
|
||||
<!-- Comments Section -->
|
||||
<div class="comments-area">
|
||||
<h3 class="comments-title">Comments ({{ $news->comments->count() }})</h3>
|
||||
<div class="comments-list">
|
||||
@foreach ($news->comments as $comment)
|
||||
<div class="single-comment-item">
|
||||
<div class="single-comment-item-content">
|
||||
<h5>{{ $comment->title }}</h5>
|
||||
<span>By {{ $comment->author_name ?? 'Anonymous' }} on {{ \Carbon\Carbon::parse($comment->created_at)->format('d M, Y') }}</span>
|
||||
<div class="blog__details-area-comment mt-40">
|
||||
<h3 class="mb-30">Comments ({{ $news->comments->count() }})</h3>
|
||||
@forelse ($news->comments as $comment)
|
||||
<div class="blog__details-area-comment-item">
|
||||
<div class="blog__details-area-comment-item-comment">
|
||||
<div class="blog__details-area-comment-item-comment-image">
|
||||
<img src="/web/assets/img/team/member-5.jpg" alt="avatar-image">
|
||||
</div>
|
||||
<div class="blog__details-area-comment-item-comment-content">
|
||||
<h5>{{ $comment->title ?? '-' }}</h5>
|
||||
<span>Submitted {{ $comment->created_at->diffForHumans() }} by {{ $comment->author_name ?? 'Anonymous' }}</span>
|
||||
<p>{{ $comment->message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="comment-form-area">
|
||||
<h3 class="comment-form-title">Leave a Comment</h3>
|
||||
@empty
|
||||
<p>No comments yet</p>
|
||||
@endforelse
|
||||
</div>
|
||||
<div class="blog__details-area-contact mt-60">
|
||||
<h3>Post Comment</h3>
|
||||
<p>Required fields are marked</p>
|
||||
<div class="blog__details-area-contact-form">
|
||||
<form action="{{ route('comments.store', $news->slug) }}" method="POST">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-lg-6 mb-25">
|
||||
<input type="text" name="title" placeholder="Comment Title*" required>
|
||||
<div class="col-sm-6 mt-25">
|
||||
<div class="blog__details-area-contact-form-item contact-item">
|
||||
<input type="text" name="author_name" placeholder="Full Name" required="required">
|
||||
</div>
|
||||
<div class="col-lg-6 mb-25">
|
||||
<input type="text" name="author_name" placeholder="Your Name (Optional)">
|
||||
</div>
|
||||
<div class="col-lg-12 mb-25">
|
||||
<textarea name="message" rows="5" placeholder="Your Message*" required></textarea>
|
||||
<div class="col-sm-6 mt-25">
|
||||
<div class="blog__details-area-contact-form-item contact-item">
|
||||
<input type="text" name="title" placeholder="Title" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 mt-25">
|
||||
<div class="blog__details-area-contact-form-item contact-item">
|
||||
<textarea name="message" placeholder="Type your comments...."></textarea>
|
||||
<input type="hidden" name="news_id" value="{{ $news->id }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 mt-25">
|
||||
<div class="blog__details-area-contact-form-item">
|
||||
<button class="build_button" type="submit">Submit Comment<i class="flaticon-right-up"></i></button>
|
||||
</div>
|
||||
<div class="col-lg-12">
|
||||
<button type="submit" class="theme-btn">Post Comment<i class="flaticon-right-up"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -77,11 +92,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-4">
|
||||
<!-- You can add a sidebar here if needed -->
|
||||
<div class="col-lg-4 columns_sticky">
|
||||
<div class="all__sidebar">
|
||||
|
||||
<div class="all__sidebar-item">
|
||||
<h4>Recent Blog</h4>
|
||||
<div class="all__sidebar-item-post dark_image">
|
||||
@foreach($recentNews as $news)
|
||||
<div class="post__item">
|
||||
<div class="post__item-image">
|
||||
<a href="{{ route('news.show', $news->slug) }}"><img src="/storage/{{ $news->image }}" alt="post-image"></a>
|
||||
</div>
|
||||
<div class="post__item-title">
|
||||
<h6><a href="{{ route('news.show', $news->slug) }}">{{ $news->title }}</a></h6>
|
||||
<span><i class="far fa-calendar-alt"></i>{{ \Carbon\Carbon::parse($news->published_at)->format('d M Y') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Blog Single Area End -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Blog Details Area End -->
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user