feat: Implement global date range filtering across all dashboard widgets and add corresponding UI controls to the dashboard page.

This commit is contained in:
Yoga Pangestu 2026-01-29 10:52:20 +07:00
parent 8f1cb16dc9
commit a62ea370f4
16 changed files with 165 additions and 19 deletions

View File

@ -2,11 +2,13 @@
namespace App\Filament\Pages; namespace App\Filament\Pages;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Filament\Pages\Dashboard as BaseDashboard; use Filament\Pages\Dashboard as BaseDashboard;
use Filament\Pages\Dashboard\Concerns\HasFiltersForm; use Filament\Pages\Dashboard\Concerns\HasFiltersForm;
use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
class Dashboard extends BaseDashboard class Dashboard extends BaseDashboard
{ {
@ -20,16 +22,36 @@ public function filtersForm(Schema $schema): Schema
->schema([ ->schema([
DatePicker::make('startDate') DatePicker::make('startDate')
->label('Tanggal Mulai') ->label('Tanggal Mulai')
->placeholder('Pilih tanggal mulai')
->native(false) ->native(false)
->placeholder('Pilih tanggal mulai'), ->displayFormat('l, d F Y'),
DatePicker::make('endDate') DatePicker::make('endDate')
->label('Tanggal Selesai') ->label('Tanggal Selesai')
->placeholder('Pilih tanggal selesai')
->native(false) ->native(false)
->placeholder('Pilih tanggal selesai'), ->displayFormat('l, d F Y'),
]) ])
->columns(2)
->columnSpanFull(), ->columnSpanFull(),
]) ]);
->columns(2); }
protected function getHeaderActions(): array
{
return [
Action::make('reset')
->label('Reset Filter')
->icon(Heroicon::XMark)
->color('danger')
->action(fn () => $this->resetFilters())
->visible(fn () => ! empty($this->filters['startDate']) || ! empty($this->filters['endDate'])),
];
}
public function resetFilters(): void
{
$this->filters['startDate'] = null;
$this->filters['endDate'] = null;
} }
} }

View File

@ -8,6 +8,8 @@
class CooperationStatusChart extends ChartWidget class CooperationStatusChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Status Kerjasama'; protected ?string $heading = 'Status Kerjasama';
protected static ?int $sort = 9; protected static ?int $sort = 9;
@ -16,7 +18,12 @@ class CooperationStatusChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = Cooperation::query() $data = Cooperation::query()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->selectRaw('status, count(*) as total') ->selectRaw('status, count(*) as total')
->groupBy('status') ->groupBy('status')
->get(); ->get();

View File

@ -9,6 +9,8 @@
class IssueLocationChart extends ChartWidget class IssueLocationChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Sebaran Isu Berdasarkan Wilayah'; protected ?string $heading = 'Sebaran Isu Berdasarkan Wilayah';
protected static ?int $sort = 15; protected static ?int $sort = 15;
@ -17,11 +19,16 @@ class IssueLocationChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$issueTable = (new IssueManagement)->getTable(); $issueTable = (new IssueManagement)->getTable();
$locationTable = (new Location)->getTable(); $locationTable = (new Location)->getTable();
$data = IssueManagement::query() $data = IssueManagement::query()
->join($locationTable, "{$issueTable}.location_id", '=', "{$locationTable}.id") ->join($locationTable, "{$issueTable}.location_id", '=', "{$locationTable}.id")
->when($startDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '<=', $endDate))
->select("{$locationTable}.name", DB::raw('count(*) as total')) ->select("{$locationTable}.name", DB::raw('count(*) as total'))
->groupBy("{$locationTable}.id", "{$locationTable}.name") ->groupBy("{$locationTable}.id", "{$locationTable}.name")
->orderByDesc('total') ->orderByDesc('total')

View File

@ -10,14 +10,24 @@
class JournalistMetricsStats extends BaseWidget class JournalistMetricsStats extends BaseWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected static ?int $sort = 7; protected static ?int $sort = 7;
protected int|string|array $columnSpan = 1; protected int|string|array $columnSpan = 1;
protected function getStats(): array protected function getStats(): array
{ {
$totalJournalists = Journalist::count(); $startDate = $this->filters['startDate'] ?? null;
$totalMedia = PartnerMedia::count(); $endDate = $this->filters['endDate'] ?? null;
$totalJournalists = Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count();
$totalMedia = PartnerMedia::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count();
$average = $totalMedia > 0 ? round($totalJournalists / $totalMedia, 1) : 0; $average = $totalMedia > 0 ? round($totalJournalists / $totalMedia, 1) : 0;

View File

@ -9,6 +9,8 @@
class JournalistRegistrationChart extends ChartWidget class JournalistRegistrationChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Tren Pertambahan Jurnalis'; protected ?string $heading = 'Tren Pertambahan Jurnalis';
protected static ?int $sort = 8; protected static ?int $sort = 8;
@ -17,12 +19,17 @@ class JournalistRegistrationChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = Journalist::query() $data = Journalist::query()
->select( ->select(
DB::raw('COUNT(*) as count'), DB::raw('COUNT(*) as count'),
DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month") DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month")
) )
->where('created_at', '>=', now()->subMonths(6)) ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when(! $startDate, fn ($q) => $q->where('created_at', '>=', now()->subMonths(6)))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->groupBy('month') ->groupBy('month')
->orderBy('month') ->orderBy('month')
->get(); ->get();

View File

@ -7,6 +7,8 @@
class JournalistUkwChart extends ChartWidget class JournalistUkwChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Status Sertifikasi UKW Jurnalis'; protected ?string $heading = 'Status Sertifikasi UKW Jurnalis';
protected static ?int $sort = 6; protected static ?int $sort = 6;
@ -15,14 +17,22 @@ class JournalistUkwChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$certified = Journalist::whereNotNull('ukw_certificate') $certified = Journalist::whereNotNull('ukw_certificate')
->where('ukw_certificate', '!=', '') ->where('ukw_certificate', '!=', '')
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count(); ->count();
$notCertified = Journalist::where(function ($query) { $notCertified = Journalist::where(function ($query) {
$query->whereNull('ukw_certificate') $query->whereNull('ukw_certificate')
->orWhere('ukw_certificate', ''); ->orWhere('ukw_certificate', '');
})->count(); })
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count();
return [ return [
'datasets' => [ 'datasets' => [

View File

@ -8,6 +8,8 @@
class MediaClassificationChart extends ChartWidget class MediaClassificationChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Klasifikasi Media'; protected ?string $heading = 'Klasifikasi Media';
protected static ?int $sort = 3; protected static ?int $sort = 3;
@ -16,7 +18,12 @@ class MediaClassificationChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = PartnerMedia::query() $data = PartnerMedia::query()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->selectRaw('classification, count(*) as total') ->selectRaw('classification, count(*) as total')
->groupBy('classification') ->groupBy('classification')
->get(); ->get();

View File

@ -9,6 +9,8 @@
class MediaCooperationRankingChart extends ChartWidget class MediaCooperationRankingChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Ranking Media (Kerjasama Disetujui)'; protected ?string $heading = 'Ranking Media (Kerjasama Disetujui)';
protected static ?int $sort = 11; protected static ?int $sort = 11;
@ -17,9 +19,14 @@ class MediaCooperationRankingChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = PartnerMedia::query() $data = PartnerMedia::query()
->join('cooperation_media', 'partner_media.id', '=', 'cooperation_media.partner_media_id') ->join('cooperation_media', 'partner_media.id', '=', 'cooperation_media.partner_media_id')
->where('cooperation_media.status', ApprovalStatus::ACCEPTED) ->where('cooperation_media.status', ApprovalStatus::ACCEPTED)
->when($startDate, fn ($q) => $q->whereDate('cooperation_media.created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('cooperation_media.created_at', '<=', $endDate))
->select('partner_media.name', DB::raw('count(*) as total')) ->select('partner_media.name', DB::raw('count(*) as total'))
->groupBy('partner_media.id', 'partner_media.name') ->groupBy('partner_media.id', 'partner_media.name')
->orderByDesc('total') ->orderByDesc('total')

View File

@ -9,6 +9,8 @@
class MediaRegistrationChart extends ChartWidget class MediaRegistrationChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Tren Pendaftaran Media'; protected ?string $heading = 'Tren Pendaftaran Media';
protected static ?int $sort = 4; protected static ?int $sort = 4;
@ -17,12 +19,17 @@ class MediaRegistrationChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = PartnerMedia::query() $data = PartnerMedia::query()
->select( ->select(
DB::raw('COUNT(*) as count'), DB::raw('COUNT(*) as count'),
DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month") DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month")
) )
->where('created_at', '>=', now()->subMonths(6)) ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when(! $startDate, fn ($q) => $q->where('created_at', '>=', now()->subMonths(6)))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->groupBy('month') ->groupBy('month')
->orderBy('month') ->orderBy('month')
->get(); ->get();

View File

@ -8,6 +8,8 @@
class MediaTypeChart extends ChartWidget class MediaTypeChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Distribusi Jenis Media'; protected ?string $heading = 'Distribusi Jenis Media';
protected static ?int $sort = 2; protected static ?int $sort = 2;
@ -16,7 +18,12 @@ class MediaTypeChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = PartnerMedia::query() $data = PartnerMedia::query()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->selectRaw('type, count(*) as total') ->selectRaw('type, count(*) as total')
->groupBy('type') ->groupBy('type')
->get(); ->get();

View File

@ -14,39 +14,59 @@
class OverviewStats extends BaseWidget class OverviewStats extends BaseWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected static ?int $sort = 1; protected static ?int $sort = 1;
protected int|string|array $columnSpan = 'full'; protected int|string|array $columnSpan = 'full';
protected function getStats(): array protected function getStats(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
return [ return [
Stat::make('Total Perusahaan', Company::count()) Stat::make('Total Perusahaan', Company::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())
->description('Perusahaan terdaftar') ->description('Perusahaan terdaftar')
->descriptionIcon(Heroicon::BuildingOffice2) ->descriptionIcon(Heroicon::BuildingOffice2)
->color('primary'), ->color('primary'),
Stat::make('Media Terverifikasi', PartnerMedia::approved()->count()) Stat::make('Media Terverifikasi', PartnerMedia::approved()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())
->description('Media aktif') ->description('Media aktif')
->descriptionIcon(Heroicon::CheckBadge) ->descriptionIcon(Heroicon::CheckBadge)
->color('success'), ->color('success'),
Stat::make('Total Jurnalis', Journalist::count()) Stat::make('Total Jurnalis', Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())
->description('Jurnalis terdaftar') ->description('Jurnalis terdaftar')
->descriptionIcon(Heroicon::Identification) ->descriptionIcon(Heroicon::Identification)
->color('info'), ->color('info'),
Stat::make('Antrean Verifikasi', VerificationRequest::pending()->count()) Stat::make('Antrean Verifikasi', VerificationRequest::pending()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())
->description('Butuh tindakan') ->description('Butuh tindakan')
->descriptionIcon(Heroicon::PencilSquare) ->descriptionIcon(Heroicon::PencilSquare)
->color('warning'), ->color('warning'),
Stat::make('Antrean Perubahan', DataChangeRequest::pending()->count()) Stat::make('Antrean Perubahan', DataChangeRequest::pending()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())
->description('Perubahan data') ->description('Perubahan data')
->descriptionIcon(Heroicon::DocumentText) ->descriptionIcon(Heroicon::DocumentText)
->color('warning'), ->color('warning'),
Stat::make('Total Pengunjung', Visitor::count()) Stat::make('Total Pengunjung', Visitor::when($startDate, fn ($q) => $q->whereDate('date', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('date', '<=', $endDate))
->count())
->description('Total traffic') ->description('Total traffic')
->descriptionIcon(Heroicon::ChartBar) ->descriptionIcon(Heroicon::ChartBar)
->color('info'), ->color('info'),

View File

@ -7,6 +7,8 @@
class PopularNewsChart extends ChartWidget class PopularNewsChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Berita Terpopuler'; protected ?string $heading = 'Berita Terpopuler';
protected static ?int $sort = 13; protected static ?int $sort = 13;
@ -15,7 +17,12 @@ class PopularNewsChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = News::query() $data = News::query()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->orderByDesc('views') ->orderByDesc('views')
->limit(10) ->limit(10)
->get(); ->get();

View File

@ -9,6 +9,8 @@
class TaskRealizationChart extends ChartWidget class TaskRealizationChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Realisasi Penugasan (Laporan)'; protected ?string $heading = 'Realisasi Penugasan (Laporan)';
protected static ?int $sort = 10; protected static ?int $sort = 10;
@ -17,13 +19,18 @@ class TaskRealizationChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = Report::query() $data = Report::query()
->select( ->select(
DB::raw('COUNT(*) as count'), DB::raw('COUNT(*) as count'),
DB::raw('status'), DB::raw('status'),
DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month") DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month")
) )
->where('created_at', '>=', now()->subMonths(6)) ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when(! $startDate, fn ($q) => $q->where('created_at', '>=', now()->subMonths(6)))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->groupBy('month', 'status') ->groupBy('month', 'status')
->orderBy('month') ->orderBy('month')
->get(); ->get();

View File

@ -7,6 +7,8 @@
class TopMediaJournalistChart extends ChartWidget class TopMediaJournalistChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Top Media Berdasarkan Jumlah Jurnalis'; protected ?string $heading = 'Top Media Berdasarkan Jumlah Jurnalis';
protected static ?int $sort = 5; protected static ?int $sort = 5;
@ -15,7 +17,12 @@ class TopMediaJournalistChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = PartnerMedia::query() $data = PartnerMedia::query()
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->withCount('journalists') ->withCount('journalists')
->orderByDesc('journalists_count') ->orderByDesc('journalists_count')
->limit(10) ->limit(10)

View File

@ -10,6 +10,8 @@
class VerificationPerformanceChart extends ChartWidget class VerificationPerformanceChart extends ChartWidget
{ {
use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Performa Verifikasi (Approved vs Rejected)'; protected ?string $heading = 'Performa Verifikasi (Approved vs Rejected)';
protected static ?int $sort = 14; protected static ?int $sort = 14;
@ -18,6 +20,9 @@ class VerificationPerformanceChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = VerificationRequest::query() $data = VerificationRequest::query()
->select( ->select(
DB::raw('COUNT(*) as count'), DB::raw('COUNT(*) as count'),
@ -25,7 +30,9 @@ protected function getData(): array
DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month") DB::raw("DATE_FORMAT(created_at, '%Y-%m') as month")
) )
->whereIn('status', [VerificationStatus::APPROVED, VerificationStatus::REJECTED]) ->whereIn('status', [VerificationStatus::APPROVED, VerificationStatus::REJECTED])
->where('created_at', '>=', now()->subMonths(6)) ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when(! $startDate, fn ($q) => $q->where('created_at', '>=', now()->subMonths(6)))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->groupBy('month', 'status') ->groupBy('month', 'status')
->orderBy('month') ->orderBy('month')
->get(); ->get();

View File

@ -9,7 +9,9 @@
class VisitorTrafficChart extends ChartWidget class VisitorTrafficChart extends ChartWidget
{ {
protected ?string $heading = 'Traffic Pengunjung (30 Hari Terakhir)'; use \Filament\Widgets\Concerns\InteractsWithPageFilters;
protected ?string $heading = 'Traffic Pengunjung';
protected static ?int $sort = 12; protected static ?int $sort = 12;
@ -17,12 +19,17 @@ class VisitorTrafficChart extends ChartWidget
protected function getData(): array protected function getData(): array
{ {
$startDate = $this->filters['startDate'] ?? null;
$endDate = $this->filters['endDate'] ?? null;
$data = Visitor::query() $data = Visitor::query()
->select( ->select(
DB::raw('SUM(counter) as total'), DB::raw('SUM(counter) as total'),
DB::raw("DATE_FORMAT(date, '%Y-%m-%d') as day") DB::raw("DATE_FORMAT(date, '%Y-%m-%d') as day")
) )
->where('date', '>=', now()->subDays(30)) ->when($startDate, fn ($q) => $q->whereDate('date', '>=', $startDate))
->when(! $startDate, fn ($q) => $q->where('date', '>=', now()->subDays(30)))
->when($endDate, fn ($q) => $q->whereDate('date', '<=', $endDate))
->groupBy('day') ->groupBy('day')
->orderBy('day') ->orderBy('day')
->get(); ->get();