feat: tambah voting system, VoteSeeder, dan halaman detail voting

This commit is contained in:
2026-04-03 04:55:33 +07:00
parent 95bdd5d033
commit bedcb9e4f0
5 changed files with 230 additions and 1 deletions
@@ -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;
use App\Models\Vote;
use App\Models\VoteItem;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
@@ -21,6 +25,18 @@ class VotesTable
->color(fn ($state) => $state === 'open' ? 'success' : 'gray'),
TextColumn::make('deadline')->label('Batas Waktu')->dateTime('d M Y H:i'),
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([
SelectFilter::make('status')->options(['open' => 'Buka', 'closed' => 'Tutup']),
@@ -30,7 +46,61 @@ class VotesTable
'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()])]);
}
}
@@ -5,6 +5,7 @@ namespace App\Filament\Resources\Votes;
use App\Filament\Resources\Votes\Pages\CreateVote;
use App\Filament\Resources\Votes\Pages\EditVote;
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\Tables\VotesTable;
use App\Models\Vote;
@@ -34,6 +35,7 @@ class VoteResource extends Resource
return [
'index' => ListVotes::route('/'),
'create' => CreateVote::route('/create'),
'view' => ViewVote::route('/{record}'),
'edit' => EditVote::route('/{record}/edit'),
];
}