feat: tambah voting system, VoteSeeder, dan halaman detail voting
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Votes\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\Votes\VoteResource;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use App\Models\VoteItem;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Schemas\Components\Section;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
|
||||||
|
class ViewVote extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = VoteResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('vote')
|
||||||
|
->label('Beri Suara')
|
||||||
|
->icon('heroicon-o-hand-raised')
|
||||||
|
->color('info')
|
||||||
|
->visible(fn () => $this->record->status === 'open'
|
||||||
|
&& ! $this->record->items()->where('user_id', auth()->id())->exists()
|
||||||
|
&& (! $this->record->deadline || $this->record->deadline->isFuture()))
|
||||||
|
->form([
|
||||||
|
\Filament\Forms\Components\Radio::make('choice')
|
||||||
|
->label('Pilihan Anda')
|
||||||
|
->options([
|
||||||
|
'approve' => '✓ Setuju',
|
||||||
|
'reject' => '✗ Tidak Setuju',
|
||||||
|
'abstain' => '○ Abstain',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
])
|
||||||
|
->action(function (array $data): void {
|
||||||
|
VoteItem::create([
|
||||||
|
'vote_id' => $this->record->id,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'choice' => $data['choice'],
|
||||||
|
]);
|
||||||
|
$this->refreshFormData([]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
EditAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function infolist(Schema $infolist): Schema
|
||||||
|
{
|
||||||
|
$record = $this->record;
|
||||||
|
$total = $record->items()->count();
|
||||||
|
$approve = $record->items()->where('choice', 'approve')->count();
|
||||||
|
$reject = $record->items()->where('choice', 'reject')->count();
|
||||||
|
$abstain = $record->items()->where('choice', 'abstain')->count();
|
||||||
|
$quorum = $total > 0 && ($approve / $total) > 0.5 ? '✓ Mayoritas Setuju' : '✗ Belum Mayoritas';
|
||||||
|
|
||||||
|
return $infolist->schema([
|
||||||
|
Section::make('Detail Voting')->schema([
|
||||||
|
TextEntry::make('title')->label('Judul'),
|
||||||
|
TextEntry::make('description')->label('Deskripsi'),
|
||||||
|
TextEntry::make('type')->badge(),
|
||||||
|
TextEntry::make('status')->badge()
|
||||||
|
->color(fn ($state) => $state === 'open' ? 'success' : 'gray'),
|
||||||
|
TextEntry::make('deadline')->label('Batas Waktu')->dateTime('d M Y H:i'),
|
||||||
|
TextEntry::make('creator.name')->label('Dibuat Oleh'),
|
||||||
|
])->columns(2),
|
||||||
|
|
||||||
|
Section::make('Rekapitulasi Suara')->schema([
|
||||||
|
TextEntry::make('total')->label('Total Suara')->state($total),
|
||||||
|
TextEntry::make('approve')->label('Setuju')->state($approve)
|
||||||
|
->color('success'),
|
||||||
|
TextEntry::make('reject')->label('Tidak Setuju')->state($reject)
|
||||||
|
->color('danger'),
|
||||||
|
TextEntry::make('abstain')->label('Abstain')->state($abstain)
|
||||||
|
->color('gray'),
|
||||||
|
TextEntry::make('result')->label('Hasil')->state($total > 0 ? $quorum : '-')
|
||||||
|
->color($total > 0 && ($approve / max($total, 1)) > 0.5 ? 'success' : 'warning'),
|
||||||
|
])->columns(5),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources\Votes\Tables;
|
namespace App\Filament\Resources\Votes\Tables;
|
||||||
|
|
||||||
|
use App\Models\Vote;
|
||||||
|
use App\Models\VoteItem;
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
@@ -21,6 +25,18 @@ class VotesTable
|
|||||||
->color(fn ($state) => $state === 'open' ? 'success' : 'gray'),
|
->color(fn ($state) => $state === 'open' ? 'success' : 'gray'),
|
||||||
TextColumn::make('deadline')->label('Batas Waktu')->dateTime('d M Y H:i'),
|
TextColumn::make('deadline')->label('Batas Waktu')->dateTime('d M Y H:i'),
|
||||||
TextColumn::make('items_count')->counts('items')->label('Suara'),
|
TextColumn::make('items_count')->counts('items')->label('Suara'),
|
||||||
|
TextColumn::make('result')
|
||||||
|
->label('Hasil')
|
||||||
|
->state(function (Vote $record): string {
|
||||||
|
$total = $record->items()->count();
|
||||||
|
$approve = $record->items()->where('choice', 'approve')->count();
|
||||||
|
$reject = $record->items()->where('choice', 'reject')->count();
|
||||||
|
$abstain = $record->items()->where('choice', 'abstain')->count();
|
||||||
|
|
||||||
|
if ($total === 0) return '-';
|
||||||
|
|
||||||
|
return "✓{$approve} ✗{$reject} ○{$abstain}";
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
SelectFilter::make('status')->options(['open' => 'Buka', 'closed' => 'Tutup']),
|
SelectFilter::make('status')->options(['open' => 'Buka', 'closed' => 'Tutup']),
|
||||||
@@ -30,7 +46,61 @@ class VotesTable
|
|||||||
'general' => 'Umum',
|
'general' => 'Umum',
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->recordActions([EditAction::make()])
|
->recordActions([
|
||||||
|
Action::make('vote')
|
||||||
|
->label('Beri Suara')
|
||||||
|
->icon('heroicon-o-hand-raised')
|
||||||
|
->color('info')
|
||||||
|
->visible(fn (Vote $record) => $record->status === 'open'
|
||||||
|
&& ! $record->items()->where('user_id', auth()->id())->exists()
|
||||||
|
&& (! $record->deadline || $record->deadline->isFuture()))
|
||||||
|
->form([
|
||||||
|
\Filament\Forms\Components\Radio::make('choice')
|
||||||
|
->label('Pilihan Anda')
|
||||||
|
->options([
|
||||||
|
'approve' => '✓ Setuju',
|
||||||
|
'reject' => '✗ Tidak Setuju',
|
||||||
|
'abstain' => '○ Abstain',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
])
|
||||||
|
->action(function (Vote $record, array $data): void {
|
||||||
|
VoteItem::create([
|
||||||
|
'vote_id' => $record->id,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'choice' => $data['choice'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
\App\Models\ActivityLog::create([
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'action' => 'voted',
|
||||||
|
'model_type' => Vote::class,
|
||||||
|
'model_id' => $record->id,
|
||||||
|
'description' => auth()->user()->name . " memilih {$data['choice']} pada voting '{$record->title}'",
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
Action::make('close')
|
||||||
|
->label('Tutup Voting')
|
||||||
|
->icon('heroicon-o-lock-closed')
|
||||||
|
->color('danger')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->visible(fn (Vote $record) => $record->status === 'open')
|
||||||
|
->action(function (Vote $record): void {
|
||||||
|
$record->update(['status' => 'closed']);
|
||||||
|
|
||||||
|
\App\Models\ActivityLog::create([
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'action' => 'vote_closed',
|
||||||
|
'model_type' => Vote::class,
|
||||||
|
'model_id' => $record->id,
|
||||||
|
'description' => "Voting '{$record->title}' ditutup",
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
EditAction::make(),
|
||||||
|
ViewAction::make()->label('Detail'),
|
||||||
|
])
|
||||||
->toolbarActions([BulkActionGroup::make([DeleteBulkAction::make()])]);
|
->toolbarActions([BulkActionGroup::make([DeleteBulkAction::make()])]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources\Votes;
|
|||||||
use App\Filament\Resources\Votes\Pages\CreateVote;
|
use App\Filament\Resources\Votes\Pages\CreateVote;
|
||||||
use App\Filament\Resources\Votes\Pages\EditVote;
|
use App\Filament\Resources\Votes\Pages\EditVote;
|
||||||
use App\Filament\Resources\Votes\Pages\ListVotes;
|
use App\Filament\Resources\Votes\Pages\ListVotes;
|
||||||
|
use App\Filament\Resources\Votes\Pages\ViewVote;
|
||||||
use App\Filament\Resources\Votes\Schemas\VoteForm;
|
use App\Filament\Resources\Votes\Schemas\VoteForm;
|
||||||
use App\Filament\Resources\Votes\Tables\VotesTable;
|
use App\Filament\Resources\Votes\Tables\VotesTable;
|
||||||
use App\Models\Vote;
|
use App\Models\Vote;
|
||||||
@@ -34,6 +35,7 @@ class VoteResource extends Resource
|
|||||||
return [
|
return [
|
||||||
'index' => ListVotes::route('/'),
|
'index' => ListVotes::route('/'),
|
||||||
'create' => CreateVote::route('/create'),
|
'create' => CreateVote::route('/create'),
|
||||||
|
'view' => ViewVote::route('/{record}'),
|
||||||
'edit' => EditVote::route('/{record}/edit'),
|
'edit' => EditVote::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
UserSeeder::class,
|
UserSeeder::class,
|
||||||
ActivitySeeder::class,
|
ActivitySeeder::class,
|
||||||
CashSeeder::class,
|
CashSeeder::class,
|
||||||
|
VoteSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Vote;
|
||||||
|
use App\Models\VoteItem;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class VoteSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$ketua = User::role('ketua')->first();
|
||||||
|
$members = User::whereHas('roles')->get();
|
||||||
|
|
||||||
|
$votes = [
|
||||||
|
[
|
||||||
|
'title' => 'Persetujuan Program Kerja 2026',
|
||||||
|
'description' => 'Voting untuk menyetujui program kerja organisasi tahun 2026',
|
||||||
|
'type' => 'general',
|
||||||
|
'status' => 'open',
|
||||||
|
'deadline' => now()->addDays(7),
|
||||||
|
'created_by' => $ketua?->id,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'Pengadaan Peralatan Olahraga',
|
||||||
|
'description' => 'Pembelian peralatan olahraga senilai Rp 2.500.000',
|
||||||
|
'type' => 'finance',
|
||||||
|
'status' => 'closed',
|
||||||
|
'deadline' => now()->subDays(3),
|
||||||
|
'created_by' => $ketua?->id,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'title' => 'Kegiatan Bakti Sosial Ramadan',
|
||||||
|
'description' => 'Persetujuan kegiatan bakti sosial pembagian sembako',
|
||||||
|
'type' => 'activity',
|
||||||
|
'status' => 'open',
|
||||||
|
'deadline' => now()->addDays(2),
|
||||||
|
'created_by' => $ketua?->id,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($votes as $data) {
|
||||||
|
$vote = Vote::create($data);
|
||||||
|
|
||||||
|
// Isi suara untuk voting yang sudah closed
|
||||||
|
if ($vote->status === 'closed') {
|
||||||
|
foreach ($members as $member) {
|
||||||
|
VoteItem::create([
|
||||||
|
'vote_id' => $vote->id,
|
||||||
|
'user_id' => $member->id,
|
||||||
|
'choice' => collect(['approve', 'approve', 'approve', 'reject', 'abstain'])->random(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isi sebagian suara untuk voting yang masih open
|
||||||
|
if ($vote->status === 'open') {
|
||||||
|
foreach ($members->take(3) as $member) {
|
||||||
|
VoteItem::create([
|
||||||
|
'vote_id' => $vote->id,
|
||||||
|
'user_id' => $member->id,
|
||||||
|
'choice' => collect(['approve', 'approve', 'reject'])->random(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user