diff --git a/.cursorrules b/.cursorrules index 871db0a..5d7ba98 100644 --- a/.cursorrules +++ b/.cursorrules @@ -210,6 +210,121 @@ $table **Önemli Not:** Filament 4.x'te `Filament\Resources\Components\Tab` diye bir class **YOK**. Tüm tab kullanımları `Filament\Schemas\Components\Tabs\Tab` ile yapılır! +#### **Filament 4.x REPEATER KULLANIM KURALLARI (KRİTİK!):** + +Repeater component'ı JSON array formatında tekrarlanan form component'leri oluşturmak için kullanılır. + +**Temel Kullanım:** +```php +use Filament\Forms\Components\Repeater; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Select; + +Repeater::make('members') + ->schema([ + TextInput::make('name')->required(), + Select::make('role') + ->options([ + 'member' => 'Member', + 'administrator' => 'Administrator', + ]) + ->required(), + ]) + ->defaultItems(0) + ->addActionLabel('Add Member') + ->reorderable() + ->collapsible() + ->itemLabel(fn (array $state): ?string => $state['name'] ?? null) +``` + +**Simple Repeater (Tek Field için):** +```php +// ✅ DOĞRU - Filament 4.x'te simple() metodu parametre alır +Repeater::make('tags') + ->simple( + TextInput::make('tag') + ->label('Tag') + ->required() + ) + +// ❌ YANLIŞ - Bu kullanım HATA VERİR +Repeater::make('tags') + ->schema([ + TextInput::make('tag')->required(), + ]) + ->simple() // ❌ Parametre gerekli! +``` + +**Nested Repeater ile Dikkat Edilmesi Gerekenler:** + +1. **Aynı İsimli Field'lar Çakışır:** +```php +// ❌ YANLIŞ - Bu HATA VERİR (FileUpload array beklerken string alır) +Repeater::make('data') + ->schema([ + TextInput::make('value'), // İlk value + FileUpload::make('value'), // İkinci value - ÇAKIŞMA! + Textarea::make('value'), // Üçüncü value - ÇAKIŞMA! + ]) + +// ✅ DOĞRU - dehydrated() ile sadece görünür olan kaydedilir +Repeater::make('data') + ->schema([ + TextInput::make('value') + ->visible(fn ($get) => $get('type') === 'text') + ->dehydrated(fn ($get) => $get('type') === 'text'), + + FileUpload::make('value') + ->visible(fn ($get) => $get('type') === 'image') + ->dehydrated(fn ($get) => $get('type') === 'image'), + + Textarea::make('value') + ->visible(fn ($get) => $get('type') === 'textarea') + ->dehydrated(fn ($get) => $get('type') === 'textarea'), + ]) +``` + +2. **Conditional Field'lar:** +```php +// Sadece görünür olan field dehydrate edilir +Select::make('type') + ->options([...]) + ->live(), // Değişikliği dinlemek için + +TextInput::make('value') + ->visible(fn (Get $get) => $get('type') === 'text') + ->dehydrated(fn (Get $get) => $get('type') === 'text'), +``` + +3. **Item Labels:** +```php +// Item'lara dinamik label ekle +Repeater::make('sections') + ->schema([...]) + ->itemLabel(fn (array $state): ?string => + isset($state['type']) + ? __('pages.section_type_' . $state['type']) + : 'Item' + ) +``` + +4. **Repeater Özellikleri:** +```php +Repeater::make('items') + ->schema([...]) + ->defaultItems(0) // Başlangıç item sayısı + ->addActionLabel('Add') // Ekleme butonu label'ı + ->reorderable() // Sürükle-bırak ile sıralama + ->collapsible() // Açılıp kapanabilir + ->collapsed() // Başlangıçta kapalı + ->cloneable() // Kopyalama butonu + ->deletable() // Silme butonu + ->minItems(1) // Minimum item sayısı + ->maxItems(10) // Maximum item sayısı +``` + +**Referans:** [Filament Repeater Docs](https://filamentphp.com/docs/4.x/forms/repeater) + ### Dil Dosyası Yapısı collapsible(true) ->collapsed(true), + // Sayfa Bölümleri (Section Builder) + Section::make(__('pages.form_section_sections')) + ->schema([ + Repeater::make('sections') + ->label(__('pages.sections_field')) + ->helperText(__('pages.sections_helper_text')) + ->schema([ + Select::make('type') + ->label(__('pages.section_type')) + ->helperText(__('pages.section_type_helper')) + ->required() + ->options([ + 'hero' => __('pages.section_type_hero'), + 'features' => __('pages.section_type_features'), + 'stats' => __('pages.section_type_stats'), + 'cta' => __('pages.section_type_cta'), + 'content' => __('pages.section_type_content'), + 'gallery' => __('pages.section_type_gallery'), + 'testimonials' => __('pages.section_type_testimonials'), + 'team' => __('pages.section_type_team'), + 'pricing' => __('pages.section_type_pricing'), + 'faq' => __('pages.section_type_faq'), + 'contact' => __('pages.section_type_contact'), + 'custom' => __('pages.section_type_custom'), + ]) + ->live() + ->columnSpanFull(), + + Repeater::make('data') + ->label(__('pages.section_data')) + ->helperText(__('pages.section_data_helper')) + ->schema([ + TextInput::make('key') + ->label(__('pages.section_data_key')) + ->helperText(__('pages.section_data_key_helper')) + ->required() + ->placeholder('title, subtitle, image, button_text, ...') + ->columnSpan(1), + + Select::make('type') + ->label(__('pages.section_data_value_type')) + ->required() + ->options([ + 'text' => __('pages.value_type_text'), + 'textarea' => __('pages.value_type_textarea'), + 'html' => __('pages.value_type_html'), + 'markdown' => __('pages.value_type_markdown'), + 'richtext' => __('pages.value_type_richtext'), + 'image' => __('pages.value_type_image'), + 'file' => __('pages.value_type_file'), + 'url' => __('pages.value_type_url'), + 'email' => __('pages.value_type_email'), + 'phone' => __('pages.value_type_phone'), + 'number' => __('pages.value_type_number'), + 'boolean' => __('pages.value_type_boolean'), + 'color' => __('pages.value_type_color'), + 'date' => __('pages.value_type_date'), + 'datetime' => __('pages.value_type_datetime'), + 'array' => __('pages.value_type_array'), + 'json' => __('pages.value_type_json'), + ]) + ->live() + ->columnSpan(1), + + // Single Hidden Field to store the actual value + Hidden::make('value'), + + // Text Input (displays and updates 'value' field) + TextInput::make('_value_text') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => in_array($get('type'), ['text', 'url', 'email', 'phone', 'number'])) + ->numeric(fn (Get $get) => $get('type') === 'number') + ->placeholder('Değer girin...') + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Textarea (displays and updates 'value' field) + Textarea::make('_value_textarea') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => in_array($get('type'), ['textarea', 'json', 'html'])) + ->rows(fn (Get $get) => $get('type') === 'html' ? 6 : 4) + ->placeholder(fn (Get $get) => $get('type') === 'html' ? '
HTML kodu girin...
' : 'Metin girin...') + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Rich Text Editor (displays and updates 'value' field) + RichEditor::make('_value_richtext') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'richtext') + ->fileAttachmentsDisk('public') + ->fileAttachmentsDirectory('pages/sections') + ->toolbarButtons([ + 'bold', + 'italic', + 'link', + 'bulletList', + 'orderedList', + ]) + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Markdown Editor (displays and updates 'value' field) + MarkdownEditor::make('_value_markdown') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'markdown') + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Image Upload (displays and updates 'value' field) + FileUpload::make('_value_image') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'image') + ->image() + ->disk('public') + ->directory('pages/sections') + ->imageEditor() + ->live() + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // File Upload (displays and updates 'value' field) + FileUpload::make('_value_file') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'file') + ->disk('public') + ->directory('pages/sections') + ->live() + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Boolean Toggle (displays and updates 'value' field) + Toggle::make('_value_boolean') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'boolean') + ->live() + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Color Picker (displays and updates 'value' field) + ColorPicker::make('_value_color') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'color') + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Date Picker (displays and updates 'value' field) + DatePicker::make('_value_date') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'date') + ->displayFormat('d/m/Y') + ->live() + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // DateTime Picker (displays and updates 'value' field) + DateTimePicker::make('_value_datetime') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'datetime') + ->displayFormat('d/m/Y H:i') + ->seconds(false) + ->live() + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + + // Array Repeater (displays and updates 'value' field) + Repeater::make('_value_array') + ->label(__('pages.section_data_value')) + ->visible(fn (Get $get) => $get('type') === 'array') + ->simple( + TextInput::make('item') + ->label('Öğe') + ->required() + ) + ->live(onBlur: true) + ->afterStateUpdated(fn ($state, callable $set) => $set('value', $state)) + ->afterStateHydrated(fn ($component, $state, Get $get) => $component->state($get('value'))) + ->dehydrated(false) + ->columnSpanFull(), + ]) + ->columns(2) + ->defaultItems(0) + ->addActionLabel(__('pages.section_data_add_field')) + ->reorderable() + ->collapsible() + ->itemLabel(fn (array $state): ?string => isset($state['key']) ? $state['key'] . ' (' . ($state['type'] ?? 'text') . ')' : null) + ->columnSpanFull(), + ]) + ->defaultItems(0) + ->addActionLabel(__('pages.section_add')) + ->reorderable() + ->collapsible() + ->itemLabel(fn (array $state): ?string => isset($state['type']) ? __('pages.section_type_' . $state['type']) : 'Bölüm') + ->columnSpanFull(), + ]) + ->columnSpanFull() + ->collapsible(true) + ->collapsed(false), + // Çeviri Sekmesi Section::make('🌍 ' . __('pages.translations_section')) ->schema([ diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index d3095cb..a23b609 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -92,7 +92,8 @@ class PageController extends Controller ? ($page->translate('meta_description') ?: ($page->excerpt ?? null)) : ($page->meta_description ?? $page->excerpt ?? null); - $sections = $page->sections ?? $page->data ?? []; + // Use parsed_sections for easier template usage (key-value format) + $sections = $page->parsed_sections ?? $page->sections ?? $page->data ?? []; $template = ($slug === 'home' || ($page->is_homepage ?? false)) ? 'home' : ($page->template ?? 'generic'); diff --git a/app/Models/Page.php b/app/Models/Page.php index e560ac8..38da609 100644 --- a/app/Models/Page.php +++ b/app/Models/Page.php @@ -88,4 +88,40 @@ class Page extends Model return null; } + + /** + * Get sections with parsed data as associative array + */ + public function getParsedSectionsAttribute() + { + if (!$this->sections || !is_array($this->sections)) { + return []; + } + + return collect($this->sections)->map(function ($section) { + if (!isset($section['data']) || !is_array($section['data'])) { + return $section; + } + + // Convert key-value-type array to associative array + $parsedData = collect($section['data'])->pluck('value', 'key')->toArray(); + + return [ + 'type' => $section['type'] ?? null, + 'data' => $parsedData, + ]; + })->toArray(); + } + + /** + * Get a specific section's data by type + */ + public function getSectionData(string $type, int $index = 0): ?array + { + $sections = $this->parsed_sections; + $matchingSections = array_filter($sections, fn($s) => ($s['type'] ?? null) === $type); + $matchingSections = array_values($matchingSections); + + return $matchingSections[$index] ?? null; + } } diff --git a/docs/SECTION_BUILDER.md b/docs/SECTION_BUILDER.md new file mode 100644 index 0000000..60bbc76 --- /dev/null +++ b/docs/SECTION_BUILDER.md @@ -0,0 +1,389 @@ +# Page Section Builder - Kullanım Kılavuzu + +## 📋 Genel Bakış + +Page Section Builder, Filament Admin panelinden sayfalarınıza dinamik bölümler (sections) eklemenizi sağlayan güçlü bir modüldür. Settings modülünden esinlenerek geliştirilmiş olup, esnek bir key-value-type sistemi kullanır. + +## ✨ Özellikler + +- **Dinamik Section Tipleri**: Hero, Features, Stats, CTA, Gallery, Testimonials ve daha fazlası +- **Esnek Data Yönetimi**: Her section için sınırsız key-value çifti +- **Çoklu Value Tipleri**: Text, Image, HTML, Rich Text, Markdown, Color, Date, Array ve daha fazlası +- **Sürükle-Bırak Sıralama**: Sections ve data fields'ları yeniden sıralayabilme +- **Kolay Entegrasyon**: Mevcut sayfalarda sorunsuz çalışır + +## 🎯 Section Tipleri + +Aşağıdaki section tipleri mevcuttur: + +| Tip | Açıklama | Kullanım Alanı | +|-----|----------|----------------| +| `hero` | Hero (Ana Banner) | Sayfa başlığı, büyük görsel banner | +| `features` | Özellikler | Ürün/hizmet özellikleri listesi | +| `stats` | İstatistikler | Sayısal veriler, başarı metrikleri | +| `cta` | Harekete Geçirme | Kullanıcıyı yönlendirme butonları | +| `content` | İçerik | Genel içerik blokları | +| `gallery` | Galeri | Görsel galeri | +| `testimonials` | Referanslar | Müşteri yorumları | +| `team` | Ekip | Ekip üyeleri | +| `pricing` | Fiyatlandırma | Fiyat tabloları | +| `faq` | SSS | Sık sorulan sorular | +| `contact` | İletişim | İletişim formu | +| `custom` | Özel | Özel içerik | + +## 📝 Value Tipleri + +Her data field'ı için aşağıdaki value tipleri kullanılabilir: + +### Metin Tipleri +- **Text**: Kısa tek satırlık metin (başlık, URL, e-posta, telefon) +- **Textarea**: Uzun metin +- **HTML**: Ham HTML kodu +- **Markdown**: Markdown formatı +- **Rich Text**: WYSIWYG zengin metin editörü + +### Medya Tipleri +- **Image**: Görsel yükleme (otomatik image editor ile) +- **File**: Dosya yükleme +- **Color**: Renk seçici + +### Veri Tipleri +- **Number**: Sayısal değer +- **Boolean**: Evet/Hayır (Toggle) +- **Date**: Tarih seçici +- **DateTime**: Tarih & saat seçici +- **Array**: Dizi (repeater) +- **JSON**: JSON formatı + +## 🔧 Admin Panelinde Kullanım + +### 1. Yeni Section Ekleme + +1. **Pages** modülünden bir sayfa açın veya yeni sayfa oluşturun +2. **Sayfa Bölümleri** (Page Sections) sekmesine gidin +3. **Yeni Bölüm Ekle** butonuna tıklayın +4. Section tipini seçin (örn: Hero, Features) +5. **Yeni Alan Ekle** ile section data'larını ekleyin + +### 2. Data Field Ekleme + +Her section için data field'ları eklerken: + +1. **Key (Anahtar)**: Data'nın anahtarı (örn: `title`, `subtitle`, `image`) +2. **Type (Değer Tipi)**: Değerin tipini seçin (örn: Text, Image, HTML) +3. **Value (Değer)**: Seçilen tipe göre değeri girin + +### 3. Örnek: Hero Section Oluşturma + +``` +Section Type: Hero (Ana Banner) + +Data Fields: +┌─────────────────────────────────────────┐ +│ Key: background_image │ +│ Type: Image │ +│ Value: [Upload Image] │ +├─────────────────────────────────────────┤ +│ Key: badge │ +│ Type: Text │ +│ Value: "YENİ PLATFORM" │ +├─────────────────────────────────────────┤ +│ Key: title │ +│ Type: HTML │ +│ Value: "Modern Web" │ +├─────────────────────────────────────────┤ +│ Key: subtitle │ +│ Type: Textarea │ +│ Value: "Açıklama metni..." │ +├─────────────────────────────────────────┤ +│ Key: button_text │ +│ Type: Text │ +│ Value: "Hemen Başla" │ +├─────────────────────────────────────────┤ +│ Key: button_url │ +│ Type: URL │ +│ Value: "#features" │ +└─────────────────────────────────────────┘ +``` + +## 💻 Kod Kullanımı + +### Page Model'de Sections Kullanımı + +```php +// Bir sayfayı al +$page = Page::find(1); + +// Tüm parsed sections'ları al (key-value formatında) +$sections = $page->parsed_sections; + +// Örnek çıktı: +[ + [ + 'type' => 'hero', + 'data' => [ + 'background_image' => 'path/to/image.jpg', + 'badge' => 'YENİ PLATFORM', + 'title' => 'Modern Web', + 'subtitle' => 'Açıklama metni...', + 'button_text' => 'Hemen Başla', + 'button_url' => '#features', + ] + ], + [ + 'type' => 'features', + 'data' => [...] + ] +] + +// Belirli bir section tipinin data'sını al +$heroData = $page->getSectionData('hero'); // İlk hero section +$secondHero = $page->getSectionData('hero', 1); // İkinci hero section +``` + +### Controller'da Kullanım + +```php +public function show($slug) +{ + $page = Page::where('slug', $slug)->firstOrFail(); + + // Parsed sections otomatik olarak view'e gönderilir + $sections = $page->parsed_sections; + + return view('templates.home', compact('page', 'sections')); +} +``` + +### Blade Template'de Kullanım + +```blade +@foreach($sections as $section) + @if($section['type'] === 'hero') + + + @elseif($section['type'] === 'features') + + + @elseif($section['type'] === 'stats') + + + @endif +@endforeach +``` + +### Component'te Data Kullanımı + +```blade +{{-- resources/views/components/blocks/hero.blade.php --}} + +@props(['data']) + +
+
+ @if(isset($data['badge'])) + {{ $data['badge'] }} + @endif + + @if(isset($data['title'])) +

{!! $data['title'] !!}

+ @endif + + @if(isset($data['subtitle'])) +

{{ $data['subtitle'] }}

+ @endif + + @if(isset($data['button_text']) && isset($data['button_url'])) + + {{ $data['button_text'] }} + + @endif +
+
+``` + +## 🎨 Best Practices + +### 1. Key Naming (Anahtar İsimlendirme) + +Tutarlı ve açıklayıcı key isimleri kullanın: + +``` +✅ İYİ: +- title +- subtitle +- background_image +- primary_button_text +- primary_button_url + +❌ KÖTÜ: +- baslik +- resim1 +- text +- btn +``` + +### 2. Value Type Seçimi + +Doğru value tipini seçin: + +``` +✅ title → Text (kısa başlık) +✅ description → Textarea (uzun açıklama) +✅ content → Rich Text (formatlanmış içerik) +✅ banner → Image (görsel) +✅ is_active → Boolean (aktif/pasif) +``` + +### 3. Section Organization + +Benzer section'ları gruplandırın: + +``` +1. Hero (Giriş) +2. Features (Özellikler) +3. Stats (İstatistikler) +4. CTA (Harekete Geçirme) +5. Contact (İletişim) +``` + +## 🔄 Mevcut Data Migrasyon + +Eğer eski formatınız varsa (örn: PageSeeder'daki gibi), parse edilmiş format otomatik olarak çalışır: + +```php +// ESKİ FORMAT (PageSeeder'dan) +'sections' => [ + [ + 'type' => 'hero', + 'data' => [ + 'title' => 'Başlık', + 'subtitle' => 'Alt başlık' + ] + ] +] + +// YENİ FORMAT (Admin'den girilince) +'sections' => [ + [ + 'type' => 'hero', + 'data' => [ + ['key' => 'title', 'type' => 'text', 'value' => 'Başlık'], + ['key' => 'subtitle', 'type' => 'text', 'value' => 'Alt başlık'] + ] + ] +] + +// parsed_sections attribute her ikisini de doğru parse eder! +``` + +## 🚀 İleri Seviye Özellikler + +### Nested Arrays (İç İçe Diziler) + +`Array` value tipi ile iç içe veri yapıları oluşturabilirsiniz: + +``` +Section Type: Features + +Data Field: +Key: features +Type: Array +Value: [ + { item: "Hızlı Çözümler" }, + { item: "Güvenli Altyapı" }, + { item: "Uzman Ekip" } +] +``` + +### JSON Data + +Kompleks veri yapıları için JSON kullanabilirsiniz: + +``` +Key: configuration +Type: JSON +Value: {"layout": "grid", "columns": 3, "gap": 20} +``` + +### Conditional Rendering + +```blade +@foreach($sections as $section) + @php + $data = $section['data']; + $bgClass = $data['bg_class'] ?? ''; + @endphp + +
+ @includeIf("components.blocks.{$section['type']}", compact('data')) +
+@endforeach +``` + +## 📚 Dosya Yapısı + +``` +app/ +├── Models/ +│ └── Page.php (getParsedSectionsAttribute, getSectionData) +├── Http/Controllers/ +│ └── PageController.php (parsed_sections kullanımı) +└── Filament/Admin/Resources/Pages/ + └── Schemas/ + └── PageForm.php (Section Builder UI) + +lang/ +├── tr/ +│ └── pages.php (Türkçe çeviriler) +└── en/ + └── pages.php (İngilizce çeviriler) + +resources/views/ +├── components/blocks/ +│ ├── hero.blade.php +│ ├── features.blade.php +│ ├── stats.blade.php +│ └── cta.blade.php +└── templates/ + └── home.blade.php (sections render) +``` + +## 🐛 Troubleshooting + +### Section görünmüyor? + +1. `$page->parsed_sections` döndüğünden emin olun +2. Section type'ının doğru yazıldığını kontrol edin +3. Blade component'in var olduğunu doğrulayın + +### Data değerleri boş? + +1. Admin'de data field'larının key değerlerini kontrol edin +2. Value tipinin doğru seçildiğini doğrulayın +3. Değerlerin kaydedildiğini kontrol edin + +### Image görünmüyor? + +1. `storage` link'inin oluşturulduğunu kontrol edin: `php artisan storage:link` +2. Image path'inin doğru olduğunu doğrulayın +3. Disk ayarlarını kontrol edin (`config/filesystems.php`) + +## 🎓 Örnekler + +Daha fazla örnek için: +- `database/seeders/PageSeeder.php` - Mevcut section örnekleri +- `resources/views/components/blocks/` - Component örnekleri +- `resources/views/templates/home.blade.php` - Template örneği + +## 📞 Destek + +Sorunlarınız için: +1. Bu dokümantasyonu kontrol edin +2. `docs/CURSOR_RULES.md` dosyasına bakın +3. Loglara göz atın: `storage/logs/laravel.log` + +--- + +**Not:** Bu sistem Settings modülünden esinlenerek geliştirilmiş olup, aynı esneklik ve güçle çalışır. Her türlü dinamik sayfa içeriği için kullanılabilir. + diff --git a/lang/en/pages.php b/lang/en/pages.php index 4b64983..460cef7 100644 --- a/lang/en/pages.php +++ b/lang/en/pages.php @@ -24,6 +24,7 @@ return [ 'form_section_content' => 'Content', 'form_section_page_settings' => 'Page Settings', 'form_section_seo_settings' => 'SEO Settings', + 'form_section_sections' => 'Page Sections', 'translations_section' => 'Translations', // Form fields @@ -95,4 +96,53 @@ return [ 'slug_required' => 'The URL slug field is required.', 'slug_unique' => 'This URL slug is already taken.', 'content_required' => 'The content field is required.', + + // Section Builder + 'sections_field' => 'Page Sections', + 'sections_helper_text' => 'Add dynamic sections to your page', + 'section_type' => 'Section Type', + 'section_type_helper' => 'Select the type of section you want to add', + 'section_data' => 'Section Data', + 'section_data_helper' => 'Enter the required data for this section', + 'section_add' => 'Add New Section', + + // Section Types + 'section_type_hero' => 'Hero (Main Banner)', + 'section_type_features' => 'Features', + 'section_type_stats' => 'Statistics', + 'section_type_cta' => 'Call to Action', + 'section_type_content' => 'Content', + 'section_type_gallery' => 'Gallery', + 'section_type_testimonials' => 'Testimonials', + 'section_type_team' => 'Team', + 'section_type_pricing' => 'Pricing', + 'section_type_faq' => 'FAQ', + 'section_type_contact' => 'Contact', + 'section_type_custom' => 'Custom', + + // Section Data Fields + 'section_data_key' => 'Key', + 'section_data_key_helper' => 'Data key (e.g: title, subtitle, image)', + 'section_data_value_type' => 'Value Type', + 'section_data_value' => 'Value', + 'section_data_add_field' => 'Add New Field', + + // Value Types + 'value_type_text' => 'Text', + 'value_type_textarea' => 'Long Text', + 'value_type_html' => 'HTML', + 'value_type_markdown' => 'Markdown', + 'value_type_richtext' => 'Rich Text', + 'value_type_image' => 'Image', + 'value_type_file' => 'File', + 'value_type_url' => 'URL', + 'value_type_email' => 'Email', + 'value_type_phone' => 'Phone', + 'value_type_number' => 'Number', + 'value_type_boolean' => 'Yes/No', + 'value_type_color' => 'Color', + 'value_type_date' => 'Date', + 'value_type_datetime' => 'Date & Time', + 'value_type_array' => 'Array', + 'value_type_json' => 'JSON', ]; diff --git a/lang/tr/pages.php b/lang/tr/pages.php index aaba588..cf61433 100644 --- a/lang/tr/pages.php +++ b/lang/tr/pages.php @@ -24,6 +24,7 @@ return [ 'form_section_content' => 'İçerik', 'form_section_page_settings' => 'Sayfa Ayarları', 'form_section_seo_settings' => 'SEO Ayarları', + 'form_section_sections' => 'Sayfa Bölümleri', 'translations_section' => 'Çeviriler', // Form fields @@ -95,4 +96,53 @@ return [ 'slug_required' => 'URL yolu alanı zorunludur.', 'slug_unique' => 'Bu URL yolu zaten kullanılıyor.', 'content_required' => 'İçerik alanı zorunludur.', + + // Section Builder + 'sections_field' => 'Sayfa Bölümleri', + 'sections_helper_text' => 'Sayfanız için dinamik bölümler ekleyin', + 'section_type' => 'Bölüm Tipi', + 'section_type_helper' => 'Eklemek istediğiniz bölüm tipini seçin', + 'section_data' => 'Bölüm Verileri', + 'section_data_helper' => 'Bu bölüm için gerekli verileri girin', + 'section_add' => 'Yeni Bölüm Ekle', + + // Section Types + 'section_type_hero' => 'Hero (Ana Banner)', + 'section_type_features' => 'Özellikler', + 'section_type_stats' => 'İstatistikler', + 'section_type_cta' => 'Harekete Geçirme', + 'section_type_content' => 'İçerik', + 'section_type_gallery' => 'Galeri', + 'section_type_testimonials' => 'Referanslar', + 'section_type_team' => 'Ekip', + 'section_type_pricing' => 'Fiyatlandırma', + 'section_type_faq' => 'SSS', + 'section_type_contact' => 'İletişim', + 'section_type_custom' => 'Özel', + + // Section Data Fields + 'section_data_key' => 'Anahtar', + 'section_data_key_helper' => 'Veri anahtarı (örn: title, subtitle, image)', + 'section_data_value_type' => 'Değer Tipi', + 'section_data_value' => 'Değer', + 'section_data_add_field' => 'Yeni Alan Ekle', + + // Value Types + 'value_type_text' => 'Metin', + 'value_type_textarea' => 'Uzun Metin', + 'value_type_html' => 'HTML', + 'value_type_markdown' => 'Markdown', + 'value_type_richtext' => 'Zengin Metin', + 'value_type_image' => 'Görsel', + 'value_type_file' => 'Dosya', + 'value_type_url' => 'URL', + 'value_type_email' => 'E-posta', + 'value_type_phone' => 'Telefon', + 'value_type_number' => 'Sayı', + 'value_type_boolean' => 'Evet/Hayır', + 'value_type_color' => 'Renk', + 'value_type_date' => 'Tarih', + 'value_type_datetime' => 'Tarih & Saat', + 'value_type_array' => 'Dizi', + 'value_type_json' => 'JSON', ];