feat: add 'mark as sent' functionality for class sessions with UI updates and notifications
This commit is contained in:
parent
2d36e961ba
commit
889e3bc510
@ -25,7 +25,7 @@ protected function setUp(): void
|
||||
->icon(function (array $arguments, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
|
||||
return $assignment?->is_sent_to_lecturer ? 'heroicon-o-lock-open' : 'heroicon-o-check-circle';
|
||||
return $assignment?->is_sent_to_lecturer ? 'heroicon-o-lock-open' : 'heroicon-o-paper-airplane';
|
||||
})
|
||||
->color(function (array $arguments, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\Learning\Assignments\Pages;
|
||||
|
||||
use App\Enums\AssignmentType;
|
||||
use App\Enums\NotifStyle;
|
||||
use App\Filament\Actions\BackAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\MarkAsSentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\AssignmentResource;
|
||||
@ -181,7 +182,7 @@ public function assignmentSummary(): array
|
||||
#[Computed]
|
||||
public function sectionIcon(): string
|
||||
{
|
||||
return SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful
|
||||
return SystemNotification::getNotifStyle() === NotifStyle::Cheerful
|
||||
? 'heroicon-o-document-magnifying-glass'
|
||||
: 'heroicon-o-document-text';
|
||||
}
|
||||
|
||||
@ -99,7 +99,14 @@ public function getSchedules()
|
||||
}
|
||||
|
||||
return ClassSession::query()
|
||||
->whereDate('date', now())
|
||||
->where(function ($query) use ($student) {
|
||||
$query->whereDate('date', now())
|
||||
->orWhere(function ($q) use ($student) {
|
||||
$q->whereDate('date', '<', now())
|
||||
->where('is_sent_to_lecturer', false)
|
||||
->whereDoesntHave('attendances', fn ($aq) => $aq->where('student_id', $student->id));
|
||||
});
|
||||
})
|
||||
->with(['course', 'attendances' => function ($q) use ($student) {
|
||||
$q->where('student_id', $student->id);
|
||||
}])
|
||||
@ -114,10 +121,11 @@ public function scheduleCards()
|
||||
->map(function ($schedule) {
|
||||
$attendance = $schedule->attendances?->first();
|
||||
$isAttended = $attendance !== null;
|
||||
$isSent = $schedule->is_sent_to_lecturer;
|
||||
|
||||
$now = now();
|
||||
$startTime = now()->setTimeFrom($schedule->start_time);
|
||||
$canAttend = $now->greaterThanOrEqualTo($startTime);
|
||||
$canAttend = $now->greaterThanOrEqualTo($startTime) && ! $isSent;
|
||||
|
||||
$statusLabel = 'Belum Presensi';
|
||||
$statusColor = 'warning';
|
||||
@ -127,6 +135,10 @@ public function scheduleCards()
|
||||
$statusLabel = 'Hadir';
|
||||
$statusColor = 'success';
|
||||
$statusIcon = 'heroicon-o-check-circle';
|
||||
} elseif ($isSent) {
|
||||
$statusLabel = 'Terkirim';
|
||||
$statusColor = 'danger';
|
||||
$statusIcon = 'heroicon-o-paper-airplane';
|
||||
} elseif (! $canAttend) {
|
||||
$statusLabel = 'Belum Dimulai';
|
||||
$statusColor = 'gray';
|
||||
@ -139,6 +151,7 @@ public function scheduleCards()
|
||||
'lecturer_name' => $schedule->course?->lecturer ?? 'Belum Ditentukan',
|
||||
'time_range' => $schedule->start_time?->format('H:i').' - '.$schedule->end_time?->format('H:i'),
|
||||
'is_attended' => $isAttended,
|
||||
'is_sent_to_lecturer' => $isSent,
|
||||
'can_attend' => $canAttend && ! $isAttended,
|
||||
'status_label' => $statusLabel,
|
||||
'status_color' => $statusColor,
|
||||
@ -210,6 +223,14 @@ public function attend(int $sessionId): void
|
||||
return;
|
||||
}
|
||||
|
||||
// Check session status
|
||||
if ($session->is_sent_to_lecturer) {
|
||||
SystemNotification::send('attendance_closed', type: 'danger')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check time
|
||||
$startTime = $session->start_time;
|
||||
if (now()->lessThan($startTime)) {
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
|
||||
|
||||
use App\Filament\Support\SystemNotification;
|
||||
use App\Models\ClassSession;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class MarkAsSentAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'markAsSentAction';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(function (array $arguments, ?ClassSession $record) {
|
||||
$session = $record ?? ClassSession::find($arguments['record'] ?? null);
|
||||
|
||||
return $session?->is_sent_to_lecturer ? 'Buka Kunci' : 'Tandai Terkirim';
|
||||
})
|
||||
->icon(function (array $arguments, ?ClassSession $record) {
|
||||
$session = $record ?? ClassSession::find($arguments['record'] ?? null);
|
||||
|
||||
return $session?->is_sent_to_lecturer ? 'heroicon-o-lock-open' : 'heroicon-o-paper-airplane';
|
||||
})
|
||||
->color(function (array $arguments, ?ClassSession $record) {
|
||||
$session = $record ?? ClassSession::find($arguments['record'] ?? null);
|
||||
|
||||
return $session?->is_sent_to_lecturer ? 'warning' : 'success';
|
||||
})
|
||||
->link()
|
||||
->visible(fn () => auth()->user()->hasRole(['Kosma', 'Developer']))
|
||||
->action(function (array $arguments, $livewire, ?ClassSession $record) {
|
||||
$session = $record ?? ClassSession::find($arguments['record'] ?? null);
|
||||
|
||||
if (! $session) {
|
||||
return;
|
||||
}
|
||||
|
||||
$session->update([
|
||||
'is_sent_to_lecturer' => ! $session->is_sent_to_lecturer,
|
||||
]);
|
||||
|
||||
SystemNotification::send($session->is_sent_to_lecturer ? 'session_sent' : 'session_unlocked')
|
||||
->send();
|
||||
|
||||
if (isset($livewire->sessions)) {
|
||||
unset($livewire->sessions);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\DeleteSessionAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\EditSessionAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\GenerateSessionsAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\MarkAsSentAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\ShareAttendanceAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\ViewAssignmentsAction;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Actions\ViewAttendanceAction;
|
||||
@ -60,6 +61,7 @@ public function sessions(): Collection
|
||||
'attendance_percentage' => $totalActiveStudents > 0 ? round(($session->attendances_count / $totalActiveStudents) * 100) : 0,
|
||||
'materials_count' => $session->materials_count,
|
||||
'assignments_count' => $session->assignments_count,
|
||||
'is_sent_to_lecturer' => $session->is_sent_to_lecturer,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -114,6 +116,11 @@ public function editSessionAction(): EditSessionAction
|
||||
return EditSessionAction::make();
|
||||
}
|
||||
|
||||
public function markAsSentAction(): MarkAsSentAction
|
||||
{
|
||||
return MarkAsSentAction::make();
|
||||
}
|
||||
|
||||
public function deleteSessionAction(): DeleteSessionAction
|
||||
{
|
||||
return DeleteSessionAction::make();
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Computed;
|
||||
|
||||
@ -85,13 +86,21 @@ public function todaySessions(): Collection
|
||||
'attendance_percentage' => $percentage,
|
||||
'materials_count' => $session->materials_count,
|
||||
'assignments_count' => $session->assignments_count,
|
||||
'is_sent_to_lecturer' => $session->is_sent_to_lecturer,
|
||||
'url' => ClassSessionResource::getUrl('course', ['course' => $session->course]),
|
||||
|
||||
// Pre-calculated classes
|
||||
'status_label' => $session->is_sent_to_lecturer ? 'Terkirim' : $session->time_range,
|
||||
'status_icon' => $session->is_sent_to_lecturer ? 'heroicon-s-paper-airplane' : 'heroicon-s-clock',
|
||||
// Pre-calculated classes
|
||||
'card_classes' => 'group flex w-full rounded-xl border border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm overflow-hidden relative transition-all hover:shadow-md',
|
||||
'session_badge_classes' => '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',
|
||||
'title_classes' => 'font-bold transition-colors text-gray-900 dark:text-white group-hover:text-primary-600',
|
||||
'status_badge_classes' => 'flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-bold shadow-sm bg-primary-500 text-white animate-pulse',
|
||||
'status_badge_classes' => Arr::toCssClasses([
|
||||
'flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-bold shadow-sm text-white',
|
||||
'bg-primary-500 animate-pulse' => ! $session->is_sent_to_lecturer,
|
||||
'bg-danger-500' => $session->is_sent_to_lecturer,
|
||||
]),
|
||||
'attendance_section_classes' => 'flex flex-col items-center justify-center border-l border-primary-200 dark:border-primary-800 p-2 bg-white/30 dark:bg-black/10 transition-colors group-hover:bg-primary-500/5',
|
||||
];
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@ protected function casts(): array
|
||||
'end_time' => 'datetime',
|
||||
'session_number' => 'integer',
|
||||
'start_time' => 'datetime',
|
||||
'is_sent_to_lecturer' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumn('class_sessions', 'is_sent_to_lecturer')) {
|
||||
Schema::table('class_sessions', function (Blueprint $table) {
|
||||
$table->boolean('is_sent_to_lecturer')->default(false)->after('end_time');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('class_sessions', function (Blueprint $table) {
|
||||
$table->dropColumn('is_sent_to_lecturer');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -157,6 +157,18 @@
|
||||
'title' => 'Tugas Terkunci! 🔒',
|
||||
'body' => 'Mohon maaf, tugas ini sudah ditandai terkirim ke dosen oleh Kosma dan tidak dapat diubah lagi. 🚫',
|
||||
],
|
||||
'session_sent' => [
|
||||
'title' => 'Sesi Terkirim! 🚀✨',
|
||||
'body' => 'Laporan presensi untuk sesi ini telah resmi ditandai terkirim ke dosen. Data aman tersimpan! ✅',
|
||||
],
|
||||
'session_unlocked' => [
|
||||
'title' => 'Kunci Presensi Dibuka! 🔓✨',
|
||||
'body' => 'Mahasiswa sekarang bisa melakukan presensi kembali untuk sesi ini. 📝',
|
||||
],
|
||||
'attendance_closed' => [
|
||||
'title' => 'Yah, Presensi Sudah Tutup! 😭⌛',
|
||||
'body' => 'Maaf ya, sesi presensi ini sudah ditutup oleh Kosma. Lain kali jangan sampai terlambat ya! 💪',
|
||||
],
|
||||
|
||||
// UI Labels & Descriptions
|
||||
'labels' => [
|
||||
@ -461,6 +473,18 @@
|
||||
'title' => 'Akses Pengumpulan Ditutup',
|
||||
'body' => 'Tugas ini telah dalam status final (terkirim ke dosen). Perubahan data tidak lagi dimungkinkan.',
|
||||
],
|
||||
'session_sent' => [
|
||||
'title' => 'Konfirmasi Pengiriman Laporan',
|
||||
'body' => 'Laporan presensi sesi perkuliahan telah berhasil ditandai sebagai terkirim ke dosen pengampu.',
|
||||
],
|
||||
'session_unlocked' => [
|
||||
'title' => 'Kunci Akses Presensi Dibuka',
|
||||
'body' => 'Otoritas presensi telah diaktifkan kembali. Seluruh mahasiswa terkait dapat melakukan pengisian data kehadiran.',
|
||||
],
|
||||
'attendance_closed' => [
|
||||
'title' => 'Akses Presensi Ditutup',
|
||||
'body' => 'Sesi presensi untuk mata kuliah ini telah ditutup secara resmi. Anda tidak dapat melakukan pengisian data kehadiran lagi.',
|
||||
],
|
||||
|
||||
// UI Labels & Descriptions
|
||||
'labels' => [
|
||||
|
||||
@ -89,6 +89,13 @@ class="text-[10px] font-bold text-primary-700 dark:text-primary-200 uppercase tr
|
||||
Selesai
|
||||
</span>
|
||||
</div>
|
||||
@elseif ($card->is_sent_to_lecturer)
|
||||
<div class="px-2 py-1 flex items-center gap-1.5">
|
||||
<x-heroicon-m-paper-airplane class="w-3 h-3 text-danger-500" />
|
||||
<span
|
||||
class="text-[10px] font-bold text-danger-500 uppercase tracking-widest leading-none italic">Sesi
|
||||
Terkirim</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="px-2 py-1">
|
||||
<span
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
|
||||
<div class="{{ $session->status_badge_classes }} shrink-0 self-start">
|
||||
<x-heroicon-s-clock class="w-3.5 h-3.5" />
|
||||
{{ $session->time_range }}
|
||||
<x-filament::icon :icon="$session->status_icon" class="w-3.5 h-3.5" />
|
||||
{{ $session->status_label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -40,6 +40,12 @@ class="text-[10px] opacity-60">/{{ $session->total_students }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }}%</span>
|
||||
|
||||
@if ($session->is_sent_to_lecturer)
|
||||
<span class="inline-flex items-center rounded-md bg-danger-50 dark:bg-danger-500/10 px-1.5 py-0.5 text-[10px] font-bold text-danger-700 dark:text-danger-400 ring-1 ring-inset ring-danger-600/20">
|
||||
Terkirim
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -60,6 +66,7 @@ class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }
|
||||
class="flex flex-wrap items-center gap-3 px-4 py-3 border-t border-gray-100 dark:border-gray-700">
|
||||
{{ ($this->viewAttendanceAction)(['session' => $session->id]) }}
|
||||
{{ ($this->shareAttendanceAction)(['session' => $session->id]) }}
|
||||
{{ ($this->markAsSentAction)(['record' => $session->id]) }}
|
||||
{{ ($this->editSessionAction)(['session' => $session->id]) }}
|
||||
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
||||
</div>
|
||||
|
||||
@ -433,4 +433,3 @@
|
||||
->assertDontSeeHtml('type="file"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user