feat: implement new actions for viewing assignments, attendance, and materials in class sessions, and update routing for course sessions
This commit is contained in:
parent
37f2c78852
commit
0519e1cd35
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
|
||||
|
||||
use App\Filament\Resources\Learning\Assignments\AssignmentResource;
|
||||
use App\Models\ClassSession;
|
||||
use App\Models\Student;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class ViewAssignmentsAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'viewAssignments';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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];
|
||||
})()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
|
||||
|
||||
use App\Models\ClassSession;
|
||||
use App\Models\Student;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class ViewAttendanceAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'viewAttendance';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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(),
|
||||
];
|
||||
})()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
|
||||
|
||||
use App\Models\ClassSession;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class ViewMaterialsAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'viewMaterials';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -29,7 +29,7 @@ public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageClassSessions::route('/'),
|
||||
'course' => ListCourseSessions::route('/course/{courseId}'),
|
||||
'course' => ListCourseSessions::route('/course/{course}'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\ClassSessions\Schemas;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\TimePicker;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class SessionForm
|
||||
{
|
||||
public static function configure(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(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
<div class="space-y-4 py-2">
|
||||
<a href="{{ $assignment->url }}"
|
||||
class="flex items-center justify-between p-3 rounded-lg border border-gray-100 dark:border-white/5 bg-gray-50/50 dark:bg-white/5 shadow-sm hover:bg-white dark:hover:bg-white/10 transition-colors group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-primary-100 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400">
|
||||
<x-heroicon-o-clipboard-document-list class="w-5 h-5" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="font-bold text-gray-900 dark:text-white leading-tight uppercase font-mono text-[11px] mb-0.5 tracking-tight text-primary-600/60 dark:text-primary-400/50 block">
|
||||
{{ $assignment->type_label }}
|
||||
</span>
|
||||
<span class="font-bold text-gray-900 dark:text-white leading-tight">{{ $assignment->title }}</span>
|
||||
<div class="flex items-center gap-1.5 mt-0.5">
|
||||
<x-heroicon-o-clock class="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" />
|
||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-medium uppercase tracking-tight">
|
||||
Deadline: {{ $assignment->due_date_formatted }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<x-heroicon-o-chevron-right
|
||||
class="w-5 h-5 text-gray-400 group-hover:text-primary-500 transition-colors shrink-0" />
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="fi-ta-content overflow-x-auto overflow-y-auto max-h-150 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 ring-1 ring-gray-950/5 dark:ring-white/10">
|
||||
<table class="w-full text-sm text-left divide-y divide-gray-200 dark:divide-white/5">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="px-4 py-2.5 font-bold text-gray-900 dark:text-white">Mahasiswa</th>
|
||||
<th class="px-4 py-2.5 font-bold text-gray-900 dark:text-white text-center">Status</th>
|
||||
<th class="px-4 py-2.5 font-bold text-gray-900 dark:text-white text-right">Waktu Unggah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-white/5">
|
||||
@foreach ($assignment->submissions as $submission)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-white/5 transition-colors">
|
||||
<td class="px-4 py-2.5 text-gray-950 dark:text-white font-medium">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{{ $submission->student_name }}</span>
|
||||
<span
|
||||
class="text-[10px] text-gray-500 dark:text-gray-400 font-normal uppercase tabular-nums">
|
||||
NIM: {{ $submission->student_number }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-center">
|
||||
@if ($submission->is_submitted)
|
||||
<span
|
||||
class="inline-flex items-center rounded-md bg-success-50 dark:bg-success-400/10 px-2 py-0.5 text-xs font-bold text-success-700 dark:text-success-400 ring-1 ring-inset ring-success-600/20">
|
||||
Sudah Unggah
|
||||
</span>
|
||||
@else
|
||||
<span
|
||||
class="inline-flex items-center rounded-md bg-gray-50 dark:bg-gray-400/10 px-2 py-0.5 text-xs font-bold text-gray-600 dark:text-gray-400 ring-1 ring-inset ring-gray-500/20">
|
||||
Belum Unggah
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-right tabular-nums">
|
||||
@if ($submission->is_submitted)
|
||||
<span class="text-gray-600 dark:text-gray-400">
|
||||
{{ $submission->submitted_at_formatted }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400 dark:text-gray-600 italic">
|
||||
-
|
||||
</span>
|
||||
@endif
|
||||
</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 Mahasiswa: <span
|
||||
class="text-primary-600 dark:text-primary-400 font-bold font-mono">{{ $assignment->total_students }}</span>
|
||||
<span class="mx-2 text-gray-300">|</span>
|
||||
Sudah Mengumpulkan: <span
|
||||
class="text-success-600 dark:text-success-400 font-bold font-mono">{{ $assignment->submission_count }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,77 +0,0 @@
|
||||
<div class="space-y-6 py-2">
|
||||
@forelse($assignments as $assignment)
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
class="flex items-center justify-between p-3 rounded-lg border border-gray-100 dark:border-white/5 bg-gray-50/50 dark:bg-white/5 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-primary-100 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400">
|
||||
<x-heroicon-o-clipboard-document-list class="w-5 h-5" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="font-bold text-gray-900 dark:text-white leading-tight uppercase font-mono text-[11px] mb-0.5 tracking-tight text-primary-600/60 dark:text-primary-400/50 block">
|
||||
{{ $assignment->type_label }}
|
||||
</span>
|
||||
<span class="font-bold text-gray-900 dark:text-white leading-tight">{{ $assignment->title }}</span>
|
||||
<div class="flex items-center gap-1.5 mt-0.5">
|
||||
<x-heroicon-o-clock class="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" />
|
||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-medium uppercase tracking-tight">
|
||||
Deadline: {{ $assignment->due_date_formatted }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($assignment->submissions->count() > 0)
|
||||
<div
|
||||
class="fi-ta-content overflow-x-auto overflow-y-auto max-h-60 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 ring-1 ring-gray-950/5 dark:ring-white/10">
|
||||
<table class="w-full text-sm text-left divide-y divide-gray-200 dark:divide-white/5">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="px-4 py-2.5 font-bold text-gray-900 dark:text-white">Mahasiswa</th>
|
||||
<th class="px-4 py-2.5 font-bold text-gray-900 dark:text-white text-right">Waktu Unggah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-white/5">
|
||||
@foreach ($assignment->submissions as $submission)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-white/5 transition-colors">
|
||||
<td class="px-4 py-2.5 text-gray-950 dark:text-white font-medium">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{{ $submission->student_name }}</span>
|
||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-normal uppercase tabular-nums">
|
||||
NIM: {{ $submission->student_number }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-right">
|
||||
<span
|
||||
class="inline-flex items-center rounded-md bg-success-50 dark:bg-success-400/10 px-2 py-0.5 text-xs font-bold text-success-700 dark:text-success-400 ring-1 ring-inset ring-success-600/20 tabular-nums">
|
||||
{{ $submission->submitted_at_formatted }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<p class="text-[10px] text-gray-500 font-medium uppercase tracking-wider">
|
||||
Total Pengumpulan: <span class="text-primary-600 dark:text-primary-400 font-bold font-mono">{{ $assignment->submissions->count() }}
|
||||
Mahasiswa</span>
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col items-center justify-center p-6 border border-dashed rounded-xl border-gray-200 dark:border-white/10 bg-gray-50/50 dark:bg-white/5">
|
||||
<x-heroicon-o-document-minus class="w-8 h-8 text-gray-400 dark:text-gray-600 mb-2" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 font-medium">Belum ada pengumpulan untuk tugas ini.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<x-filament::empty-state icon="heroicon-o-clipboard-document" heading="Tidak ada data yang ditemukan"
|
||||
description="Belum ada tugas untuk sesi ini." iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<div class="space-y-3">
|
||||
@forelse ($attendances as $attendance)
|
||||
@foreach ($students as $item)
|
||||
@if ($loop->first)
|
||||
<div
|
||||
class="fi-ta-content overflow-x-auto overflow-y-auto max-h-96 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
|
||||
class="fi-ta-content overflow-x-auto overflow-y-auto max-h-150 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 sticky-header-table">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800 sticky top-0 z-10">
|
||||
<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-center">Status</th>
|
||||
<th class="px-4 py-3 font-bold text-gray-900 dark:text-white text-right">Waktu Presensi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -15,16 +16,34 @@ class="fi-ta-content overflow-x-auto overflow-y-auto max-h-96 rounded-xl border
|
||||
<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>{{ $item->student->full_name }}</span>
|
||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-normal uppercase tabular-nums">NIM:
|
||||
{{ $attendance->student->student_number }}</span>
|
||||
{{ $item->student->student_number }}</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') }}
|
||||
</span>
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if ($item->has_attended)
|
||||
<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">
|
||||
Hadir
|
||||
</span>
|
||||
@else
|
||||
<span
|
||||
class="inline-flex items-center rounded-md bg-gray-50 dark:bg-gray-400/10 px-2 py-1 text-xs font-bold text-gray-600 dark:text-gray-400 ring-1 ring-inset ring-gray-500/20">
|
||||
Belum Hadir
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right tabular-nums">
|
||||
@if ($item->has_attended)
|
||||
<span class="text-gray-600 dark:text-gray-400">
|
||||
{{ $item->attended_at }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400 dark:text-gray-600 italic">
|
||||
-
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@if ($loop->last)
|
||||
@ -33,14 +52,13 @@ class="inline-flex items-center rounded-md bg-success-50 dark:bg-success-400/10
|
||||
</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">{{ $loop->count }}
|
||||
Mahasiswa</span>
|
||||
Total Mahasiswa: <span
|
||||
class="text-primary-600 dark:text-primary-400 font-bold font-mono">{{ $loop->count }}</span>
|
||||
<span class="mx-2 text-gray-300">|</span>
|
||||
Sudah Hadir: <span
|
||||
class="text-success-600 dark:text-success-400 font-bold font-mono">{{ $attendedCount }}</span>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
@empty
|
||||
<x-filament::empty-state icon="heroicon-o-user-group" heading="Tidak ada data yang ditemukan"
|
||||
description="Belum ada mahasiswa yang melakukan presensi pada sesi ini." iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
@endforelse
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-sparkles' : 'heroicon-o-bolt' }}"
|
||||
icon="{{ $this->sessionsIcon }}"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ \App\Filament\Support\SystemNotification::getMessage('Sesi Kelas Hari Ini ✨🚀', 'Sesi Hari Ini') }}</x-slot>
|
||||
<x-slot name="description">{{ \App\Filament\Support\SystemNotification::getMessage('Daftar sesi seru yang udah dijadwalkan buat hari ini (' . $this->today_date . '). Semangat belajar! 💪', 'Sesi yang dijadwalkan pada ' . $this->today_date) }}</x-slot>
|
||||
<x-slot name="heading">{{ $this->sessionsHeading }}</x-slot>
|
||||
<x-slot name="description">{{ $this->sessionsDescription }}</x-slot>
|
||||
|
||||
<div class="space-y-4">
|
||||
@forelse ($this->todaySessions as $session)
|
||||
@ -92,9 +92,9 @@
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<x-filament::empty-state icon="heroicon-o-calendar-days"
|
||||
heading="Tidak ada data yang ditemukan"
|
||||
description="Tidak ada sesi perkuliahan yang dijadwalkan untuk hari ini."
|
||||
<x-filament::empty-state icon="{{ $this->sessionsEmptyIcon }}"
|
||||
heading="{{ $this->sessionsEmptyHeading }}"
|
||||
description="{{ $this->sessionsEmptyDescription }}"
|
||||
iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
@endforelse
|
||||
@ -102,11 +102,11 @@
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-book-open' : 'heroicon-o-academic-cap' }}"
|
||||
icon="{{ $this->coursesIcon }}"
|
||||
icon-color="gray"
|
||||
>
|
||||
<x-slot name="heading">{{ \App\Filament\Support\SystemNotification::getMessage('Daftar Mata Kuliah Semester Ini 📚🎓', 'Daftar Mata Kuliah Semester Ini') }}</x-slot>
|
||||
<x-slot name="description">{{ \App\Filament\Support\SystemNotification::getMessage('Pilih aja mata kuliahnya buat ngecek materi sama tugas-tugas seru lainnya! 🧐✨', 'Pilih mata kuliah untuk melihat dan mengelola semua riwayat sesi.') }}</x-slot>
|
||||
<x-slot name="heading">{{ $this->coursesHeading }}</x-slot>
|
||||
<x-slot name="description">{{ $this->coursesDescription }}</x-slot>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
@forelse ($this->courses as $course)
|
||||
@ -137,9 +137,9 @@ class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white d
|
||||
</a>
|
||||
@empty
|
||||
<div class="col-span-full text-center">
|
||||
<x-filament::empty-state icon="heroicon-o-academic-cap"
|
||||
heading="Mata Kuliah Belum Terdaftar"
|
||||
description="Belum ada mata kuliah yang terdaftar untuk semester aktif ini."
|
||||
<x-filament::empty-state icon="{{ $this->coursesEmptyIcon }}"
|
||||
heading="{{ $this->coursesEmptyHeading }}"
|
||||
description="{{ $this->coursesEmptyDescription }}"
|
||||
iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
<div class="space-y-4 py-2">
|
||||
@foreach($record->getMedia('materials') as $item)
|
||||
<div class="space-y-4">
|
||||
@foreach ($record->getMedia('materials') as $item)
|
||||
<div class="rounded-xl border border-gray-100 dark:border-white/5 overflow-hidden shadow-sm">
|
||||
<div class="p-3 bg-gray-50/50 dark:bg-white/5 border-b border-gray-100 dark:border-white/5">
|
||||
<span class="text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-tight">Lampiran PDF</span>
|
||||
</div>
|
||||
<div class="h-[600px] w-full bg-white dark:bg-gray-900 overflow-hidden">
|
||||
<iframe src="{{ $item->getUrl() }}" class="w-full h-full border-0" frameborder="0"></iframe>
|
||||
</div>
|
||||
@ -1,29 +0,0 @@
|
||||
<div class="space-y-4 py-2">
|
||||
@forelse($materials as $material)
|
||||
<div
|
||||
class="flex items-center justify-between p-3 rounded-lg border border-gray-100 dark:border-white/5 bg-gray-50/50 dark:bg-white/5 hover:bg-white dark:hover:bg-white/10 transition-colors shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-100 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400">
|
||||
<x-heroicon-o-document-text class="w-5 h-5" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold text-gray-900 dark:text-white leading-tight">{{ $material->title }}</span>
|
||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 font-medium uppercase mt-0.5">
|
||||
Dibuat: {{ $material->created_at_formatted }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="button" wire:click="mountAction('viewMaterialDetail', { record: {{ $material->id }} })"
|
||||
class="p-1.5 rounded-full hover:bg-gray-200 dark:hover:bg-white/10 text-gray-500 transition-colors"
|
||||
title="Lihat Detail">
|
||||
<x-heroicon-o-chevron-right class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<x-filament::empty-state icon="heroicon-o-document-minus" heading="Tidak ada data yang ditemukan"
|
||||
description="Belum ada materi untuk sesi ini." iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
@endforelse
|
||||
</div>
|
||||
@ -2,67 +2,72 @@
|
||||
<x-filament::section>
|
||||
<div class="space-y-4">
|
||||
@forelse ($this->sessions as $session)
|
||||
<div class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm hover:shadow-md transition flex flex-col">
|
||||
<div
|
||||
class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm hover:shadow-md transition flex flex-col">
|
||||
|
||||
<div class="flex flex-1 items-center gap-4 p-4 sm:p-5 min-w-0">
|
||||
<div class="flex flex-col items-center justify-center w-12 h-12 rounded-lg bg-primary-100 dark:bg-primary-500/10 text-primary-700 dark:text-primary-300 font-bold border border-primary-200 dark:border-primary-500/20 shrink-0">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center w-12 h-12 rounded-lg bg-primary-100 dark:bg-primary-500/10 text-primary-700 dark:text-primary-300 font-bold border border-primary-200 dark:border-primary-500/20 shrink-0">
|
||||
<span class="text-[10px] uppercase leading-none opacity-60 mb-0.5 mt-1">Sesi</span>
|
||||
<span class="text-sm leading-none mb-1 text-primary-600 dark:text-primary-400 font-bold">Ke-{{ $session->session_number }}</span>
|
||||
<span
|
||||
class="text-sm leading-none mb-1 text-primary-600 dark:text-primary-400 font-bold">Ke-{{ $session->session_number }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 flex-wrap items-center justify-between gap-x-4 gap-y-2 min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center gap-1.5 font-bold text-gray-900 dark:text-white">
|
||||
<x-heroicon-o-calendar class="w-4 h-4 text-primary-500 shrink-0" />
|
||||
{{ $session->date_formatted }}
|
||||
<div class="flex flex-1 flex-wrap items-center justify-between gap-x-6 gap-y-2 min-w-0">
|
||||
<div class="flex flex-col gap-1.5 min-w-0">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center gap-1.5 font-bold text-gray-900 dark:text-white">
|
||||
<x-heroicon-o-calendar class="w-4 h-4 text-primary-500 shrink-0" />
|
||||
{{ $session->date_formatted }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 font-medium">
|
||||
<x-heroicon-o-clock class="w-4 h-4 text-primary-500 shrink-0" />
|
||||
{{ $session->time_range }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 font-medium">
|
||||
<x-heroicon-o-clock class="w-4 h-4 text-primary-500 shrink-0" />
|
||||
{{ $session->time_range }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div 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"
|
||||
wire:click="mountAction('viewAttendance', { session: {{ $session->id }} })">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex items-center gap-1.5 text-xs font-bold text-success-600 dark:text-success-400">
|
||||
<x-heroicon-o-user-group class="w-4 h-4 shrink-0" />
|
||||
{{ $session->attendances_count }}<span class="text-[10px] opacity-60 ml-0.5">/{{ $session->total_students }}</span> Presensi
|
||||
{{ $session->attendances_count }}<span
|
||||
class="text-[10px] opacity-60">/{{ $session->total_students }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-2">
|
||||
<div class="w-20 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 class="w-16 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>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5 font-bold text-amber-600 dark:text-amber-400 hover:bg-amber-50 dark:hover:bg-amber-900/10 px-2 py-0.5 rounded-lg transition-colors cursor-pointer"
|
||||
wire:click="mountAction('viewMaterials', { session: {{ $session->id }} })">
|
||||
<x-heroicon-o-document-text class="w-4 h-4 shrink-0" />
|
||||
{{ $session->materials_count }} Materi
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5 font-bold text-primary-600 dark:text-primary-400 hover:bg-primary-50 dark:hover:bg-primary-900/10 px-2 py-0.5 rounded-lg transition-colors cursor-pointer"
|
||||
wire:click="mountAction('viewAssignments', { session: {{ $session->id }} })">
|
||||
<x-heroicon-o-clipboard-document-list class="w-4 h-4 shrink-0" />
|
||||
{{ $session->assignments_count }} Tugas
|
||||
<span
|
||||
class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($session->materials_count || $session->assignments_count)
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
@if ($session->materials_count)
|
||||
{{ ($this->viewMaterialsAction)(['session' => $session->id]) }}
|
||||
@endif
|
||||
@if ($session->assignments_count)
|
||||
{{ ($this->viewAssignmentsAction)(['session' => $session->id]) }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-t border-gray-100 dark:border-gray-700">
|
||||
<div
|
||||
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->editSessionAction)(['session' => $session->id]) }}
|
||||
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<x-filament::empty-state icon="heroicon-o-presentation-chart-bar"
|
||||
heading="{{ \App\Filament\Support\SystemNotification::getMessage('Duh, Belum Ada Sesi Kelas! 🏢🛌', 'Tidak ada data yang ditemukan') }}"
|
||||
description="{{ \App\Filament\Support\SystemNotification::getMessage('Belum ada sesi perkuliahan yang dibuat buat mata kuliah ini. Waktunya istirahat mungkin? 😴✨', 'Belum ada sesi perkuliahan yang dibuat untuk mata kuliah ini.') }}"
|
||||
<x-filament::empty-state icon="{{ $this->emptyStateIcon }}"
|
||||
heading="{{ $this->emptyStateHeading }}"
|
||||
description="{{ $this->emptyStateDescription }}"
|
||||
iconColor="gray">
|
||||
</x-filament::empty-state>
|
||||
@endforelse
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
use App\Enums\RoleEnum;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Pages\ListCourseSessions;
|
||||
use App\Filament\Resources\Learning\ClassSessions\Pages\ManageClassSessions;
|
||||
use App\Filament\Support\SystemNotification;
|
||||
use App\Models\Assignment;
|
||||
use App\Models\AssignmentSubmission;
|
||||
use App\Models\ClassSession;
|
||||
@ -65,15 +66,15 @@
|
||||
|
||||
// Check actions on ListCourseSessions page
|
||||
$this->actingAs($developer);
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->assertActionVisible('generateSessions');
|
||||
|
||||
$this->actingAs($kosma);
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->assertActionVisible('generateSessions');
|
||||
|
||||
$this->actingAs($regularStudent);
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->assertActionHidden('generateSessions');
|
||||
});
|
||||
});
|
||||
@ -116,7 +117,7 @@
|
||||
'end_time' => '10:00',
|
||||
]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->callAction('generateSessions', [
|
||||
'total_sessions' => 2,
|
||||
'start_date' => now()->toDateString(),
|
||||
@ -137,7 +138,7 @@
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
$session = ClassSession::factory()->create(['course_id' => $course->id, 'session_number' => 1]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->callAction('editSession', [
|
||||
'session_number' => 5,
|
||||
'date' => now()->toDateString(),
|
||||
@ -153,7 +154,7 @@
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
$session = ClassSession::factory()->create(['course_id' => $course->id]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->callAction('shareAttendance', [], ['session' => $session->id])
|
||||
->assertHasNoActionErrors();
|
||||
});
|
||||
@ -162,7 +163,7 @@
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
$session = ClassSession::factory()->create(['course_id' => $course->id]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->assertActionVisible('viewAttendance')
|
||||
->assertActionVisible('viewMaterials')
|
||||
->assertActionVisible('viewAssignments');
|
||||
@ -184,11 +185,46 @@
|
||||
'submitted_at' => now(),
|
||||
]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['courseId' => $course->id])
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->mountAction('viewAssignments', ['session' => $session->id])
|
||||
->assertActionMounted('viewAssignments')
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
it('can delete a session', function () {
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
$session = ClassSession::factory()->create(['course_id' => $course->id]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->callAction('deleteSession', [], ['session' => $session->id])
|
||||
->assertHasNoActionErrors();
|
||||
|
||||
expect($session->refresh()->trashed())->toBeTrue();
|
||||
});
|
||||
|
||||
it('shows attendance in the attendance modal', function () {
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
$session = ClassSession::factory()->create(['course_id' => $course->id]);
|
||||
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->mountAction('viewAttendance', ['session' => $session->id])
|
||||
->assertActionMounted('viewAttendance')
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
it('displays dynamic empty states based on system notification style', function () {
|
||||
$course = Course::factory()->create(['semester' => $this->currentSemester]);
|
||||
|
||||
// Test ManageClassSessions empty states
|
||||
Livewire::test(ManageClassSessions::class)
|
||||
->assertSee(SystemNotification::getByKey('labels.empty_today_sessions.title'))
|
||||
->assertSee(SystemNotification::getByKey('labels.empty_today_sessions.description'));
|
||||
|
||||
// Test ListCourseSessions empty states
|
||||
Livewire::test(ListCourseSessions::class, ['course' => $course])
|
||||
->assertSee(SystemNotification::getByKey('labels.empty_course_sessions.title'))
|
||||
->assertSee(SystemNotification::getByKey('labels.empty_course_sessions.description'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search and Dashboard Filter', function () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user