Skip to content

Improve SystemAdmin resources with proper fields, filters, and sorting#120

Merged
ManukMinasyan merged 4 commits intomainfrom
chore/improve-sysadmin-resources
Feb 15, 2026
Merged

Improve SystemAdmin resources with proper fields, filters, and sorting#120
ManukMinasyan merged 4 commits intomainfrom
chore/improve-sysadmin-resources

Conversation

@ManukMinasyan
Copy link
Copy Markdown
Contributor

Summary

  • Default sort by created_at desc on all 8 SystemAdmin resources
  • Show created_at/updated_at columns by default (removed isToggledHiddenByDefault)
  • Remove ghost fields that no longer exist in DB (address, phone on Companies; user_id, assignee_id on Tasks)
  • Add relationship columns: creator.name, company.name, contact.name, accountOwner.name, owner.name where applicable
  • Add TrashedFilter on all soft-deletable models (Company, People, Opportunity, Task, Note)
  • Add team and creation source filters across CRM resources
  • Make relationship selects searchable in forms
  • Add navigation badges to all resources
  • Improve UserResource: icon column for email verified, copyable email, proper password handling
  • Improve TeamResource: owner relationship select, slug column, personal team filter

Test plan

  • Verify all SystemAdmin resource list pages load without errors
  • Confirm tables show latest records first
  • Test filters (trashed, team, creation source) work correctly
  • Verify forms save without errors (no ghost field references)

…rting

- default sort by created_at desc on all 8 resources
- show created_at/updated_at by default (not hidden)
- remove ghost fields (address, phone on companies; user_id, assignee_id on tasks)
- add creator, company, contact relationship columns where applicable
- add TrashedFilter on soft-deletable models
- add team and creation source filters across CRM resources
- make relationship selects searchable in forms
- add navigation badges to all resources
Copilot AI review requested due to automatic review settings February 15, 2026 22:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the SystemAdmin Filament resources to make list pages more useful and consistent (sorting, visible timestamps, relationship columns), while removing stale/ghost fields and adding common filters like soft-delete handling.

Changes:

  • Adds consistent default sorting (created_at desc), shows created_at / updated_at by default, and introduces navigation badges across resources.
  • Improves tables and forms with relationship-based columns/selects (e.g., team, creator, owner, account owner) and adds TrashedFilter where applicable.
  • Improves filtering/search UX (team/source filters, searchable relationship selects) and updates User/Team resources (verified icon, copyable email, team owner/slug fields).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
app-modules/SystemAdmin/src/Filament/Resources/UserResource.php Adds navigation badge, improves email/password handling, verified icon column, default sort, and filter.
app-modules/SystemAdmin/src/Filament/Resources/TeamResource.php Adds owner relationship select, slug column, personal-team filter, default sort, and navigation badge.
app-modules/SystemAdmin/src/Filament/Resources/TaskResource.php Removes ghost fields, adds relationship columns, TrashedFilter, team/source filters, default sort, and navigation badge.
app-modules/SystemAdmin/src/Filament/Resources/SystemAdministrators/Tables/SystemAdministratorsTable.php Sets default sort and makes timestamps visible by default.
app-modules/SystemAdmin/src/Filament/Resources/PeopleResource.php Adds relationship columns, team/company filters, TrashedFilter, default sort, and navigation badge.
app-modules/SystemAdmin/src/Filament/Resources/OpportunityResource.php Adds missing form fields + relationship columns/filters, TrashedFilter, default sort, and navigation badge.
app-modules/SystemAdmin/src/Filament/Resources/NoteResource.php Adds title + relationship columns/filters, TrashedFilter, default sort, and navigation badge.
app-modules/SystemAdmin/src/Filament/Resources/CompanyResource.php Removes ghost fields, adds relationship columns/filters, TrashedFilter, default sort, and navigation badge.


public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count > 0 ? (string) $count : null;

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
if ($count === 0) {
return null;
}
return (string) $count;

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count > 0 ? (string) $count : null;

Copilot uses AI. Check for mistakes.
Comment on lines 69 to 71
return $table
->defaultSort('created_at', 'desc')
->columns([
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These resource changes introduce new default sorting and filters, but there are no SystemAdmin-panel Filament tests verifying the sysadmin pages render and the new sort/filter behavior works. Add Pest/Livewire tests for the sysadmin resources similar to tests/Feature/Filament/App/Resources/*ResourceTest.php (at least: render index, default sort, trashed filter).

Copilot uses AI. Check for mistakes.
TextInput::make('name')
->required()
->maxLength(255),
TextInput::make('slug')
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slug is NOT NULL + UNIQUE in the teams table, but the form input is optional and has no uniqueness/format validation. This can allow clearing the slug (or entering duplicates) and then failing at save time with a DB error; add required() and unique(ignoreRecord: true) (and ideally validate against Team::SLUG_REGEX).

Suggested change
TextInput::make('slug')
TextInput::make('slug')
->required()
->rules(['regex:' . Team::SLUG_REGEX])
->unique(ignoreRecord: true)

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count > 0 ? (string) $count : null;

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count > 0 ? (string) $count : null;

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count > 0 ? (string) $count : null;

Copilot uses AI. Check for mistakes.

public static function getNavigationBadge(): ?string
{
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavigationBadge() runs count() twice, which results in two DB queries on every navigation render. Store the count in a local variable (single query) and return null when it is zero.

Suggested change
return self::getModel()::query()->count() > 0 ? (string) self::getModel()::query()->count() : null;
$count = self::getModel()::query()->count();
return $count === 0 ? null : (string) $count;

Copilot uses AI. Check for mistakes.
Replace CRM-focused widgets (business overview, sales analytics, team
performance) with growth and adoption metrics: platform stats with
sparklines, signup trend chart, record distribution doughnut, and top
teams table. Add time period filter (7d/30d/90d/12m) via header modal.
- Add required, regex, and unique validation to TeamResource slug field
- Fix double-count queries in navigation badges
- Add #[Override] attribute to CompanyResource::form()
- Import FQCNs in SignupTrendChartWidget and TopTeamsTableWidget
- Extract ENTITY_CLASSES/ENTITY_TABLES constants in widgets
- Add resource-level Filament tests for all sysadmin resources
- Narrow Dashboard::getColumns() return type to array
- Use instanceof Closure check in PlatformGrowthStatsWidget
- Replace toArray() with all() on collections
- Privatize pollingInterval in TopTeamsTableWidget
@ManukMinasyan ManukMinasyan merged commit d499e9f into main Feb 15, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants