feat: Filter course-related data by current semester from general settings and rename attendance management page.

This commit is contained in:
Yoga Pangestu 2026-03-12 08:03:51 +07:00
parent 2019928fb3
commit c8f2ac6c26
8 changed files with 70 additions and 95 deletions

View File

@ -7,6 +7,7 @@
use App\Filament\Pages\Information\CourseSchedule\Actions\EditScheduleAction; use App\Filament\Pages\Information\CourseSchedule\Actions\EditScheduleAction;
use App\Models\Course; use App\Models\Course;
use App\Models\CourseSchedule; use App\Models\CourseSchedule;
use App\Settings\GeneralSettings;
use BackedEnum; use BackedEnum;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Concerns\InteractsWithActions;
@ -43,8 +44,6 @@ class Index extends Page implements HasActions, HasForms
protected string $view = 'filament.pages.information.list-course-schedules'; protected string $view = 'filament.pages.information.list-course-schedules';
public ?int $course_semester = null;
public ?string $search = ''; public ?string $search = '';
public static function getPagePermission(): string public static function getPagePermission(): string
@ -54,24 +53,13 @@ public static function getPagePermission(): string
public function mount(): void public function mount(): void
{ {
$this->course_semester = null; $this->form->fill();
$this->form->fill([
'course_semester' => $this->course_semester,
]);
} }
public function form(Schema $schema): Schema public function form(Schema $schema): Schema
{ {
return $schema return $schema
->schema([ ->schema([
Select::make('course_semester')
->label('Semester')
->options(array_combine(range(1, 8), array_map(fn ($i) => "Semester $i", range(1, 8))))
->native(false)
->live()
->afterStateUpdated(fn ($state) => $this->course_semester = $state),
TextInput::make('search') TextInput::make('search')
->label('Cari') ->label('Cari')
->placeholder('Cari Mata Kuliah atau Dosen...') ->placeholder('Cari Mata Kuliah atau Dosen...')
@ -81,7 +69,6 @@ public function form(Schema $schema): Schema
]) ])
->columns([ ->columns([
'sm' => 1, 'sm' => 1,
'md' => 3,
]); ]);
} }
@ -113,9 +100,7 @@ public function getSchedules(): Collection
$query->whereHas('course', fn ($q) => $q->where('lecturer_id', $lecturer->id)); $query->whereHas('course', fn ($q) => $q->where('lecturer_id', $lecturer->id));
} }
if ($this->course_semester) { $query->whereHas('course', fn ($q) => $q->where('semester', app(GeneralSettings::class)->current_semester));
$query->whereHas('course', fn ($q) => $q->where('semester', $this->course_semester));
}
if ($this->search) { if ($this->search) {
$query->where(function ($q) { $query->where(function ($q) {
@ -156,20 +141,15 @@ public function scheduleFormSchema(): array
Select::make('course_id') Select::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->searchable() ->searchable()
->live() ->live()
->required() ->required()
->columnSpanFull() ->columnSpanFull(),
->optionsLimit(100),
Grid::make(3) Grid::make(3)
->schema([ ->schema([

View File

@ -8,6 +8,7 @@
use App\Models\Course; use App\Models\Course;
use App\Models\Student; use App\Models\Student;
use App\Models\StudyGroup; use App\Models\StudyGroup;
use App\Settings\GeneralSettings;
use BackedEnum; use BackedEnum;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Concerns\InteractsWithActions;
@ -58,22 +59,20 @@ public function form(Schema $schema): Schema
->schema([ ->schema([
Select::make('course_id') Select::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->placeholder('Semua Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->searchable() ->searchable()
->required() ->live(),
->optionsLimit(100),
]) ])
->columns(1); ->columns([
'sm' => 1,
]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
@ -95,7 +94,8 @@ protected function deleteStudyGroupAction(): Action
public function getStudyGroups(): Collection public function getStudyGroups(): Collection
{ {
$query = StudyGroup::with(['leader', 'students', 'courses']); $query = StudyGroup::with(['leader', 'students', 'courses'])
->whereHas('courses', fn ($q) => $q->where('courses.semester', app(GeneralSettings::class)->current_semester));
if ($this->course_id) { if ($this->course_id) {
$query->whereHas('courses', fn ($q) => $q->where('courses.id', $this->course_id)); $query->whereHas('courses', fn ($q) => $q->where('courses.id', $this->course_id));
@ -114,7 +114,7 @@ protected function getViewData(): array
public function isMyGroup(StudyGroup $record): bool public function isMyGroup(StudyGroup $record): bool
{ {
$studentId = $this->studentId; $studentId = auth()->user()?->student?->id;
if (! $studentId) { if (! $studentId) {
return false; return false;
@ -131,19 +131,14 @@ public function studyGroupFormSchema(): array
Select::make('course_id') Select::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->multiple() ->multiple()
->searchable() ->searchable()
->required() ->required()
->optionsLimit(100)
->columnSpanFull(), ->columnSpanFull(),
TextInput::make('name') TextInput::make('name')

View File

@ -6,6 +6,7 @@
use App\Filament\Resources\Learning\Assignments\Actions\SelectAllStudentsAction; use App\Filament\Resources\Learning\Assignments\Actions\SelectAllStudentsAction;
use App\Models\Course; use App\Models\Course;
use App\Models\Student; use App\Models\Student;
use App\Settings\GeneralSettings;
use Asmit\FilamentUpload\Enums\PdfViewFit; use Asmit\FilamentUpload\Enums\PdfViewFit;
use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload; use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload;
use Filament\Forms\Components\DateTimePicker; use Filament\Forms\Components\DateTimePicker;
@ -43,19 +44,14 @@ public static function configure(Schema $schema): Schema
Select::make('course_id') Select::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->searchable() ->searchable()
->live() ->live()
->required() ->required(),
->optionsLimit(100),
DateTimePicker::make('due_date') DateTimePicker::make('due_date')
->label('Batas Waktu') ->label('Batas Waktu')

View File

@ -2,7 +2,7 @@
namespace App\Filament\Resources\Learning\Attendances; namespace App\Filament\Resources\Learning\Attendances;
use App\Filament\Resources\Learning\Attendances\Pages\AttendancePage; use App\Filament\Resources\Learning\Attendances\Pages\ManageAttendances;
use App\Models\Attendance; use App\Models\Attendance;
use BackedEnum; use BackedEnum;
use Filament\Resources\Resource; use Filament\Resources\Resource;
@ -28,7 +28,7 @@ class AttendanceResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => AttendancePage::route('/'), 'index' => ManageAttendances::route('/'),
]; ];
} }
} }

View File

@ -7,9 +7,10 @@
use App\Models\Attendance; use App\Models\Attendance;
use App\Models\CourseSchedule; use App\Models\CourseSchedule;
use App\Models\User; use App\Models\User;
use App\Settings\GeneralSettings;
use Filament\Resources\Pages\Page; use Filament\Resources\Pages\Page;
class AttendancePage extends Page class ManageAttendances extends Page
{ {
protected static string $resource = AttendanceResource::class; protected static string $resource = AttendanceResource::class;
@ -23,11 +24,23 @@ public function getSchedules()
{ {
$student = auth()->user()->student; $student = auth()->user()->student;
$today = now()->dayOfWeek; if (! $student) {
return collect();
}
$today = now()->dayOfWeekIso;
$semester = app(GeneralSettings::class)->current_semester;
return CourseSchedule::query() return CourseSchedule::query()
->where('day_of_week', $today) ->where('day_of_week', $today)
->with(['course', 'attendances' => function ($q) use ($student) { ->whereHas('course', function ($q) use ($semester, $student) {
$q->where('semester', $semester)
->whereHas('studyGroups', function ($sq) use ($student) {
$sq->where('leader_id', $student->id)
->orWhereHas('students', fn ($asq) => $asq->where('students.id', $student->id));
});
})
->with(['course.lecturer.user', 'attendances' => function ($q) use ($student) {
$q->where('student_id', $student->id) $q->where('student_id', $student->id)
->whereDate('date', now()->toDateString()); ->whereDate('date', now()->toDateString());
}]) }])
@ -64,6 +77,7 @@ public function getScheduleCards()
return (object) [ return (object) [
'id' => $schedule->id, 'id' => $schedule->id,
'course_name' => $schedule->course->name, 'course_name' => $schedule->course->name,
'lecturer_name' => $schedule->course->lecturer?->full_name ?? 'Belum Ditentukan',
'time_range' => $schedule->start_time->format('H:i').' - '.$schedule->end_time->format('H:i'), 'time_range' => $schedule->start_time->format('H:i').' - '.$schedule->end_time->format('H:i'),
'is_attended' => $isAttended, 'is_attended' => $isAttended,
'can_attend' => $canAttend && ! $isAttended, 'can_attend' => $canAttend && ! $isAttended,

View File

@ -11,6 +11,7 @@
use App\Filament\Resources\Learning\Materials\Pages\ManageMaterials; use App\Filament\Resources\Learning\Materials\Pages\ManageMaterials;
use App\Models\Course; use App\Models\Course;
use App\Models\Material; use App\Models\Material;
use App\Settings\GeneralSettings;
use Asmit\FilamentUpload\Enums\PdfViewFit; use Asmit\FilamentUpload\Enums\PdfViewFit;
use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload; use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload;
use BackedEnum; use BackedEnum;
@ -60,19 +61,14 @@ public static function form(Schema $schema): Schema
Select::make('course_id') Select::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->searchable() ->searchable()
->live() ->live()
->required() ->required(),
->optionsLimit(100),
DatePicker::make('published_at') DatePicker::make('published_at')
->label('Tanggal Publikasi') ->label('Tanggal Publikasi')
@ -147,17 +143,12 @@ public static function table(Table $table): Table
SelectFilter::make('course_id') SelectFilter::make('course_id')
->label('Mata Kuliah') ->label('Mata Kuliah')
->options(function () { ->options(function () {
return Course::all() return Course::query()
->groupBy('semester') ->where('semester', app(GeneralSettings::class)->current_semester)
->mapWithKeys(function ($courses, $semester) { ->pluck('name', 'id')
return [
"Semester $semester" => $courses->pluck('name', 'id')->toArray(),
];
})
->toArray(); ->toArray();
}) })
->searchable() ->searchable(),
->optionsLimit(100),
Filter::make('published_at') Filter::make('published_at')
->schema([ ->schema([

View File

@ -12,7 +12,7 @@ class="fi-section-content rounded-xl border border-info-200 bg-info-50 dark:bg-i
</div> </div>
<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"> <div class="w-full max-w-xl">
{{ $this->form }} {{ $this->form }}
</div> </div>
</div> </div>

View File

@ -25,34 +25,33 @@ class="inline-flex items-center gap-1 rounded-full bg-primary-600 px-3 py-1 text
@endif @endif
<div class="p-6 space-y-4 flex-1"> <div class="p-6 space-y-4 flex-1">
<div class="flex flex-wrap items-center gap-1.5">
<span @class([
'inline-flex items-center rounded-md px-2.5 py-1 text-[10px] font-bold ring-1 ring-inset uppercase tracking-tight',
'bg-primary-50 dark:bg-primary-500/10 text-primary-700 dark:text-primary-300 ring-primary-600/20' => !$card->is_attended,
'bg-white dark:bg-primary-800/40 text-primary-700 dark:text-primary-200 ring-primary-500/30' =>
$card->is_attended,
])>
{{ $card->course_name }}
</span>
</div>
<div class="space-y-1"> <div class="space-y-1">
<h3 @class([ <h3 @class([
'text-lg font-bold leading-tight flex items-center gap-2 transition-colors', 'text-lg font-bold leading-tight flex items-center gap-2 transition-colors',
'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => !$card->is_attended, 'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => !$card->is_attended,
'text-primary-900 dark:text-primary-100' => $card->is_attended, 'text-primary-900 dark:text-primary-100' => $card->is_attended,
])> ])>
<x-heroicon-o-finger-print @class([ <x-heroicon-o-book-open @class([
'w-5 h-5 shrink-0', 'w-5 h-5 shrink-0',
'opacity-40' => !$card->is_attended, 'opacity-40' => !$card->is_attended,
'text-primary-500' => $card->is_attended, 'text-primary-500' => $card->is_attended,
]) /> ]) />
Presensi Kuliah {{ $card->course_name }}
</h3> </h3>
<div @class([
'flex items-center gap-1.5',
'text-gray-500 dark:text-gray-400' => !$card->is_attended,
'text-primary-600 dark:text-primary-400' => $card->is_attended,
])>
<x-heroicon-m-user @class(['w-3.5 h-3.5 shrink-0', 'opacity-50' => !$card->is_attended]) />
<span class="text-[11px] font-medium leading-none">
{{ $card->lecturer_name }}
</span>
</div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400"> <div class="flex items-center text-sm text-gray-600 dark:text-gray-400 py-4">
<div @class([ <div @class([
'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border', 'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border',
'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => !$card->is_attended, 'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => !$card->is_attended,
@ -80,7 +79,7 @@ class="text-[10px] font-bold uppercase text-gray-400 tracking-widest leading-non
</div> </div>
<div @class([ <div @class([
'pt-4 border-t', 'border-t',
'border-gray-100 dark:border-gray-800' => !$card->is_attended, 'border-gray-100 dark:border-gray-800' => !$card->is_attended,
'border-primary-200 dark:border-primary-800' => $card->is_attended, 'border-primary-200 dark:border-primary-800' => $card->is_attended,
])> ])>