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:
parent
0732a3b402
commit
92cfdf7ea1
@ -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,
|
||||
]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user