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 +