Filament Admin Panels
Profile PS3's dual-panel Filament 4.x architecture for admin and user interfaces.
Panel Architecture Overview
Profile PS3 uses Filament's multi-panel capability to provide two distinct application interfaces:
Admin Panel (/admin)
- Purpose: Full administrative control and system management
- Access: Administrators and Super Admins only
- Features: All CRUD resources, user management, material databases, system settings
App Panel (/app)
- Purpose: User-facing project management and calculators
- Access: All approved registered users
- Features: Personal projects, calculators, profile management
Admin Panel Configuration
Location: app/Providers/Filament/AdminPanelProvider.php
Core Features
public function panel(Panel $panel): Panel
{
return $panel
->default() // Default panel
->id('admin')
->path('admin')
->login(Login::class) // Custom login page
->passwordReset() // Password reset flow
->emailVerification() // Email verification
->databaseNotifications() // Database-backed notifications
->sidebarCollapsibleOnDesktop() // Collapsible sidebar
->profile(Profile::class, false) // Custom profile page
->multiFactorAuthentication( // MFA with app authenticator
AppAuthentication::make()
->recoverable() // Recovery codes
)
->viteTheme('resources/css/filament/admin/theme.css')
->colors([
'primary' => '#9fcf68', // Brand green
])
->brandLogo(asset('images/ps3_final.png'))
->brandName('Profile PS3 Admin')
->favicon(asset('images/favicon.png'))
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
->globalSearchFieldKeyBindingSuffix();
}
Resource Discovery
Auto-discovers resources from multiple directories:
->discoverResources(
in: app_path('Filament/Resources'),
for: 'App\\Filament\\Resources'
)
->discoverResources(
in: app_path('Filament/Shared/Resources'),
for: 'App\\Filament\\Shared\\Resources'
)
->discoverPages(
in: app_path('Filament/Pages'),
for: 'App\\Filament\\Pages'
)
->discoverPages(
in: app_path('Filament/Shared/Pages'),
for: 'App\\Filament\\Shared\\Pages'
)
Installed Plugins
->plugins([
FilamentSpatieRolesPermissionsPlugin::make(),
ImpersonatePlugin::make(),
])
FilamentSpatieRolesPermissionsPlugin:
- Role management UI
- Permission management UI
- User role assignment
- Shield policies integration
ImpersonatePlugin:
- Admin user impersonation for support
- Audit trail of impersonation sessions
- One-click return to admin account
Navigation Groups
->navigationGroups([
NavigationGroup::make('Projects')
->icon('heroicon-o-folder')
->collapsed(false),
NavigationGroup::make('Materials')
->icon('heroicon-o-cube')
->collapsed(true),
NavigationGroup::make('Geographic Data')
->icon('heroicon-o-globe-alt')
->collapsed(true),
NavigationGroup::make('Users & Access')
->icon('heroicon-o-users')
->collapsed(true),
NavigationGroup::make('System')
->icon('heroicon-o-cog')
->collapsed(true),
])
Middleware Stack
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
\Hasnayeen\Themes\Http\Middleware\SetTheme::class,
])
->authMiddleware([
Authenticate::class,
'verified',
])
Admin Panel Resources
User Management Resources
UserResource
Path: app/Filament/Resources/UserResource.php
Features:
- User CRUD with company details
- Account approval workflow (pending → approved/denied)
- Role assignment
- MFA status display
- Impersonation action
- Login count tracking
- Regional manager assignment
Form Fields:
Forms\Components\TextInput::make('fname')->required(),
Forms\Components\TextInput::make('lname')->required(),
Forms\Components\TextInput::make('email')->email()->unique()->required(),
Forms\Components\TextInput::make('company'),
Forms\Components\TextInput::make('phone')->tel(),
Forms\Components\Textarea::make('address'),
Forms\Components\Select::make('state_province_id')
->relationship('state', 'name')
->searchable(),
Forms\Components\TextInput::make('city'),
Forms\Components\TextInput::make('zip')->maxLength(10),
Forms\Components\Select::make('country')
->options(Country::pluck('name', 'code')),
Forms\Components\Select::make('approved')
->options([
'pending' => 'Pending',
'approved' => 'Approved',
'denied' => 'Denied',
])->required(),
Forms\Components\Toggle::make('view_comparables')
->label('Can View Material Comparisons'),
Forms\Components\Toggle::make('get_product_updates')
->label('Receive Product Updates'),
Table Columns:
Tables\Columns\TextColumn::make('fname')->searchable(),
Tables\Columns\TextColumn::make('lname')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(),
Tables\Columns\TextColumn::make('company')->searchable(),
Tables\Columns\BadgeColumn::make('approved')
->colors([
'warning' => 'pending',
'success' => 'approved',
'danger' => 'denied',
]),
Tables\Columns\IconColumn::make('hasEnabledTwoFactorAuthentication')
->label('MFA')
->boolean(),
Tables\Columns\TextColumn::make('login_count')->sortable(),
Tables\Columns\TextColumn::make('created_at')->dateTime()->sortable(),
Actions:
- Impersonate user
- Approve/deny account
- Reset password
- Disable MFA
- Send notification email
RoleResource & PermissionResource
Provided by: althinect/filament-spatie-roles-permissions
Features:
- Role CRUD (super-admin, admin, manager, user)
- Permission CRUD and assignment
- Guard-aware permissions
- User role assignment
Project Management Resources
ProjectResource
Path: app/Filament/Resources/ProjectResource.php
Features:
- Project CRUD with location
- Relationship management (slopes, channels, soil tests, attachments)
- Project type/stage/energy classification
- GPS coordinate mapping
- Project status management (active/archived/deleted)
- Auto-generated project numbers
Relation Managers:
SlopesRelationManager— Slope protection calculationsChannelsRelationManager— Channel protection calculationsSoilTestsRelationManager— Soil analysis dataAttachmentsRelationManager— File uploads with S3 integration
SlopeResource
Path: app/Filament/Resources/SlopeResource.php
Features:
- Slope calculation CRUD
- Dimension inputs (length, width, gradient)
- Soil type and vegetation selection
- Recommended product calculations
- Project association
ChannelResource
Path: app/Filament/Resources/ChannelResource.php
Features:
- Channel calculation CRUD
- Dimensions and flow rate inputs
- Soil and vegetation parameters
- Product recommendations
- Project association
Material Database Resources
EcbResource (Erosion Control Blankets)
Path: app/Filament/Resources/EcbResource.php
Features:
- ECB product CRUD
- Specifications (material, thickness, weight, longevity)
- Coverage rates and pricing
- Performance ratings (slope, flow velocity)
HecpResource (Hydraulically Applied Erosion Control Products)
Path: app/Filament/Resources/HecpResource.php
Features:
- HECP product CRUD
- Application rates and coverage
- Material specifications
- Pricing data
TrmResource (Turf Reinforcement Mats)
Path: app/Filament/Resources/TrmResource.php
Features:
- TRM product CRUD
- Tensile strength and load ratings
- Coverage and pricing
- Material specifications
Geographic Resources
CountryResource
Path: app/Filament/Resources/CountryResource.php
Features:
- Country CRUD with ISO codes
- State relationship management
StateResource
Path: app/Filament/Resources/StateResource.php
Features:
- State/province CRUD
- Country association
- City relationship management
CityResource
Path: app/Filament/Resources/CityResource.php
Features:
- City CRUD
- State association
- Project count display
SalesRegionResource
Path: app/Filament/Resources/SalesRegionResource.php
Features:
- Sales territory CRUD
- State selection (multi-select)
- Regional manager assignment
- User notification preferences
System Resources
BdoExportResource
Path: app/Filament/Resources/BdoExportResource.php
Features:
- Export history display
- Export status tracking (pending/success/failed)
- Record count and error messages
- Manual export triggering
- Checkpoint management
App Panel Configuration
Location: app/Providers/Filament/AppPanelProvider.php
Core Features
public function panel(Panel $panel): Panel
{
return $panel
->id('app')
->path('app')
->login()
->registration() // User registration
->passwordReset()
->emailVerification()
->profile(EditProfile::class)
->colors([
'primary' => '#9fcf68',
])
->brandLogo(asset('images/ps3_final.png'))
->brandName('Profile PS3')
->favicon(asset('images/favicon.png'))
->sidebarCollapsibleOnDesktop()
->discoverResources(
in: app_path('Filament/App/Resources'),
for: 'App\\Filament\\App\\Resources'
)
->discoverPages(
in: app_path('Filament/App/Pages'),
for: 'App\\Filament\\App\\Pages'
);
}
App Panel Resources
MyProjectsResource
Path: app/Filament/App/Resources/MyProjectsResource.php
Features:
- User's own projects only (scoped by
UserProjectScope) - Project CRUD
- Slope, channel, soil test management
- Attachment uploads
ApplicationCalculatorPage
Path: app/Filament/App/Pages/ApplicationCalculatorPage.php
Features:
- Application rate calculator form
- Area and dimension inputs
- Soil type and vegetation selection
- Product recommendation engine
- Cost estimation
- Save calculation to project
ProganicsCalculatorPage
Path: app/Filament/App/Pages/ProganicsCalculatorPage.php
Features:
- ProGanics BSM calculator form
- Biotic Soil Media calculations
- Application rate recommendations
- Save calculation to project
Custom Filament Components
Custom Actions
ApproveUserAction:
Tables\Actions\Action::make('approve')
->icon('heroicon-o-check-circle')
->color('success')
->requiresConfirmation()
->action(fn (User $record) => $record->update(['approved' => 'approved']))
->visible(fn (User $record) => $record->approved === 'pending')
->after(function (User $record) {
// Send approval email
Notification::make()
->title('Account Approved')
->success()
->send();
});
ImpersonateAction:
Tables\Actions\Action::make('impersonate')
->icon('heroicon-o-user')
->url(fn (User $record) => route('filament.admin.impersonate', $record))
->visible(fn () => auth()->user()->hasRole('super-admin'));
Custom Widgets
ProjectStatsWidget:
- Total projects count
- Active vs archived breakdown
- Projects by type chart
- Recent activity timeline
MaterialStatsWidget:
- ECB/HECP/TRM product counts
- Coverage rate averages
- Price range displays
UserStatsWidget:
- Total users
- Pending approval count
- MFA adoption rate
- Login activity
Filament Forms Best Practices
Repeater Fields:
Forms\Components\Repeater::make('slopes')
->relationship()
->schema([
Forms\Components\TextInput::make('slopeDescription')->required(),
Forms\Components\TextInput::make('slopeLength')->numeric()->required(),
Forms\Components\TextInput::make('slopeWidth')->numeric()->required(),
Forms\Components\TextInput::make('slopeGradient')->numeric()->required(),
])
->collapsible()
->itemLabel(fn (array $state): ?string => $state['slopeDescription'] ?? null)
->defaultItems(0)
->addActionLabel('Add Slope');
Dependent Selects:
Forms\Components\Select::make('country_id')
->relationship('country', 'name')
->reactive()
->afterStateUpdated(fn (callable $set) => $set('state_id', null)),
Forms\Components\Select::make('state_id')
->relationship('state', 'name')
->options(function (callable $get) {
$countryId = $get('country_id');
if (!$countryId) return [];
return State::where('country_id', $countryId)->pluck('name', 'id');
})
->reactive()
->afterStateUpdated(fn (callable $set) => $set('city', null)),
Performance Optimization
Eager Loading:
// In Resource table() method
->modifyQueryUsing(fn ($query) => $query->with([
'user', 'state', 'country', 'type', 'stage'
]))
Caching:
// Cache expensive queries
Forms\Components\Select::make('state_id')
->options(Cache::remember('states', 3600, function () {
return State::pluck('name', 'id');
}))
Custom Themes
Admin Theme: resources/css/filament/admin/theme.css
Tailwind Configuration:
// tailwind.config.js for admin panel
export default {
content: [
'./resources/views/filament/admin/**/*.blade.php',
'./app/Filament/**/*.php',
],
theme: {
extend: {
colors: {
primary: {
50: '#f5faf0',
100: '#e8f4d8',
500: '#9fcf68',
700: '#6fa43f',
900: '#3e5a22',
},
},
},
},
};
Testing Filament Panels
Feature Tests:
use function Pest\Livewire\livewire;
test('admin can access user resource', function () {
$admin = User::factory()->create();
$admin->assignRole('admin');
$this->actingAs($admin);
livewire(UserResource\Pages\ListUsers::class)
->assertSuccessful();
});
test('user cannot access admin panel', function () {
$user = User::factory()->create(['approved' => 'approved']);
$this->actingAs($user)
->get('/admin')
->assertForbidden();
});