diff --git a/app/Enums/NotifStyle.php b/app/Enums/NotifStyle.php new file mode 100644 index 0000000..03959cd --- /dev/null +++ b/app/Enums/NotifStyle.php @@ -0,0 +1,19 @@ + 'Ceria (Banyak Emoji) 🚀✨', + self::Formal => 'Formal & Profesional 💼', + }; + } +} diff --git a/app/Filament/Pages/Profile.php b/app/Filament/Pages/Profile.php index e827f76..b86e53c 100644 --- a/app/Filament/Pages/Profile.php +++ b/app/Filament/Pages/Profile.php @@ -2,14 +2,18 @@ namespace App\Filament\Pages; +use App\Enums\NotifStyle; use App\Enums\Sex; use App\Filament\Support\SystemNotification; use Carbon\Carbon; use Filament\Auth\Pages\EditProfile; +use Filament\Forms\Components\ColorPicker; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Radio; +use Filament\Forms\Components\Select; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Toggle; use Filament\Notifications\Notification; use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Section; @@ -157,33 +161,117 @@ public function form(Schema $schema): Schema ->prefixIcon('heroicon-o-lock-closed'), ]), ]), + + Section::make(SystemNotification::getMessage('Pengaturan Tampilan & UX 🎨✨', 'Preferensi Tampilan')) + ->description(SystemNotification::getMessage('Personalisasi pengalaman aplikasi Anda agar lebih nyaman dan sesuai selera! 🌈🚀', 'Sesuaikan gaya bahasa, warna tema, dan jenis huruf aplikasi Anda.')) + ->icon(SystemNotification::getNotifStyle() === NotifStyle::Cheerful ? 'heroicon-o-swatch' : 'heroicon-o-paint-brush') + ->schema([ + Grid::make(2) + ->schema([ + Select::make('settings.notif_style') + ->label('Gaya Bahasa') + ->options(NotifStyle::class) + ->native(false) + ->required(), + + Select::make('settings.primary_color') + ->label('Tema Warna') + ->options([ + 'blue' => '🔵 Biru (Default)', + 'sky' => '💎 Biru Langit', + 'cyan' => '🌊 Biru Tosca (Cyan)', + 'emerald' => '🟢 Emerald (Hijau)', + 'teal' => '🍃 Teal (Hijau Keunguan)', + 'lime' => '🍋 Lime (Hijah Muda)', + 'amber' => '🟡 Amber (Kuning)', + 'orange' => '🟠 Orange', + 'rose' => '🔴 Rose (Merah)', + 'fuchsia' => '🌸 Fuchsia (Pink Cerah)', + 'violet' => '🟣 Violet (Ungu)', + 'indigo' => '🌌 Indigo (Ungu Gelap)', + ]) + ->placeholder('Pilih warna tema') + ->native(false), + + Select::make('settings.font') + ->label('Jenis Huruf (Font)') + ->options([ + 'Inter' => 'Inter (Modern)', + 'Roboto' => 'Roboto (Clean)', + 'Poppins' => 'Poppins (Rounder)', + 'Outfit' => 'Outfit (Premium)', + 'Montserrat' => 'Montserrat (Classic)', + 'Lexend' => 'Lexend (Readable)', + ]) + ->native(false), + + Select::make('settings.content_width') + ->label('Lebar Konten') + ->options([ + 'full' => '↔️ Lebar Penuh (Full)', + 'centered' => '🏢 Terpusat (Centered)', + ]) + ->native(false), + + Select::make('settings.border_radius') + ->label('Radius Sudut (Border)') + ->options([ + 'none' => '📐 Tegas (Sharp)', + 'md' => '📱 Modern (Default)', + 'lg' => '🎉 Rounded (Cheerful)', + 'xl' => '🎈 Extra Round', + ]) + ->native(false), + + Toggle::make('settings.top_navigation') + ->label('Navigasi Atas (Top Nav)') + ->helperText('Pindahkan menu navigasi dari samping ke bagian atas layar.') + ->onIcon('heroicon-m-window') + ->offIcon('heroicon-m-arrow-top-right-on-square') + ->inline(false), + ]), + ]), ]); } protected function mutateFormDataBeforeFill(array $data): array { - $student = $this->getUser()->student; + $user = $this->getUser(); + $student = $user->student; + $settings = $user->settings; if ($student) { $data['student'] = $student->toArray(); } + if ($settings) { + $data['settings'] = $settings->toArray(); + } + return $data; } protected function handleRecordUpdate(Model $record, array $data): Model { $studentData = $data['student'] ?? null; - unset($data['student']); + $settingsData = $data['settings'] ?? null; + unset($data['student'], $data['settings']); $record->update($data); - if ($studentData && $record->student) { + /** @var \App\Models\User $record */ + if ($studentData && $record->student()->exists()) { $record->student->update($studentData); } elseif ($studentData) { $record->student()->create($studentData); } + if ($settingsData && $record->settings()->exists()) { + $record->settings->update($settingsData); + } elseif ($settingsData) { + $record->settings()->create($settingsData); + } + return $record; } diff --git a/app/Http/Middleware/DynamicFilamentTheme.php b/app/Http/Middleware/DynamicFilamentTheme.php new file mode 100644 index 0000000..51b996c --- /dev/null +++ b/app/Http/Middleware/DynamicFilamentTheme.php @@ -0,0 +1,102 @@ +user(); + + if ($user && $user->settings) { + // Register Colors + FilamentColor::register([ + 'primary' => match ($user->settings->primary_color) { + 'blue' => Color::Blue, + 'sky' => Color::Sky, + 'cyan' => Color::Cyan, + 'emerald' => Color::Emerald, + 'teal' => Color::Teal, + 'lime' => Color::Lime, + 'amber' => Color::Amber, + 'orange' => Color::Orange, + 'rose' => Color::Rose, + 'fuchsia' => Color::Fuchsia, + 'violet' => Color::Violet, + 'indigo' => Color::Indigo, + default => Color::Blue, + }, + ]); + + // Register Top Navigation + $panel = Filament::getCurrentOrDefaultPanel(); + if ($panel && method_exists($panel, 'topNavigation')) { + $panel->topNavigation((bool) $user->settings->top_navigation); + } + + // Register Font & UI Styles + $font = $user->settings->font ?? 'Inter'; + $radius = match ($user->settings->border_radius ?? 'md') { + 'none' => '0px', + 'md' => '0.375rem', + 'lg' => '0.5rem', + 'xl' => '0.75rem', + '2xl' => '1rem', + default => '0.5rem', + }; + $maxWidth = match ($user->settings->content_width ?? 'full') { + 'centered' => '80rem', + default => 'none', + }; + + FilamentView::registerRenderHook( + PanelsRenderHook::HEAD_END, + fn () => new HtmlString(" + + + + + ") + ); + } + + return $next($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index c5f0a8b..5db7a54 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\IsActive; +use App\Enums\NotifStyle; use Filament\Models\Contracts\FilamentUser; use Filament\Panel; use Illuminate\Database\Eloquent\Attributes\Scope; @@ -63,4 +64,16 @@ public function student(): HasOne { return $this->hasOne(Student::class); } + + public function settings(): HasOne + { + return $this->hasOne(UserSetting::class)->withDefault([ + 'notif_style' => NotifStyle::Cheerful, + 'primary_color' => 'amber', // Cheerful yellow + 'font' => 'Inter', + 'content_width' => 'full', + 'border_radius' => 'lg', + 'top_navigation' => false, + ]); + } } diff --git a/app/Models/UserSetting.php b/app/Models/UserSetting.php new file mode 100644 index 0000000..cc7cb93 --- /dev/null +++ b/app/Models/UserSetting.php @@ -0,0 +1,25 @@ +belongsTo(User::class); + } + + protected function casts(): array + { + return [ + 'notif_style' => NotifStyle::class, + 'top_navigation' => 'boolean', + ]; + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index c5743c8..e3716a3 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -5,6 +5,8 @@ use AchyutN\FilamentLogViewer\FilamentLogViewer; use App\Filament\Pages\Auth\Login; use App\Filament\Pages\Profile; +use App\Http\Middleware\DynamicFilamentTheme; +use App\Models\User; use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use DiogoGPinto\AuthUIEnhancer\AuthUIEnhancerPlugin; use Filament\Http\Middleware\Authenticate; @@ -58,6 +60,7 @@ public function panel(Panel $panel): Panel SubstituteBindings::class, DisableBladeIconComponents::class, DispatchServingFilamentEvent::class, + DynamicFilamentTheme::class, ]) ->authMiddleware([ Authenticate::class, diff --git a/database/migrations/2026_03_30_172335_create_user_settings_table.php b/database/migrations/2026_03_30_172335_create_user_settings_table.php new file mode 100644 index 0000000..1b314db --- /dev/null +++ b/database/migrations/2026_03_30_172335_create_user_settings_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('user_id')->unique()->constrained()->cascadeOnDelete(); + $table->enum('notif_style', NotifStyle::cases())->default(NotifStyle::Cheerful->value); + $table->string('primary_color')->nullable(); + $table->string('font')->default('Inter'); + $table->string('content_width')->default('full'); + $table->string('border_radius')->default('lg'); + $table->boolean('top_navigation')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_settings'); + } +};