Add Menu Tree functionality and integrate Filament Tree plugin: Introduced a new MenuTree page for managing page structures, added PagesMenuWidget for displaying a tree layout of pages, and updated localization files for menu-related actions. Enhanced Page model with tree management capabilities and included necessary CSS and JS files for the Filament Tree plugin.
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Filament\Admin\Resources\Pages;
|
|||||||
use App\Filament\Admin\Resources\Pages\Pages\CreatePage;
|
use App\Filament\Admin\Resources\Pages\Pages\CreatePage;
|
||||||
use App\Filament\Admin\Resources\Pages\Pages\EditPage;
|
use App\Filament\Admin\Resources\Pages\Pages\EditPage;
|
||||||
use App\Filament\Admin\Resources\Pages\Pages\ListPages;
|
use App\Filament\Admin\Resources\Pages\Pages\ListPages;
|
||||||
|
use App\Filament\Admin\Resources\Pages\Pages\MenuTree;
|
||||||
use App\Filament\Admin\Resources\Pages\Schemas\PageForm;
|
use App\Filament\Admin\Resources\Pages\Schemas\PageForm;
|
||||||
use App\Filament\Admin\Resources\Pages\Tables\PagesTable;
|
use App\Filament\Admin\Resources\Pages\Tables\PagesTable;
|
||||||
use App\Models\Page;
|
use App\Models\Page;
|
||||||
@@ -60,6 +61,7 @@ class PageResource extends Resource
|
|||||||
'index' => ListPages::route('/'),
|
'index' => ListPages::route('/'),
|
||||||
'create' => CreatePage::route('/create'),
|
'create' => CreatePage::route('/create'),
|
||||||
'edit' => EditPage::route('/{record}/edit'),
|
'edit' => EditPage::route('/{record}/edit'),
|
||||||
|
'menu' => MenuTree::route('/menu'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Filament\Admin\Resources\Pages\Pages;
|
namespace App\Filament\Admin\Resources\Pages\Pages;
|
||||||
|
|
||||||
use App\Filament\Admin\Resources\Pages\PageResource;
|
use App\Filament\Admin\Resources\Pages\PageResource;
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
@@ -18,6 +19,11 @@ class ListPages extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
Action::make('menu')
|
||||||
|
->label(__('pages.menu_tree_title'))
|
||||||
|
->icon('heroicon-o-bars-3')
|
||||||
|
->url(PageResource::getUrl('menu'))
|
||||||
|
->color('gray'),
|
||||||
CreateAction::make()
|
CreateAction::make()
|
||||||
->label(__('pages.create')),
|
->label(__('pages.create')),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\Pages\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\Pages\PageResource;
|
||||||
|
use App\Filament\Admin\Widgets\PagesMenuWidget;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Resources\Pages\Page;
|
||||||
|
|
||||||
|
class MenuTree extends Page
|
||||||
|
{
|
||||||
|
protected static string $resource = PageResource::class;
|
||||||
|
|
||||||
|
protected string $view = 'filament.admin.pages.menu-tree';
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return __('pages.menu_tree_title');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('refresh')
|
||||||
|
->label(__('pages.menu_refresh'))
|
||||||
|
->icon('heroicon-o-arrow-path')
|
||||||
|
->color('gray')
|
||||||
|
->action(function () {
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
}),
|
||||||
|
Action::make('back_to_pages')
|
||||||
|
->label(__('pages.menu_back_to_pages'))
|
||||||
|
->icon('heroicon-o-arrow-left')
|
||||||
|
->url(PageResource::getUrl('index'))
|
||||||
|
->color('gray'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeaderWidgets(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
PagesMenuWidget::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Widgets;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\Pages\PageResource;
|
||||||
|
use App\Models\Page;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use SolutionForest\FilamentTree\Actions\EditAction;
|
||||||
|
use SolutionForest\FilamentTree\Actions\ViewAction;
|
||||||
|
use SolutionForest\FilamentTree\Widgets\Tree as BaseWidget;
|
||||||
|
|
||||||
|
class PagesMenuWidget extends BaseWidget
|
||||||
|
{
|
||||||
|
protected static string $model = Page::class;
|
||||||
|
|
||||||
|
protected static int $maxDepth = 4;
|
||||||
|
|
||||||
|
protected ?string $treeTitle = null;
|
||||||
|
|
||||||
|
protected bool $enableTreeTitle = true;
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
parent::mount();
|
||||||
|
$this->treeTitle = __('pages.menu_tree_title');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTreeQuery(): Builder
|
||||||
|
{
|
||||||
|
return Page::query()
|
||||||
|
->where('status', 'published')
|
||||||
|
->where('show_in_menu', true)
|
||||||
|
->orderBy('sort_order');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTreeRecordTitle(?\Illuminate\Database\Eloquent\Model $record = null): string
|
||||||
|
{
|
||||||
|
if (!$record) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = $record->title;
|
||||||
|
|
||||||
|
// Add child count indicator
|
||||||
|
$childCount = $record->children()
|
||||||
|
->where('status', 'published')
|
||||||
|
->where('show_in_menu', true)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($childCount > 0) {
|
||||||
|
$title .= " ({$childCount})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTreeRecordIcon(?\Illuminate\Database\Eloquent\Model $record = null): ?string
|
||||||
|
{
|
||||||
|
if (!$record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root pages get folder icon (parent_id is null)
|
||||||
|
if ($record->parent_id === null) {
|
||||||
|
return 'heroicon-o-folder';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Homepage gets special icon
|
||||||
|
if ($record->is_homepage) {
|
||||||
|
return 'heroicon-o-home';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child pages get document icon
|
||||||
|
return 'heroicon-o-document-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasDeleteAction(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasEditAction(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasViewAction(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configureEditAction(EditAction $action): EditAction
|
||||||
|
{
|
||||||
|
return $action
|
||||||
|
->icon('heroicon-o-pencil')
|
||||||
|
->label(__('pages.edit'))
|
||||||
|
->url(fn (Page $record): string => PageResource::getUrl('edit', ['record' => $record]))
|
||||||
|
->openUrlInNewTab(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configureViewAction(ViewAction $action): ViewAction
|
||||||
|
{
|
||||||
|
return $action
|
||||||
|
->icon('heroicon-o-eye')
|
||||||
|
->label(__('pages.view'))
|
||||||
|
->url(fn (Page $record): string => $record->url)
|
||||||
|
->openUrlInNewTab(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeCollapsedState(?\Illuminate\Database\Eloquent\Model $record = null): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+25
-1
@@ -7,10 +7,11 @@ use App\Traits\HasTranslations;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use SolutionForest\FilamentTree\Concern\ModelTree;
|
||||||
|
|
||||||
class Page extends Model
|
class Page extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, SoftDeletes, HasTranslations;
|
use HasFactory, SoftDeletes, HasTranslations, ModelTree;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'title',
|
'title',
|
||||||
@@ -172,4 +173,27 @@ class Page extends Model
|
|||||||
];
|
];
|
||||||
})->filter(fn($section) => $section['template'] !== null);
|
})->filter(fn($section) => $section['template'] !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModelTree - Custom column names
|
||||||
|
*/
|
||||||
|
public function determineOrderColumnName(): string
|
||||||
|
{
|
||||||
|
return 'sort_order';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function determineParentColumnName(): string
|
||||||
|
{
|
||||||
|
return 'parent_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function determineTitleColumnName(): string
|
||||||
|
{
|
||||||
|
return 'title';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function defaultParentKey()
|
||||||
|
{
|
||||||
|
return null; // Mevcut migration nullable olduğu için null kullanıyoruz
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"filament/filament": "~4.0",
|
"filament/filament": "~4.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"solution-forest/filament-tree": "^3.0",
|
||||||
"spatie/laravel-permission": "^6.21",
|
"spatie/laravel-permission": "^6.21",
|
||||||
"tomatophp/filament-users": "^4.0"
|
"tomatophp/filament-users": "^4.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+72
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ae5eda2a09c03c50635001cd6005dbec",
|
"content-hash": "131798d367c222bf82f94782fbafa942",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
@@ -5395,6 +5395,77 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-12-17T21:53:22+00:00"
|
"time": "2022-12-17T21:53:22+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "solution-forest/filament-tree",
|
||||||
|
"version": "3.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/solutionforest/filament-tree.git",
|
||||||
|
"reference": "2722a10d782585c6063ec50903eb53f186e5202f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/solutionforest/filament-tree/zipball/2722a10d782585c6063ec50903eb53f186e5202f",
|
||||||
|
"reference": "2722a10d782585c6063ec50903eb53f186e5202f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"filament/filament": "^4.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"spatie/laravel-package-tools": "^1.15.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"larastan/larastan": "^3.0",
|
||||||
|
"laravel/pint": "^1.0",
|
||||||
|
"nunomaduro/collision": "^8.0",
|
||||||
|
"orchestra/testbench": "^9.0|^10.0",
|
||||||
|
"pestphp/pest": "^3.0",
|
||||||
|
"pestphp/pest-plugin-arch": "^3.0",
|
||||||
|
"pestphp/pest-plugin-laravel": "^3.0",
|
||||||
|
"pestphp/pest-plugin-livewire": "^3.0",
|
||||||
|
"phpstan/extension-installer": "^1.1",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"rector/rector": "^2.0",
|
||||||
|
"spatie/laravel-ray": "^1.26"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"SolutionForest\\FilamentTree\\FilamentTreeServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SolutionForest\\FilamentTree\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Carly",
|
||||||
|
"email": "info@solutionforest.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "This is a tree layout plugin for Filament Admin",
|
||||||
|
"homepage": "https://github.com/solution-forest/filament-tree",
|
||||||
|
"keywords": [
|
||||||
|
"Solution Forest",
|
||||||
|
"filament-tree",
|
||||||
|
"laravel"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/solution-forest/filament-tree/issues",
|
||||||
|
"source": "https://github.com/solution-forest/filament-tree"
|
||||||
|
},
|
||||||
|
"time": "2025-10-22T03:25:18+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/invade",
|
"name": "spatie/invade",
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|||||||
@@ -16,12 +16,19 @@ return [
|
|||||||
// Actions
|
// Actions
|
||||||
'create' => 'Create New Page',
|
'create' => 'Create New Page',
|
||||||
'edit' => 'Edit Page',
|
'edit' => 'Edit Page',
|
||||||
|
'view' => 'View',
|
||||||
'save' => 'Save',
|
'save' => 'Save',
|
||||||
'cancel' => 'Cancel',
|
'cancel' => 'Cancel',
|
||||||
'delete' => 'Delete Page',
|
'delete' => 'Delete Page',
|
||||||
'restore' => 'Restore Page',
|
'restore' => 'Restore Page',
|
||||||
'force_delete' => 'Force Delete',
|
'force_delete' => 'Force Delete',
|
||||||
|
|
||||||
|
// Menu Tree
|
||||||
|
'menu_tree_title' => 'Menu Structure',
|
||||||
|
'menu_refresh' => 'Refresh',
|
||||||
|
'menu_back_to_pages' => 'Back to Pages',
|
||||||
|
'menu_tree_saved' => 'Menu structure saved successfully.',
|
||||||
|
|
||||||
// Form sections
|
// Form sections
|
||||||
'form_section_content' => 'Content',
|
'form_section_content' => 'Content',
|
||||||
'form_section_page_settings' => 'Page Settings',
|
'form_section_page_settings' => 'Page Settings',
|
||||||
|
|||||||
@@ -16,12 +16,19 @@ return [
|
|||||||
// Actions
|
// Actions
|
||||||
'create' => 'Yeni Sayfa Oluştur',
|
'create' => 'Yeni Sayfa Oluştur',
|
||||||
'edit' => 'Sayfayı Düzenle',
|
'edit' => 'Sayfayı Düzenle',
|
||||||
|
'view' => 'Görüntüle',
|
||||||
'save' => 'Kaydet',
|
'save' => 'Kaydet',
|
||||||
'cancel' => 'İptal',
|
'cancel' => 'İptal',
|
||||||
'delete' => 'Sayfayı Sil',
|
'delete' => 'Sayfayı Sil',
|
||||||
'restore' => 'Sayfayı Geri Yükle',
|
'restore' => 'Sayfayı Geri Yükle',
|
||||||
'force_delete' => 'Kalıcı Olarak Sil',
|
'force_delete' => 'Kalıcı Olarak Sil',
|
||||||
|
|
||||||
|
// Menu Tree
|
||||||
|
'menu_tree_title' => 'Menü Yapısı',
|
||||||
|
'menu_refresh' => 'Yenile',
|
||||||
|
'menu_back_to_pages' => 'Sayfalara Dön',
|
||||||
|
'menu_tree_saved' => 'Menü yapısı başarıyla kaydedildi.',
|
||||||
|
|
||||||
// Form sections
|
// Form sections
|
||||||
'form_section_content' => 'İçerik',
|
'form_section_content' => 'İçerik',
|
||||||
'form_section_page_settings' => 'Sayfa Ayarları',
|
'form_section_page_settings' => 'Sayfa Ayarları',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
|||||||
|
<x-filament-panels::page>
|
||||||
|
@if ($this->hasHeaderWidgets())
|
||||||
|
<x-filament-widgets::widgets
|
||||||
|
:widgets="$this->getHeaderWidgets()"
|
||||||
|
:columns="$this->getHeaderWidgetsColumns()"
|
||||||
|
/>
|
||||||
|
@endif
|
||||||
|
</x-filament-panels::page>
|
||||||
|
|
||||||
Reference in New Issue
Block a user