Refactor PageForm schema to modular sections: Introduced dedicated classes for each section (Content, Page Settings, SEO, Section Builder, Header Template, Section Templates, Footer Template, Translations) to enhance maintainability and organization. Each section now encapsulates its own schema, improving clarity and separation of concerns in the form structure.
This commit is contained in:
@@ -1,904 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\ContentSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\FooterTemplateSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\HeaderTemplateSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\PageSettingsSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\SectionBuilderSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\SectionTemplatesSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\SeoSection;
|
||||
use App\Filament\Admin\Resources\Pages\Schemas\Sections\TranslationsSection;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use App\Models\HeaderTemplate;
|
||||
use App\Models\SectionTemplate;
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\CodeEditor\Enums\Language;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PageForm
|
||||
{
|
||||
/**
|
||||
* Configure the page form schema with modular sections.
|
||||
*
|
||||
* This form is organized into 8 sections for better maintainability:
|
||||
* 1. Content - Basic page content (title, slug, content, excerpt)
|
||||
* 2. Page Settings - Metadata and settings (image, author, status, etc.)
|
||||
* 3. SEO - SEO metadata (meta title, meta description)
|
||||
* 4. Section Builder - Dynamic page sections
|
||||
* 5. Header Template - Dynamic header template system
|
||||
* 6. Section Templates - Repeatable section templates
|
||||
* 7. Footer Template - Dynamic footer template system
|
||||
* 8. Translations - Multi-language support
|
||||
*/
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(3)
|
||||
->schema([
|
||||
// Sol kolon - Ana içerik (2 sütun genişliğinde)
|
||||
Section::make(__('pages.form_section_content'))
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('pages.title_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (string $operation, $state, callable $set) {
|
||||
if ($operation !== 'create') {
|
||||
return;
|
||||
}
|
||||
$set('slug', \Str::slug($state));
|
||||
}),
|
||||
// Left Column - Main Content (2 columns width)
|
||||
ContentSection::make(),
|
||||
|
||||
TextInput::make('slug')
|
||||
->label(__('pages.slug_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->rules(['alpha_dash'])
|
||||
->helperText(__('pages.slug_helper_text')),
|
||||
// Right Column - Settings and Metadata (1 column width)
|
||||
PageSettingsSection::make(),
|
||||
|
||||
RichEditor::make('content')
|
||||
->label(__('pages.content_field'))
|
||||
->required()
|
||||
->fileAttachmentsDisk('public')
|
||||
->fileAttachmentsDirectory('pages')
|
||||
->fileAttachmentsVisibility('public')
|
||||
->columnSpanFull(),
|
||||
// Full Width - SEO Settings
|
||||
SeoSection::make(),
|
||||
|
||||
Textarea::make('excerpt')
|
||||
->label(__('pages.excerpt_field'))
|
||||
->rows(3)
|
||||
->maxLength(500)
|
||||
->helperText(__('pages.excerpt_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(2)
|
||||
->collapsible(false),
|
||||
// Full Width - Section Builder (Custom Page Sections)
|
||||
SectionBuilderSection::make(),
|
||||
|
||||
// Sağ kolon - Metadata ve öne çıkan görsel (1 sütun genişliğinde)
|
||||
Section::make(__('pages.form_section_page_settings'))
|
||||
->schema([
|
||||
FileUpload::make('featured_image')
|
||||
->label(__('pages.featured_image_field'))
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('pages/featured')
|
||||
->visibility('public')
|
||||
->imageEditor()
|
||||
->imageEditorAspectRatios([
|
||||
'16:9',
|
||||
'4:3',
|
||||
'1:1',
|
||||
])
|
||||
->helperText(__('pages.featured_image_helper_text'))
|
||||
->columnSpanFull(),
|
||||
// Full Width - Dynamic Template System
|
||||
HeaderTemplateSection::make(),
|
||||
SectionTemplatesSection::make(),
|
||||
FooterTemplateSection::make(),
|
||||
|
||||
Select::make('author_id')
|
||||
->label(__('pages.author_field'))
|
||||
->relationship('author', 'name')
|
||||
->default(auth()->id())
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
|
||||
DateTimePicker::make('published_at')
|
||||
->label(__('pages.published_at_field'))
|
||||
->displayFormat('d.m.Y H:i')
|
||||
->helperText(__('pages.published_at_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('status')
|
||||
->label(__('pages.status_field'))
|
||||
->options([
|
||||
'draft' => __('pages.status_draft'),
|
||||
'published' => __('pages.status_published'),
|
||||
'archived' => __('pages.status_archived'),
|
||||
])
|
||||
->default('draft')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('parent_id')
|
||||
->label(__('pages.parent_field'))
|
||||
->relationship('parent', 'title')
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText(__('pages.parent_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('template')
|
||||
->label(__('pages.template_field'))
|
||||
->options([
|
||||
'default' => __('pages.template_default'),
|
||||
'landing' => __('pages.template_landing'),
|
||||
'blog' => __('pages.template_blog'),
|
||||
'contact' => __('pages.template_contact'),
|
||||
])
|
||||
->default('default')
|
||||
->columnSpanFull(),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('pages.sort_order_field'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->helperText(__('pages.sort_order_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Checkbox::make('is_homepage')
|
||||
->label(__('pages.is_homepage_field'))
|
||||
->helperText(__('pages.is_homepage_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Checkbox::make('show_in_menu')
|
||||
->label(__('pages.show_in_menu_field'))
|
||||
->default(true)
|
||||
->helperText(__('pages.show_in_menu_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(1)
|
||||
->collapsible(false),
|
||||
|
||||
// Alt kısım - SEO ayarları (tam genişlik)
|
||||
Section::make(__('pages.form_section_seo_settings'))
|
||||
->schema([
|
||||
TextInput::make('meta_title')
|
||||
->label(__('pages.meta_title_field'))
|
||||
->maxLength(60)
|
||||
->helperText(__('pages.meta_title_helper_text')),
|
||||
|
||||
Textarea::make('meta_description')
|
||||
->label(__('pages.meta_description_field'))
|
||||
->rows(3)
|
||||
->maxLength(160)
|
||||
->helperText(__('pages.meta_description_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->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(function ($component, $state, Get $get) {
|
||||
// Only hydrate if this field type is active
|
||||
if (!in_array($get('type'), ['text', 'url', 'email', 'phone', 'number'])) {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Textarea (displays and updates 'value' field)
|
||||
Textarea::make('_value_textarea')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'textarea')
|
||||
->rows(4)
|
||||
->placeholder('Metin girin...')
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
// Only hydrate if this field type is active
|
||||
if ($get('type') !== 'textarea') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// HTML Code Editor (displays and updates 'value' field)
|
||||
CodeEditor::make('_value_html')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'html')
|
||||
->language(Language::Html)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
// Only hydrate if this field type is html
|
||||
if ($get('type') !== 'html') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// JSON Code Editor (displays and updates 'value' field)
|
||||
CodeEditor::make('_value_json')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'json')
|
||||
->language(Language::Json)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
// Only hydrate if this field type is json
|
||||
if ($get('type') !== 'json') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
// Only hydrate if this field type is richtext
|
||||
if ($get('type') !== 'richtext') {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $get('value');
|
||||
|
||||
// Skip if null or empty
|
||||
if ($value === null || $value === '' || $value === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set if it's valid TipTap JSON format
|
||||
if (is_array($value) && isset($value['type']) && $value['type'] === 'doc') {
|
||||
$component->state($value);
|
||||
} elseif (is_string($value)) {
|
||||
try {
|
||||
$decoded = json_decode($value, true);
|
||||
if (is_array($decoded) && isset($decoded['type']) && $decoded['type'] === 'doc') {
|
||||
$component->state($decoded);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Invalid JSON, skip
|
||||
}
|
||||
}
|
||||
})
|
||||
->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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'markdown') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'image') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'file') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'boolean') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_bool($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'color') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'date') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'datetime') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'array') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_array($value)) {
|
||||
$component->state($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()
|
||||
->collapsed()
|
||||
->itemLabel(fn (array $state): ?string => isset($state['type']) ? __('pages.section_type_' . $state['type']) : 'Bölüm')
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
// Dynamic Template System
|
||||
Section::make(__('pages.form_section_header_template'))
|
||||
->description(__('pages.form_section_header_template_desc'))
|
||||
->schema([
|
||||
Select::make('header_template_id')
|
||||
->label(__('pages.header_template_field'))
|
||||
->options(HeaderTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
// Template seçildiğinde default verileri doldur
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = HeaderTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
// default_data'yı güvenli bir şekilde al
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mevcut header_data'yı al
|
||||
$existingData = $get('header_data') ?? [];
|
||||
|
||||
// Nested array'leri dot notation'a çeviren recursive fonksiyon
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Default data'yı düzleştir
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
// Her default veri için boş olan alanları doldur
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
// Dot notation ile nested değeri kontrol et
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
// Nested değere eriş
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Eğer bu alan boşsa veya yoksa, default değeri kullan
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("header_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $record): array {
|
||||
$templateId = $get('header_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = HeaderTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get existing page data
|
||||
$existingData = $record?->header_data ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
// Pass both existingData and defaultData separately
|
||||
// TemplateService will handle empty value fallback
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'header_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('header_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
Section::make(__('pages.form_section_template_sections'))
|
||||
->description(__('pages.form_section_template_sections_desc'))
|
||||
->schema([
|
||||
Repeater::make('sections_data')
|
||||
->label(__('pages.template_sections_field'))
|
||||
->schema([
|
||||
Select::make('section_template_id')
|
||||
->label(__('pages.section_template_field'))
|
||||
->options(SectionTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->required()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
// Template seçildiğinde default verileri doldur
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = SectionTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
// default_data'yı güvenli bir şekilde al
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mevcut section_data'yı al
|
||||
$existingData = $get('section_data') ?? [];
|
||||
|
||||
// Nested array'leri dot notation'a çeviren recursive fonksiyon
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Default data'yı düzleştir
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
// Her default veri için boş olan alanları doldur
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
// Dot notation ile nested değeri kontrol et
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
// Nested değere eriş
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Eğer bu alan boşsa veya yoksa, default değeri kullan
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("section_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $state): array {
|
||||
$templateId = $get('section_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = SectionTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get existing section data
|
||||
$existingData = $get('section_data') ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
// Pass both existingData and defaultData separately
|
||||
// TemplateService will handle empty value fallback
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'section_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('section_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->defaultItems(0)
|
||||
->addActionLabel(__('pages.add_template_section'))
|
||||
->reorderable()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->itemLabel(__('pages.template_section'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
Section::make(__('pages.form_section_footer_template'))
|
||||
->description(__('pages.form_section_footer_template_desc'))
|
||||
->schema([
|
||||
Select::make('footer_template_id')
|
||||
->label(__('pages.footer_template_field'))
|
||||
->options(FooterTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
// Template seçildiğinde default verileri doldur
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = FooterTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
// default_data'yı güvenli bir şekilde al
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mevcut footer_data'yı al
|
||||
$existingData = $get('footer_data') ?? [];
|
||||
|
||||
// Nested array'leri dot notation'a çeviren recursive fonksiyon
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Default data'yı düzleştir
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
// Her default veri için boş olan alanları doldur
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
// Dot notation ile nested değeri kontrol et
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
// Nested değere eriş
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Eğer bu alan boşsa veya yoksa, default değeri kullan
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("footer_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $record): array {
|
||||
$templateId = $get('footer_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = FooterTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get existing page data
|
||||
$existingData = $record?->footer_data ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
// Pass both existingData and defaultData separately
|
||||
// TemplateService will handle empty value fallback
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'footer_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('footer_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true),
|
||||
|
||||
// Çeviri Sekmesi
|
||||
Section::make('🌍 ' . __('pages.translations_section'))
|
||||
->schema([
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
'label' => __('pages.content_field'),
|
||||
'required' => false,
|
||||
],
|
||||
'excerpt' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.excerpt_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 500,
|
||||
'rows' => 3,
|
||||
],
|
||||
'meta_title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.meta_title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 60,
|
||||
],
|
||||
'meta_description' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.meta_description_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 160,
|
||||
'rows' => 3,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false),
|
||||
// Full Width - Translations
|
||||
TranslationsSection::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
|
||||
class ContentSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_content'))
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('pages.title_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (string $operation, $state, callable $set) {
|
||||
if ($operation !== 'create') {
|
||||
return;
|
||||
}
|
||||
$set('slug', \Str::slug($state));
|
||||
}),
|
||||
|
||||
TextInput::make('slug')
|
||||
->label(__('pages.slug_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->rules(['alpha_dash'])
|
||||
->helperText(__('pages.slug_helper_text')),
|
||||
|
||||
RichEditor::make('content')
|
||||
->label(__('pages.content_field'))
|
||||
->required()
|
||||
->fileAttachmentsDisk('public')
|
||||
->fileAttachmentsDirectory('pages')
|
||||
->fileAttachmentsVisibility('public')
|
||||
->columnSpanFull(),
|
||||
|
||||
Textarea::make('excerpt')
|
||||
->label(__('pages.excerpt_field'))
|
||||
->rows(3)
|
||||
->maxLength(500)
|
||||
->helperText(__('pages.excerpt_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(2)
|
||||
->collapsible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
|
||||
class FooterTemplateSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_footer_template'))
|
||||
->description(__('pages.form_section_footer_template_desc'))
|
||||
->schema([
|
||||
Select::make('footer_template_id')
|
||||
->label(__('pages.footer_template_field'))
|
||||
->options(FooterTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = FooterTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existingData = $get('footer_data') ?? [];
|
||||
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("footer_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $record): array {
|
||||
$templateId = $get('footer_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = FooterTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$existingData = $record?->footer_data ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'footer_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('footer_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use App\Models\HeaderTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
|
||||
class HeaderTemplateSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_header_template'))
|
||||
->description(__('pages.form_section_header_template_desc'))
|
||||
->schema([
|
||||
Select::make('header_template_id')
|
||||
->label(__('pages.header_template_field'))
|
||||
->options(HeaderTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = HeaderTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existingData = $get('header_data') ?? [];
|
||||
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("header_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $record): array {
|
||||
$templateId = $get('header_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = HeaderTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$existingData = $record?->header_data ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'header_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('header_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
|
||||
class PageSettingsSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_page_settings'))
|
||||
->schema([
|
||||
FileUpload::make('featured_image')
|
||||
->label(__('pages.featured_image_field'))
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('pages/featured')
|
||||
->visibility('public')
|
||||
->imageEditor()
|
||||
->imageEditorAspectRatios([
|
||||
'16:9',
|
||||
'4:3',
|
||||
'1:1',
|
||||
])
|
||||
->helperText(__('pages.featured_image_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('author_id')
|
||||
->label(__('pages.author_field'))
|
||||
->relationship('author', 'name')
|
||||
->default(auth()->id())
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
|
||||
DateTimePicker::make('published_at')
|
||||
->label(__('pages.published_at_field'))
|
||||
->displayFormat('d.m.Y H:i')
|
||||
->helperText(__('pages.published_at_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('status')
|
||||
->label(__('pages.status_field'))
|
||||
->options([
|
||||
'draft' => __('pages.status_draft'),
|
||||
'published' => __('pages.status_published'),
|
||||
'archived' => __('pages.status_archived'),
|
||||
])
|
||||
->default('draft')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('parent_id')
|
||||
->label(__('pages.parent_field'))
|
||||
->relationship('parent', 'title')
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText(__('pages.parent_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('template')
|
||||
->label(__('pages.template_field'))
|
||||
->options([
|
||||
'default' => __('pages.template_default'),
|
||||
'landing' => __('pages.template_landing'),
|
||||
'blog' => __('pages.template_blog'),
|
||||
'contact' => __('pages.template_contact'),
|
||||
])
|
||||
->default('default')
|
||||
->columnSpanFull(),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('pages.sort_order_field'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->helperText(__('pages.sort_order_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Checkbox::make('is_homepage')
|
||||
->label(__('pages.is_homepage_field'))
|
||||
->helperText(__('pages.is_homepage_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Checkbox::make('show_in_menu')
|
||||
->label(__('pages.show_in_menu_field'))
|
||||
->default(true)
|
||||
->helperText(__('pages.show_in_menu_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(1)
|
||||
->collapsible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\CodeEditor\Enums\Language;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
|
||||
class SectionBuilderSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return 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
|
||||
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(function ($component, $state, Get $get) {
|
||||
if (!in_array($get('type'), ['text', 'url', 'email', 'phone', 'number'])) {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Textarea
|
||||
Textarea::make('_value_textarea')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'textarea')
|
||||
->rows(4)
|
||||
->placeholder('Metin girin...')
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'textarea') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// HTML Code Editor
|
||||
CodeEditor::make('_value_html')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'html')
|
||||
->language(Language::Html)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'html') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// JSON Code Editor
|
||||
CodeEditor::make('_value_json')
|
||||
->label(__('pages.section_data_value'))
|
||||
->visible(fn (Get $get) => $get('type') === 'json')
|
||||
->language(Language::Json)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('value', $state))
|
||||
->afterStateHydrated(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'json') {
|
||||
return;
|
||||
}
|
||||
$value = $get('value');
|
||||
if ($value !== null) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Rich Text Editor
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'richtext') {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $get('value');
|
||||
|
||||
if ($value === null || $value === '' || $value === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($value) && isset($value['type']) && $value['type'] === 'doc') {
|
||||
$component->state($value);
|
||||
} elseif (is_string($value)) {
|
||||
try {
|
||||
$decoded = json_decode($value, true);
|
||||
if (is_array($decoded) && isset($decoded['type']) && $decoded['type'] === 'doc') {
|
||||
$component->state($decoded);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Invalid JSON, skip
|
||||
}
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Markdown Editor
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'markdown') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Image Upload
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'image') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// File Upload
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'file') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Boolean Toggle
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'boolean') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_bool($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Color Picker
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'color') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Date Picker
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'date') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// DateTime Picker
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'datetime') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_string($value)) {
|
||||
$component->state($value);
|
||||
}
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Array Repeater
|
||||
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(function ($component, $state, Get $get) {
|
||||
if ($get('type') !== 'array') return;
|
||||
$value = $get('value');
|
||||
if ($value !== null && is_array($value)) {
|
||||
$component->state($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()
|
||||
->collapsed()
|
||||
->itemLabel(fn (array $state): ?string => isset($state['type']) ? __('pages.section_type_' . $state['type']) : 'Bölüm')
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use App\Models\SectionTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
|
||||
class SectionTemplatesSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_template_sections'))
|
||||
->description(__('pages.form_section_template_sections_desc'))
|
||||
->schema([
|
||||
Repeater::make('sections_data')
|
||||
->label(__('pages.template_sections_field'))
|
||||
->schema([
|
||||
Select::make('section_template_id')
|
||||
->label(__('pages.section_template_field'))
|
||||
->options(SectionTemplate::where('is_active', true)->pluck('title', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->required()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = SectionTemplate::find($state);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultData = $template->default_data;
|
||||
if (is_string($defaultData)) {
|
||||
$defaultData = json_decode($defaultData, true) ?? [];
|
||||
}
|
||||
if (!is_array($defaultData) || empty($defaultData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existingData = $get('section_data') ?? [];
|
||||
|
||||
$flattenDefaultData = function ($array, $prefix = '') use (&$flattenDefaultData) {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $prefix ? "{$prefix}.{$key}" : $key;
|
||||
if (is_array($value)) {
|
||||
$result = array_merge($result, $flattenDefaultData($value, $newKey));
|
||||
} else {
|
||||
$result[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$flattenedDefaults = $flattenDefaultData($defaultData);
|
||||
|
||||
foreach ($flattenedDefaults as $key => $defaultValue) {
|
||||
$keys = explode('.', $key);
|
||||
$currentValue = $existingData;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
$currentValue = $currentValue[$k] ?? null;
|
||||
if ($currentValue === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentValue === null ||
|
||||
$currentValue === '' ||
|
||||
(is_array($currentValue) && empty($currentValue))) {
|
||||
$set("section_data.{$key}", $defaultValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Group::make()
|
||||
->schema(function (Get $get, $state): array {
|
||||
$templateId = $get('section_template_id');
|
||||
if (!$templateId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$template = SectionTemplate::find($templateId);
|
||||
if (!$template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$existingData = $get('section_data') ?? [];
|
||||
$templateDefaults = $template->default_data ?? [];
|
||||
|
||||
return TemplateService::generateDynamicFields(
|
||||
$template,
|
||||
'section_data',
|
||||
$existingData,
|
||||
$templateDefaults
|
||||
);
|
||||
})
|
||||
->visible(fn (Get $get): bool => filled($get('section_template_id')))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->defaultItems(0)
|
||||
->addActionLabel(__('pages.add_template_section'))
|
||||
->reorderable()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->itemLabel(__('pages.template_section'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
|
||||
class SeoSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make(__('pages.form_section_seo_settings'))
|
||||
->schema([
|
||||
TextInput::make('meta_title')
|
||||
->label(__('pages.meta_title_field'))
|
||||
->maxLength(60)
|
||||
->helperText(__('pages.meta_title_helper_text')),
|
||||
|
||||
Textarea::make('meta_description')
|
||||
->label(__('pages.meta_description_field'))
|
||||
->rows(3)
|
||||
->maxLength(160)
|
||||
->helperText(__('pages.meta_description_helper_text'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Schemas\Components\Section;
|
||||
|
||||
class TranslationsSection
|
||||
{
|
||||
public static function make(): Section
|
||||
{
|
||||
return Section::make('🌍 ' . __('pages.translations_section'))
|
||||
->schema([
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
'label' => __('pages.content_field'),
|
||||
'required' => false,
|
||||
],
|
||||
'excerpt' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.excerpt_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 500,
|
||||
'rows' => 3,
|
||||
],
|
||||
'meta_title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('pages.meta_title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 60,
|
||||
],
|
||||
'meta_description' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('pages.meta_description_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 160,
|
||||
'rows' => 3,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user