refactor: update class session management by introducing dedicated actions for viewing assignments, attendance, and materials, enhancing modularity and user interaction

This commit is contained in:
Yoga Pangestu 2026-03-30 00:31:00 +07:00
parent 66c07556b7
commit b7e8f64d8b
32 changed files with 320 additions and 385 deletions

View File

@ -13,6 +13,7 @@ protected function setUp(): void
$this->successNotification( $this->successNotification(
SystemNotification::update() SystemNotification::update()
); )
->color('warning');
} }
} }

View File

@ -1,35 +0,0 @@
<?php
namespace App\Filament\Actions;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Support\Enums\Width;
class GoToDateAction extends Action
{
public static function getDefaultName(): ?string
{
return 'goToDate';
}
protected function setUp(): void
{
parent::setUp();
$this->label('Lompat ke Tanggal')
->icon('heroicon-o-calendar-days')
->schema([
DatePicker::make('date')
->label('Pilih Tanggal')
->placeholder(fn () => now()->addMonth(10)->format('l, d F Y'))
->required()
->native(false)
->displayFormat('l, d F Y'),
])
->action(function (array $data, $livewire) {
$livewire->setOption('date', $data['date']);
})
->modalWidth(Width::Large);
}
}

View File

@ -29,7 +29,7 @@ public function getHeading(): string|Htmlable
return ''; return '';
} }
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->components([ ->components([

View File

@ -4,7 +4,6 @@
use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\DeleteAction;
use App\Models\CourseSchedule; use App\Models\CourseSchedule;
use Filament\Support\Icons\Heroicon;
class DeleteScheduleAction extends DeleteAction class DeleteScheduleAction extends DeleteAction
{ {
@ -21,9 +20,9 @@ protected function setUp(): void
return CourseSchedule::findOrFail($arguments['schedule']); return CourseSchedule::findOrFail($arguments['schedule']);
}) })
->label('Hapus') ->label('Hapus')
->icon(Heroicon::OutlinedTrash) ->icon('heroicon-o-trash')
->color('danger') ->color('danger')
->iconButton() ->link()
->tooltip('Hapus') ->tooltip('Hapus')
->modalHeading('Hapus Jadwal Kuliah') ->modalHeading('Hapus Jadwal Kuliah')
->modalDescription('Apakah Anda yakin ingin menghapus jadwal ini? Tindakan ini tidak dapat dibatalkan.') ->modalDescription('Apakah Anda yakin ingin menghapus jadwal ini? Tindakan ini tidak dapat dibatalkan.')

View File

@ -5,7 +5,6 @@
use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\EditAction;
use App\Models\CourseSchedule; use App\Models\CourseSchedule;
use Filament\Support\Enums\Width; use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon;
class EditScheduleAction extends EditAction class EditScheduleAction extends EditAction
{ {
@ -20,8 +19,9 @@ protected function setUp(): void
$this $this
->label('Ubah') ->label('Ubah')
->icon(Heroicon::OutlinedPencilSquare) ->color('warning')
->iconButton() ->icon('heroicon-o-pencil-square')
->link()
->tooltip('Ubah') ->tooltip('Ubah')
->record(function (array $arguments): CourseSchedule { ->record(function (array $arguments): CourseSchedule {
return CourseSchedule::findOrFail($arguments['schedule']); return CourseSchedule::findOrFail($arguments['schedule']);

View File

@ -58,7 +58,7 @@ public function mount(): void
$this->form->fill(); $this->form->fill();
} }
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->schema([ ->schema([

View File

@ -4,7 +4,6 @@
use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\DeleteAction;
use App\Models\StudyGroup; use App\Models\StudyGroup;
use Filament\Support\Icons\Heroicon;
class DeleteStudyGroupAction extends DeleteAction class DeleteStudyGroupAction extends DeleteAction
{ {
@ -19,9 +18,9 @@ protected function setUp(): void
$this->record(fn (array $arguments): StudyGroup => StudyGroup::findOrFail($arguments['studyGroup'])) $this->record(fn (array $arguments): StudyGroup => StudyGroup::findOrFail($arguments['studyGroup']))
->label('Hapus') ->label('Hapus')
->icon(Heroicon::OutlinedTrash) ->icon('heroicon-o-trash')
->color('danger') ->color('danger')
->iconButton() ->link()
->tooltip('Hapus') ->tooltip('Hapus')
->modalHeading(fn (StudyGroup $record) => "Hapus {$record->name}") ->modalHeading(fn (StudyGroup $record) => "Hapus {$record->name}")
->modalDescription('Apakah Anda yakin ingin menghapus kelompok ini? Tindakan ini tidak dapat dibatalkan.') ->modalDescription('Apakah Anda yakin ingin menghapus kelompok ini? Tindakan ini tidak dapat dibatalkan.')

View File

@ -5,7 +5,6 @@
use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\EditAction;
use App\Models\StudyGroup; use App\Models\StudyGroup;
use Filament\Support\Enums\Width; use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon;
class EditStudyGroupAction extends EditAction class EditStudyGroupAction extends EditAction
{ {
@ -20,8 +19,9 @@ protected function setUp(): void
$this $this
->label('Ubah') ->label('Ubah')
->icon(Heroicon::OutlinedPencilSquare) ->color('warning')
->iconButton() ->icon('heroicon-o-pencil-square')
->link()
->tooltip('Ubah') ->tooltip('Ubah')
->record(fn (array $arguments): StudyGroup => StudyGroup::findOrFail($arguments['studyGroup'])) ->record(fn (array $arguments): StudyGroup => StudyGroup::findOrFail($arguments['studyGroup']))
->modalHeading(fn (StudyGroup $record) => "Ubah {$record->name}") ->modalHeading(fn (StudyGroup $record) => "Ubah {$record->name}")

View File

@ -61,7 +61,7 @@ public function mount(): void
]); ]);
} }
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->schema([ ->schema([

View File

@ -15,7 +15,7 @@
class Profile extends EditProfile class Profile extends EditProfile
{ {
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->components([ ->components([

View File

@ -40,7 +40,7 @@ public static function getPagePermission(): string
protected static string $settings = GeneralSettings::class; protected static string $settings = GeneralSettings::class;
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->components([ ->components([

View File

@ -18,15 +18,14 @@ protected function setUp(): void
$this $this
->label('Hapus') ->label('Hapus')
->icon('heroicon-o-trash')
->link()
->modalHeading(fn (Assignment $record) => "Hapus {$record->title}") ->modalHeading(fn (Assignment $record) => "Hapus {$record->title}")
->record(fn (array $arguments) => Assignment::find($arguments['record'])) ->record(fn (array $arguments) => Assignment::find($arguments['record']))
->after(function ($livewire) { ->after(function ($livewire) {
unset($livewire->assignments); unset($livewire->assignments);
}) })
->icon('heroicon-m-trash')
->color('danger') ->color('danger')
->tooltip('Hapus') ->tooltip('Hapus');
->size('sm')
->iconButton();
} }
} }

View File

@ -23,15 +23,15 @@ protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->icon('heroicon-m-pencil-square') $this->label('Ubah')
->color('warning') ->color('warning')
->icon('heroicon-o-pencil-square')
->link()
->tooltip('Ubah') ->tooltip('Ubah')
->size('sm')
->iconButton()
->record(fn (array $arguments) => Assignment::find($arguments['record'])) ->record(fn (array $arguments) => Assignment::find($arguments['record']))
->modalHeading(fn (Assignment $record) => "Ubah {$record->title}") ->modalHeading(fn (Assignment $record) => "Ubah {$record->title}")
->modalWidth(Width::FourExtraLarge) ->modalWidth(Width::FourExtraLarge)
->schema(fn (Schema $schema) => AssignmentForm::configure($schema)->getComponents()) ->form(AssignmentForm::configure(new Schema)->getComponents())
->fillForm(function (Assignment $record): array { ->fillForm(function (Assignment $record): array {
$data = $record->toArray(); $data = $record->toArray();

View File

@ -17,14 +17,18 @@ protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->icon(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'heroicon-s-bookmark' : 'heroicon-o-bookmark') $this->label(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'Lepas Pin' : 'Pin Tugas')
->color(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'primary' : 'gray') ->color(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'primary' : 'gray')
->icon(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'heroicon-s-bookmark' : 'heroicon-o-bookmark')
->link()
->tooltip(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'Lepas pin' : 'Pin tugas ini') ->tooltip(fn (array $arguments, $livewire) => in_array($arguments['record'], $livewire->pinnedIds) ? 'Lepas pin' : 'Pin tugas ini')
->size('sm')
->iconButton()
->action(function (array $arguments, $livewire) { ->action(function (array $arguments, $livewire) {
$assignmentId = $arguments['record']; $assignmentId = $arguments['record'];
$studentProfile = auth()->user()->student; $studentProfile = auth()->user()?->student;
if (! $studentProfile) {
return;
}
$existing = AssignmentPin::where('student_id', $studentProfile->id) $existing = AssignmentPin::where('student_id', $studentProfile->id)
->where('assignment_id', $assignmentId) ->where('assignment_id', $assignmentId)

View File

@ -19,8 +19,9 @@ protected function setUp(): void
parent::setUp(); parent::setUp();
$this->label('Kumpulkan') $this->label('Kumpulkan')
->icon('heroicon-o-arrow-up-tray') ->color('success')
->color('primary') ->icon('heroicon-o-paper-airplane')
->link()
->url(fn (Assignment $record) => AssignmentResource::getUrl('submit', ['record' => $record->id])) ->url(fn (Assignment $record) => AssignmentResource::getUrl('submit', ['record' => $record->id]))
->visible(function (Assignment $record) { ->visible(function (Assignment $record) {
if (now()->isAfter($record->due_date)) { if (now()->isAfter($record->due_date)) {

View File

@ -19,11 +19,11 @@ class AttendanceResource extends Resource
protected static ?string $navigationLabel = 'Presensi'; protected static ?string $navigationLabel = 'Presensi';
protected static ?int $navigationSort = 3; protected static ?int $navigationSort = 20;
protected static ?string $modelLabel = 'Presensi'; protected static ?string $recordTitleAttribute = 'date';
protected static ?string $pluralModelLabel = 'Presensi'; protected static ?string $slug = 'learning/attendances';
public static function getPages(): array public static function getPages(): array
{ {

View File

@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Models\ClassSession;
class DeleteSessionAction extends DeleteAction
{
public static function getDefaultName(): ?string
{
return 'deleteSession';
}
protected function setUp(): void
{
parent::setUp();
$this->label('Hapus')
->icon('heroicon-o-trash')
->link()
->record(fn (array $arguments) => ClassSession::find($arguments['session'], ['*']))
->color('danger');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Filament\Actions\Cheerful\EditAction;
use App\Models\ClassSession;
use Filament\Support\Enums\Width;
class EditSessionAction extends EditAction
{
public static function getDefaultName(): ?string
{
return 'editSession';
}
protected function setUp(): void
{
parent::setUp();
$this->label('Ubah')
->color('warning')
->icon('heroicon-o-pencil')
->link()
->record(fn (array $arguments) => ClassSession::find($arguments['session'], ['*']))
->schema(fn ($livewire) => $livewire->schema())
->modalWidth(Width::TwoExtraLarge);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Filament\Support\SystemNotification;
use App\Models\ClassSession;
use Carbon\Carbon;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
class GenerateSessionsAction extends Action
{
public static function getDefaultName(): ?string
{
return 'generateSessions';
}
protected function setUp(): void
{
parent::setUp();
$this->label('Generate Sesi')
->color('warning')
->requiresConfirmation()
->modalHeading('Generate Sesi Pembelajaran')
->modalDescription('Sistem akan men-generate atau memperbarui sesi 1 sampai 16 secara otomatis berdasarkan jadwal mata kuliah ini.')
->schema([
DatePicker::make('start_date')
->label('Tanggal Pertemuan Ke-1')
->default(now())
->required()
->native(false)
->displayFormat('l, d F Y'),
])
->action(function (array $data, $livewire) {
$course = $livewire->course;
$schedule = $course->courseSchedules()->first();
if (! $schedule) {
SystemNotification::danger('Batal!', 'Jadwal belum ditentukan untuk mata kuliah ini.')->send();
return;
}
$startDate = Carbon::parse($data['start_date']);
for ($i = 1; $i <= 16; $i++) {
ClassSession::updateOrCreate(
[
'course_id' => $course->id,
'session_number' => $i,
],
[
'date' => $startDate->copy()->addWeeks($i - 1)->toDateString(),
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
]
);
}
SystemNotification::success('Selesai!', '16 Sesi berhasil digenerate/diperbarui.')->send();
});
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Models\ClassSession;
use Filament\Actions\Action;
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('info')
->icon('heroicon-o-clipboard-document-list')
->link()
->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

@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Models\ClassSession;
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(fn (array $arguments) => 'Daftar Presensi - Sesi Ke-'.ClassSession::find($arguments['session'], ['*'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::ExtraLarge)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [
'attendances' => ClassSession::find($arguments['session'], ['*'])->attendances()->with('student')->latest('attended_at')->get(),
]));
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Models\ClassSession;
use Filament\Actions\Action;
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('warning')
->icon('heroicon-o-book-open')
->link()
->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(),
]));
}
}

View File

@ -2,33 +2,14 @@
namespace App\Filament\Resources\Learning\ClassSessions; namespace App\Filament\Resources\Learning\ClassSessions;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
use App\Filament\Actions\Cheerful\RestoreAction;
use App\Filament\Actions\DefaultBulkActions;
use App\Filament\Columns\TimestampColumns;
use App\Filament\Resources\Learning\ClassSessions\Pages\ListCourseSessions; use App\Filament\Resources\Learning\ClassSessions\Pages\ListCourseSessions;
use App\Filament\Resources\Learning\ClassSessions\Pages\ManageClassSessions; use App\Filament\Resources\Learning\ClassSessions\Pages\ManageClassSessions;
use App\Filament\Resources\Learning\ClassSessions\RelationManagers\AttendanceRelationManager; use App\Filament\Resources\Learning\ClassSessions\RelationManagers\AttendanceRelationManager;
use App\Models\ClassSession; use App\Models\ClassSession;
use App\Models\Course;
use App\Settings\GeneralSettings; use App\Settings\GeneralSettings;
use BackedEnum; use BackedEnum;
use Filament\Actions\BulkActionGroup;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\TimePicker;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon; use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Database\Eloquent\SoftDeletingScope;
use UnitEnum; use UnitEnum;
@ -43,155 +24,11 @@ class ClassSessionResource extends Resource
protected static ?string $navigationLabel = 'Sesi Kelas'; protected static ?string $navigationLabel = 'Sesi Kelas';
protected static ?int $navigationSort = 1; protected static ?int $navigationSort = 5;
protected static ?string $modelLabel = 'Sesi Kelas'; protected static ?string $recordTitleAttribute = 'session_number';
protected static ?string $pluralModelLabel = 'Sesi Kelas'; protected static ?string $slug = 'learning/class-sessions';
public static function form(Schema $schema): Schema
{
return $schema
->components([
Grid::make(2)
->schema([
Select::make('course_id')
->label('Mata Kuliah')
->placeholder('Pilih Mata Kuliah')
->options(function () {
return Course::query()
->where('semester', app(GeneralSettings::class)->current_semester)
->pluck('name', 'id')
->toArray();
})
->searchable()
->required()
->preload()
->live()
->afterStateUpdated(function ($state, $set) {
if (! $state) {
return;
}
$schedule = \App\Models\CourseSchedule::where('course_id', $state)->first();
if ($schedule) {
$set('start_time', $schedule->start_time?->format('H:i'));
$set('end_time', $schedule->end_time?->format('H:i'));
}
// Ambil pertemuan terakhir + 1
$lastSession = \App\Models\ClassSession::where('course_id', $state)->max('session_number');
$set('session_number', ($lastSession ?? 0) + 1);
})
->columnSpanFull(),
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(),
])
->columnSpanFull(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('session_number')
->label('Pertemuan Ke-')
->sortable()
->badge(),
TextColumn::make('course.name')
->label('Mata Kuliah')
->searchable()
->sortable()
->wrap()
->description(fn (ClassSession $record) => $record->course->code),
TextColumn::make('date')
->label('Tanggal')
->date('l, d F Y')
->sortable()
->description(fn (ClassSession $record) => $record->start_time->format('H:i').' - '.$record->end_time->format('H:i').' WIB'),
TextColumn::make('attendances_count')
->counts('attendances')
->label('Presensi')
->badge()
->color('success'),
TextColumn::make('materials_count')
->counts('materials')
->label('Materi')
->badge()
->color('warning'),
TextColumn::make('assignments_count')
->counts('assignments')
->label('Tugas')
->badge()
->color('info'),
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()->hasRole(RoleEnum::Developer)),
])
->recordActions([
EditAction::make()
->modalWidth(Width::TwoExtraLarge),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
...DefaultBulkActions::make('Sesi Kelas'),
]),
])
->emptyStateIcon(Heroicon::OutlinedPresentationChartBar)
->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.')
->defaultSort('created_at', 'desc')
->deferFilters(false)
->paginated([25, 50, 100, 'all'])
->defaultPaginationPageOption(25);
}
public static function getRelations(): array public static function getRelations(): array
{ {

View File

@ -4,11 +4,13 @@
use App\Filament\Actions\BackAction; use App\Filament\Actions\BackAction;
use App\Filament\Actions\Cheerful\CreateAction; use App\Filament\Actions\Cheerful\CreateAction;
use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Resources\Learning\ClassSessions\Actions\DeleteSessionAction;
use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Resources\Learning\ClassSessions\Actions\EditSessionAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\GenerateSessionsAction;
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\Filament\Resources\Learning\ClassSessions\ClassSessionResource;
use App\Filament\Support\SystemNotification;
use App\Models\ClassSession;
use App\Models\Course; use App\Models\Course;
use App\Models\Student; use App\Models\Student;
use Filament\Actions\Action; use Filament\Actions\Action;
@ -70,34 +72,43 @@ public function sessions(): Collection
]); ]);
} }
public function formSchema(): array public function schema(): array
{ {
return [ return [
Grid::make(['default' => 3]) Grid::make(['default' => 2])
->schema([ ->schema([
TextInput::make('session_number') TextInput::make('session_number')
->label('Pertemuan Ke-') ->label('Pertemuan Ke-')
->placeholder('1')
->numeric() ->numeric()
->default(fn () => ($this->course->classSessions()->max('session_number') ?? 0) + 1) ->required()
->required(), ->minValue(1)
->maxValue(16)
->autocomplete(false),
DatePicker::make('date') DatePicker::make('date')
->label('Tanggal') ->label('Tanggal')
->default(now()) ->placeholder('Pilih Tanggal')
->required()
->native(false) ->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(), ->required(),
Grid::make(['default' => 2])
->schema([
TimePicker::make('start_time')
->label('Mulai')
->native(false)
->seconds(false)
->required(),
TimePicker::make('end_time')
->label('Selesai')
->native(false)
->seconds(false)
->required(),
])->columnSpan(1),
]), ]),
]; ];
} }
@ -119,14 +130,15 @@ protected function getHeaderActions(): array
BackAction::make() BackAction::make()
->url(ManageClassSessions::getUrl()), ->url(ManageClassSessions::getUrl()),
$this->generateSessionsAction(), GenerateSessionsAction::make(),
CreateAction::make() CreateAction::make()
->label('Tambah Sesi') ->label('Tambah')
->modalHeading('Tambah Sesi') ->modalHeading('Tambah Sesi')
->modalSubmitActionLabel('Simpan') ->modalSubmitActionLabel('Simpan')
->modalCancelActionLabel('Batal') ->modalCancelActionLabel('Batal')
->form($this->formSchema()) ->schema($this->schema())
->mutateFormDataUsing(function (array $data): array { ->mutateDataUsing(function (array $data): array {
$data['course_id'] = $this->courseId; $data['course_id'] = $this->courseId;
return $data; return $data;
@ -139,102 +151,28 @@ protected function getHeaderActions(): array
]; ];
} }
public function generateSessionsAction(): Action public function viewAttendanceAction(): Action
{ {
return Action::make('generateSessions') return ViewAttendanceAction::make();
->label('Generate Sesi')
->icon('heroicon-o-sparkles')
->color('warning')
->requiresConfirmation()
->modalHeading('Generate Sesi Pembelajaran')
->modalDescription('Sistem akan men-generate atau memperbarui sesi 1 sampai 16 secara otomatis berdasarkan jadwal mata kuliah ini.')
->form([
DatePicker::make('start_date')
->label('Tanggal Pertemuan Ke-1')
->default(now())
->required()
->native(false),
])
->action(function (array $data) {
$course = $this->course;
$schedule = $course->courseSchedules()->first();
if (! $schedule) {
SystemNotification::danger('Batal!', 'Jadwal belum ditentukan untuk mata kuliah ini.')->send();
return;
}
$startDate = \Carbon\Carbon::parse($data['start_date']);
for ($i = 1; $i <= 16; $i++) {
ClassSession::updateOrCreate(
[
'course_id' => $course->id,
'session_number' => $i,
],
[
'date' => $startDate->copy()->addWeeks($i - 1)->toDateString(),
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
]
);
}
SystemNotification::success('Selesai!', '16 Sesi berhasil digenerate/diperbarui.')->send();
});
} }
protected function viewAttendanceAction(): Action public function viewMaterialsAction(): Action
{ {
return Action::make('viewAttendance') return ViewMaterialsAction::make();
->label('Lihat Presensi')
->modalHeading(fn (array $arguments) => 'Daftar Presensi - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::ExtraLarge)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [
'attendances' => ClassSession::find($arguments['session'])->attendances()->with('student')->latest('attended_at')->get(),
]));
} }
protected function viewMaterialsAction(): Action public function viewAssignmentsAction(): Action
{ {
return Action::make('viewMaterials') return ViewAssignmentsAction::make();
->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 public function editSessionAction(): Action
{ {
return EditAction::make('editSession') return EditSessionAction::make();
->record(fn (array $arguments) => ClassSession::find($arguments['session']))
->form($this->formSchema())
->modalWidth(Width::TwoExtraLarge);
} }
public function deleteSessionAction(): Action public function deleteSessionAction(): Action
{ {
return DeleteAction::make('deleteSession') return DeleteSessionAction::make();
->record(fn (array $arguments) => ClassSession::find($arguments['session']));
} }
} }

View File

@ -2,6 +2,9 @@
namespace App\Filament\Resources\Learning\ClassSessions\Pages; namespace App\Filament\Resources\Learning\ClassSessions\Pages;
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\Filament\Resources\Learning\ClassSessions\ClassSessionResource;
use App\Models\ClassSession; use App\Models\ClassSession;
use App\Models\Course; use App\Models\Course;
@ -15,7 +18,6 @@
use Filament\Forms\Contracts\HasForms; use Filament\Forms\Contracts\HasForms;
use Filament\Resources\Pages\Page; use Filament\Resources\Pages\Page;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
@ -132,42 +134,18 @@ public function courses(): Collection
}); });
} }
protected function viewAttendanceAction(): Action public function viewAttendanceAction(): Action
{ {
return Action::make('viewAttendance') return ViewAttendanceAction::make();
->label('Lihat Presensi')
->modalHeading(fn (array $arguments) => 'Daftar Presensi - Sesi Ke-'.ClassSession::find($arguments['session'])->session_number)
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup')
->modalWidth(Width::ExtraLarge)
->modalContent(fn (array $arguments) => view('filament.resources.learning.class-sessions.attendance-modal', [
'attendances' => ClassSession::find($arguments['session'])->attendances()->with('student')->latest('attended_at')->get(),
]));
} }
protected function viewMaterialsAction(): Action public function viewMaterialsAction(): Action
{ {
return Action::make('viewMaterials') return ViewMaterialsAction::make();
->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 public function viewAssignmentsAction(): Action
{ {
return Action::make('viewAssignments') return ViewAssignmentsAction::make();
->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

@ -21,7 +21,7 @@ class AttendanceRelationManager extends RelationManager
protected static ?string $title = 'Presensi Mahasiswa'; protected static ?string $title = 'Presensi Mahasiswa';
public function form(Schema $schema): Schema public function schema(Schema $schema): Schema
{ {
return $schema return $schema
->components([ ->components([

View File

@ -34,11 +34,9 @@ class CourseResource extends Resource
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
protected static ?string $title = 'Mata Kuliah';
protected static ?string $navigationLabel = 'Mata Kuliah'; protected static ?string $navigationLabel = 'Mata Kuliah';
protected static ?int $navigationSort = 15; protected static ?int $navigationSort = 10;
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';

View File

@ -7,7 +7,6 @@
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Support\Enums\Width; use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class ChangePasswordAction extends Action class ChangePasswordAction extends Action
@ -17,7 +16,6 @@ protected function setUp(): void
parent::setUp(); parent::setUp();
$this->label('Ganti Kata Sandi') $this->label('Ganti Kata Sandi')
->icon(Heroicon::OutlinedKey)
->color('warning') ->color('warning')
->modalWidth(Width::Medium) ->modalWidth(Width::Medium)
->schema([ ->schema([

View File

@ -1,8 +1,10 @@
<?php <?php
use App\Models\Course;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
return new class extends Migration return new class extends Migration
{ {
@ -16,8 +18,8 @@ public function up(): void
}); });
// Populate existing courses with a random token // Populate existing courses with a random token
\App\Models\Course::all()->each(function ($course) { Course::all()->each(function ($course) {
$course->update(['sharing_token' => \Illuminate\Support\Str::random(32)]); $course->update(['sharing_token' => Str::random(32)]);
}); });
} }

View File

@ -91,7 +91,7 @@ class="h-4 w-4 text-gray-400 group-hover:text-primary-500 mt-1 transition-colors
</a> </a>
<div <div
class="flex flex-col items-center justify-center border-l border-gray-100 dark:border-gray-700 p-1 gap-3 p-3"> class="flex flex-col items-start justify-center border-l border-gray-100 dark:border-gray-700 p-1 gap-3 p-3">
{{ ($this->pinAction)(['record' => $card->id]) }} {{ ($this->pinAction)(['record' => $card->id]) }}
@canAny(['Update:Assignment', 'Delete:Assignment']) @canAny(['Update:Assignment', 'Delete:Assignment'])

View File

@ -52,6 +52,8 @@ class="flex items-center gap-1.5 font-bold text-success-600 dark:text-success-40
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
{{ ($this->viewAttendanceAction)(['session' => $session->id]) }} {{ ($this->viewAttendanceAction)(['session' => $session->id]) }}
{{ ($this->viewMaterialsAction)(['session' => $session->id]) }}
{{ ($this->viewAssignmentsAction)(['session' => $session->id]) }}
{{ ($this->editSessionAction)(['session' => $session->id]) }} {{ ($this->editSessionAction)(['session' => $session->id]) }}
{{ ($this->deleteSessionAction)(['session' => $session->id]) }} {{ ($this->deleteSessionAction)(['session' => $session->id]) }}
</div> </div>

View File

@ -1,9 +1,10 @@
<?php <?php
use App\Http\Controllers\ShareAttendanceController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/', function () { Route::get('/', function () {
return redirect()->route('filament.admin.auth.login'); return redirect()->route('filament.admin.auth.login');
}); });
Route::get('/share/presensi/{token}', [\App\Http\Controllers\ShareAttendanceController::class, 'show'])->name('share.attendance'); Route::get('/share/presensi/{token}', [ShareAttendanceController::class, 'show'])->name('share.attendance');