From 0189ef874d990482a0af35f007e4b82105db3ccc Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 26 Mar 2026 10:30:31 +0700 Subject: [PATCH] feat: introduce Issue Sentiment By Department chart to the Analytic page, relocating Company Status chart from Dashboard and adding Cooperation Status chart. --- app/Filament/Pages/Analytic.php | 4 + app/Filament/Pages/Dashboard.php | 2 - .../IssueSentimentByDepartmentChart.php | 124 ++++++++++++++++++ database/seeders/ShieldSeeder.php | 4 + 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 app/Filament/Widgets/Charts/IssueSentimentByDepartmentChart.php diff --git a/app/Filament/Pages/Analytic.php b/app/Filament/Pages/Analytic.php index abc5d5d..8171ef0 100644 --- a/app/Filament/Pages/Analytic.php +++ b/app/Filament/Pages/Analytic.php @@ -3,9 +3,11 @@ namespace App\Filament\Pages; use App\Enums\RoleEnum; +use App\Filament\Widgets\Charts\CompanyStatusChart; use App\Filament\Widgets\Charts\CooperationStatusChart; use App\Filament\Widgets\Charts\IssueDepartmentChart; use App\Filament\Widgets\Charts\IssueLocationChart; +use App\Filament\Widgets\Charts\IssueSentimentByDepartmentChart; use App\Filament\Widgets\Charts\JournalistRegistrationChart; use App\Filament\Widgets\Charts\MediaClassificationChart; use App\Filament\Widgets\Charts\MediaCooperationRankingChart; @@ -98,8 +100,10 @@ public function resetFilters(): void public function getWidgets(): array { return [ + CompanyStatusChart::class, CooperationStatusChart::class, IssueDepartmentChart::class, + IssueSentimentByDepartmentChart::class, IssueLocationChart::class, JournalistRegistrationChart::class, MediaClassificationChart::class, diff --git a/app/Filament/Pages/Dashboard.php b/app/Filament/Pages/Dashboard.php index 843e07a..65ea64c 100644 --- a/app/Filament/Pages/Dashboard.php +++ b/app/Filament/Pages/Dashboard.php @@ -2,7 +2,6 @@ namespace App\Filament\Pages; -use App\Filament\Widgets\Charts\CompanyStatusChart; use App\Filament\Widgets\Stats\OverviewStats; use BezhanSalleh\FilamentShield\Traits\HasPageShield; use Filament\Pages\Dashboard as BaseDashboard; @@ -22,7 +21,6 @@ public function getWidgets(): array { return [ OverviewStats::class, - CompanyStatusChart::class, ]; } } diff --git a/app/Filament/Widgets/Charts/IssueSentimentByDepartmentChart.php b/app/Filament/Widgets/Charts/IssueSentimentByDepartmentChart.php new file mode 100644 index 0000000..e966882 --- /dev/null +++ b/app/Filament/Widgets/Charts/IssueSentimentByDepartmentChart.php @@ -0,0 +1,124 @@ +filters['startDate'] ?? null; + $endDate = $this->filters['endDate'] ?? null; + + $issueTable = (new IssueManagement)->getTable(); + $departmentTable = (new Department)->getTable(); + $pivotTable = 'department_issue_management'; + + // Get Top 10 Departments by total issues + $topDepartments = Department::query() + ->join($pivotTable, "{$departmentTable}.id", '=', "{$pivotTable}.department_id") + ->join($issueTable, "{$pivotTable}.issue_management_id", '=', "{$issueTable}.id") + ->when($startDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '<=', $endDate)) + ->select("{$departmentTable}.id", "{$departmentTable}.name", DB::raw('count(*) as total')) + ->groupBy("{$departmentTable}.id", "{$departmentTable}.name") + ->orderByDesc('total') + ->limit(10) + ->get(); + + if ($topDepartments->isEmpty()) { + return [ + 'datasets' => [], + 'labels' => [], + ]; + } + + $labels = $topDepartments->pluck('name')->toArray(); + $departmentIds = $topDepartments->pluck('id')->toArray(); + + // Get counts per sentiment for these departments + $rawCounts = IssueManagement::query() + ->join($pivotTable, "{$issueTable}.id", '=', "{$pivotTable}.issue_management_id") + ->whereIn("{$pivotTable}.department_id", $departmentIds) + ->when($startDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '<=', $endDate)) + ->select("{$pivotTable}.department_id", 'issue', DB::raw('count(*) as total')) + ->groupBy("{$pivotTable}.department_id", 'issue') + ->get(); + + $datasets = []; + $sentiments = IssueSentiment::cases(); + + foreach ($sentiments as $sentiment) { + $data = []; + foreach ($departmentIds as $id) { + $count = $rawCounts->where('department_id', $id) + ->where('issue', $sentiment->value) + ->first(); + $data[] = $count ? $count->total : 0; + } + + $datasets[] = [ + 'label' => $sentiment->getLabel(), + 'data' => $data, + 'backgroundColor' => $this->getHexColor($sentiment), + ]; + } + + return [ + 'datasets' => $datasets, + 'labels' => $labels, + ]; + } + + private function getHexColor(IssueSentiment $sentiment): string + { + return match ($sentiment) { + IssueSentiment::POSITIVE => '#10B981', // green-500 + IssueSentiment::NEGATIVE => '#EF4444', // red-500 + IssueSentiment::NEUTRAL => '#6B7280', // gray-500 + IssueSentiment::CRISIS => '#F97316', // orange-500 + }; + } + + protected function getType(): string + { + return 'bar'; + } + + protected function getOptions(): array + { + return [ + 'plugins' => [ + 'legend' => [ + 'display' => true, + ], + ], + 'scales' => [ + 'x' => [ + 'stacked' => true, + ], + 'y' => [ + 'stacked' => true, + ], + ], + ]; + } +} diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index 0a8b06d..64aa799 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -140,6 +140,8 @@ public function run(): void "Replicate:IssueManagement", "Reorder:IssueManagement", + "View:IssueSentimentByDepartmentChart", + "View:JournalistRegistrationChart", "View:LogTable", @@ -405,6 +407,8 @@ public function run(): void "Replicate:IssueManagement", "Reorder:IssueManagement", + "View:IssueSentimentByDepartmentChart", + "View:JournalistRegistrationChart", "ViewAny:Location",