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\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,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user