feat: add class session materials and assignments viewing functionality, enhancing user experience with modals for detailed content display

This commit is contained in:
Yoga Pangestu 2026-03-29 23:45:56 +07:00
parent 5580ab5d55
commit 66c07556b7
10 changed files with 190 additions and 34 deletions

View File

@ -4,6 +4,7 @@
use App\Enums\AssignmentType;
use App\Filament\Resources\Learning\Assignments\Actions\SelectAllStudentsAction;
use App\Models\ClassSession;
use App\Models\Course;
use App\Models\Student;
use App\Settings\GeneralSettings;
@ -39,7 +40,7 @@ public static function configure(Schema $schema): Schema
->autofocus()
->columnSpanFull(),
Grid::make(3)
Grid::make(2)
->schema([
Select::make('course_id')
->label('Mata Kuliah')
@ -53,6 +54,25 @@ public static function configure(Schema $schema): Schema
->live()
->required(),
Select::make('class_session_id')
->label('Pertemuan Ke-')
->placeholder('Pilih Pertemuan (Opsional)')
->options(function (Get $get) {
$courseId = $get('course_id');
if (! $courseId) {
return [];
}
return ClassSession::where('course_id', $courseId)
->orderBy('session_number')
->pluck('session_number', 'id')
->map(fn ($num) => "Sesi Ke-$num")
->toArray();
})
->searchable()
->nullable()
->helperText('Kosongkan jika tugas tidak terkait dengan sesi tertentu.'),
DateTimePicker::make('due_date')
->label('Batas Waktu')
->placeholder('Pilih Tanggal & Waktu')
@ -70,6 +90,7 @@ public static function configure(Schema $schema): Schema
->afterStateUpdated(function (Set $set) {
$set('student_ids', []);
$set('study_group_ids', []);
$set('class_session_id', null);
}),
])
->columnSpanFull(),

View File

@ -185,7 +185,7 @@ public function generateSessionsAction(): Action
});
}
public function viewAttendanceAction(): Action
protected function viewAttendanceAction(): Action
{
return Action::make('viewAttendance')
->label('Lihat Presensi')
@ -198,6 +198,32 @@ public function viewAttendanceAction(): Action
]));
}
protected function viewMaterialsAction(): Action
{
return Action::make('viewMaterials')
->label('Daftar Materi')
->modalHeading(fn (array $arguments) => 'Materi Kuliah - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::Large)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.materials-modal', [
'materials' => ClassSession::find($arguments['session'])->materials()->latest()->get(),
]));
}
protected function viewAssignmentsAction(): Action
{
return Action::make('viewAssignments')
->label('Daftar Tugas')
->modalHeading(fn (array $arguments) => 'Tugas Kuliah - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::Large)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.assignments-modal', [
'assignments' => ClassSession::find($arguments['session'])->assignments()->latest()->get(),
]));
}
public function editSessionAction(): Action
{
return EditAction::make('editSession')

View File

@ -132,7 +132,7 @@ public function courses(): Collection
});
}
public function viewAttendanceAction(): Action
protected function viewAttendanceAction(): Action
{
return Action::make('viewAttendance')
->label('Lihat Presensi')
@ -144,4 +144,30 @@ public function viewAttendanceAction(): Action
'attendances' => ClassSession::find($arguments['session'])->attendances()->with('student')->latest('attended_at')->get(),
]));
}
protected function viewMaterialsAction(): Action
{
return Action::make('viewMaterials')
->label('Daftar Materi')
->modalHeading(fn (array $arguments) => 'Materi Kuliah - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::Large)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.materials-modal', [
'materials' => ClassSession::find($arguments['session'])->materials()->latest()->get(),
]));
}
protected function viewAssignmentsAction(): Action
{
return Action::make('viewAssignments')
->label('Daftar Tugas')
->modalHeading(fn (array $arguments) => 'Tugas Kuliah - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::Large)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.assignments-modal', [
'assignments' => ClassSession::find($arguments['session'])->assignments()->latest()->get(),
]));
}
}

View File

@ -10,6 +10,7 @@
use App\Filament\Actions\DefaultBulkActions;
use App\Filament\Columns\TimestampColumns;
use App\Filament\Resources\Learning\Materials\Pages\ManageMaterials;
use App\Models\ClassSession;
use App\Models\Course;
use App\Models\Material;
use App\Settings\GeneralSettings;
@ -71,13 +72,24 @@ public static function form(Schema $schema): Schema
->live()
->required(),
DatePicker::make('published_at')
->label('Tanggal Publikasi')
->placeholder('Pilih Tanggal')
->required()
->native(false)
->displayFormat('l, d F Y')
->default(now()->toDateString()),
Select::make('class_session_id')
->label('Pertemuan Ke-')
->placeholder('Pilih Pertemuan (Opsional)')
->options(function ($get) {
$courseId = $get('course_id');
if (! $courseId) {
return [];
}
return ClassSession::where('course_id', $courseId)
->orderBy('session_number')
->pluck('session_number', 'id')
->map(fn ($num) => "Sesi Ke-$num")
->toArray();
})
->searchable()
->nullable()
->helperText('Kosongkan jika materi tidak terkait dengan sesi tertentu.'),
])
->columnSpanFull(),
@ -128,15 +140,17 @@ public static function table(Table $table): Table
->searchable()
->sortable(),
TextColumn::make('published_at')
->label('Tanggal Publikasi')
TextColumn::make('created_at')
->label('Tanggal Dibuat')
->date('l, d F Y')
->sortable(),
TextColumn::make('description')
->label('Deskripsi')
->searchable()
->sortable(),
TextColumn::make('classSession.session_number')
->label('Pertemuan')
->formatStateUsing(fn ($state) => $state ? "Sesi Ke-$state" : '-')
->sortable()
->badge()
->color('gray'),
...TimestampColumns::make(),
])
@ -151,17 +165,17 @@ public static function table(Table $table): Table
})
->searchable(),
Filter::make('published_at')
Filter::make('created_at')
->schema([
DatePicker::make('date')
->label('Tanggal Publikasi')
->label('Tanggal Dibuat')
->placeholder('Pilih Tanggal')
->native(false),
])
->query(function (Builder $query, array $data): Builder {
return $query->when(
$data['date'],
fn (Builder $query, $date): Builder => $query->whereDate('published_at', $date),
fn (Builder $query, $date): Builder => $query->whereDate('created_at', $date),
);
}),

View File

@ -15,10 +15,6 @@ class Material extends Model implements HasMedia
protected $guarded = ['id'];
protected $casts = [
'published_at' => 'datetime',
];
public function course(): BelongsTo
{
return $this->belongsTo(Course::class);

View File

@ -17,7 +17,6 @@ public function up(): void
$table->foreignId('class_session_id')->nullable()->constrained()->nullOnDelete();
$table->string('title', 100);
$table->text('description')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();

View File

@ -0,0 +1,35 @@
<div class="space-y-4 py-2">
@forelse($assignments as $assignment)
<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-primary-100 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400">
<x-heroicon-o-pencil-square 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() ?? 'TUGAS' }}
</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?->translatedFormat('d F Y, H:i') ?? '-' }}
</span>
</div>
</div>
</div>
<div class="flex items-center gap-2">
<a href="{{ \App\Filament\Resources\Learning\Assignments\AssignmentResource::getUrl('index') . '?tableFilters[course_id][value]=' . $assignment->course_id . '&tableSearch=' . urlencode($assignment->title) }}"
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" />
</a>
</div>
</div>
@empty
<div class="flex flex-col items-center justify-center py-8 text-center bg-gray-50 dark:bg-white/5 rounded-xl border border-dashed border-gray-200 dark:border-white/10">
<x-heroicon-o-pencil-square class="w-12 h-12 text-gray-300 dark:text-gray-600" />
<p class="mt-2 text-sm font-medium text-gray-500 dark:text-gray-400">Belum ada tugas untuk sesi ini.</p>
</div>
@endforelse
</div>

View File

@ -41,14 +41,22 @@
<x-heroicon-s-clock class="w-3.5 h-3.5" />
{{ $session->time_range }}
</div>
<div class="flex items-center gap-2 mt-2">
<div class="flex items-center gap-1 text-[10px] font-bold text-amber-600 dark:text-amber-400">
<x-heroicon-o-document-text class="w-3 h-3" />
{{ $session->materials_count }} Materi
<div class="flex gap-4">
<div class="flex items-center gap-1.5 px-2 py-0.5 rounded-lg hover:bg-amber-50 dark:hover:bg-amber-900/10 cursor-pointer transition-colors"
wire:click="mountAction('viewMaterials', { session: {{ $session->id }} })"
onclick="event.stopPropagation()">
<x-heroicon-o-document-text class="w-4 h-4 text-amber-500" />
<span class="text-xs font-bold text-gray-600 dark:text-gray-400">
{{ $session->materials_count }}<span class="ml-1 font-medium text-gray-400">Materi</span>
</span>
</div>
<div class="flex items-center gap-1 text-[10px] font-bold text-info-600 dark:text-info-400">
<x-heroicon-o-clipboard-document-list class="w-3 h-3" />
{{ $session->assignments_count }} Tugas
<div class="flex items-center gap-1.5 px-2 py-0.5 rounded-lg hover:bg-primary-50 dark:hover:bg-primary-900/10 cursor-pointer transition-colors"
wire:click="mountAction('viewAssignments', { session: {{ $session->id }} })"
onclick="event.stopPropagation()">
<x-heroicon-o-pencil-square class="w-4 h-4 text-primary-500" />
<span class="text-xs font-bold text-gray-600 dark:text-gray-400">
{{ $session->assignments_count }}<span class="ml-1 font-medium text-gray-400">Tugas</span>
</span>
</div>
</div>
@if ($session->is_pending)

View File

@ -0,0 +1,29 @@
<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?->translatedFormat('d F Y') ?? '-' }}
</span>
</div>
</div>
<div class="flex items-center gap-2">
<a href="{{ \App\Filament\Resources\Learning\Materials\MaterialResource::getUrl('index') . '?tableFilters[course_id][value]=' . $material->course_id . '&tableSearch=' . urlencode($material->title) }}"
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" />
</a>
</div>
</div>
@empty
<div class="flex flex-col items-center justify-center py-8 text-center bg-gray-50 dark:bg-white/5 rounded-xl border border-dashed border-gray-200 dark:border-white/10">
<x-heroicon-o-document-minus class="w-12 h-12 text-gray-300 dark:text-gray-600" />
<p class="mt-2 text-sm font-medium text-gray-500 dark:text-gray-400">Belum ada materi untuk sesi ini.</p>
</div>
@endforelse
</div>

View File

@ -36,12 +36,14 @@ class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-40
<span class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }}%</span>
</div>
</div>
<div class="flex items-center gap-1.5 font-bold text-amber-600 dark:text-amber-400">
<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" />
{{ $session->materials_count }} Materi
</div>
<div class="flex items-center gap-1.5 font-bold text-info-600 dark:text-info-400">
<x-heroicon-o-clipboard-document-list class="w-4 h-4" />
<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-pencil-square class="w-4 h-4" />
{{ $session->assignments_count }} Tugas
</div>
</div>