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

View File

@ -1,20 +1,18 @@
<x-filament-panels::page>
<x-filament::section
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-clipboard-document-list' : 'heroicon-o-briefcase' }}"
icon-color="primary"
>
<x-filament::section
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-clipboard-document-list' : 'heroicon-o-briefcase' }}"
icon-color="primary">
<x-slot name="heading">{{ $this->heading }}</x-slot>
<x-slot name="description">{{ $this->description }}</x-slot>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full">
{{ $this->form }}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
@forelse ($this->assignmentCards as $card)
<div class="{{ $card->card_classes }} flex-col">
<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">
<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>
@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">
<x-filament::icon icon="heroicon-o-arrow-up-tray"
class="h-4 w-4 shrink-0" />
<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">
<x-filament::icon icon="heroicon-o-arrow-up-tray" class="h-4 w-4 shrink-0" />
<span>Dikumpulkan: {{ $card->submitted_at_formatted }}</span>
</span>
@endif
@ -88,7 +87,8 @@ class="h-4 w-4 shrink-0" />
</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->shareAssignmentAction)(['record' => $card->id]) }}
@ -101,9 +101,9 @@ class="h-4 w-4 shrink-0" />
</div>
@empty
@empty
<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') }}"
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">

View File

@ -298,6 +298,30 @@
->assertDontSee('Tugas Wajib')
->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 () {