feat: add attendance viewing functionality for class sessions, including a modal display of attendance records
This commit is contained in:
parent
1eaa8fff59
commit
0732a3b402
@ -6,6 +6,7 @@
|
|||||||
use App\Filament\Resources\Learning\Attendances\AttendanceResource;
|
use App\Filament\Resources\Learning\Attendances\AttendanceResource;
|
||||||
use App\Filament\Support\SystemNotification;
|
use App\Filament\Support\SystemNotification;
|
||||||
use App\Models\Attendance;
|
use App\Models\Attendance;
|
||||||
|
use App\Models\ClassSession;
|
||||||
use App\Models\Course;
|
use App\Models\Course;
|
||||||
use App\Models\CourseSchedule;
|
use App\Models\CourseSchedule;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -45,17 +46,10 @@ public function getSchedules()
|
|||||||
return collect();
|
return collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$today = now()->dayOfWeekIso;
|
return ClassSession::query()
|
||||||
$semester = app(GeneralSettings::class)->current_semester;
|
->whereDate('date', now())
|
||||||
|
|
||||||
return CourseSchedule::query()
|
|
||||||
->where('day_of_week', $today)
|
|
||||||
->whereHas('course', function ($q) use ($semester) {
|
|
||||||
$q->where('semester', $semester);
|
|
||||||
})
|
|
||||||
->with(['course', 'attendances' => function ($q) use ($student) {
|
->with(['course', 'attendances' => function ($q) use ($student) {
|
||||||
$q->where('student_id', $student->id)
|
$q->where('student_id', $student->id);
|
||||||
->whereDate('date', now()->toDateString());
|
|
||||||
}])
|
}])
|
||||||
->orderBy('start_time')
|
->orderBy('start_time')
|
||||||
->get();
|
->get();
|
||||||
@ -138,7 +132,7 @@ public function scheduleCards()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attend(int $scheduleId): void
|
public function attend(int $sessionId): void
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
@ -147,41 +141,46 @@ public function attend(int $scheduleId): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$schedule = CourseSchedule::find($scheduleId);
|
$session = ClassSession::find($sessionId);
|
||||||
if (! $schedule) {
|
if (! $session) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already attended
|
// Check if already attended
|
||||||
$existing = Attendance::where('student_id', $student->id)
|
$existing = Attendance::where('student_id', $student->id)
|
||||||
->where('course_schedule_id', $scheduleId)
|
->where('class_session_id', $sessionId)
|
||||||
->whereDate('date', now()->toDateString())
|
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
SystemNotification::warning(
|
SystemNotification::warning(
|
||||||
'Presensi Sudah Tercatat ⚠️',
|
'Presensi Sudah Tercatat ⚠️',
|
||||||
'Anda telah melakukan presensi untuk jadwal perkuliahan ini pada hari ini.'
|
'Anda telah melakukan presensi untuk sesi perkuliahan ini.'
|
||||||
)->send();
|
)->send();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check time
|
// Check time
|
||||||
$startTime = now()->setTimeFrom($schedule->start_time);
|
$startTime = $session->start_time;
|
||||||
if (now()->lessThan($startTime)) {
|
if (now()->lessThan($startTime)) {
|
||||||
SystemNotification::warning(
|
SystemNotification::warning(
|
||||||
'Presensi Belum Dibuka ⏳',
|
'Presensi Belum Dibuka ⏳',
|
||||||
'Waktu presensi untuk jadwal perkuliahan ini belum dimulai. Silakan lakukan presensi setelah waktu dimulai.'
|
'Waktu presensi untuk sesi perkuliahan ini belum dimulai.'
|
||||||
)->send();
|
)->send();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find schedule for compatibility
|
||||||
|
$schedule = CourseSchedule::where('course_id', $session->course_id)
|
||||||
|
->where('day_of_week', $session->date->dayOfWeekIso)
|
||||||
|
->first();
|
||||||
|
|
||||||
Attendance::create([
|
Attendance::create([
|
||||||
'student_id' => $student->id,
|
'student_id' => $student->id,
|
||||||
'course_schedule_id' => $scheduleId,
|
'class_session_id' => $sessionId,
|
||||||
'date' => now()->toDateString(),
|
'course_schedule_id' => $schedule?->id,
|
||||||
|
'date' => $session->date->toDateString(),
|
||||||
'attended_at' => now(),
|
'attended_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -178,6 +178,19 @@ public function generateSessionsAction(): Action
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewAttendanceAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('viewAttendance')
|
||||||
|
->label('Lihat Presensi')
|
||||||
|
->modalHeading(fn (array $arguments) => 'Daftar Presensi - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
|
||||||
|
->modalSubmitAction(false)
|
||||||
|
->modalCancelActionLabel('Tutup')
|
||||||
|
->modalWidth(Width::ExtraLarge)
|
||||||
|
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [
|
||||||
|
'attendances' => ClassSession::find($arguments['session'])->attendances()->with('student')->latest('attended_at')->get(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
public function editSessionAction(): Action
|
public function editSessionAction(): Action
|
||||||
{
|
{
|
||||||
return EditAction::make('editSession')
|
return EditAction::make('editSession')
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Models\ClassSession;
|
use App\Models\ClassSession;
|
||||||
use App\Models\Course;
|
use App\Models\Course;
|
||||||
use App\Settings\GeneralSettings;
|
use App\Settings\GeneralSettings;
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\Concerns\InteractsWithActions;
|
use Filament\Actions\Concerns\InteractsWithActions;
|
||||||
use Filament\Actions\Contracts\HasActions;
|
use Filament\Actions\Contracts\HasActions;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
@ -13,6 +14,7 @@
|
|||||||
use Filament\Forms\Contracts\HasForms;
|
use Filament\Forms\Contracts\HasForms;
|
||||||
use Filament\Resources\Pages\Page;
|
use Filament\Resources\Pages\Page;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Support\Enums\Width;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Attributes\Computed;
|
use Livewire\Attributes\Computed;
|
||||||
|
|
||||||
@ -94,9 +96,9 @@ public function courses(): Collection
|
|||||||
|
|
||||||
if ($this->search) {
|
if ($this->search) {
|
||||||
$query->where(function ($q) {
|
$query->where(function ($q) {
|
||||||
$q->where('name', 'like', "%{$this->search}%")
|
$q->where('name', 'like', '%'.$this->search.'%')
|
||||||
->orWhere('code', 'like', "%{$this->search}%")
|
->orWhere('code', 'like', '%'.$this->search.'%')
|
||||||
->orWhere('lecturer', 'like', "%{$this->search}%");
|
->orWhere('lecturer', 'like', '%'.$this->search.'%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,4 +113,17 @@ public function courses(): Collection
|
|||||||
'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]),
|
'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewAttendanceAction(): Action
|
||||||
|
{
|
||||||
|
return Action::make('viewAttendance')
|
||||||
|
->label('Lihat Presensi')
|
||||||
|
->modalHeading(fn (array $arguments) => 'Daftar Presensi - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
|
||||||
|
->modalSubmitAction(false)
|
||||||
|
->modalCancelActionLabel('Tutup')
|
||||||
|
->modalWidth(Width::ExtraLarge)
|
||||||
|
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [
|
||||||
|
'attendances' => ClassSession::find($arguments['session'])->attendances()->with('student')->latest('attended_at')->get(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
<div class="space-y-3">
|
||||||
|
@if ($attendances->isEmpty())
|
||||||
|
<div class="flex flex-col items-center justify-center py-8 text-center">
|
||||||
|
<x-heroicon-o-user-group class="w-12 h-12 text-gray-400 mb-3" />
|
||||||
|
<p class="text-sm text-gray-500 font-medium">Belum ada mahasiswa yang melakukan presensi pada sesi ini.</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="fi-ta-content overflow-x-auto rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
|
||||||
|
<table class="w-full text-sm text-left divider-y dark:divider-white/5">
|
||||||
|
<thead class="bg-gray-50 dark:bg-white/5">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 font-bold text-gray-900 dark:text-white">Mahasiswa</th>
|
||||||
|
<th class="px-4 py-3 font-bold text-gray-900 dark:text-white text-right">Waktu Presensi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 dark:divide-white/5">
|
||||||
|
@foreach ($attendances as $attendance)
|
||||||
|
<tr class="hover:bg-gray-50 dark:hover:bg-white/5 transition-colors">
|
||||||
|
<td class="px-4 py-3 text-gray-950 dark:text-white font-medium">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>{{ $attendance->student->full_name }}</span>
|
||||||
|
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-normal uppercase tabular-nums">NIM: {{ $attendance->student->nim }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right">
|
||||||
|
<span class="inline-flex items-center rounded-md bg-success-50 dark:bg-success-400/10 px-2 py-1 text-xs font-bold text-success-700 dark:text-success-400 ring-1 ring-inset ring-success-600/20 tabular-nums">
|
||||||
|
{{ $attendance->attended_at->format('H:i') }} WIB
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex items-center justify-between px-2">
|
||||||
|
<p class="text-xs text-gray-500 font-medium">
|
||||||
|
Total Kehadiran: <span class="text-primary-600 dark:text-primary-400 font-bold font-mono">{{ $attendances->count() }} Mahasiswa</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
@ -57,14 +57,16 @@ 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">
|
<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"
|
||||||
<span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none">
|
onclick="event.preventDefault(); event.stopPropagation();"
|
||||||
{{ $session->attendances_count }}
|
wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
|
||||||
</span>
|
<span class="text-lg font-bold text-primary-600 dark:text-primary-400 leading-none">
|
||||||
<span class="text-[9px] uppercase font-bold text-gray-500 mt-1 leading-none tracking-wider">
|
{{ $session->attendances_count }}
|
||||||
Hadir
|
</span>
|
||||||
</span>
|
<span class="text-[9px] uppercase font-bold text-gray-500 mt-1 leading-none tracking-wider">
|
||||||
</div>
|
Hadir
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -40,6 +40,7 @@ class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-40
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
|
{{ ($this->viewAttendanceAction)(['session' => $session->id]) }}
|
||||||
{{ ($this->editSessionAction)(['session' => $session->id]) }}
|
{{ ($this->editSessionAction)(['session' => $session->id]) }}
|
||||||
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user