From 4a2a66c55c0fdcf228dfcca4728c96dc81e56ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Mon, 3 Nov 2025 15:43:47 -0300 Subject: [PATCH] Add Menu Template functionality: Created MenuTemplate resource with CRUD pages, schemas, and table configuration. Implemented localization for menu template fields and integrated menu rendering capabilities. Enhanced TemplatePreviewController to support menu templates and updated dynamic template documentation accordingly. --- .../MenuTemplates/MenuTemplateResource.php | 86 +++++++++ .../Pages/CreateMenuTemplate.php | 18 ++ .../MenuTemplates/Pages/EditMenuTemplate.php | 31 ++++ .../MenuTemplates/Pages/ListMenuTemplates.php | 19 ++ .../MenuTemplates/Pages/ViewMenuTemplate.php | 19 ++ .../Schemas/MenuTemplateForm.php | 59 ++++++ .../Schemas/MenuTemplateInfolist.php | 17 ++ .../Tables/MenuTemplatesTable.php | 65 +++++++ .../Controllers/TemplatePreviewController.php | 4 +- app/Models/MenuTemplate.php | 21 +++ app/Services/MenuService.php | 121 ++++++++++++ app/Services/TemplatePreviewService.php | 1 + app/Services/TemplateService.php | 7 + ..._03_183415_create_menu_templates_table.php | 31 ++++ docs/DYNAMIC_TEMPLATE_SYSTEM.md | 13 ++ lang/en/menu-templates.php | 42 +++++ lang/tr/menu-templates.php | 42 +++++ .../menu-template-preview.blade.php | 173 ++++++++++++++++++ 18 files changed, 768 insertions(+), 1 deletion(-) create mode 100644 app/Filament/Admin/Resources/MenuTemplates/MenuTemplateResource.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Pages/CreateMenuTemplate.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Pages/EditMenuTemplate.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Pages/ListMenuTemplates.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Pages/ViewMenuTemplate.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Schemas/MenuTemplateForm.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Schemas/MenuTemplateInfolist.php create mode 100644 app/Filament/Admin/Resources/MenuTemplates/Tables/MenuTemplatesTable.php create mode 100644 app/Models/MenuTemplate.php create mode 100644 app/Services/MenuService.php create mode 100644 database/migrations/2025_11_03_183415_create_menu_templates_table.php create mode 100644 lang/en/menu-templates.php create mode 100644 lang/tr/menu-templates.php create mode 100644 resources/views/components/menu-template-preview.blade.php diff --git a/app/Filament/Admin/Resources/MenuTemplates/MenuTemplateResource.php b/app/Filament/Admin/Resources/MenuTemplates/MenuTemplateResource.php new file mode 100644 index 0000000..58196a8 --- /dev/null +++ b/app/Filament/Admin/Resources/MenuTemplates/MenuTemplateResource.php @@ -0,0 +1,86 @@ + ListMenuTemplates::route('/'), + 'create' => CreateMenuTemplate::route('/create'), + 'view' => ViewMenuTemplate::route('/{record}'), + 'edit' => EditMenuTemplate::route('/{record}/edit'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Admin/Resources/MenuTemplates/Pages/CreateMenuTemplate.php b/app/Filament/Admin/Resources/MenuTemplates/Pages/CreateMenuTemplate.php new file mode 100644 index 0000000..eb32877 --- /dev/null +++ b/app/Filament/Admin/Resources/MenuTemplates/Pages/CreateMenuTemplate.php @@ -0,0 +1,18 @@ +components([ + Section::make(__('menu-templates.section_general')) + ->schema([ + TextInput::make('title') + ->label(__('menu-templates.field_title')) + ->required() + ->maxLength(255) + ->columnSpanFull(), + + CodeEditor::make('html_content') + ->label(__('menu-templates.field_html_content')) + ->required() + ->live(onBlur: true) + ->language(Language::Html) + ->columnSpanFull() + ->helperText(__('menu-templates.field_html_content_help')) + ->extraAttributes([ + 'style' => 'max-height: 400px;overflow-y: auto;', + 'data-field-name' => 'html_content', + ]), + + // Preview Component + View::make('components.menu-template-preview') + ->viewData(function ($record) { + return [ + 'type' => 'menu', + 'fieldName' => 'html_content', + 'recordId' => $record?->id, + ]; + }) + ->columnSpanFull(), + + Toggle::make('is_active') + ->label(__('menu-templates.field_is_active')) + ->default(true) + ->inline(false), + ]) + ->columnSpanFull(), + ]); + } +} diff --git a/app/Filament/Admin/Resources/MenuTemplates/Schemas/MenuTemplateInfolist.php b/app/Filament/Admin/Resources/MenuTemplates/Schemas/MenuTemplateInfolist.php new file mode 100644 index 0000000..7057bf7 --- /dev/null +++ b/app/Filament/Admin/Resources/MenuTemplates/Schemas/MenuTemplateInfolist.php @@ -0,0 +1,17 @@ +components([ + // + ]); + } +} + diff --git a/app/Filament/Admin/Resources/MenuTemplates/Tables/MenuTemplatesTable.php b/app/Filament/Admin/Resources/MenuTemplates/Tables/MenuTemplatesTable.php new file mode 100644 index 0000000..67f903c --- /dev/null +++ b/app/Filament/Admin/Resources/MenuTemplates/Tables/MenuTemplatesTable.php @@ -0,0 +1,65 @@ +columns([ + TextColumn::make('title') + ->label(__('menu-templates.column_title')) + ->searchable() + ->sortable(), + + IconColumn::make('is_active') + ->label(__('menu-templates.column_is_active')) + ->boolean() + ->sortable(), + + TextColumn::make('created_at') + ->label(__('menu-templates.column_created_at')) + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('updated_at') + ->label(__('menu-templates.column_updated_at')) + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('deleted_at') + ->label(__('menu-templates.column_deleted_at')) + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + TrashedFilter::make(), + ]) + ->recordActions([ + ViewAction::make(), + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Http/Controllers/TemplatePreviewController.php b/app/Http/Controllers/TemplatePreviewController.php index 098163a..b10a2e2 100644 --- a/app/Http/Controllers/TemplatePreviewController.php +++ b/app/Http/Controllers/TemplatePreviewController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Models\HeaderTemplate; use App\Models\FooterTemplate; use App\Models\SectionTemplate; +use App\Models\MenuTemplate; use App\Services\TemplatePreviewService; use App\Services\TemplateService; use Illuminate\Http\Request; @@ -23,7 +24,7 @@ class TemplatePreviewController extends Controller $type = $request->input('type', 'section'); // header, footer, section // Validate type - if (!in_array($type, ['header', 'footer', 'section'])) { + if (!in_array($type, ['header', 'footer', 'section', 'menu'])) { $type = 'section'; } @@ -70,6 +71,7 @@ class TemplatePreviewController extends Controller 'header' => HeaderTemplate::find($id), 'footer' => FooterTemplate::find($id), 'section' => SectionTemplate::find($id), + 'menu' => MenuTemplate::find($id), default => null, }; } diff --git a/app/Models/MenuTemplate.php b/app/Models/MenuTemplate.php new file mode 100644 index 0000000..673ccf7 --- /dev/null +++ b/app/Models/MenuTemplate.php @@ -0,0 +1,21 @@ + 'boolean', + ]; +} diff --git a/app/Services/MenuService.php b/app/Services/MenuService.php new file mode 100644 index 0000000..c56ee10 --- /dev/null +++ b/app/Services/MenuService.php @@ -0,0 +1,121 @@ + function ($query) { + $query->where('status', 'published') + ->where('show_in_menu', true) + ->orderBy('sort_order', 'asc') + ->orderBy('title', 'asc'); + }]) + ->whereNull('parent_id') + ->where('status', 'published') + ->where('show_in_menu', true) + ->orderBy('sort_order', 'asc') + ->orderBy('title', 'asc') + ->get(); + }); + + // If menu template is provided, use it to wrap the menu + if ($menuTemplate && !empty($menuTemplate['html_content'])) { + $html = $menuTemplate['html_content']; + + // Replace {menu} placeholder with rendered menu structure + $renderedMenu = self::buildMenuStructure($menuItems); + $html = str_replace('{menu}', $renderedMenu, $html); + + return $html; + } + + // Otherwise, return default menu structure + return self::buildMenuStructure($menuItems); + } + + /** + * Build menu structure HTML from menu items + * + * @param \Illuminate\Support\Collection $menuItems + * @return string HTML structure + */ + protected static function buildMenuStructure($menuItems): string + { + if ($menuItems->isEmpty()) { + return ''; + } + + $html = ''; + + return $html; + } + + /** + * Build submenu structure recursively + * + * @param \Illuminate\Support\Collection $children + * @return string Submenu HTML + */ + protected static function buildSubmenu($children): string + { + $html = ''; + + return $html; + } + + /** + * Clear menu cache + */ + public static function clearCache(): void + { + Cache::forget('menu_items'); + } +} + diff --git a/app/Services/TemplatePreviewService.php b/app/Services/TemplatePreviewService.php index 1f6b301..6634986 100644 --- a/app/Services/TemplatePreviewService.php +++ b/app/Services/TemplatePreviewService.php @@ -214,6 +214,7 @@ class TemplatePreviewService $bodyContent = match($type) { 'header' => $content, 'footer' => $content, + 'menu' => $content, 'section' => '
' . $content . '
', default => $content, }; diff --git a/app/Services/TemplateService.php b/app/Services/TemplateService.php index a2a02d1..375cbc1 100644 --- a/app/Services/TemplateService.php +++ b/app/Services/TemplateService.php @@ -267,9 +267,16 @@ class TemplateService /** * Replace placeholders in HTML with actual data * Supports both {text.title} and {{text.title}} formats + * Also supports {menu} placeholder for menu rendering */ public static function replacePlaceholders(string $html, array $data): string { + // Handle special {menu} placeholder first + if (str_contains($html, '{menu}')) { + $renderedMenu = \App\Services\MenuService::render(); + $html = str_replace('{menu}', $renderedMenu, $html); + } + // First, parse all placeholders in the format {type.field_name} preg_match_all('/\{([a-z]+\.[a-z_]+)\}/i', $html, $matches); $placeholders = array_unique($matches[1] ?? []); diff --git a/database/migrations/2025_11_03_183415_create_menu_templates_table.php b/database/migrations/2025_11_03_183415_create_menu_templates_table.php new file mode 100644 index 0000000..ec6bec0 --- /dev/null +++ b/database/migrations/2025_11_03_183415_create_menu_templates_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('title'); + $table->longText('html_content'); + $table->boolean('is_active')->default(true); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('menu_templates'); + } +}; diff --git a/docs/DYNAMIC_TEMPLATE_SYSTEM.md b/docs/DYNAMIC_TEMPLATE_SYSTEM.md index 9c2a869..2c292c0 100644 --- a/docs/DYNAMIC_TEMPLATE_SYSTEM.md +++ b/docs/DYNAMIC_TEMPLATE_SYSTEM.md @@ -35,6 +35,15 @@ Bu sistem, sayfalara dinamik header, section ve footer şablonları ekleyerek i - `is_active` (boolean) - Aktif/Pasif - `created_at`, `updated_at`, `deleted_at` +#### 1.4 Menu Template +- **Tablo:** `menu_templates` +- **Alanlar:** + - `id` (bigint, PK) + - `title` (string, 255) - Şablon başlığı + - `html_content` (longtext) - HTML içerik + {menu} placeholder ✅ **4GB kapasiteli** + - `is_active` (boolean) - Aktif/Pasif + - `created_at`, `updated_at`, `deleted_at` + **Not:** `longtext` veri tipi MySQL'de **4,294,967,295 karakter** (4GB) kapasiteye sahiptir. Bu, en karmaşık HTML template'ler ve zengin içerikler için fazlasıyla yeterlidir. ### 2. Placeholder Sistemi @@ -44,6 +53,10 @@ Bu sistem, sayfalara dinamik header, section ve footer şablonları ekleyerek i {form_type.field_name} ``` +**Özel Placeholder'lar:** +- `{menu}` → Menü yapısını render eder (MenuTemplate için kullanılır) +- Diğer tüm template tiplerinde (header, footer, section) normal placeholder'lar kullanılır + **Desteklenen Form Tipleri:** #### Text Input Variants: diff --git a/lang/en/menu-templates.php b/lang/en/menu-templates.php new file mode 100644 index 0000000..1726c06 --- /dev/null +++ b/lang/en/menu-templates.php @@ -0,0 +1,42 @@ + 'Templates', + 'navigation_label' => 'Menu Templates', + 'model_label' => 'Menu Template', + 'plural_model_label' => 'Menu Templates', + + // Sections + 'section_general' => 'General Information', + 'section_structure' => 'Menu Structure', + 'section_structure_desc' => 'You can edit the menu structure using drag-and-drop or write code in JSON format.', + + // Form Fields + 'field_title' => 'Title', + 'field_html_content' => 'Menu HTML Code', + 'field_html_content_help' => 'You can add the menu structure to the HTML using the {menu} placeholder. The menu will be automatically generated with page-based rendering.', + 'field_is_active' => 'Active', + + // Table Columns + 'column_title' => 'Title', + 'column_is_active' => 'Active', + 'column_created_at' => 'Created At', + 'column_updated_at' => 'Updated At', + 'column_deleted_at' => 'Deleted At', + + // Actions + 'create' => 'New Menu Template', + 'edit' => 'Edit Menu Template', + 'view' => 'View Menu Template', + 'delete' => 'Delete', + 'restore' => 'Restore', + 'force_delete' => 'Force Delete', + + // Messages + 'created_successfully' => 'Menu template created successfully.', + 'updated_successfully' => 'Menu template updated successfully.', + 'deleted_successfully' => 'Menu template deleted successfully.', + 'restored_successfully' => 'Menu template restored successfully.', +]; + diff --git a/lang/tr/menu-templates.php b/lang/tr/menu-templates.php new file mode 100644 index 0000000..1f36fb5 --- /dev/null +++ b/lang/tr/menu-templates.php @@ -0,0 +1,42 @@ + 'Şablonlar', + 'navigation_label' => 'Menü Şablonları', + 'model_label' => 'Menü Şablonu', + 'plural_model_label' => 'Menü Şablonları', + + // Sections + 'section_general' => 'Genel Bilgiler', + 'section_structure' => 'Menü Yapısı', + 'section_structure_desc' => 'Menü yapısını sürükle-bırak ile düzenleyebilir veya JSON formatında kod olarak yazabilirsiniz.', + + // Form Fields + 'field_title' => 'Başlık', + 'field_html_content' => 'Menü HTML Kodu', + 'field_html_content_help' => 'Menü yapısını {menu} placeholder\'ı ile HTML içine ekleyebilirsiniz. Menü otomatik olarak sayfalar tabanlı rendering ile oluşturulur.', + 'field_is_active' => 'Aktif', + + // Table Columns + 'column_title' => 'Başlık', + 'column_is_active' => 'Aktif', + 'column_created_at' => 'Oluşturulma', + 'column_updated_at' => 'Güncellenme', + 'column_deleted_at' => 'Silinme', + + // Actions + 'create' => 'Yeni Menü Şablonu', + 'edit' => 'Menü Şablonunu Düzenle', + 'view' => 'Menü Şablonunu Görüntüle', + 'delete' => 'Sil', + 'restore' => 'Geri Yükle', + 'force_delete' => 'Kalıcı Sil', + + // Messages + 'created_successfully' => 'Menü şablonu başarıyla oluşturuldu.', + 'updated_successfully' => 'Menü şablonu başarıyla güncellendi.', + 'deleted_successfully' => 'Menü şablonu başarıyla silindi.', + 'restored_successfully' => 'Menü şablonu başarıyla geri yüklendi.', +]; + diff --git a/resources/views/components/menu-template-preview.blade.php b/resources/views/components/menu-template-preview.blade.php new file mode 100644 index 0000000..98774ae --- /dev/null +++ b/resources/views/components/menu-template-preview.blade.php @@ -0,0 +1,173 @@ +@php + $previewUrl = route('template.preview'); + $type = $type ?? 'section'; + $fieldName = $fieldName ?? 'html_content'; + $recordId = $recordId ?? null; +@endphp +
+
+ +
+ +
+ + +
+ +
+ +
+
+ +
+
+
+
+
+
+ + + +
+
+ +@push('scripts') + +@endpush + +@push('styles') + +@endpush +