Pular para o conteúdo
He4rt / Docs
Toggle sidebar
Interno noindex

Referência técnica — acessível, mas fora dos buscadores.

Discord
Engenharia Leitura · 6 min Moderation 02/05/2026 documento interno · noindex

Moderation Dashboard Widgets — Design Spec

Artefato de planejamento de 02/05/2026 — pode não refletir o estado atual do código.

Overview

Replace the current minimal ModerationDashboard page (2 widgets) with a full analytics dashboard containing 8 widgets, a global time period filter, and real-time data queries.

Architecture Decision

Switch ModerationDashboard from extends Page to extends Dashboard.

Filament\Pages\Dashboard extends Page — the $cluster property is inherited and works unchanged. This gives us native support for HasFiltersForm + InteractsWithPageFilters on all widgets.

Global Filter

Type: HasFiltersForm (inline, always visible)

The dashboard page uses the HasFiltersForm trait to display a form at the top with:

All widgets use the InteractsWithPageFilters trait and read $this->pageFilters['period'] to scope their queries. A shared helper method on each widget converts the period preset to a Carbon start date.

 MODERATOR                            SYSTEM
  │                                     │
  │  👆 Selects "Last 30 days"          │
  │ ────────────────────────────────►   │
  │                                     │  HasFiltersForm: period='30d'
  │                                     │  Session: persists filter
  │                                     │  Livewire: re-renders all widgets
  │                                     │
  │    All 8 widgets update with        │
  │    data scoped to last 30 days      │
  │ ◄────────────────────────────────── │

Widget Inventory

Grid Layout (2 columns)

┌─────────────────────────────────────────────────────┐
│  [Period Filter: Last 30 days ▼]                    │  ← HasFiltersForm
├─────────────────────────────────────────────────────┤
│  ① Stats Overview (full width)                      │
│  ┌──────────┬──────────┬──────────┬──────────┐      │
│  │Pendentes │Resolvidos│Tempo Méd.│Taxa Appeal│     │
│  └──────────┴──────────┴──────────┴──────────┘      │
├────────────────────────┬────────────────────────────┤
│  ② Casos por Status    │  ③ Casos por Plataforma   │
│  (Doughnut Chart)      │  (Doughnut Chart)          │
├────────────────────────┬────────────────────────────┤
│  ④ Top Violações       │  ⑤ Taxa de Falso Positivo  │
│  (Bar Chart horizontal)│  (Stats Overview)           │
├────────────────────────┬────────────────────────────┤
│  ⑥ Performance Mods    │  ⑦ SLA Appeals             │
│  (Table Widget)        │  (Table Widget)             │
├─────────────────────────────────────────────────────┤
│  ⑧ Recent Actions (full width) — already exists     │
└─────────────────────────────────────────────────────┘

① ModerationStatsWidget

Stat Value Query Description/Icon
Pendentes count ModerationCase::where('status', 'pending')->count() icon: clock, color: warning
Resolvidos count ModerationCase::where('status', 'resolved')->where('resolved_at', '>=', $start)->count() icon: check-circle, color: success, desc: "este período"
Tempo Médio minutes AVG(resolved_at - created_at) on resolved cases in period icon: clock, color: info, desc: "min p/ resolver"
Taxa de Appeal percentage appeals_count / actions_count * 100 in period icon: scale, color: purple, desc: "das ações"

Each stat includes a sparkline chart() with the last 7 data points (daily counts within the period).

② CasesByStatusChartWidget

③ CasesByPlatformChartWidget

④ TopViolationTypesChartWidget

⑤ FalsePositiveRateWidget

Stat Value Query
FP Rate percentage dismissed_cases / (dismissed + resolved) * 100 in period

⑥ ModeratorPerformanceWidget

⑦ AppealSlaWidget

⑧ RecentActionsWidget (existing)

Page Changes

ModerationDashboard

Before:

class ModerationDashboard extends Page
{
    // 2 widgets via getHeaderWidgets()
}

After:

class ModerationDashboard extends Dashboard
{
    use HasFiltersForm;

    // $cluster, navigation properties unchanged

    public function filtersForm(Schema $schema): Schema
    {
        return $schema->components([
            Section::make()
                ->schema([
                    Select::make('period')
                        ->options([
                            '7d' => 'Last 7 days',
                            '30d' => 'Last 30 days',
                            '90d' => 'Last 90 days',
                            'month' => 'This month',
                            'year' => 'This year',
                        ])
                        ->default('30d'),
                ])
                ->columns(3),
        ]);
    }

    public function getWidgets(): array
    {
        return [
            ModerationStatsWidget::class,
            CasesByStatusChartWidget::class,
            CasesByPlatformChartWidget::class,
            TopViolationTypesChartWidget::class,
            FalsePositiveRateWidget::class,
            ModeratorPerformanceWidget::class,
            AppealSlaWidget::class,
            RecentActionsWidget::class,
        ];
    }

    public function getColumns(): int|array
    {
        return 2;
    }
}

Shared Pattern: Period Filter Resolution

All widgets that use InteractsWithPageFilters share the same period-to-date logic. Extract to a trait:

trait ResolvesFilterPeriod
{
    protected function periodStart(): Carbon
    {
        $period = $this->pageFilters['period'] ?? '30d';

        return match ($period) {
            '7d' => now()->subDays(7),
            '30d' => now()->subDays(30),
            '90d' => now()->subDays(90),
            'month' => now()->startOfMonth(),
            'year' => now()->startOfYear(),
        };
    }

    protected function previousPeriodStart(): Carbon
    {
        $period = $this->pageFilters['period'] ?? '30d';

        return match ($period) {
            '7d' => now()->subDays(14),
            '30d' => now()->subDays(60),
            '90d' => now()->subDays(180),
            'month' => now()->subMonth()->startOfMonth(),
            'year' => now()->subYear()->startOfYear(),
        };
    }
}

Data Flow

┌──────────────┐     ┌──────────────────┐     ┌─────────────────┐
│ FilterForm   │────►│ $this->filters   │────►│ Livewire re-    │
│ (Select)     │     │ ['period'=>'30d']│     │ render widgets  │
└──────────────┘     └──────────────────┘     └────────┬────────┘
                                                       │
                     ┌─────────────────────────────────┘
                     ▼
        ┌────────────────────────┐
        │ Each widget reads      │
        │ $this->pageFilters     │
        │ via InteractsWithPage  │
        │ Filters trait          │
        ├────────────────────────┤
        │ ResolvesFilterPeriod   │
        │ →periodStart(): Carbon │
        ├────────────────────────┤
        │ Real-time DB query     │
        │ scoped by period       │
        └────────────────────────┘

Files to Create/Modify

New files (8):

  1. app-modules/panel-admin/src/Moderation/Widgets/ModerationStatsWidget.php
  2. app-modules/panel-admin/src/Moderation/Widgets/CasesByStatusChartWidget.php
  3. app-modules/panel-admin/src/Moderation/Widgets/CasesByPlatformChartWidget.php
  4. app-modules/panel-admin/src/Moderation/Widgets/TopViolationTypesChartWidget.php
  5. app-modules/panel-admin/src/Moderation/Widgets/FalsePositiveRateWidget.php
  6. app-modules/panel-admin/src/Moderation/Widgets/ModeratorPerformanceWidget.php
  7. app-modules/panel-admin/src/Moderation/Widgets/AppealSlaWidget.php
  8. app-modules/panel-admin/src/Moderation/Widgets/Concerns/ResolvesFilterPeriod.php

Modified files (3):

  1. app-modules/panel-admin/src/Moderation/Pages/ModerationDashboard.php — switch to extends Dashboard, add HasFiltersForm, register all widgets
  2. app-modules/panel-admin/src/Moderation/Widgets/RecentActionsWidget.php — add InteractsWithPageFilters + ResolvesFilterPeriod, scope query by period
  3. app-modules/panel-admin/src/Moderation/Widgets/CasesByStatusWidget.phpdelete (replaced by ModerationStatsWidget + CasesByStatusChartWidget)

Translation files (2):

  1. app-modules/panel-admin/lang/en/moderation.php — add dashboard widget keys
  2. app-modules/panel-admin/lang/pt_BR/moderation.php — add dashboard widget keys

Test files (1):

  1. app-modules/panel-admin/tests/Feature/Moderation/ModerationDashboardTest.php — test page renders, filter works, widgets are present

Testing Strategy