feat: enhance class session views by adding total students and attendance percentage metrics, improving data presentation in both list and detail views

This commit is contained in:
Yoga Pangestu 2026-03-29 23:32:34 +07:00
parent 0732a3b402
commit 92cfdf7ea1
4 changed files with 73 additions and 24 deletions

View File

@ -10,6 +10,7 @@
use App\Filament\Support\SystemNotification; use App\Filament\Support\SystemNotification;
use App\Models\ClassSession; use App\Models\ClassSession;
use App\Models\Course; use App\Models\Course;
use App\Models\Student;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions; use Filament\Actions\Contracts\HasActions;
@ -48,6 +49,10 @@ public function course(): Course
#[Computed] #[Computed]
public function sessions(): Collection public function sessions(): Collection
{ {
$totalActiveStudents = Student::query()
->whereHas('user', fn ($q) => $q->where('is_active', true))
->count();
return $this->course->classSessions() return $this->course->classSessions()
->withCount(['attendances', 'materials', 'assignments']) ->withCount(['attendances', 'materials', 'assignments'])
->orderBy('session_number', 'asc') ->orderBy('session_number', 'asc')
@ -58,6 +63,8 @@ public function sessions(): Collection
'date_formatted' => $session->date->translatedFormat('l, d F Y'), 'date_formatted' => $session->date->translatedFormat('l, d F Y'),
'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'), 'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'),
'attendances_count' => $session->attendances_count, 'attendances_count' => $session->attendances_count,
'total_students' => $totalActiveStudents,
'attendance_percentage' => $totalActiveStudents > 0 ? round(($session->attendances_count / $totalActiveStudents) * 100) : 0,
'materials_count' => $session->materials_count, 'materials_count' => $session->materials_count,
'assignments_count' => $session->assignments_count, 'assignments_count' => $session->assignments_count,
]); ]);

View File

@ -5,6 +5,7 @@
use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource; use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource;
use App\Models\ClassSession; use App\Models\ClassSession;
use App\Models\Course; use App\Models\Course;
use App\Models\Student;
use App\Settings\GeneralSettings; use App\Settings\GeneralSettings;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Concerns\InteractsWithActions;
@ -60,12 +61,18 @@ public function form(Schema $schema): Schema
#[Computed] #[Computed]
public function todaySessions(): Collection public function todaySessions(): Collection
{ {
$totalActiveStudents = Student::query()
->whereHas('user', fn ($q) => $q->where('is_active', true))
->count();
return ClassSession::query() return ClassSession::query()
->whereDate('date', now()) ->whereDate('date', now())
->with(['course']) ->with(['course'])
->withCount('attendances') ->withCount(['attendances', 'materials', 'assignments'])
->get() ->get()
->map(function ($session) { ->map(function ($session) use ($totalActiveStudents) {
$percentage = $totalActiveStudents > 0 ? round(($session->attendances_count / $totalActiveStudents) * 100) : 0;
return (object) [ return (object) [
'id' => $session->id, 'id' => $session->id,
'is_pending' => false, 'is_pending' => false,
@ -75,6 +82,10 @@ public function todaySessions(): Collection
'lecturer' => $session->course->lecturer ?? '-', 'lecturer' => $session->course->lecturer ?? '-',
'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'), 'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'),
'attendances_count' => $session->attendances_count, 'attendances_count' => $session->attendances_count,
'total_students' => $totalActiveStudents,
'attendance_percentage' => $percentage,
'materials_count' => $session->materials_count,
'assignments_count' => $session->assignments_count,
'url' => ClassSessionResource::getUrl('course', ['courseId' => $session->course_id]), 'url' => ClassSessionResource::getUrl('course', ['courseId' => $session->course_id]),
// Pre-calculated classes // Pre-calculated classes
@ -92,7 +103,7 @@ public function courses(): Collection
{ {
$query = Course::query() $query = Course::query()
->where('semester', app(GeneralSettings::class)->current_semester) ->where('semester', app(GeneralSettings::class)->current_semester)
->with(['classSessions' => fn ($q) => $q->orderBy('session_number', 'asc')]); ->with(['classSessions', 'studyGroups.students.user']);
if ($this->search) { if ($this->search) {
$query->where(function ($q) { $query->where(function ($q) {
@ -102,16 +113,23 @@ public function courses(): Collection
}); });
} }
$totalActiveStudents = Student::query()
->whereHas('user', fn ($q) => $q->where('is_active', true))
->count();
return $query->get() return $query->get()
->sortBy('name') ->sortBy('name')
->map(fn ($course) => (object) [ ->map(function ($course) use ($totalActiveStudents) {
'id' => $course->id, return (object) [
'name' => $course->name, 'id' => $course->id,
'code' => $course->code, 'name' => $course->name,
'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan', 'code' => $course->code,
'sessions_count' => $course->classSessions->count(), 'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan',
'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]), 'sessions_count' => $course->classSessions->count(),
]); 'total_students' => $totalActiveStudents,
'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]),
];
});
} }
public function viewAttendanceAction(): Action public function viewAttendanceAction(): Action

View File

@ -41,6 +41,16 @@
<x-heroicon-s-clock class="w-3.5 h-3.5" /> <x-heroicon-s-clock class="w-3.5 h-3.5" />
{{ $session->time_range }} {{ $session->time_range }}
</div> </div>
<div class="flex items-center gap-2 mt-2">
<div class="flex items-center gap-1 text-[10px] font-bold text-amber-600 dark:text-amber-400">
<x-heroicon-o-document-text class="w-3 h-3" />
{{ $session->materials_count }} Materi
</div>
<div class="flex items-center gap-1 text-[10px] font-bold text-info-600 dark:text-info-400">
<x-heroicon-o-clipboard-document-list class="w-3 h-3" />
{{ $session->assignments_count }} Tugas
</div>
</div>
@if ($session->is_pending) @if ($session->is_pending)
<div <div
class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-500 font-medium italic"> class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-500 font-medium italic">
@ -57,15 +67,20 @@ class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-5
<x-heroicon-m-clock class="w-5 h-5" /> <x-heroicon-m-clock class="w-5 h-5" />
</div> </div>
@else @else
<div class="flex flex-col items-center justify-center min-w-[60px] p-2 hover:bg-white/50 dark:hover:bg-white/5 rounded-lg transition-colors cursor-pointer" <div class="flex flex-col items-center justify-center min-w-[80px] p-2 hover:bg-white/50 dark:hover:bg-white/5 rounded-lg transition-colors cursor-pointer"
onclick="event.preventDefault(); event.stopPropagation();" onclick="event.preventDefault(); event.stopPropagation();"
wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })"> wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
<span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none"> <div class="flex flex-col items-center">
{{ $session->attendances_count }} <span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none">
</span> {{ $session->attendances_count }}<span class="text-[10px] text-gray-400 font-normal ml-0.5">/{{ $session->total_students }}</span>
<span class="text-[9px] uppercase font-bold text-gray-500 mt-1 leading-none tracking-wider"> </span>
Hadir <div class="mt-1.5 w-12 h-1 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
</span> <div class="h-full bg-primary-500 rounded-full transition-all duration-500" style="width: {{ $session->attendance_percentage }}%"></div>
</div>
<span class="text-[9px] uppercase font-bold text-gray-500 mt-1 leading-none tracking-wider">
{{ $session->attendance_percentage }}% Hadir
</span>
</div>
</div> </div>
@endif @endif
</div> </div>

View File

@ -22,10 +22,19 @@ class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500 dark:te
<x-heroicon-o-clock class="w-4 h-4 text-primary-500" /> <x-heroicon-o-clock class="w-4 h-4 text-primary-500" />
{{ $session->time_range }} {{ $session->time_range }}
</div> </div>
<div <div class="flex flex-col gap-1">
class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-400"> <div
<x-heroicon-o-user-group class="w-4 h-4" /> class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-400 hover:bg-success-50 dark:hover:bg-success-900/10 px-2 py-0.5 rounded-lg transition-colors cursor-pointer"
{{ $session->attendances_count }} Presensi wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
<x-heroicon-o-user-group class="w-4 h-4" />
{{ $session->attendances_count }}<span class="text-[10px] opacity-60 ml-0.5">/{{ $session->total_students }}</span> Presensi
</div>
<div class="flex items-center gap-2 px-2">
<div class="flex-1 h-1 bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden">
<div class="h-full bg-success-500 rounded-full transition-all duration-500" style="width: {{ $session->attendance_percentage }}%"></div>
</div>
<span class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }}%</span>
</div>
</div> </div>
<div class="flex items-center gap-1.5 font-bold text-amber-600 dark:text-amber-400"> <div class="flex items-center gap-1.5 font-bold text-amber-600 dark:text-amber-400">
<x-heroicon-o-document-text class="w-4 h-4" /> <x-heroicon-o-document-text class="w-4 h-4" />