diff --git a/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAssignmentsAction.php b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAssignmentsAction.php new file mode 100644 index 0000000..302622c --- /dev/null +++ b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAssignmentsAction.php @@ -0,0 +1,72 @@ +label('Tugas') + ->color(Color::Sky) + ->icon('heroicon-o-clipboard-document-list') + ->outlined() + ->modalHeading('Tugas Sesi') + ->modalSubmitAction(false) + ->modalCancelActionLabel('Tutup') + ->modalWidth(Width::FourExtraLarge) + ->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.assignment-modal', (function () use ($arguments) { + $session = ClassSession::with([ + 'assignments.assignmentSubmissions', + ])->find($arguments['session'] ?? null); + + if (! $session) { + return ['assignment' => null]; + } + + $assignment = $session->assignments->first(); + + if (! $assignment) { + return ['assignment' => null]; + } + + $activeStudents = Student::query() + ->whereHas('user', fn ($q) => $q->active()) + ->orderBy('full_name') + ->get(); + + $submissionMap = $assignment->assignmentSubmissions->keyBy('student_id'); + + $data = (object) [ + 'id' => $assignment->id, + 'title' => $assignment->title, + 'type_label' => $assignment->type?->getLabel() ?? 'Tugas', + 'due_date_formatted' => $assignment->due_date?->translatedFormat('d F Y H:i') ?? '-', + 'submissions' => $activeStudents->map(fn ($student) => (object) [ + 'student_name' => $student->full_name, + 'student_number' => $student->student_number, + 'submitted_at_formatted' => $submissionMap->get($student->id)?->submitted_at?->translatedFormat('d F Y H:i'), + 'is_submitted' => $submissionMap->has($student->id), + ]), + 'submission_count' => $submissionMap->count(), + 'total_students' => $activeStudents->count(), + 'url' => AssignmentResource::getUrl('submit', ['record' => $assignment->id]), + ]; + + return ['assignment' => $data]; + })())); + } +} diff --git a/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAttendanceAction.php b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAttendanceAction.php new file mode 100644 index 0000000..6a33013 --- /dev/null +++ b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewAttendanceAction.php @@ -0,0 +1,57 @@ +label('Presensi') + ->color('success') + ->icon('heroicon-o-check-circle') + ->link() + ->modalHeading('Daftar Presensi') + ->modalSubmitAction(false) + ->modalCancelActionLabel('Tutup') + ->modalWidth(Width::TwoExtraLarge) + ->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', (function () use ($arguments) { + $session = ClassSession::with([ + 'attendances.student', + ])->find($arguments['session'] ?? null); + + if (! $session) { + return ['students' => collect(), 'attendedCount' => 0]; + } + + $activeStudents = Student::query() + ->whereHas('user', fn ($q) => $q->active()) + ->orderBy('full_name') + ->get(); + + $attendanceMap = $session->attendances->keyBy('student_id'); + + $students = $activeStudents->map(fn ($student) => (object) [ + 'student' => $student, + 'attended_at' => $attendanceMap->get($student->id)?->attended_at?->translatedFormat('d F Y H:i'), + 'has_attended' => $attendanceMap->has($student->id), + ]); + + return [ + 'students' => $students, + 'attendedCount' => $attendanceMap->count(), + ]; + })())); + } +} diff --git a/app/Filament/Resources/Learning/ClassSessions/Actions/ViewMaterialsAction.php b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewMaterialsAction.php new file mode 100644 index 0000000..3733556 --- /dev/null +++ b/app/Filament/Resources/Learning/ClassSessions/Actions/ViewMaterialsAction.php @@ -0,0 +1,35 @@ +label('Materi') + ->color(Color::Purple) + ->icon('heroicon-o-book-open') + ->outlined() + ->modalHeading('Materi Sesi') + ->modalSubmitAction(false) + ->modalCancelActionLabel('Tutup') + ->modalWidth(Width::FourExtraLarge) + ->modalContent(function (array $arguments) { + $material = ClassSession::find($arguments['session'] ?? null)?->materials()->first(); + + return view('filament.resources.learning.class-sessions.material-modal', ['record' => $material]); + }); + } +} diff --git a/app/Filament/Resources/Learning/ClassSessions/ClassSessionResource.php b/app/Filament/Resources/Learning/ClassSessions/ClassSessionResource.php index 75bf799..05f6880 100644 --- a/app/Filament/Resources/Learning/ClassSessions/ClassSessionResource.php +++ b/app/Filament/Resources/Learning/ClassSessions/ClassSessionResource.php @@ -29,7 +29,7 @@ public static function getPages(): array { return [ 'index' => ManageClassSessions::route('/'), - 'course' => ListCourseSessions::route('/course/{courseId}'), + 'course' => ListCourseSessions::route('/course/{course}'), ]; } diff --git a/app/Filament/Resources/Learning/ClassSessions/Pages/ListCourseSessions.php b/app/Filament/Resources/Learning/ClassSessions/Pages/ListCourseSessions.php index 2761bb7..3fcc534 100644 --- a/app/Filament/Resources/Learning/ClassSessions/Pages/ListCourseSessions.php +++ b/app/Filament/Resources/Learning/ClassSessions/Pages/ListCourseSessions.php @@ -7,23 +7,20 @@ use App\Filament\Resources\Learning\ClassSessions\Actions\EditSessionAction; use App\Filament\Resources\Learning\ClassSessions\Actions\GenerateSessionsAction; use App\Filament\Resources\Learning\ClassSessions\Actions\ShareAttendanceAction; +use App\Filament\Resources\Learning\ClassSessions\Actions\ViewAssignmentsAction; +use App\Filament\Resources\Learning\ClassSessions\Actions\ViewAttendanceAction; +use App\Filament\Resources\Learning\ClassSessions\Actions\ViewMaterialsAction; use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource; -use App\Models\ClassSession; +use App\Filament\Resources\Learning\ClassSessions\Schemas\SessionForm; +use App\Filament\Support\SystemNotification; use App\Models\Course; -use App\Models\Material; use App\Models\Student; -use Filament\Actions\Action; use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Contracts\HasActions; -use Filament\Forms\Components\DatePicker; -use Filament\Forms\Components\TextInput; -use Filament\Forms\Components\TimePicker; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Resources\Pages\Page; -use Filament\Schemas\Components\Grid; use Filament\Schemas\Schema; -use Filament\Support\Enums\Width; use Illuminate\Support\Collection; use Livewire\Attributes\Computed; @@ -35,17 +32,11 @@ class ListCourseSessions extends Page implements HasActions, HasForms protected string $view = 'filament.resources.learning.class-sessions.show'; - public $courseId; + public Course $course; - public function mount($courseId): void + public function mount(Course $course): void { - $this->courseId = $courseId; - } - - #[Computed] - public function course(): Course - { - return Course::findOrFail($this->courseId); + $this->course = $course; } #[Computed] @@ -74,44 +65,7 @@ public function sessions(): Collection public function form(Schema $schema): Schema { - return $schema - ->schema([ - Grid::make(['default' => 2]) - ->schema([ - TextInput::make('session_number') - ->label('Pertemuan Ke-') - ->placeholder('1') - ->numeric() - ->required() - ->minValue(1) - ->maxValue(16) - ->autocomplete(false), - - DatePicker::make('date') - ->label('Tanggal') - ->placeholder('Pilih Tanggal') - ->required() - ->native(false) - ->displayFormat('l, d F Y') - ->default(now()->toDateString()), - - TimePicker::make('start_time') - ->label('Waktu Mulai') - ->placeholder('08:00') - ->native(false) - ->displayFormat('H:i') - ->seconds(false) - ->required(), - - TimePicker::make('end_time') - ->label('Waktu Selesai') - ->placeholder('10:00') - ->native(false) - ->displayFormat('H:i') - ->seconds(false) - ->required(), - ]), - ]); + return SessionForm::configure($schema); } #[Computed] @@ -135,114 +89,51 @@ protected function getHeaderActions(): array ]; } - protected function getActions(): array + public function viewAttendanceAction(): ViewAttendanceAction { - return [ - $this->shareAttendanceAction(), - $this->viewAttendanceAction(), - $this->viewMaterialsAction(), - $this->viewAssignmentsAction(), - $this->editSessionAction(), - $this->deleteSessionAction(), - $this->viewMaterialDetailAction(), - ]; + return ViewAttendanceAction::make(); } - public function shareAttendanceAction(): Action + public function viewMaterialsAction(): ViewMaterialsAction + { + return ViewMaterialsAction::make(); + } + + public function viewAssignmentsAction(): ViewAssignmentsAction + { + return ViewAssignmentsAction::make(); + } + + public function shareAttendanceAction(): ShareAttendanceAction { return ShareAttendanceAction::make(); } - public function viewAttendanceAction(): Action - { - return Action::make('viewAttendance') - ->label('Presensi') - ->color('success') - ->icon('heroicon-o-check-circle') - ->link() - ->modalHeading('Daftar Presensi') - ->modalSubmitAction(false) - ->modalCancelActionLabel('Tutup') - ->modalWidth(Width::TwoExtraLarge) - ->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [ - 'attendances' => ClassSession::find($arguments['session'] ?? null, ['*'])?->attendances()->with('student')->latest('attended_at')->get() ?? collect(), - ])); - } - - public function viewMaterialsAction(): Action - { - return Action::make('viewMaterials') - ->label('Materi') - ->color('purple') - ->icon('heroicon-o-book-open') - ->link() - ->modalHeading('Materi Sesi') - ->modalSubmitAction(false) - ->modalCancelActionLabel('Tutup') - ->modalWidth(Width::TwoExtraLarge) - ->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.materials-modal', [ - 'materials' => ClassSession::find($arguments['session'] ?? null, ['*'])?->materials() - ->latest() - ->get() - ->map(fn ($m) => (object) [ - 'id' => $m->id, - 'title' => $m->title, - 'created_at_formatted' => $m->created_at?->translatedFormat('d F Y') ?? '-', - ]) ?? collect(), - ])); - } - - public function viewAssignmentsAction(): Action - { - return Action::make('viewAssignments') - ->label('Tugas') - ->color('sky') - ->icon('heroicon-o-clipboard-document-list') - ->link() - ->modalHeading('Tugas Sesi') - ->modalSubmitAction(false) - ->modalCancelActionLabel('Tutup') - ->modalWidth(Width::TwoExtraLarge) - ->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.assignments-modal', [ - 'assignments' => ClassSession::find($arguments['session'] ?? null, ['*'])?->assignments() - ->with(['assignmentSubmissions.student']) - ->latest() - ->get() - ->map(fn ($a) => (object) [ - 'id' => $a->id, - 'title' => $a->title, - 'type_label' => $a->type?->getLabel() ?? 'Tugas', - 'due_date_formatted' => $a->due_date?->translatedFormat('d F Y H:i') ?? '-', - 'submissions' => $a->assignmentSubmissions->map(fn ($s) => (object) [ - 'student_name' => $s->student->full_name, - 'student_number' => $s->student->student_number, - 'submitted_at_formatted' => $s->submitted_at?->translatedFormat('d F Y H:i') ?? '-', - 'id' => $s->id, - ]), - ]) ?? collect(), - ])); - } - - public function editSessionAction(): Action + public function editSessionAction(): EditSessionAction { return EditSessionAction::make(); } - public function deleteSessionAction(): Action + public function deleteSessionAction(): DeleteSessionAction { return DeleteSessionAction::make(); } - public function viewMaterialDetailAction(): Action + #[Computed] + public function emptyStateHeading(): string { - return Action::make('viewMaterialDetail') - ->record(fn (array $arguments) => Material::find($arguments['record'] ?? null, ['*'])) - ->modalHeading('Detail Materi') - ->modalSubmitAction(false) - ->modalCancelActionLabel('Tutup') - ->modalWidth(Width::FourExtraLarge) - ->modalContent(fn (?Material $record) => $record ? view('filament.resources.learning.class-sessions.material-detail-modal', [ - 'record' => $record, - ]) : null); + return SystemNotification::getByKey('labels.empty_course_sessions.title'); + } + + #[Computed] + public function emptyStateDescription(): string + { + return SystemNotification::getByKey('labels.empty_course_sessions.description'); + } + + #[Computed] + public function emptyStateIcon(): string + { + return SystemNotification::getByKey('icons.empty_course_sessions'); } } diff --git a/app/Filament/Resources/Learning/ClassSessions/Pages/ManageClassSessions.php b/app/Filament/Resources/Learning/ClassSessions/Pages/ManageClassSessions.php index 1d6087a..bcef960 100644 --- a/app/Filament/Resources/Learning/ClassSessions/Pages/ManageClassSessions.php +++ b/app/Filament/Resources/Learning/ClassSessions/Pages/ManageClassSessions.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Learning\ClassSessions\Pages; use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource; +use App\Filament\Support\SystemNotification; use App\Models\ClassSession; use App\Models\Course; use App\Models\Student; @@ -84,7 +85,7 @@ public function todaySessions(): Collection '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', ['course' => $session->course]), // 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', @@ -125,8 +126,80 @@ public function courses(): Collection 'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan', 'sessions_count' => $course->classSessions->count(), 'total_students' => $totalActiveStudents, - 'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]), + 'url' => ClassSessionResource::getUrl('course', ['course' => $course]), ]; }); } + + #[Computed] + public function sessionsHeading(): string + { + return SystemNotification::getByKey('labels.today_sessions.title'); + } + + #[Computed] + public function sessionsDescription(): string + { + return SystemNotification::getByKey('labels.today_sessions.description', ['date' => $this->todayDate]); + } + + #[Computed] + public function coursesHeading(): string + { + return SystemNotification::getByKey('labels.semester_courses.title'); + } + + #[Computed] + public function coursesDescription(): string + { + return SystemNotification::getByKey('labels.semester_courses.description'); + } + + #[Computed] + public function sessionsIcon(): string + { + return SystemNotification::getByKey('icons.today_sessions'); + } + + #[Computed] + public function coursesIcon(): string + { + return SystemNotification::getByKey('icons.semester_courses'); + } + + #[Computed] + public function sessionsEmptyHeading(): string + { + return SystemNotification::getByKey('labels.empty_today_sessions.title'); + } + + #[Computed] + public function sessionsEmptyDescription(): string + { + return SystemNotification::getByKey('labels.empty_today_sessions.description'); + } + + #[Computed] + public function sessionsEmptyIcon(): string + { + return SystemNotification::getByKey('icons.empty_today_sessions'); + } + + #[Computed] + public function coursesEmptyHeading(): string + { + return SystemNotification::getByKey('labels.empty_semester_courses.title'); + } + + #[Computed] + public function coursesEmptyDescription(): string + { + return SystemNotification::getByKey('labels.empty_semester_courses.description'); + } + + #[Computed] + public function coursesEmptyIcon(): string + { + return SystemNotification::getByKey('icons.empty_semester_courses'); + } } diff --git a/app/Filament/Resources/Learning/ClassSessions/Schemas/SessionForm.php b/app/Filament/Resources/Learning/ClassSessions/Schemas/SessionForm.php new file mode 100644 index 0000000..1d97916 --- /dev/null +++ b/app/Filament/Resources/Learning/ClassSessions/Schemas/SessionForm.php @@ -0,0 +1,54 @@ +schema([ + Grid::make(['default' => 2]) + ->schema([ + TextInput::make('session_number') + ->label('Pertemuan Ke-') + ->placeholder('1') + ->numeric() + ->required() + ->minValue(1) + ->maxValue(16) + ->autocomplete(false), + + DatePicker::make('date') + ->label('Tanggal') + ->placeholder('Pilih Tanggal') + ->required() + ->native(false) + ->displayFormat('l, d F Y') + ->default(now()->toDateString()), + + TimePicker::make('start_time') + ->label('Waktu Mulai') + ->placeholder('08:00') + ->native(false) + ->displayFormat('H:i') + ->seconds(false) + ->required(), + + TimePicker::make('end_time') + ->label('Waktu Selesai') + ->placeholder('10:00') + ->native(false) + ->displayFormat('H:i') + ->seconds(false) + ->required(), + ]), + ]); + } +} diff --git a/lang/id/notif.php b/lang/id/notif.php index 6e874bb..d9a8de3 100644 --- a/lang/id/notif.php +++ b/lang/id/notif.php @@ -201,6 +201,26 @@ 'title' => 'Belum Ada Catatan Rilis 📭', 'description' => 'Belum ada update yang tercatat nih. Buat mulai catat riwayat pembaruan pertama! 🚀', ], + 'today_sessions' => [ + 'title' => 'Sesi Kelas Hari Ini ✨🚀', + 'description' => 'Daftar sesi seru yang udah dijadwalkan buat hari ini (:date). Semangat belajar! 💪', + ], + 'semester_courses' => [ + 'title' => 'Daftar Mata Kuliah Semester Ini 📚🎓', + 'description' => 'Pilih aja mata kuliahnya buat ngecek materi sama tugas-tugas seru lainnya! 🧐✨', + ], + 'empty_course_sessions' => [ + 'title' => 'Duh, Belum Ada Sesi Kelas! 🏢🛌', + 'description' => 'Belum ada sesi perkuliahan yang dibuat buat mata kuliah ini. Waktunya istirahat mungkin? 😴✨', + ], + 'empty_today_sessions' => [ + 'title' => 'Yah, Gak Ada Kelas Hari Ini! 😴🏖️', + 'description' => 'Hari ini kosong melompong. Kamu bisa rebahan sebentar atau kejar materi lain! ✨', + ], + 'empty_semester_courses' => [ + 'title' => 'Belum Ada Mata Kuliah Nih! 📚😴', + 'description' => 'Semester ini kayanya masih kosong. Coba hubungi admin kalau ada yang salah ya! ✨', + ], ], // Icons @@ -212,6 +232,11 @@ 'user_account' => 'heroicon-o-face-smile', 'account_security' => 'heroicon-o-shield-check', 'appearance_settings' => 'heroicon-o-swatch', + 'today_sessions' => 'heroicon-o-sparkles', + 'semester_courses' => 'heroicon-o-book-open', + 'empty_course_sessions' => 'heroicon-o-presentation-chart-bar', + 'empty_today_sessions' => 'heroicon-o-calendar-days', + 'empty_semester_courses' => 'heroicon-o-academic-cap', ], ], 'formal' => [ @@ -414,6 +439,26 @@ 'title' => 'Tidak Ada Riwayat Pembaruan', 'description' => 'Belum ada catatan rilis yang tersedia. Buat untuk mendokumentasikan pembaruan aplikasi.', ], + 'today_sessions' => [ + 'title' => 'Sesi Hari Ini', + 'description' => 'Sesi yang dijadwalkan pada :date', + ], + 'semester_courses' => [ + 'title' => 'Daftar Mata Kuliah Semester Ini', + 'description' => 'Pilih mata kuliah untuk melihat dan mengelola semua riwayat sesi.', + ], + 'empty_course_sessions' => [ + 'title' => 'Tidak ada data yang ditemukan', + 'description' => 'Belum ada sesi perkuliahan yang dibuat untuk mata kuliah ini.', + ], + 'empty_today_sessions' => [ + 'title' => 'Tidak ada data yang ditemukan', + 'description' => 'Tidak ada sesi perkuliahan yang dijadwalkan untuk hari ini.', + ], + 'empty_semester_courses' => [ + 'title' => 'Mata Kuliah Belum Terdaftar', + 'description' => 'Belum ada mata kuliah yang terdaftar untuk semester aktif ini.', + ], ], // Icons @@ -425,6 +470,11 @@ 'user_account' => 'heroicon-o-user', 'account_security' => 'heroicon-o-lock-closed', 'appearance_settings' => 'heroicon-o-paint-brush', + 'today_sessions' => 'heroicon-o-bolt', + 'semester_courses' => 'heroicon-o-academic-cap', + 'empty_course_sessions' => 'heroicon-o-presentation-chart-bar', + 'empty_today_sessions' => 'heroicon-o-calendar-days', + 'empty_semester_courses' => 'heroicon-o-academic-cap', ], ], ]; diff --git a/resources/views/filament/resources/learning/class-sessions/assignment-modal.blade.php b/resources/views/filament/resources/learning/class-sessions/assignment-modal.blade.php new file mode 100644 index 0000000..01c0d10 --- /dev/null +++ b/resources/views/filament/resources/learning/class-sessions/assignment-modal.blade.php @@ -0,0 +1,86 @@ +
| Mahasiswa | +Status | +Waktu Unggah | +
|---|---|---|
|
+
+ {{ $submission->student_name }}
+
+ NIM: {{ $submission->student_number }}
+
+
+ |
+ + @if ($submission->is_submitted) + + Sudah Unggah + + @else + + Belum Unggah + + @endif + | ++ @if ($submission->is_submitted) + + {{ $submission->submitted_at_formatted }} + + @else + + - + + @endif + | +
+ Total Mahasiswa: {{ $assignment->total_students }} + | + Sudah Mengumpulkan: {{ $assignment->submission_count }} +
+| Mahasiswa | -Waktu Unggah | -
|---|---|
|
-
- {{ $submission->student_name }}
-
- NIM: {{ $submission->student_number }}
-
-
- |
- - - {{ $submission->submitted_at_formatted }} - - | -
- Total Pengumpulan: {{ $assignment->submissions->count() }} - Mahasiswa -
-Belum ada pengumpulan untuk tugas ini.
-| Mahasiswa | +Status | Waktu Presensi | |
|---|---|---|---|
|
- {{ $attendance->student->full_name }}
+ {{ $item->student->full_name }}
NIM:
- {{ $attendance->student->student_number }}
+ {{ $item->student->student_number }}
|
- - - {{ $attendance->attended_at?->format('H:i') }} - + | + @if ($item->has_attended) + + Hadir + + @else + + Belum Hadir + + @endif + | ++ @if ($item->has_attended) + + {{ $item->attended_at }} + + @else + + - + + @endif |