diff --git a/app/Console/Commands/MigrateAllCommand.php b/app/Console/Commands/MigrateAllCommand.php index fb9a7a8..6f105bc 100644 --- a/app/Console/Commands/MigrateAllCommand.php +++ b/app/Console/Commands/MigrateAllCommand.php @@ -35,13 +35,13 @@ public function handle() 'migrate:classification' => '🏷️ Migrating classifications & sub-classifications...', 'migrate:theme' => '🎨 Migrating themes...', 'migrate:media-monitoring' => '🖥️ Migrating media monitoring...', + 'migrate:issue-management' => '⚠️ Migrating issue management...', 'migrate:company' => '🏭 Migrating companies & partner media...', 'migrate:journalist' => '📰 Migrating journalists...', 'migrate:news' => '🗞️ Migrating news...', 'migrate:announcement' => '📢 Migrating announcements...', 'migrate:cooperation' => '🤝 Migrating cooperations, proposals & reports...', 'migrate:content-recap' => '📊 Migrating content recaps...', - 'migrate:issue-management' => '⚠️ Migrating issue management...', ]; foreach ($commands as $command => $description) { diff --git a/app/Console/Commands/MigrateIssueManagementCommand.php b/app/Console/Commands/MigrateIssueManagementCommand.php index cfd7f24..01d9517 100644 --- a/app/Console/Commands/MigrateIssueManagementCommand.php +++ b/app/Console/Commands/MigrateIssueManagementCommand.php @@ -2,9 +2,12 @@ namespace App\Console\Commands; +use App\Enums\IsActive; use App\Enums\IssueSentiment; +use App\Models\Department; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class MigrateIssueManagementCommand extends Command { @@ -57,6 +60,38 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); + + // Handle Departments (Legacy 'agency' field is string, can be multiple separated by comma) + $agencyString = trim($legacy->agency); + if (! empty($agencyString)) { + $agencyNames = explode(',', $agencyString); + + foreach ($agencyNames as $agencyName) { + $agencyName = trim($agencyName); + if (empty($agencyName)) { + continue; + } + + $department = Department::firstOrCreate( + ['name' => $agencyName], + [ + 'alias' => Str::limit($agencyName, 20, ''), + 'is_active' => IsActive::ACTIVE, + ] + ); + + DB::table('department_issue_management')->updateOrInsert( + [ + 'department_id' => $department->id, + 'issue_management_id' => $legacy->id, + ], + [ + 'created_at' => now(), + 'updated_at' => now(), + ] + ); + } + } }); $this->newLine(); diff --git a/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php b/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php index 711def6..0016606 100644 --- a/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php +++ b/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php @@ -32,6 +32,14 @@ public static function configure(Schema $schema): Schema ->autofocus() ->helperText('Jika judul berita dari monitoring media tidak ada di opsi, silakan lakukan pencarian.'), + Select::make('departments') + ->label('Perangkat Daerah') + ->relationship('departments', 'name', fn (Builder $query): Builder => $query->active()) + ->searchable() + ->preload() + ->multiple() + ->required(), + Grid::make(2) ->schema([ Select::make('location_id') diff --git a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php index 7e13ca5..7c1397c 100644 --- a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php +++ b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php @@ -50,6 +50,12 @@ public static function configure(Table $table): Table ->searchable() ->sortable(), + TextColumn::make('departments.name') + ->label('Perangkat Daerah') + ->badge() + ->searchable() + ->sortable(), + TextColumn::make('location.name') ->label('Lokus') ->searchable() @@ -101,6 +107,14 @@ public static function configure(Table $table): Table ->preload() ->native(false), + SelectFilter::make('departments') + ->label('Perangkat Daerah') + ->relationship('departments', 'name') + ->searchable() + ->preload() + ->multiple() + ->native(false), + SelectFilter::make('classification') ->label('Klasifikasi') ->relationship('classification', 'name') diff --git a/app/Filament/Widgets/Charts/IssueDepartmentChart.php b/app/Filament/Widgets/Charts/IssueDepartmentChart.php new file mode 100644 index 0000000..d15ff71 --- /dev/null +++ b/app/Filament/Widgets/Charts/IssueDepartmentChart.php @@ -0,0 +1,71 @@ +filters['startDate'] ?? null; + $endDate = $this->filters['endDate'] ?? null; + + $issueTable = (new IssueManagement)->getTable(); + $departmentTable = (new Department)->getTable(); + $pivotTable = 'department_issue_management'; + + $data = IssueManagement::query() + ->join($pivotTable, "{$issueTable}.id", '=', "{$pivotTable}.issue_management_id") + ->join($departmentTable, "{$pivotTable}.department_id", '=', "{$departmentTable}.id") + ->when($startDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate("{$issueTable}.created_at", '<=', $endDate)) + ->select("{$departmentTable}.name", DB::raw('count(*) as total')) + ->groupBy("{$departmentTable}.id", "{$departmentTable}.name") + ->orderByDesc('total') + ->limit(10) + ->get(); + + return [ + 'datasets' => [ + [ + 'label' => 'Jumlah Isu', + 'data' => $data->pluck('total')->toArray(), + 'backgroundColor' => [ + '#10B981', + '#3B82F6', + '#F59E0B', + '#EC4899', + '#6366F1', + '#8B5CF6', + '#F43F5E', + '#06B6D4', + '#14B8A6', + '#F97316', + ], + ], + ], + 'labels' => $data->pluck('name')->toArray(), + ]; + } + + protected function getType(): string + { + return 'bar'; + } +} diff --git a/app/Models/Department.php b/app/Models/Department.php index f10f5e9..ed51806 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -3,8 +3,11 @@ namespace App\Models; use App\Enums\IsActive; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Swindon\FilamentHashids\Traits\HasHashid; @@ -20,4 +23,15 @@ protected function casts(): array 'is_active' => IsActive::class, ]; } + + #[Scope] + protected function active(Builder $query): void + { + $query->where('is_active', IsActive::ACTIVE); + } + + public function issueManagements(): BelongsToMany + { + return $this->belongsToMany(IssueManagement::class, 'department_issue_management'); + } } diff --git a/app/Models/IssueManagement.php b/app/Models/IssueManagement.php index 0e9da66..9729e0b 100644 --- a/app/Models/IssueManagement.php +++ b/app/Models/IssueManagement.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Swindon\FilamentHashids\Traits\HasHashid; @@ -52,4 +53,9 @@ public function subLocation(): BelongsTo { return $this->belongsTo(SubLocation::class); } + + public function departments(): BelongsToMany + { + return $this->belongsToMany(Department::class, 'department_issue_management'); + } } diff --git a/database/migrations/2026_03_26_100443_create_department_issue_management_table.php b/database/migrations/2026_03_26_100443_create_department_issue_management_table.php new file mode 100644 index 0000000..e658b94 --- /dev/null +++ b/database/migrations/2026_03_26_100443_create_department_issue_management_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('department_id')->constrained()->cascadeOnDelete(); + $table->foreignId('issue_management_id')->constrained('issue_management')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('department_issue_management'); + } +}; diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index 602f0bc..0a8b06d 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -124,6 +124,8 @@ public function run(): void "Replicate:Department", "Reorder:Department", + "View:IssueDepartmentChart", + "View:IssueLocationChart", "ViewAny:IssueManagement", @@ -387,6 +389,8 @@ public function run(): void "Replicate:Department", "Reorder:Department", + "View:IssueDepartmentChart", + "View:IssueLocationChart", "ViewAny:IssueManagement", diff --git a/tests/Feature/Models/IssueManagementTest.php b/tests/Feature/Models/IssueManagementTest.php index b166532..0b3a0ae 100644 --- a/tests/Feature/Models/IssueManagementTest.php +++ b/tests/Feature/Models/IssueManagementTest.php @@ -2,6 +2,7 @@ use App\Enums\IssueSentiment; use App\Models\Classification; +use App\Models\Department; use App\Models\IssueManagement; use App\Models\Location; use App\Models\MediaMonitoring; @@ -13,6 +14,7 @@ $location = Location::factory()->create(); $classification = Classification::factory()->create(); $monitoring = MediaMonitoring::factory()->create(); + $department = Department::factory()->create(); $issue = IssueManagement::factory()->create([ 'location_id' => $location->id, @@ -21,7 +23,10 @@ 'issue' => IssueSentiment::POSITIVE, ]); + $issue->departments()->attach($department->id); + expect($issue->issue)->toBe(IssueSentiment::POSITIVE); expect($issue->location->id)->toBe($location->id); expect($issue->mediaMonitoring->id)->toBe($monitoring->id); + expect($issue->departments->first()->id)->toBe($department->id); });