feat: implement automated daily session generation command and update session management UI with attendance tracking

This commit is contained in:
Yoga Pangestu 2026-03-29 22:06:46 +07:00
parent c8a98d8d65
commit 59af7bb595
4 changed files with 105 additions and 66 deletions

View File

@ -0,0 +1,55 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class GenerateDailySessions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:generate-daily-sessions';
protected $description = 'Membangun sesi kelas harian secara otomatis berdasarkan jadwal.';
/**
* Execute the console command.
*/
public function handle()
{
$dayOfWeek = now()->dayOfWeekIso; // 1 (Mon) - 7 (Sun)
$currentSemester = app(\App\Settings\GeneralSettings::class)->current_semester;
$schedules = \App\Models\CourseSchedule::query()
->where('day_of_week', $dayOfWeek)
->whereHas('course', function ($query) use ($currentSemester) {
$query->where('semester', $currentSemester);
})
->get();
$count = 0;
foreach ($schedules as $schedule) {
$exists = \App\Models\ClassSession::where('course_id', $schedule->course_id)
->whereDate('date', now())
->exists();
if (! $exists) {
$lastSession = \App\Models\ClassSession::where('course_id', $schedule->course_id)->max('session_number');
\App\Models\ClassSession::create([
'course_id' => $schedule->course_id,
'session_number' => ($lastSession ?? 0) + 1,
'date' => now()->toDateString(),
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
]);
$count++;
}
}
$this->info("Generated {$count} sessions for ".now()->toDateString());
}
}

View File

@ -3,7 +3,6 @@
namespace App\Filament\Resources\Learning\ClassSessions\Pages;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource;
use App\Models\ClassSession;
use App\Models\Course;
@ -17,7 +16,6 @@
use Filament\Forms\Contracts\HasForms;
use Filament\Resources\Pages\Page;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
use Illuminate\Support\Collection;
class ManageClassSessions extends Page implements HasActions, HasForms
@ -58,59 +56,12 @@ protected function getHeaderActions(): array
return [];
}
public function editSessionAction(): Action
{
return EditAction::make('editSession')
->record(fn (array $arguments) => ClassSession::find($arguments['session']))
->modalWidth(Width::TwoExtraLarge);
}
public function deleteSessionAction(): Action
{
return DeleteAction::make('deleteSession')
->record(fn (array $arguments) => ClassSession::find($arguments['session']));
}
public function generateTodaySessionAction(): Action
{
return Action::make('generateTodaySession')
->label('Generate Sesi')
->icon('heroicon-o-sparkles')
->color('primary')
->requiresConfirmation()
->modalHeading('Generate Sesi Hari Ini?')
->modalDescription('Sistem akan membuat sesi baru secara otomatis berdasarkan jadwal aktif.')
->modalSubmitActionLabel('Generate Sekarang')
->action(function (array $arguments) {
$courseId = $arguments['course'];
$schedule = CourseSchedule::where('course_id', $courseId)->first();
if (! $schedule) {
\Filament\Notifications\Notification::make()
->title('Jadwal Tidak Ditemukan')
->danger()
->send();
return;
}
$lastSession = ClassSession::where('course_id', $courseId)->max('session_number');
ClassSession::create([
'course_id' => $courseId,
'session_number' => ($lastSession ?? 0) + 1,
'date' => now()->toDateString(),
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
]);
\Filament\Notifications\Notification::make()
->title('Sesi Berhasil Digenerate')
->success()
->send();
});
}
public function getTodaySessions(): Collection
{
$dayOfWeek = now()->dayOfWeekIso; // 1 (Mon) - 7 (Sun)
@ -120,7 +71,10 @@ public function getTodaySessions(): Collection
->whereHas('course', function ($query) {
$query->where('semester', app(GeneralSettings::class)->current_semester);
})
->with(['course', 'course.classSessions' => fn ($q) => $q->whereDate('date', now())])
->with([
'course',
'course.classSessions' => fn ($q) => $q->whereDate('date', now())->withCount('attendances'),
])
->orderBy('start_time', 'asc')
->get();
@ -135,6 +89,7 @@ public function getTodaySessions(): Collection
'end_time' => $schedule->end_time,
'title' => $session?->title,
'is_pending' => ! $session,
'attendances_count' => $session?->attendances_count ?? 0,
];
});
}

View File

@ -15,21 +15,31 @@
Sesi Hari Ini
</x-slot>
<x-slot name="description">
Sesi yang dijadwalkan pada {{ now()->format('l, d F Y') }}
Sesi yang dijadwalkan pada {{ now()->translatedFormat('l, d F Y') }}
</x-slot>
<div class="space-y-4">
@foreach ($this->getTodaySessions() as $session)
<div @class([
'group flex w-full rounded-xl border overflow-hidden relative transition-all hover:shadow-md',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' => !$session->is_pending,
'border-gray-300 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/10 border-dashed' => $session->is_pending,
])>
@php
$cardClass = \Illuminate\Support\Arr::toCssClasses([
'group flex w-full rounded-xl border overflow-hidden relative transition-all hover:shadow-md',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' => !$session->is_pending,
'border-gray-300 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/10 border-dashed' => $session->is_pending,
]);
@endphp
@if ($session->is_pending)
<div class="{{ $cardClass }}">
@else
<a href="{{ \App\Filament\Resources\Learning\ClassSessions\ClassSessionResource::getUrl('course', ['courseId' => $session->course->id]) }}"
class="{{ $cardClass }}">
@endif
<div class="flex flex-1 items-start gap-4 p-5">
<div @class([
'flex h-12 w-12 shrink-0 flex-col items-center justify-center rounded-lg font-bold border',
'bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-300 border-primary-200 dark:border-primary-700' => !$session->is_pending,
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700' => $session->is_pending,
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700' =>
$session->is_pending,
])>
<span class="text-[9px] uppercase leading-none opacity-60 mb-0.5 font-bold">Sesi</span>
<span class="text-xl leading-none italic">#{{ $session->session_number }}</span>
@ -55,12 +65,14 @@
'bg-gray-500 text-white opacity-60' => $session->is_pending,
])>
<x-heroicon-s-clock class="w-3.5 h-3.5" />
{{ $session->start_time->format('H:i') }} - {{ $session->end_time->format('H:i') }}
{{ $session->start_time->format('H:i') }} -
{{ $session->end_time->format('H:i') }}
</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">
<x-heroicon-m-sparkles class="w-3.5 h-3.5" />
Sesi belum digenerate hari ini
<div
class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-500 font-medium italic">
<x-heroicon-m-calendar-days class="w-3.5 h-3.5" />
Sesuai Jadwal Hari Ini
</div>
@endif
</div>
@ -72,13 +84,27 @@
'border-gray-200 dark:border-gray-800' => $session->is_pending,
])>
@if ($session->is_pending)
{{ ($this->generateTodaySessionAction)(['course' => $session->course->id]) }}
<div class="flex items-center gap-2 text-gray-400 dark:text-gray-500 p-2 opacity-50">
<x-heroicon-m-clock class="w-5 h-5" />
</div>
@else
{{ ($this->editSessionAction)(['session' => $session->id]) }}
<div class="flex flex-col items-center justify-center min-w-[60px] p-2">
<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>
@endif
</div>
</div>
</div>
@if ($session->is_pending)
</div>
@else
</a>
@endif
@endforeach
</div>
</x-filament::section>
@ -122,8 +148,7 @@ class="text-base font-bold text-gray-950 dark:text-white leading-tight group-hov
</h3>
<div class="flex items-center text-sm text-gray-500 dark:text-gray-400">
<x-heroicon-m-user class="w-4 h-4 mr-1.5 opacity-70" />
<span
class="truncate">{{ $course->lecturer ?? 'Dosen Belum Ditentukan' }}</span>
<span class="truncate">{{ $course->lecturer ?? 'Dosen Belum Ditentukan' }}</span>
</div>
</div>
</div>

View File

@ -6,3 +6,7 @@
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
use Illuminate\Support\Facades\Schedule;
Schedule::command('app:generate-daily-sessions')->dailyAt('00:00');