From d54601cb6bceb40e93c12cba3b0ac7e415920ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Sat, 27 Sep 2025 14:03:03 -0300 Subject: [PATCH] Add Page resource to Filament admin panel: Implemented Page model, resource, and associated pages (create, edit, list). Defined page form schema and table configuration. Added migration for pages table. --- .../Admin/Resources/Pages/PageResource.php | 58 ++++++++ .../Resources/Pages/Pages/CreatePage.php | 11 ++ .../Admin/Resources/Pages/Pages/EditPage.php | 23 +++ .../Admin/Resources/Pages/Pages/ListPages.php | 19 +++ .../Resources/Pages/Schemas/PageForm.php | 133 +++++++++++++++++ .../Resources/Pages/Tables/PagesTable.php | 134 ++++++++++++++++++ app/Models/Page.php | 66 +++++++++ .../2025_09_27_165243_create_pages_table.php | 47 ++++++ 8 files changed, 491 insertions(+) create mode 100644 app/Filament/Admin/Resources/Pages/PageResource.php create mode 100644 app/Filament/Admin/Resources/Pages/Pages/CreatePage.php create mode 100644 app/Filament/Admin/Resources/Pages/Pages/EditPage.php create mode 100644 app/Filament/Admin/Resources/Pages/Pages/ListPages.php create mode 100644 app/Filament/Admin/Resources/Pages/Schemas/PageForm.php create mode 100644 app/Filament/Admin/Resources/Pages/Tables/PagesTable.php create mode 100644 app/Models/Page.php create mode 100644 database/migrations/2025_09_27_165243_create_pages_table.php diff --git a/app/Filament/Admin/Resources/Pages/PageResource.php b/app/Filament/Admin/Resources/Pages/PageResource.php new file mode 100644 index 0000000..27a2cff --- /dev/null +++ b/app/Filament/Admin/Resources/Pages/PageResource.php @@ -0,0 +1,58 @@ + ListPages::route('/'), + 'create' => CreatePage::route('/create'), + 'edit' => EditPage::route('/{record}/edit'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Admin/Resources/Pages/Pages/CreatePage.php b/app/Filament/Admin/Resources/Pages/Pages/CreatePage.php new file mode 100644 index 0000000..fda89b3 --- /dev/null +++ b/app/Filament/Admin/Resources/Pages/Pages/CreatePage.php @@ -0,0 +1,11 @@ +components([ + TextInput::make('title') + ->label('Başlık') + ->required() + ->maxLength(255) + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, $state, callable $set) { + if ($operation !== 'create') { + return; + } + $set('slug', \Str::slug($state)); + }), + + TextInput::make('slug') + ->label('URL Slug') + ->required() + ->maxLength(255) + ->unique(ignoreRecord: true) + ->rules(['alpha_dash']) + ->helperText('URL\'de görünecek kısım. Örnek: hakkimizda'), + + Textarea::make('excerpt') + ->label('Özet') + ->rows(3) + ->maxLength(500) + ->helperText('Sayfa özeti (maksimum 500 karakter)'), + + RichEditor::make('content') + ->label('İçerik') + ->required() + ->fileAttachmentsDisk('public') + ->fileAttachmentsDirectory('pages') + ->fileAttachmentsVisibility('public'), + + Select::make('status') + ->label('Durum') + ->options([ + 'draft' => 'Taslak', + 'published' => 'Yayında', + 'archived' => 'Arşivlendi', + ]) + ->default('draft') + ->required(), + + DateTimePicker::make('published_at') + ->label('Yayın Tarihi') + ->displayFormat('d.m.Y H:i') + ->helperText('Boş bırakılırsa şu anki tarih kullanılır'), + + Select::make('author_id') + ->label('Yazar') + ->relationship('author', 'name') + ->default(auth()->id()) + ->required(), + + Select::make('parent_id') + ->label('Üst Sayfa') + ->relationship('parent', 'title') + ->searchable() + ->preload() + ->helperText('Bu sayfayı başka bir sayfanın alt sayfası yapmak için seçin'), + + TextInput::make('sort_order') + ->label('Sıra') + ->numeric() + ->default(0) + ->helperText('Menüde görünme sırası'), + + Select::make('template') + ->label('Şablon') + ->options([ + 'default' => 'Varsayılan', + 'landing' => 'Landing Page', + 'blog' => 'Blog', + 'contact' => 'İletişim', + ]) + ->default('default'), + + Checkbox::make('is_homepage') + ->label('Ana Sayfa') + ->helperText('Bu sayfayı ana sayfa olarak ayarla'), + + Checkbox::make('show_in_menu') + ->label('Menüde Göster') + ->default(true) + ->helperText('Bu sayfa menüde görünsün mü?'), + + TextInput::make('meta_title') + ->label('Meta Başlık') + ->maxLength(60) + ->helperText('Arama motorları için başlık (maksimum 60 karakter)'), + + Textarea::make('meta_description') + ->label('Meta Açıklama') + ->rows(3) + ->maxLength(160) + ->helperText('Arama motorları için açıklama (maksimum 160 karakter)'), + + FileUpload::make('featured_image') + ->label('Öne Çıkan Görsel') + ->image() + ->disk('public') + ->directory('pages/featured') + ->visibility('public') + ->imageEditor() + ->imageEditorAspectRatios([ + '16:9', + '4:3', + '1:1', + ]) + ->helperText('Sayfa için öne çıkan görsel seçin'), + ]); + } +} diff --git a/app/Filament/Admin/Resources/Pages/Tables/PagesTable.php b/app/Filament/Admin/Resources/Pages/Tables/PagesTable.php new file mode 100644 index 0000000..5444b21 --- /dev/null +++ b/app/Filament/Admin/Resources/Pages/Tables/PagesTable.php @@ -0,0 +1,134 @@ +columns([ + ImageColumn::make('featured_image') + ->label('Görsel') + ->circular() + ->size(40) + ->defaultImageUrl('/images/placeholder-page.png'), + + TextColumn::make('title') + ->label('Başlık') + ->searchable() + ->sortable() + ->weight('bold') + ->description(fn ($record) => $record->excerpt ? \Str::limit($record->excerpt, 50) : null), + + TextColumn::make('slug') + ->label('URL') + ->searchable() + ->sortable() + ->copyable() + ->copyMessage('URL kopyalandı') + ->url(fn ($record) => $record->url, shouldOpenInNewTab: true) + ->color('primary'), + + TextColumn::make('status') + ->label('Durum') + ->badge() + ->color(fn (string $state): string => match ($state) { + 'published' => 'success', + 'draft' => 'warning', + 'archived' => 'gray', + }) + ->formatStateUsing(fn (string $state): string => match ($state) { + 'published' => 'Yayında', + 'draft' => 'Taslak', + 'archived' => 'Arşivlendi', + }), + + TextColumn::make('author.name') + ->label('Yazar') + ->searchable() + ->sortable() + ->toggleable(), + + TextColumn::make('published_at') + ->label('Yayın Tarihi') + ->dateTime('d.m.Y H:i') + ->sortable() + ->toggleable(), + + TextColumn::make('parent.title') + ->label('Üst Sayfa') + ->searchable() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('template') + ->label('Şablon') + ->badge() + ->color('info') + ->toggleable(isToggledHiddenByDefault: true), + + ToggleColumn::make('is_homepage') + ->label('Ana Sayfa') + ->toggleable(isToggledHiddenByDefault: true), + + ToggleColumn::make('show_in_menu') + ->label('Menüde Göster') + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('sort_order') + ->label('Sıra') + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('created_at') + ->label('Oluşturulma') + ->dateTime('d.m.Y H:i') + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + SelectFilter::make('status') + ->label('Durum') + ->options([ + 'published' => 'Yayında', + 'draft' => 'Taslak', + 'archived' => 'Arşivlendi', + ]), + + SelectFilter::make('template') + ->label('Şablon') + ->options([ + 'default' => 'Varsayılan', + 'landing' => 'Landing Page', + 'blog' => 'Blog', + 'contact' => 'İletişim', + ]), + + TrashedFilter::make(), + ]) + ->recordActions([ + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Models/Page.php b/app/Models/Page.php new file mode 100644 index 0000000..587d960 --- /dev/null +++ b/app/Models/Page.php @@ -0,0 +1,66 @@ + 'datetime', + 'is_homepage' => 'boolean', + 'show_in_menu' => 'boolean', + 'sort_order' => 'integer', + ]; + + public function author() + { + return $this->belongsTo(User::class, 'author_id'); + } + + public function parent() + { + return $this->belongsTo(Page::class, 'parent_id'); + } + + public function children() + { + return $this->hasMany(Page::class, 'parent_id'); + } + + public function getRouteKeyName() + { + return 'slug'; + } + + public function getUrlAttribute() + { + if ($this->is_homepage) { + return '/'; + } + + return '/' . $this->slug; + } +} diff --git a/database/migrations/2025_09_27_165243_create_pages_table.php b/database/migrations/2025_09_27_165243_create_pages_table.php new file mode 100644 index 0000000..54899a6 --- /dev/null +++ b/database/migrations/2025_09_27_165243_create_pages_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->longText('content')->nullable(); + $table->text('excerpt')->nullable(); + $table->string('meta_title')->nullable(); + $table->text('meta_description')->nullable(); + $table->enum('status', ['draft', 'published', 'archived'])->default('draft'); + $table->string('featured_image')->nullable(); + $table->timestamp('published_at')->nullable(); + $table->foreignId('author_id')->constrained('users')->onDelete('cascade'); + $table->foreignId('parent_id')->nullable()->constrained('pages')->onDelete('cascade'); + $table->integer('sort_order')->default(0); + $table->string('template')->default('default'); + $table->boolean('is_homepage')->default(false); + $table->boolean('show_in_menu')->default(true); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['status', 'published_at']); + $table->index(['parent_id', 'sort_order']); + $table->index('slug'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pages'); + } +};