diff --git a/app/Filament/Admin/Resources/Proposals/Pages/CreateProposal.php b/app/Filament/Admin/Resources/Proposals/Pages/CreateProposal.php index 896314b..da5bd90 100644 --- a/app/Filament/Admin/Resources/Proposals/Pages/CreateProposal.php +++ b/app/Filament/Admin/Resources/Proposals/Pages/CreateProposal.php @@ -4,6 +4,7 @@ namespace App\Filament\Admin\Resources\Proposals\Pages; use App\Filament\Admin\Resources\Proposals\ProposalResource; use Filament\Resources\Pages\CreateRecord; +use Filament\Support\Enums\Width; class CreateProposal extends CreateRecord { @@ -13,4 +14,10 @@ class CreateProposal extends CreateRecord { return __('proposal.create'); } + + public function getMaxContentWidth(): Width | string | null + { + return Width::Full; + } } + diff --git a/app/Filament/Admin/Resources/Proposals/Pages/EditProposal.php b/app/Filament/Admin/Resources/Proposals/Pages/EditProposal.php index cfd3ecd..062604d 100644 --- a/app/Filament/Admin/Resources/Proposals/Pages/EditProposal.php +++ b/app/Filament/Admin/Resources/Proposals/Pages/EditProposal.php @@ -4,6 +4,7 @@ namespace App\Filament\Admin\Resources\Proposals\Pages; use App\Filament\Admin\Resources\Proposals\ProposalResource; use Filament\Resources\Pages\EditRecord; +use Filament\Support\Enums\Width; class EditProposal extends EditRecord { @@ -13,4 +14,10 @@ class EditProposal extends EditRecord { return __('proposal.edit'); } + + public function getMaxContentWidth(): Width | string | null + { + return Width::Full; + } } + diff --git a/app/Filament/Admin/Resources/Proposals/Schemas/ProposalForm.php b/app/Filament/Admin/Resources/Proposals/Schemas/ProposalForm.php index 794ce7c..aba1fc7 100644 --- a/app/Filament/Admin/Resources/Proposals/Schemas/ProposalForm.php +++ b/app/Filament/Admin/Resources/Proposals/Schemas/ProposalForm.php @@ -5,9 +5,14 @@ namespace App\Filament\Admin\Resources\Proposals\Schemas; use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Select; +use Filament\Schemas\Components\Tabs; +use Filament\Schemas\Components\Tabs\Tab; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\CodeEditor; +use Filament\Forms\Components\CodeEditor\Enums\Language as CodeEditorLanguage; use Filament\Schemas\Components\Section; +use Filament\Schemas\Components\View; use Filament\Schemas\Schema; class ProposalForm @@ -15,126 +20,145 @@ class ProposalForm 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(__('proposal.content_section')) - ->schema([ - TextInput::make('title') - ->label(__('proposal.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(__('proposal.slug_field')) - ->required() - ->maxLength(255) - ->unique(ignoreRecord: true) - ->rules(['alpha_dash']) - ->helperText(__('proposal.slug_helper')), - - Textarea::make('content') - ->label(__('proposal.content_field')) - ->required() - ->rows(18) - ->extraInputAttributes(['style' => 'font-family: monospace; font-size: 0.9rem; line-height: 1.5;']) - ->columnSpanFull(), - - Textarea::make('client_feedback') - ->label('Müşteri Geri Bildirimi / Revizyon Notu') - ->rows(3) - ->disabled() - ->dehydrated(false) - ->placeholder('Henüz geri bildirim bırakılmadı.') - ->visible(fn ($record) => $record !== null && !empty($record->client_feedback)) - ->columnSpanFull(), - ]) - ->columnSpan(2) - ->collapsible(false), - - // Sağ kolon - Durum, Fiyat, Müşteri ve Tasarım (1 sütun genişliğinde) - Section::make(__('proposal.settings_section')) - ->schema([ - Select::make('status') - ->label(__('proposal.status_field')) - ->options([ - 'draft' => __('proposal.status_draft'), - 'sent' => __('proposal.status_sent'), - 'accepted' => __('proposal.status_accepted'), - 'rejected' => __('proposal.status_rejected'), - 'revised' => __('proposal.status_revised'), - ]) - ->default('draft') - ->required() - ->columnSpanFull(), + Tabs::make('Tabs') + ->tabs([ + Tab::make('Teklif Detayları ve İçerik') + ->icon('heroicon-m-document-text') + ->schema([ + // Section 2: side-by-side Markdown editor (CodeMirror) + live preview + Section::make(__('proposal.content_section')) + ->description('Sol tarafta Markdown formatında içeriği düzenleyin, sağ tarafta canlı olarak nasıl görüneceğini izleyin.') + ->columns(2) + ->schema([ + CodeEditor::make('content') + ->label(__('proposal.content_field')) + ->hiddenLabel() + ->required() + ->language(CodeEditorLanguage::Markdown) + ->live(onBlur: false) + ->columnSpan(1) + ->extraAttributes([ + 'style' => 'min-height: 700px; max-height: 700px; overflow-y: auto;', + 'data-field-name' => 'content', + ]), - TextInput::make('client_name') - ->label(__('proposal.client_name_field')) - ->required() - ->maxLength(255) - ->columnSpanFull(), + View::make('filament.components.markdown-preview') + ->columnSpan(1), + ]), + ]), + Tab::make('Durum ve Ayarlar') + ->icon('heroicon-m-cog-6-tooth') + ->schema([ + // Section 1: compact top metadata and configuration + Section::make(__('proposal.settings_section')) + ->columns(12) + ->schema([ + TextInput::make('title') + ->label(__('proposal.title_field')) + ->required() + ->maxLength(255) + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, $state, callable $set) { + if ($operation !== 'create') { + return; + } + $set('slug', \Str::slug($state)); + }) + ->columnSpan(4), + + TextInput::make('slug') + ->label(__('proposal.slug_field')) + ->required() + ->maxLength(255) + ->unique(ignoreRecord: true) + ->rules(['alpha_dash']) + ->helperText(__('proposal.slug_helper')) + ->columnSpan(4), - TextInput::make('client_email') - ->label(__('proposal.client_email_field')) - ->email() - ->maxLength(255) - ->columnSpanFull(), + Select::make('status') + ->label(__('proposal.status_field')) + ->options([ + 'draft' => __('proposal.status_draft'), + 'sent' => __('proposal.status_sent'), + 'accepted' => __('proposal.status_accepted'), + 'rejected' => __('proposal.status_rejected'), + 'revised' => __('proposal.status_revised'), + ]) + ->default('draft') + ->required() + ->columnSpan(4), - TextInput::make('total_price') - ->label(__('proposal.total_price_field')) - ->numeric() - ->prefix(fn ($get) => match ($get('currency')) { - 'USD' => '$', - 'EUR' => '€', - default => '₺', - }) - ->columnSpan(2), + TextInput::make('client_name') + ->label(__('proposal.client_name_field')) + ->required() + ->maxLength(255) + ->columnSpan(4), - Select::make('currency') - ->label(__('proposal.currency_field')) - ->options([ - 'TRY' => 'TL (₺)', - 'USD' => 'USD ($)', - 'EUR' => 'EUR (€)', - ]) - ->default('TRY') - ->required() - ->live() - ->columnSpan(1), + TextInput::make('client_email') + ->label(__('proposal.client_email_field')) + ->email() + ->maxLength(255) + ->columnSpan(4), - DatePicker::make('valid_until') - ->label(__('proposal.valid_until_field')) - ->native(false) - ->displayFormat('d.m.Y') - ->columnSpanFull(), + DatePicker::make('valid_until') + ->label(__('proposal.valid_until_field')) + ->native(false) + ->displayFormat('d.m.Y') + ->columnSpan(4), - Select::make('meta.accent_color') - ->label(__('proposal.accent_color_field')) - ->options([ - 'indigo' => 'Premium Indigo (Mor/Mavi)', - 'emerald' => 'Emerald Medical (Zümrüt Yeşil)', - 'cyberpunk' => 'Cyberpunk Neon (Pembe/Mavi)', - 'coral' => 'Coral Corporate (Mercan Kırmızı)', - 'amber' => 'Amber Executive (Kehribar/Altın)', - ]) - ->default('indigo') - ->columnSpanFull(), + TextInput::make('total_price') + ->label(__('proposal.total_price_field')) + ->numeric() + ->prefix(fn ($get) => match ($get('currency')) { + 'USD' => '$', + 'EUR' => '€', + default => '₺', + }) + ->columnSpan(3), - Checkbox::make('meta.show_calculator') - ->label(__('proposal.show_calculator_field')) - ->default(true) - ->columnSpanFull(), - ]) - ->columns(3) - ->columnSpan(1) - ->collapsible(false), + Select::make('currency') + ->label(__('proposal.currency_field')) + ->options([ + 'TRY' => 'TL (₺)', + 'USD' => 'USD ($)', + 'EUR' => 'EUR (€)', + ]) + ->default('TRY') + ->required() + ->live() + ->columnSpan(1), + + Select::make('meta.accent_color') + ->label(__('proposal.accent_color_field')) + ->options([ + 'indigo' => 'Premium Indigo (Mor/Mavi)', + 'emerald' => 'Emerald Medical (Zümrüt Yeşil)', + 'cyberpunk' => 'Cyberpunk Neon (Pembe/Mavi)', + 'coral' => 'Coral Corporate (Mercan Kırmızı)', + 'amber' => 'Amber Executive (Kehribar/Altın)', + ]) + ->default('indigo') + ->live() + ->columnSpan(4), + + Checkbox::make('meta.show_calculator') + ->label(__('proposal.show_calculator_field')) + ->default(true) + ->columnSpan(4), + + // Client feedback inside this section — only visible when present + Textarea::make('client_feedback') + ->label('Müşteri Geri Bildirimi / Revizyon Notu') + ->rows(3) + ->disabled() + ->dehydrated(false) + ->placeholder('Henüz geri bildirim bırakılmadı.') + ->visible(fn ($record) => $record !== null && !empty($record->client_feedback)) + ->columnSpanFull(), + ]), + ]), + ])->columnSpanFull() ]); } } diff --git a/resources/views/filament/components/markdown-preview.blade.php b/resources/views/filament/components/markdown-preview.blade.php new file mode 100644 index 0000000..2ff1c87 --- /dev/null +++ b/resources/views/filament/components/markdown-preview.blade.php @@ -0,0 +1,344 @@ +@php + // Server-side: get initial values for the header metadata + $accent = $get('meta.accent_color') ?? 'indigo'; + $accentColor = match($accent) { + 'emerald' => '#059669', + 'cyberpunk' => '#ec4899', + 'coral' => '#ea580c', + 'amber' => '#d97706', + default => '#4f46e5', + }; + $accentGradient = match($accent) { + 'emerald' => 'linear-gradient(135deg, #059669 0%, #10b981 100%)', + 'cyberpunk' => 'linear-gradient(135deg, #ec4899 0%, #f43f5e 100%)', + 'coral' => 'linear-gradient(135deg, #ea580c 0%, #f97316 100%)', + 'amber' => 'linear-gradient(135deg, #d97706 0%, #f59e0b 100%)', + default => 'linear-gradient(135deg, #4f46e5 0%, #6366f1 100%)', + }; +@endphp + +{{-- Google Fonts --}} + + + + +{{-- Marked.js for client-side markdown rendering --}} + + +