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');
+ }
+};