feat: add course filtering to assignments list and enhance search functionality

This commit is contained in:
Yoga Pangestu 2026-04-01 14:35:33 +07:00
parent a4f615ad20
commit b097168c09
3 changed files with 75 additions and 17 deletions

View File

@ -12,8 +12,10 @@
use App\Filament\Support\SystemNotification; use App\Filament\Support\SystemNotification;
use App\Models\Assignment; use App\Models\Assignment;
use App\Models\AssignmentPin; use App\Models\AssignmentPin;
use App\Models\Course;
use App\Settings\GeneralSettings; use App\Settings\GeneralSettings;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Resources\Pages\Page; use Filament\Resources\Pages\Page;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
@ -27,6 +29,9 @@ class ListAssignments extends Page
#[Url] #[Url]
public ?string $search = ''; public ?string $search = '';
#[Url]
public ?int $course_id = null;
protected static string $resource = AssignmentResource::class; protected static string $resource = AssignmentResource::class;
protected string $view = 'filament.resources.learning.assignments.index'; protected string $view = 'filament.resources.learning.assignments.index';
@ -42,7 +47,10 @@ public static function getPagePermission(): string
public function mount(): void public function mount(): void
{ {
$this->form->fill(); $this->form->fill([
'search' => $this->search,
'course_id' => $this->course_id,
]);
} }
public function form(Schema $schema): Schema public function form(Schema $schema): Schema
@ -51,13 +59,27 @@ public function form(Schema $schema): Schema
->schema([ ->schema([
TextInput::make('search') TextInput::make('search')
->label('Cari') ->label('Cari')
->placeholder('Cari Judul Tugas atau Mata Kuliah...') ->placeholder('Cari Judul Tugas...')
->autocomplete(false) ->autocomplete(false)
->live(debounce: 500) ->live(debounce: 500)
->afterStateUpdated(fn ($state) => $this->search = $state), ->afterStateUpdated(fn ($state) => $this->search = $state),
Select::make('course_id')
->label('Mata Kuliah')
->placeholder('Semua Mata Kuliah')
->options(function () {
return Course::query()
->where('semester', app(GeneralSettings::class)->current_semester)
->whereHas('courseSchedules')
->pluck('name', 'id')
->toArray();
})
->searchable()
->live()
->afterStateUpdated(fn ($state) => $this->course_id = $state),
]) ])
->columns([ ->columns([
'sm' => 1, 'sm' => 2,
]); ]);
} }
@ -112,6 +134,15 @@ public function assignments(): Collection
->whereHas('course', function ($q) { ->whereHas('course', function ($q) {
$q->where('semester', app(GeneralSettings::class)->current_semester); $q->where('semester', app(GeneralSettings::class)->current_semester);
}) })
->when($this->search, function ($query) {
$query->where(function ($q) {
$q->where('title', 'like', "%{$this->search}%")
->orWhereHas('course', fn ($cq) => $cq->where('name', 'like', "%{$this->search}%"));
});
})
->when($this->course_id, function ($query) {
$query->where('course_id', $this->course_id);
})
->get(); ->get();
} }
@ -134,6 +165,9 @@ public function assignments(): Collection
->orWhereHas('course', fn ($cq) => $cq->where('name', 'like', "%{$this->search}%")); ->orWhereHas('course', fn ($cq) => $cq->where('name', 'like', "%{$this->search}%"));
}); });
}) })
->when($this->course_id, function ($query) {
$query->where('course_id', $this->course_id);
})
->get(); ->get();
return $assignments->sort(function ($a, $b) use ($pinnedIds) { return $assignments->sort(function ($a, $b) use ($pinnedIds) {

View File

@ -1,20 +1,18 @@
<x-filament-panels::page> <x-filament-panels::page>
<x-filament::section <x-filament::section
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-clipboard-document-list' : 'heroicon-o-briefcase' }}" icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-clipboard-document-list' : 'heroicon-o-briefcase' }}"
icon-color="primary" icon-color="primary">
>
<x-slot name="heading">{{ $this->heading }}</x-slot> <x-slot name="heading">{{ $this->heading }}</x-slot>
<x-slot name="description">{{ $this->description }}</x-slot> <x-slot name="description">{{ $this->description }}</x-slot>
<div class="flex items-center justify-between gap-4 mb-6"> <div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl"> <div class="w-full">
{{ $this->form }} {{ $this->form }}
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
@forelse ($this->assignmentCards as $card) @forelse ($this->assignmentCards as $card)
<div class="{{ $card->card_classes }} flex-col"> <div class="{{ $card->card_classes }} flex-col">
<a href="{{ \App\Filament\Resources\Learning\Assignments\AssignmentResource::getUrl('submit', ['record' => $card->id]) }}" <a href="{{ \App\Filament\Resources\Learning\Assignments\AssignmentResource::getUrl('submit', ['record' => $card->id]) }}"
@ -73,13 +71,14 @@ class="inline-flex items-center gap-1.5 rounded-full bg-danger-500 text-white px
<div class="flex flex-wrap gap-x-4 gap-y-2 mt-4 text-xs text-gray-500 dark:text-gray-400"> <div class="flex flex-wrap gap-x-4 gap-y-2 mt-4 text-xs text-gray-500 dark:text-gray-400">
<span class="flex items-center gap-1.5"> <span class="flex items-center gap-1.5">
<x-filament::icon icon="heroicon-o-clock" class="h-4 w-4 shrink-0 text-primary-500" /> <x-filament::icon icon="heroicon-o-clock"
class="h-4 w-4 shrink-0 text-primary-500" />
<span class="font-medium">Batas: {{ $card->due_date_formatted }}</span> <span class="font-medium">Batas: {{ $card->due_date_formatted }}</span>
</span> </span>
@if ($card->submitted_at_formatted) @if ($card->submitted_at_formatted)
<span class="flex items-center gap-1.5 text-success-600 dark:text-success-400 font-bold bg-success-50 dark:bg-success-900/25 px-2 py-0.5 rounded-lg"> <span
<x-filament::icon icon="heroicon-o-arrow-up-tray" class="flex items-center gap-1.5 text-success-600 dark:text-success-400 font-bold bg-success-50 dark:bg-success-900/25 px-2 py-0.5 rounded-lg">
class="h-4 w-4 shrink-0" /> <x-filament::icon icon="heroicon-o-arrow-up-tray" class="h-4 w-4 shrink-0" />
<span>Dikumpulkan: {{ $card->submitted_at_formatted }}</span> <span>Dikumpulkan: {{ $card->submitted_at_formatted }}</span>
</span> </span>
@endif @endif
@ -88,7 +87,8 @@ class="h-4 w-4 shrink-0" />
</a> </a>
<div class="flex flex-wrap items-center gap-3 px-5 py-4 border-t border-gray-100 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/20"> <div
class="flex flex-wrap items-center gap-3 px-5 py-4 border-t border-gray-100 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/20">
{{ ($this->pinAction)(['record' => $card->id]) }} {{ ($this->pinAction)(['record' => $card->id]) }}
{{ ($this->shareAssignmentAction)(['record' => $card->id]) }} {{ ($this->shareAssignmentAction)(['record' => $card->id]) }}
@ -101,9 +101,9 @@ class="h-4 w-4 shrink-0" />
</div> </div>
@empty @empty
<div class="col-span-full"> <div class="col-span-full">
<x-filament::empty-state icon="heroicon-o-clipboard-document-list" <x-filament::empty-state icon="heroicon-o-clipboard-document-list"
heading="{{ \App\Filament\Support\SystemNotification::getMessage('Hore, Belum Ada Tugas! 🎊✨', 'Tidak ada data yang ditemukan') }}" heading="{{ \App\Filament\Support\SystemNotification::getMessage('Hore, Belum Ada Tugas! 🎊✨', 'Tidak ada data yang ditemukan') }}"
description="{{ \App\Filament\Support\SystemNotification::getMessage('Belum ada tugas baru yang perlu dikerjakan. Hidup tenang tanpa beban tugas itu asik ya! 😊🌈', 'Belum ada tugas baru yang perlu dikerjakan untuk saat ini. Tetap semangat!') }}" description="{{ \App\Filament\Support\SystemNotification::getMessage('Belum ada tugas baru yang perlu dikerjakan. Hidup tenang tanpa beban tugas itu asik ya! 😊🌈', 'Belum ada tugas baru yang perlu dikerjakan untuk saat ini. Tetap semangat!') }}"
iconColor="gray"> iconColor="gray">

View File

@ -298,6 +298,30 @@
->assertDontSee('Tugas Wajib') ->assertDontSee('Tugas Wajib')
->assertSee('Hore, Belum Ada Tugas!'); ->assertSee('Hore, Belum Ada Tugas!');
}); });
it('can filter assignments by course', function () {
$course1 = Course::factory()->create(['name' => 'Matematika', 'semester' => $this->currentSemester]);
$course2 = Course::factory()->create(['name' => 'Fisika', 'semester' => $this->currentSemester]);
$assignment1 = Assignment::factory()->create([
'title' => 'Tugas Mat',
'course_id' => $course1->id,
'due_date' => now()->addDays(1),
]);
$assignment2 = Assignment::factory()->create([
'title' => 'Tugas Fis',
'course_id' => $course2->id,
'due_date' => now()->addDays(1),
]);
$assignment1->students()->attach($this->studentProfile->id);
$assignment2->students()->attach($this->studentProfile->id);
Livewire::test(ListAssignments::class)
->set('course_id', $course1->id)
->assertSee('Tugas Mat')
->assertDontSee('Tugas Fis');
});
}); });
describe('Assignment Sharing', function () { describe('Assignment Sharing', function () {