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

View File

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

View File

@ -41,6 +41,16 @@
<x-heroicon-s-clock class="w-3.5 h-3.5" />
{{ $session->time_range }}
</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)
<div
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" />
</div>
@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"
onclick="event.preventDefault(); event.stopPropagation();"
wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
<span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none">
{{ $session->attendances_count }}
</span>
<span class="text-[9px] uppercase font-bold text-gray-500 mt-1 leading-none tracking-wider">
Hadir
</span>
<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();"
wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
<div class="flex flex-col items-center">
<span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none">
{{ $session->attendances_count }}<span class="text-[10px] text-gray-400 font-normal ml-0.5">/{{ $session->total_students }}</span>
</span>
<div class="mt-1.5 w-12 h-1 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<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>
@endif
</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" />
{{ $session->time_range }}
</div>
<div
class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-400">
<x-heroicon-o-user-group class="w-4 h-4" />
{{ $session->attendances_count }} Presensi
<div class="flex flex-col gap-1">
<div
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"
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 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" />