From 3339a452432fae678140809830bb97d1c71bb3d0 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Fri, 3 Apr 2026 05:18:34 +0700 Subject: [PATCH] feat: tambah modul blog dengan resource Filament, halaman publik, dan PostSeeder --- .../Resources/Posts/Pages/CreatePost.php | 11 +++ .../Resources/Posts/Pages/EditPost.php | 19 +++++ .../Resources/Posts/Pages/ListPosts.php | 19 +++++ app/Filament/Resources/Posts/PostResource.php | 40 ++++++++++ .../Resources/Posts/Schemas/PostForm.php | 33 ++++++++ .../Resources/Posts/Tables/PostsTable.php | 39 ++++++++++ app/Http/Controllers/PublicController.php | 15 ++++ app/Models/Post.php | 31 ++++++++ app/Policies/PostPolicy.php | 75 +++++++++++++++++++ .../2026_04_02_221324_create_posts_table.php | 27 +++++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/PostSeeder.php | 43 +++++++++++ resources/views/public/blog-detail.blade.php | 26 +++++++ resources/views/public/blog.blade.php | 31 ++++++++ resources/views/public/layout.blade.php | 1 + routes/web.php | 2 + 16 files changed, 413 insertions(+) create mode 100644 app/Filament/Resources/Posts/Pages/CreatePost.php create mode 100644 app/Filament/Resources/Posts/Pages/EditPost.php create mode 100644 app/Filament/Resources/Posts/Pages/ListPosts.php create mode 100644 app/Filament/Resources/Posts/PostResource.php create mode 100644 app/Filament/Resources/Posts/Schemas/PostForm.php create mode 100644 app/Filament/Resources/Posts/Tables/PostsTable.php create mode 100644 app/Models/Post.php create mode 100644 app/Policies/PostPolicy.php create mode 100644 database/migrations/2026_04_02_221324_create_posts_table.php create mode 100644 database/seeders/PostSeeder.php create mode 100644 resources/views/public/blog-detail.blade.php create mode 100644 resources/views/public/blog.blade.php diff --git a/app/Filament/Resources/Posts/Pages/CreatePost.php b/app/Filament/Resources/Posts/Pages/CreatePost.php new file mode 100644 index 0000000..47a1e04 --- /dev/null +++ b/app/Filament/Resources/Posts/Pages/CreatePost.php @@ -0,0 +1,11 @@ + ListPosts::route('/'), + 'create' => CreatePost::route('/create'), + 'edit' => EditPost::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/Posts/Schemas/PostForm.php b/app/Filament/Resources/Posts/Schemas/PostForm.php new file mode 100644 index 0000000..aaa744c --- /dev/null +++ b/app/Filament/Resources/Posts/Schemas/PostForm.php @@ -0,0 +1,33 @@ +components([ + TextInput::make('title')->label('Judul')->required() + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, $set) => $set('slug', Str::slug($state))), + TextInput::make('slug')->required()->unique(ignoreRecord: true), + Select::make('category')->label('Kategori') + ->options([ + 'umum' => 'Umum', + 'pengumuman' => 'Pengumuman', + 'berita' => 'Berita', + ]) + ->default('umum')->required(), + DateTimePicker::make('published_at')->label('Tanggal Publikasi') + ->helperText('Kosongkan untuk menyimpan sebagai draft'), + RichEditor::make('content')->label('Konten')->required()->columnSpanFull(), + ]); + } +} diff --git a/app/Filament/Resources/Posts/Tables/PostsTable.php b/app/Filament/Resources/Posts/Tables/PostsTable.php new file mode 100644 index 0000000..21cf0d5 --- /dev/null +++ b/app/Filament/Resources/Posts/Tables/PostsTable.php @@ -0,0 +1,39 @@ +columns([ + TextColumn::make('title')->label('Judul')->searchable()->sortable(), + TextColumn::make('category')->label('Kategori')->badge() + ->color(fn ($state) => match ($state) { + 'pengumuman' => 'warning', + 'berita' => 'info', + default => 'gray', + }), + TextColumn::make('author.name')->label('Penulis'), + TextColumn::make('published_at')->label('Dipublikasi') + ->dateTime('d M Y')->default('Draft')->sortable(), + ]) + ->filters([ + SelectFilter::make('category')->options([ + 'umum' => 'Umum', + 'pengumuman' => 'Pengumuman', + 'berita' => 'Berita', + ]), + ]) + ->recordActions([EditAction::make()]) + ->toolbarActions([BulkActionGroup::make([DeleteBulkAction::make()])]); + } +} diff --git a/app/Http/Controllers/PublicController.php b/app/Http/Controllers/PublicController.php index 99817d1..2d7ec2d 100644 --- a/app/Http/Controllers/PublicController.php +++ b/app/Http/Controllers/PublicController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use App\Models\Activity; use App\Models\Division; +use App\Models\Post; use App\Models\User; class PublicController extends Controller @@ -39,4 +40,18 @@ class PublicController extends Controller return view('public.kegiatan-detail', compact('activity')); } + + public function blog() + { + return view('public.blog', [ + 'posts' => Post::published()->with('author')->latest('published_at')->paginate(9), + ]); + } + + public function blogDetail(Post $post) + { + abort_if(! $post->published_at || $post->published_at->isFuture(), 404); + + return view('public.blog-detail', compact('post')); + } } diff --git a/app/Models/Post.php b/app/Models/Post.php new file mode 100644 index 0000000..b59406c --- /dev/null +++ b/app/Models/Post.php @@ -0,0 +1,31 @@ + 'datetime']; + + protected static function booted(): void + { + static::creating(function (Post $post) { + $post->slug ??= Str::slug($post->title); + }); + } + + public function author(): BelongsTo + { + return $this->belongsTo(User::class, 'author_id'); + } + + public function scopePublished($query) + { + return $query->whereNotNull('published_at')->where('published_at', '<=', now()); + } +} diff --git a/app/Policies/PostPolicy.php b/app/Policies/PostPolicy.php new file mode 100644 index 0000000..5b605ae --- /dev/null +++ b/app/Policies/PostPolicy.php @@ -0,0 +1,75 @@ +can('ViewAny:Post'); + } + + public function view(AuthUser $authUser, Post $post): bool + { + return $authUser->can('View:Post'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Post'); + } + + public function update(AuthUser $authUser, Post $post): bool + { + return $authUser->can('Update:Post'); + } + + public function delete(AuthUser $authUser, Post $post): bool + { + return $authUser->can('Delete:Post'); + } + + public function deleteAny(AuthUser $authUser): bool + { + return $authUser->can('DeleteAny:Post'); + } + + public function restore(AuthUser $authUser, Post $post): bool + { + return $authUser->can('Restore:Post'); + } + + public function forceDelete(AuthUser $authUser, Post $post): bool + { + return $authUser->can('ForceDelete:Post'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Post'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Post'); + } + + public function replicate(AuthUser $authUser, Post $post): bool + { + return $authUser->can('Replicate:Post'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Post'); + } + +} \ No newline at end of file diff --git a/database/migrations/2026_04_02_221324_create_posts_table.php b/database/migrations/2026_04_02_221324_create_posts_table.php new file mode 100644 index 0000000..dcdc877 --- /dev/null +++ b/database/migrations/2026_04_02_221324_create_posts_table.php @@ -0,0 +1,27 @@ +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->string('category')->default('umum'); // umum, pengumuman, berita + $table->longText('content'); + $table->foreignId('author_id')->constrained('users'); + $table->timestamp('published_at')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('posts'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 8bca644..42ecb62 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -16,6 +16,7 @@ class DatabaseSeeder extends Seeder ActivitySeeder::class, CashSeeder::class, VoteSeeder::class, + PostSeeder::class, ]); } } diff --git a/database/seeders/PostSeeder.php b/database/seeders/PostSeeder.php new file mode 100644 index 0000000..89c0cdf --- /dev/null +++ b/database/seeders/PostSeeder.php @@ -0,0 +1,43 @@ +first() ?? User::first(); + + $posts = [ + [ + 'title' => 'Selamat Datang di Website Persegi', + 'category' => 'pengumuman', + 'content' => '

Kami dengan bangga mempersembahkan website resmi organisasi Persegi. Melalui website ini, masyarakat dapat mengikuti perkembangan kegiatan dan informasi terbaru dari organisasi kami.

', + 'published_at' => now()->subDays(10), + ], + [ + 'title' => 'Rekrutmen Anggota Baru 2026', + 'category' => 'pengumuman', + 'content' => '

Persegi membuka pendaftaran anggota baru untuk periode 2026. Bagi pemuda Desa Karangdadap yang ingin bergabung, silakan hubungi pengurus melalui kontak yang tersedia.

Pendaftaran dibuka hingga akhir bulan April 2026.

', + 'published_at' => now()->subDays(5), + ], + [ + 'title' => 'Laporan Kegiatan Kerja Bakti Desa', + 'category' => 'berita', + 'content' => '

Kegiatan kerja bakti yang dilaksanakan pada bulan lalu berjalan dengan lancar. Sebanyak 30 anggota turut berpartisipasi dalam membersihkan lingkungan desa.

Terima kasih kepada seluruh anggota yang telah berkontribusi.

', + 'published_at' => now()->subDays(2), + ], + ]; + + foreach ($posts as $data) { + Post::firstOrCreate( + ['slug' => \Illuminate\Support\Str::slug($data['title'])], + array_merge($data, ['author_id' => $author->id]) + ); + } + } +} diff --git a/resources/views/public/blog-detail.blade.php b/resources/views/public/blog-detail.blade.php new file mode 100644 index 0000000..de5bd83 --- /dev/null +++ b/resources/views/public/blog-detail.blade.php @@ -0,0 +1,26 @@ +@extends('public.layout') + +@section('title', $post->title) + +@section('content') + +← Kembali ke Blog + +
+
+ + {{ ucfirst($post->category) }} + + {{ $post->published_at->format('d M Y') }} +
+ +

{{ $post->title }}

+

Oleh {{ $post->author->name }}

+ +
+ {!! $post->content !!} +
+
+ +@endsection diff --git a/resources/views/public/blog.blade.php b/resources/views/public/blog.blade.php new file mode 100644 index 0000000..24e186d --- /dev/null +++ b/resources/views/public/blog.blade.php @@ -0,0 +1,31 @@ +@extends('public.layout') + +@section('title', 'Blog') + +@section('content') + +

Blog

+

Artikel, pengumuman, dan berita dari Persegi

+ +
+ @forelse($posts as $post) + +
+ + {{ ucfirst($post->category) }} + + {{ $post->published_at->format('d M Y') }} +
+

{{ $post->title }}

+

{{ strip_tags($post->content) }}

+
{{ $post->author->name }}
+
+ @empty +

Belum ada artikel.

+ @endforelse +
+ +
{{ $posts->links() }}
+ +@endsection diff --git a/resources/views/public/layout.blade.php b/resources/views/public/layout.blade.php index b40dc54..8bc3aac 100644 --- a/resources/views/public/layout.blade.php +++ b/resources/views/public/layout.blade.php @@ -15,6 +15,7 @@ Beranda Tentang Kegiatan + Blog Login diff --git a/routes/web.php b/routes/web.php index 54fa156..840bf14 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,3 +6,5 @@ Route::get('/', [\App\Http\Controllers\PublicController::class, 'home'])->name(' Route::get('/tentang', [\App\Http\Controllers\PublicController::class, 'tentang'])->name('tentang'); Route::get('/kegiatan', [\App\Http\Controllers\PublicController::class, 'kegiatan'])->name('kegiatan'); Route::get('/kegiatan/{activity}', [\App\Http\Controllers\PublicController::class, 'kegiatanDetail'])->name('kegiatan.detail'); +Route::get('/blog', [\App\Http\Controllers\PublicController::class, 'blog'])->name('blog'); +Route::get('/blog/{post:slug}', [\App\Http\Controllers\PublicController::class, 'blogDetail'])->name('blog.detail');