diff --git a/config/filament-menu-builder.php b/config/filament-menu-builder.php index 49f25de..c451948 100644 --- a/config/filament-menu-builder.php +++ b/config/filament-menu-builder.php @@ -6,5 +6,6 @@ 'tables' => [ 'menus' => 'menus', 'menu_items' => 'menu_items', + 'menu_locations' => 'menu_locations', ], ]; diff --git a/database/migrations/create_menus_table.php.stub b/database/migrations/create_menus_table.php.stub index c950e07..3f20b67 100644 --- a/database/migrations/create_menus_table.php.stub +++ b/database/migrations/create_menus_table.php.stub @@ -29,10 +29,18 @@ return new class extends Migration $table->integer('order')->default(0); $table->timestamps(); }); + + Schema::create(config('filament-menu-builder.tables.menu_locations'), function (Blueprint $table) { + $table->id(); + $table->foreignIdFor(Menu::class); + $table->string('location', 50); + $table->timestamps(); + }); } public function down(): void { + Schema::dropIfExists('menu_locations'); Schema::dropIfExists('menu_items'); Schema::dropIfExists('menus'); } diff --git a/resources/views/edit-record.blade.php b/resources/views/edit-record.blade.php index ab9bb5a..1e33733 100644 --- a/resources/views/edit-record.blade.php +++ b/resources/views/edit-record.blade.php @@ -1,23 +1,15 @@ -getResource()::getSlug()), - 'fi-resource-record-' . $record->getKey(), - ]) -> +getResource()::getSlug()), + 'fi-resource-record-' . $record->getKey(), +])> @capture($form) - - {{ $this->form }} + + {{ $this->form }} - - + + @endcapture @php @@ -25,21 +17,14 @@ $hasCombinedRelationManagerTabsWithContent = $this->hasCombinedRelationManagerTabsWithContent(); @endphp - @if ((! $hasCombinedRelationManagerTabsWithContent) || (! count($relationManagers))) + @if (!$hasCombinedRelationManagerTabsWithContent || !count($relationManagers)) {{ $form() }} @endif @if (count($relationManagers)) - + @if ($hasCombinedRelationManagerTabsWithContent) {{ $form() }} @@ -50,7 +35,7 @@
- @foreach(\Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin::get()->getMenuPanels() as $menuPanel) + @foreach (\Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin::get()->getMenuPanels() as $menuPanel) @endforeach diff --git a/resources/views/livewire/panel.blade.php b/resources/views/livewire/panel.blade.php index 19a6eae..316f696 100644 --- a/resources/views/livewire/panel.blade.php +++ b/resources/views/livewire/panel.blade.php @@ -1,10 +1,5 @@
- + {{ $this->form }} diff --git a/src/Contracts/MenuPanel.php b/src/Contracts/MenuPanel.php new file mode 100644 index 0000000..0fe432e --- /dev/null +++ b/src/Contracts/MenuPanel.php @@ -0,0 +1,14 @@ + + * @var MenuPanel[] */ protected array $menuPanels = []; @@ -48,27 +48,29 @@ public static function get(): static return $plugin; } - public function location(string $key, string $label): static + public function addLocation(string $key, string $label): static { $this->locations[$key] = $label; return $this; } - public function menuPanel(Closure $callback): static + public function addMenuPanel(MenuPanel $menuPanel): static { - $panel = value($callback); - - if ($panel instanceof StaticMenu) { - if (! $panel->getItems()) { - return $this; - } - - $this->menuPanels[] = MenuPanel::make() - ->heading('Liên kết cố định') - ->addItems($panel->getItems()); - } else { - $this->menuPanels[] = $panel; + if ($menuPanel->getItems()) { + $this->menuPanels[] = $menuPanel; + } + + return $this; + } + + /** + * @param array $menuPanels + */ + public function addMenuPanels(array $menuPanels): static + { + foreach ($menuPanels as $menuPanel) { + $this->addMenuPanel($menuPanel); } return $this; @@ -79,7 +81,9 @@ public function menuPanel(Closure $callback): static */ public function getMenuPanels(): array { - return $this->menuPanels; + return collect($this->menuPanels) + ->sortBy(fn(MenuPanel $menuPanel) => $menuPanel->getSort()) + ->all(); } public function getLocations(): array diff --git a/src/FilamentMenuBuilderServiceProvider.php b/src/FilamentMenuBuilderServiceProvider.php index 603b912..711fd79 100644 --- a/src/FilamentMenuBuilderServiceProvider.php +++ b/src/FilamentMenuBuilderServiceProvider.php @@ -6,6 +6,7 @@ use Datlechin\FilamentMenuBuilder\Livewire\CreateCustomLink; use Datlechin\FilamentMenuBuilder\Livewire\MenuItems; +use Datlechin\FilamentMenuBuilder\Livewire\MenuPanel; use Filament\Support\Assets\AlpineComponent; use Filament\Support\Assets\Css; use Filament\Support\Facades\FilamentAsset; diff --git a/src/Livewire/MenuItems.php b/src/Livewire/MenuItems.php index 379900c..6087df0 100644 --- a/src/Livewire/MenuItems.php +++ b/src/Livewire/MenuItems.php @@ -49,7 +49,7 @@ public function reorder(array $order, ?string $parentId = null): void ->update([ 'order' => DB::raw( 'case ' . collect($order) - ->map(fn ($recordKey, int $recordIndex): string => 'when id = ' . DB::getPdo()->quote($recordKey) . ' then ' . ($recordIndex + 1)) + ->map(fn($recordKey, int $recordIndex): string => 'when id = ' . DB::getPdo()->quote($recordKey) . ' then ' . ($recordIndex + 1)) ->implode(' ') . ' end', ), 'parent_id' => $parentId, @@ -74,9 +74,9 @@ public function editAction(): Action ->label(__('filament-actions::edit.single.label')) ->iconButton() ->size(ActionSize::Small) - ->modalHeading(fn (array $arguments): string => __('filament-actions::edit.single.modal.heading', ['label' => $arguments['title']])) + ->modalHeading(fn(array $arguments): string => __('filament-actions::edit.single.modal.heading', ['label' => $arguments['title']])) ->icon('heroicon-m-pencil-square') - ->fillForm(fn (array $arguments): array => MenuItem::query() + ->fillForm(fn(array $arguments): array => MenuItem::query() ->where('id', $arguments['id']) ->select(['id', 'title', 'url', 'target']) ->first() @@ -112,7 +112,7 @@ public function deleteAction(): Action ->iconButton() ->size(ActionSize::Small) ->requiresConfirmation() - ->modalHeading(fn (array $arguments): string => __('filament-actions::delete.single.modal.heading', ['label' => $arguments['title']])) + ->modalHeading(fn(array $arguments): string => __('filament-actions::delete.single.modal.heading', ['label' => $arguments['title']])) ->modalSubmitActionLabel(__('filament-actions::delete.single.modal.actions.delete.label')) ->modalIcon(FilamentIcon::resolve('actions::delete-action.modal') ?? 'heroicon-o-trash') ->action(function (array $arguments): void { diff --git a/src/Livewire/MenuPanel.php b/src/Livewire/MenuPanel.php index 4297c15..0d4dcf6 100644 --- a/src/Livewire/MenuPanel.php +++ b/src/Livewire/MenuPanel.php @@ -4,6 +4,7 @@ namespace Datlechin\FilamentMenuBuilder\Livewire; +use Datlechin\FilamentMenuBuilder\Contracts\MenuPanel as ContractsMenuPanel; use Datlechin\FilamentMenuBuilder\Models\Menu; use Filament\Forms\Components\CheckboxList; use Filament\Forms\Concerns\InteractsWithForms; @@ -19,17 +20,22 @@ class MenuPanel extends Component implements HasForms public Menu $menu; - public string $heading; + public string $name; public array $items = []; #[Validate('required|array')] public array $data = []; - public function mount(\Datlechin\FilamentMenuBuilder\MenuPanel $menuPanel): void + public function mount(ContractsMenuPanel $menuPanel): void { - $this->heading = $menuPanel->getHeading(); - $this->items = $menuPanel->getItems(); + $this->name = $menuPanel->getName(); + $this->items = collect($menuPanel->getItems())->map(function ($item) { + return [ + 'title' => $item['title'], + 'url' => value($item['url']), + ]; + })->all(); } public function add(): void @@ -39,7 +45,7 @@ public function add(): void $order = $this->menu->menuItems()->max('order') ?? 0; $selectedItems = collect($this->items) - ->filter(fn ($item) => in_array($item['title'], $this->data)) + ->filter(fn($item) => in_array($item['title'], $this->data)) ->map(function ($item) use (&$order) { return [ ...$item, @@ -65,7 +71,7 @@ public function form(Form $form): Form ->hiddenLabel() ->required() ->bulkToggleable() - ->options(collect($this->items)->mapWithKeys(fn ($item) => [$item['title'] => $item['title']])), + ->options(collect($this->items)->mapWithKeys(fn($item) => [$item['title'] => $item['title']])), ]); } diff --git a/src/MenuPanel/AbstractMenuPanel.php b/src/MenuPanel/AbstractMenuPanel.php new file mode 100644 index 0000000..75cfa81 --- /dev/null +++ b/src/MenuPanel/AbstractMenuPanel.php @@ -0,0 +1,24 @@ +sort = $sort; + + return $this; + } + + public function getSort(): int + { + return $this->sort; + } +} diff --git a/src/MenuPanel/MenuPanel.php b/src/MenuPanel/MenuPanel.php deleted file mode 100644 index 9315699..0000000 --- a/src/MenuPanel/MenuPanel.php +++ /dev/null @@ -1,53 +0,0 @@ -heading = $heading; - - return $this; - } - - public function addItem(string $title, \Closure | string $url): static - { - $this->items[] = [ - 'title' => $title, - 'url' => $url, - ]; - - return $this; - } - - public function addItems(array $items): static - { - foreach ($items as $item) { - $this->addItem($item['title'], $item['url']); - } - - return $this; - } - - public function getHeading(): string - { - return $this->heading; - } - - public function getItems(): array - { - return $this->items; - } -} diff --git a/src/MenuPanel/StaticMenu.php b/src/MenuPanel/StaticMenu.php deleted file mode 100644 index 2391e7b..0000000 --- a/src/MenuPanel/StaticMenu.php +++ /dev/null @@ -1,30 +0,0 @@ -items[] = [ - 'title' => $title, - 'url' => $url, - ]; - - return $this; - } - - public function getItems(): array - { - return $this->items; - } -} diff --git a/src/MenuPanel/StaticMenuPanel.php b/src/MenuPanel/StaticMenuPanel.php new file mode 100644 index 0000000..8981822 --- /dev/null +++ b/src/MenuPanel/StaticMenuPanel.php @@ -0,0 +1,46 @@ +name = $name; + } + } + + public static function make(?string $name = null): static + { + return new static($name); + } + + public function add(string $title, Closure | string $url): static + { + $this->items[] = [ + 'title' => $title, + 'url' => $url, + ]; + + return $this; + } + + public function getItems(): array + { + return $this->items; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/src/Models/Menu.php b/src/Models/Menu.php index 9dc8ae1..9779732 100644 --- a/src/Models/Menu.php +++ b/src/Models/Menu.php @@ -23,6 +23,11 @@ protected function casts(): array ]; } + public function locations(): HasMany + { + return $this->hasMany(MenuLocation::class); + } + public function menuItems(): HasMany { return $this->hasMany(MenuItem::class) diff --git a/src/Models/MenuItem.php b/src/Models/MenuItem.php index da7ad41..a45b2fe 100644 --- a/src/Models/MenuItem.php +++ b/src/Models/MenuItem.php @@ -59,6 +59,6 @@ public function linkable(): MorphTo protected function type(): Attribute { - return Attribute::get(fn () => $this->linkable ? $this->linkable->title : 'Liên kết tùy chỉnh'); + return Attribute::get(fn() => $this->linkable ? $this->linkable->title : 'Liên kết tùy chỉnh'); } } diff --git a/src/Models/MenuLocation.php b/src/Models/MenuLocation.php new file mode 100644 index 0000000..d1798c7 --- /dev/null +++ b/src/Models/MenuLocation.php @@ -0,0 +1,23 @@ +belongsTo(Menu::class); + } +} diff --git a/src/Resources/MenuResource.php b/src/Resources/MenuResource.php index f9ac736..1b59999 100644 --- a/src/Resources/MenuResource.php +++ b/src/Resources/MenuResource.php @@ -6,10 +6,11 @@ use Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin; use Datlechin\FilamentMenuBuilder\Models\Menu; -use Filament\Forms\Components\Checkbox; -use Filament\Forms\Components\Radio; +use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Toggle; use Filament\Forms\Form; +use Filament\Forms\Set; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; @@ -22,18 +23,22 @@ class MenuResource extends Resource public static function form(Form $form): Form { + $locations = FilamentMenuBuilderPlugin::get()->getLocations(); + return $form ->columns(1) ->schema([ TextInput::make('name') ->label('Tên') ->required(), - Radio::make('location') - ->visible(fn () => FilamentMenuBuilderPlugin::get()->getLocations()) + CheckboxList::make('locations') + ->bulkToggleable() + ->visible(fn(string $context) => $context === 'edit' && $locations) ->label('Vị trí') + ->afterStateHydrated(fn(Menu $menu, Set $set) => $set('locations', $menu->locations->pluck('location'))) ->helperText('Chọn vị trí hiển thị menu.') - ->options(FilamentMenuBuilderPlugin::get()->getLocations()), - Checkbox::make('is_visible') + ->options($locations), + Toggle::make('is_visible') ->label('Hiển thị') ->default(true), ]); diff --git a/src/Resources/MenuResource/Pages/EditMenu.php b/src/Resources/MenuResource/Pages/EditMenu.php index 05666a2..66d0c5e 100644 --- a/src/Resources/MenuResource/Pages/EditMenu.php +++ b/src/Resources/MenuResource/Pages/EditMenu.php @@ -4,11 +4,14 @@ namespace Datlechin\FilamentMenuBuilder\Resources\MenuResource\Pages; +use Datlechin\FilamentMenuBuilder\Models\Menu; use Datlechin\FilamentMenuBuilder\Resources\MenuResource; use Filament\Actions; use Filament\Forms\Components\Section; use Filament\Forms\Form; use Filament\Resources\Pages\EditRecord; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; class EditMenu extends EditRecord { @@ -30,4 +33,24 @@ protected function getHeaderActions(): array Actions\DeleteAction::make(), ]; } + + protected function handleRecordUpdate(Model $record, array $data): Model + { + $locations = Arr::pull($data, 'locations') ?: []; + + /** @var Menu */ + $record = parent::handleRecordUpdate($record, $data); + + $record->locations() + ->whereNotIn('location', $locations) + ->delete(); + + foreach ($locations as $location) { + $record->locations()->firstOrCreate([ + 'location' => $location, + ]); + } + + return $record; + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 92e49e3..bbc551d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -27,7 +27,7 @@ protected function setUp(): void parent::setUp(); Factory::guessFactoryNamesUsing( - fn (string $modelName) => 'Datlechin\\FilamentMenuBuilder\\Database\\Factories\\' . class_basename($modelName) . 'Factory', + fn(string $modelName) => 'Datlechin\\FilamentMenuBuilder\\Database\\Factories\\' . class_basename($modelName) . 'Factory', ); }