feat: Add many-to-many relationship for Issue Management with Departments, including migration, Filament UI updates, and a new department issue chart.
This commit is contained in:
parent
89b4793667
commit
01db1c4794
@ -35,13 +35,13 @@ public function handle()
|
|||||||
'migrate:classification' => '🏷️ Migrating classifications & sub-classifications...',
|
'migrate:classification' => '🏷️ Migrating classifications & sub-classifications...',
|
||||||
'migrate:theme' => '🎨 Migrating themes...',
|
'migrate:theme' => '🎨 Migrating themes...',
|
||||||
'migrate:media-monitoring' => '🖥️ Migrating media monitoring...',
|
'migrate:media-monitoring' => '🖥️ Migrating media monitoring...',
|
||||||
|
'migrate:issue-management' => '⚠️ Migrating issue management...',
|
||||||
'migrate:company' => '🏭 Migrating companies & partner media...',
|
'migrate:company' => '🏭 Migrating companies & partner media...',
|
||||||
'migrate:journalist' => '📰 Migrating journalists...',
|
'migrate:journalist' => '📰 Migrating journalists...',
|
||||||
'migrate:news' => '🗞️ Migrating news...',
|
'migrate:news' => '🗞️ Migrating news...',
|
||||||
'migrate:announcement' => '📢 Migrating announcements...',
|
'migrate:announcement' => '📢 Migrating announcements...',
|
||||||
'migrate:cooperation' => '🤝 Migrating cooperations, proposals & reports...',
|
'migrate:cooperation' => '🤝 Migrating cooperations, proposals & reports...',
|
||||||
'migrate:content-recap' => '📊 Migrating content recaps...',
|
'migrate:content-recap' => '📊 Migrating content recaps...',
|
||||||
'migrate:issue-management' => '⚠️ Migrating issue management...',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($commands as $command => $description) {
|
foreach ($commands as $command => $description) {
|
||||||
|
|||||||
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\IsActive;
|
||||||
use App\Enums\IssueSentiment;
|
use App\Enums\IssueSentiment;
|
||||||
|
use App\Models\Department;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class MigrateIssueManagementCommand extends Command
|
class MigrateIssueManagementCommand extends Command
|
||||||
{
|
{
|
||||||
@ -57,6 +60,38 @@ public function handle()
|
|||||||
'deleted_at' => $legacy->deleted_at,
|
'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();
|
$this->newLine();
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,14 @@ public static function configure(Schema $schema): Schema
|
|||||||
->autofocus()
|
->autofocus()
|
||||||
->helperText('Jika judul berita dari monitoring media tidak ada di opsi, silakan lakukan pencarian.'),
|
->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)
|
Grid::make(2)
|
||||||
->schema([
|
->schema([
|
||||||
Select::make('location_id')
|
Select::make('location_id')
|
||||||
|
|||||||
@ -50,6 +50,12 @@ public static function configure(Table $table): Table
|
|||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
|
|
||||||
|
TextColumn::make('departments.name')
|
||||||
|
->label('Perangkat Daerah')
|
||||||
|
->badge()
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
TextColumn::make('location.name')
|
TextColumn::make('location.name')
|
||||||
->label('Lokus')
|
->label('Lokus')
|
||||||
->searchable()
|
->searchable()
|
||||||
@ -101,6 +107,14 @@ public static function configure(Table $table): Table
|
|||||||
->preload()
|
->preload()
|
||||||
->native(false),
|
->native(false),
|
||||||
|
|
||||||
|
SelectFilter::make('departments')
|
||||||
|
->label('Perangkat Daerah')
|
||||||
|
->relationship('departments', 'name')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->multiple()
|
||||||
|
->native(false),
|
||||||
|
|
||||||
SelectFilter::make('classification')
|
SelectFilter::make('classification')
|
||||||
->label('Klasifikasi')
|
->label('Klasifikasi')
|
||||||
->relationship('classification', 'name')
|
->relationship('classification', 'name')
|
||||||
|
|||||||
71
app/Filament/Widgets/Charts/IssueDepartmentChart.php
Normal file
71
app/Filament/Widgets/Charts/IssueDepartmentChart.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Widgets\Charts;
|
||||||
|
|
||||||
|
use App\Models\Department;
|
||||||
|
use App\Models\IssueManagement;
|
||||||
|
use BezhanSalleh\FilamentShield\Traits\HasWidgetShield;
|
||||||
|
use Filament\Widgets\ChartWidget;
|
||||||
|
use Filament\Widgets\Concerns\InteractsWithPageFilters;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class IssueDepartmentChart extends ChartWidget
|
||||||
|
{
|
||||||
|
use HasWidgetShield, InteractsWithPageFilters;
|
||||||
|
|
||||||
|
protected ?string $heading = 'Sebaran Isu Berdasarkan Perangkat Daerah';
|
||||||
|
|
||||||
|
protected static ?int $sort = 16;
|
||||||
|
|
||||||
|
protected int|string|array $columnSpan = 1;
|
||||||
|
|
||||||
|
protected ?string $pollingInterval = null;
|
||||||
|
|
||||||
|
protected function getData(): array
|
||||||
|
{
|
||||||
|
$startDate = $this->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';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,8 +3,11 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\IsActive;
|
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\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Swindon\FilamentHashids\Traits\HasHashid;
|
use Swindon\FilamentHashids\Traits\HasHashid;
|
||||||
|
|
||||||
@ -20,4 +23,15 @@ protected function casts(): array
|
|||||||
'is_active' => IsActive::class,
|
'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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Swindon\FilamentHashids\Traits\HasHashid;
|
use Swindon\FilamentHashids\Traits\HasHashid;
|
||||||
|
|
||||||
@ -52,4 +53,9 @@ public function subLocation(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(SubLocation::class);
|
return $this->belongsTo(SubLocation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function departments(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Department::class, 'department_issue_management');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('department_issue_management', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -124,6 +124,8 @@ public function run(): void
|
|||||||
"Replicate:Department",
|
"Replicate:Department",
|
||||||
"Reorder:Department",
|
"Reorder:Department",
|
||||||
|
|
||||||
|
"View:IssueDepartmentChart",
|
||||||
|
|
||||||
"View:IssueLocationChart",
|
"View:IssueLocationChart",
|
||||||
|
|
||||||
"ViewAny:IssueManagement",
|
"ViewAny:IssueManagement",
|
||||||
@ -387,6 +389,8 @@ public function run(): void
|
|||||||
"Replicate:Department",
|
"Replicate:Department",
|
||||||
"Reorder:Department",
|
"Reorder:Department",
|
||||||
|
|
||||||
|
"View:IssueDepartmentChart",
|
||||||
|
|
||||||
"View:IssueLocationChart",
|
"View:IssueLocationChart",
|
||||||
|
|
||||||
"ViewAny:IssueManagement",
|
"ViewAny:IssueManagement",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Enums\IssueSentiment;
|
use App\Enums\IssueSentiment;
|
||||||
use App\Models\Classification;
|
use App\Models\Classification;
|
||||||
|
use App\Models\Department;
|
||||||
use App\Models\IssueManagement;
|
use App\Models\IssueManagement;
|
||||||
use App\Models\Location;
|
use App\Models\Location;
|
||||||
use App\Models\MediaMonitoring;
|
use App\Models\MediaMonitoring;
|
||||||
@ -13,6 +14,7 @@
|
|||||||
$location = Location::factory()->create();
|
$location = Location::factory()->create();
|
||||||
$classification = Classification::factory()->create();
|
$classification = Classification::factory()->create();
|
||||||
$monitoring = MediaMonitoring::factory()->create();
|
$monitoring = MediaMonitoring::factory()->create();
|
||||||
|
$department = Department::factory()->create();
|
||||||
|
|
||||||
$issue = IssueManagement::factory()->create([
|
$issue = IssueManagement::factory()->create([
|
||||||
'location_id' => $location->id,
|
'location_id' => $location->id,
|
||||||
@ -21,7 +23,10 @@
|
|||||||
'issue' => IssueSentiment::POSITIVE,
|
'issue' => IssueSentiment::POSITIVE,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$issue->departments()->attach($department->id);
|
||||||
|
|
||||||
expect($issue->issue)->toBe(IssueSentiment::POSITIVE);
|
expect($issue->issue)->toBe(IssueSentiment::POSITIVE);
|
||||||
expect($issue->location->id)->toBe($location->id);
|
expect($issue->location->id)->toBe($location->id);
|
||||||
expect($issue->mediaMonitoring->id)->toBe($monitoring->id);
|
expect($issue->mediaMonitoring->id)->toBe($monitoring->id);
|
||||||
|
expect($issue->departments->first()->id)->toBe($department->id);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user