From ba0cadf4f9b65acf42bbc56c94eca4c6ea80fdc8 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sat, 4 Apr 2026 01:35:30 +0700 Subject: [PATCH] feat: enhance assignment submission process with improved notifications, form handling, and UI updates --- .../Actions/CopyAssignmentLinkAction.php | 4 +- .../Actions/ShareAssignmentAction.php | 5 +- .../Assignments/Pages/ListAssignments.php | 30 ++++- .../Pages/SubmitAssignmentPage.php | 123 ++++++++++++++---- .../Schemas/SubmitAssignmentForm.php | 33 +++++ lang/id/notif.php | 52 ++++++++ .../learning/assignments/index.blade.php | 14 +- .../assignments/submit-assignment.blade.php | 121 +++-------------- tests/Feature/AssignmentTest.php | 6 +- 9 files changed, 241 insertions(+), 147 deletions(-) create mode 100644 app/Filament/Resources/Learning/Assignments/Schemas/SubmitAssignmentForm.php diff --git a/app/Filament/Resources/Learning/Assignments/Actions/CopyAssignmentLinkAction.php b/app/Filament/Resources/Learning/Assignments/Actions/CopyAssignmentLinkAction.php index a5115be..ee3d829 100644 --- a/app/Filament/Resources/Learning/Assignments/Actions/CopyAssignmentLinkAction.php +++ b/app/Filament/Resources/Learning/Assignments/Actions/CopyAssignmentLinkAction.php @@ -34,7 +34,9 @@ protected function setUp(): void 'assignment_id' => $assignment->id, ]); - $livewire->js("if (navigator.clipboard) { navigator.clipboard.writeText('{$url}').catch(() => {}); }"); + $escapedUrl = json_encode($url); + + $livewire->js("if (navigator.clipboard) { navigator.clipboard.writeText({$escapedUrl}).catch(() => {}); }"); SystemNotification::send('link_copied') ->send(); diff --git a/app/Filament/Resources/Learning/Assignments/Actions/ShareAssignmentAction.php b/app/Filament/Resources/Learning/Assignments/Actions/ShareAssignmentAction.php index 651b160..1baf4e6 100644 --- a/app/Filament/Resources/Learning/Assignments/Actions/ShareAssignmentAction.php +++ b/app/Filament/Resources/Learning/Assignments/Actions/ShareAssignmentAction.php @@ -28,8 +28,9 @@ protected function setUp(): void ->modalDescription('Pilih metode untuk membagikan tautan publik status pengumpulan tugas ini.') ->modalSubmitActionLabel('Ke WhatsApp') ->modalCancelAction(false) - ->extraModalFooterActions([ - CopyAssignmentLinkAction::make(), + ->extraModalFooterActions(fn (Action $action): array => [ + CopyAssignmentLinkAction::make() + ->arguments($action->getArguments()), ]) ->action(function (array $arguments, $livewire) { $assignment = Assignment::with(['course'])->find($arguments['record'] ?? null); diff --git a/app/Filament/Resources/Learning/Assignments/Pages/ListAssignments.php b/app/Filament/Resources/Learning/Assignments/Pages/ListAssignments.php index a8fbfc1..e4da2dc 100644 --- a/app/Filament/Resources/Learning/Assignments/Pages/ListAssignments.php +++ b/app/Filament/Resources/Learning/Assignments/Pages/ListAssignments.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Learning\Assignments\Pages; use App\Enums\AssignmentType; +use App\Enums\NotifStyle; use App\Filament\Resources\Learning\Assignments\Actions\CreateAssignmentAction; use App\Filament\Resources\Learning\Assignments\Actions\DeleteAssignmentAction; use App\Filament\Resources\Learning\Assignments\Actions\EditAssignmentAction; @@ -100,6 +101,26 @@ public function description(): string return SystemNotification::getByKey('labels.assignment_list.description'); } + #[Computed] + public function emptyStateHeading(): string + { + return SystemNotification::getByKey('labels.empty_assignment.title'); + } + + #[Computed] + public function emptyStateDescription(): string + { + return SystemNotification::getByKey('labels.empty_assignment.description'); + } + + #[Computed] + public function icon(): string + { + return SystemNotification::getNotifStyle() === NotifStyle::Cheerful + ? 'heroicon-o-clipboard-document-list' + : 'heroicon-o-briefcase'; + } + public function pinAction(): Action { return PinAction::make(); @@ -245,20 +266,20 @@ public function assignmentCards() $isNew = $assignment->created_at->diffInDays(now()) <= 3; $isPinned = in_array($assignment->id, $pinnedIds); - $statusLabel = 'Belum Dikumpulkan'; + $statusLabel = SystemNotification::getByKey('labels.assignment_status.not_submitted'); $statusColor = 'warning'; $statusIcon = 'heroicon-o-arrow-up-tray'; if ($isSubmitted) { - $statusLabel = '✓ Sudah Dikumpulkan'; + $statusLabel = SystemNotification::getByKey('labels.assignment_status.submitted'); $statusColor = 'success'; $statusIcon = 'heroicon-o-check-circle'; } elseif ($isOverdue) { - $statusLabel = '⏰ Waktu Habis'; + $statusLabel = SystemNotification::getByKey('labels.assignment_status.overdue'); $statusColor = 'danger'; $statusIcon = 'heroicon-o-clock'; } elseif ($isGroup && ! $isLeader && ! $isSubmitted) { - $statusLabel = 'Menunggu Ketua'; + $statusLabel = SystemNotification::getByKey('labels.assignment_status.waiting_leader'); $statusColor = 'gray'; $statusIcon = 'heroicon-o-user-group'; } @@ -279,6 +300,7 @@ public function assignmentCards() 'is_pinned' => $isPinned, 'status_label' => $statusLabel, 'status_icon' => $statusIcon, + 'url' => AssignmentResource::getUrl('submit', ['record' => $assignment->id]), // Pre-calculated classes 'card_classes' => Arr::toCssClasses([ 'group flex w-full rounded-xl border transition-all overflow-hidden relative', diff --git a/app/Filament/Resources/Learning/Assignments/Pages/SubmitAssignmentPage.php b/app/Filament/Resources/Learning/Assignments/Pages/SubmitAssignmentPage.php index ea0947a..f6a46e2 100644 --- a/app/Filament/Resources/Learning/Assignments/Pages/SubmitAssignmentPage.php +++ b/app/Filament/Resources/Learning/Assignments/Pages/SubmitAssignmentPage.php @@ -3,23 +3,28 @@ namespace App\Filament\Resources\Learning\Assignments\Pages; use App\Enums\AssignmentType; +use App\Enums\NotifStyle; use App\Filament\Actions\BackAction; use App\Filament\Resources\Learning\Assignments\AssignmentResource; +use App\Filament\Resources\Learning\Assignments\Schemas\SubmitAssignmentForm; use App\Filament\Support\SystemNotification; use App\Models\Assignment; use App\Models\AssignmentSubmission; use App\Models\Student; use App\Models\StudyGroup; use App\Models\User; +use Filament\Forms\Concerns\InteractsWithForms; +use Filament\Forms\Contracts\HasForms; use Filament\Resources\Pages\Page; +use Filament\Schemas\Schema; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; use Livewire\Attributes\Computed; use Livewire\WithFileUploads; -class SubmitAssignmentPage extends Page +class SubmitAssignmentPage extends Page implements HasForms { - use WithFileUploads; + use InteractsWithForms, WithFileUploads; protected static string $resource = AssignmentResource::class; @@ -29,7 +34,24 @@ class SubmitAssignmentPage extends Page public Assignment $record; - public $file = null; + public array $data = []; + + public $file; + + public function mount(Assignment $record): void + { + $this->record = $record; + + $this->form->fill([ + 'is_resubmit' => $this->isResubmit, + ]); + } + + public function form(Schema $schema): Schema + { + return SubmitAssignmentForm::configure($schema) + ->statePath('data'); + } #[Computed] public function statusCards(): array @@ -135,17 +157,75 @@ public function submissionStatus(): object return (object) [ 'type' => 'submitted', 'badge_color' => 'success', - 'badge_label' => 'Sudah Dikumpulkan', + 'badge_label' => SystemNotification::getByKey('assignment_status.submitted'), ]; } return (object) [ 'type' => 'none', 'badge_color' => 'danger', - 'badge_label' => 'Tidak Mengumpulkan', + 'badge_label' => SystemNotification::getByKey('assignment_status.not_submitted'), ]; } + #[Computed] + public function overdueHeading(): string + { + return SystemNotification::getByKey('submission_overdue.title'); + } + + #[Computed] + public function overdueDescription(): string + { + return SystemNotification::getByKey('submission_overdue.body'); + } + + #[Computed] + public function submissionHeading(): string + { + return $this->isResubmit + ? SystemNotification::getByKey('submission_update.title') + : SystemNotification::getByKey('submission_create.title'); + } + + #[Computed] + public function submissionDescription(): string + { + return $this->isResubmit + ? SystemNotification::getByKey('submission_update.body') + : SystemNotification::getByKey('submission_create.body'); + } + + #[Computed] + public function isCheerful(): bool + { + return SystemNotification::getNotifStyle() === NotifStyle::Cheerful; + } + + #[Computed] + public function groupHintTitle(): string + { + return SystemNotification::getByKey('labels.group_submission_hint.title'); + } + + #[Computed] + public function groupHintDescription(): string + { + return SystemNotification::getByKey('labels.group_submission_hint.description'); + } + + #[Computed] + public function overdueIcon(): string + { + return $this->isCheerful ? 'heroicon-o-exclamation-triangle' : 'heroicon-o-lock-closed'; + } + + #[Computed] + public function submissionIcon(): string + { + return $this->isCheerful ? 'heroicon-o-paper-airplane' : 'heroicon-o-document-arrow-up'; + } + public function getSubmissionFileUrl(): ?string { return $this->existingSubmission?->getFirstMediaUrl('submission'); @@ -171,32 +251,19 @@ public function submit(): void } $existingSubmission = $this->existingSubmission; + $state = $this->form->getState(); - if (! $existingSubmission && ! $this->file) { + if (! $existingSubmission && empty($state['file'])) { SystemNotification::send('submission_file_missing', type: 'warning') ->send(); return; } - $this->validate([ - 'file' => [ - 'nullable', - 'file', - 'max:'.(1024 * 5), - 'mimes:pdf', - ], - ], [ - 'file.max' => 'Ukuran file maksimal 5MB.', - 'file.mimes' => 'Hanya file PDF yang diizinkan.', - ]); - if ($existingSubmission) { - if ($this->file) { + if ($state['file']) { $existingSubmission->clearMediaCollection('submission'); - $existingSubmission->addMedia($this->file->getRealPath()) - ->usingName($this->file->getClientOriginalName()) - ->usingFileName($this->file->getClientOriginalName()) + $existingSubmission->addMediaFromDisk($state['file'], config('filesystems.default')) ->toMediaCollection('submission'); } @@ -214,16 +281,18 @@ public function submit(): void 'submitted_at' => now(), ]); - $submission->addMedia($this->file->getRealPath()) - ->usingName($this->file->getClientOriginalName()) - ->usingFileName($this->file->getClientOriginalName()) - ->toMediaCollection('submission'); + if ($state['file']) { + $submission->addMediaFromDisk($state['file'], config('filesystems.default')) + ->toMediaCollection('submission'); + } SystemNotification::send('submission_success') ->send(); } - $this->file = null; + $this->form->fill([ + 'is_resubmit' => $this->isResubmit, + ]); unset($this->existingSubmission, $this->isResubmit); $this->dispatch('submission-completed'); diff --git a/app/Filament/Resources/Learning/Assignments/Schemas/SubmitAssignmentForm.php b/app/Filament/Resources/Learning/Assignments/Schemas/SubmitAssignmentForm.php new file mode 100644 index 0000000..6b2c332 --- /dev/null +++ b/app/Filament/Resources/Learning/Assignments/Schemas/SubmitAssignmentForm.php @@ -0,0 +1,33 @@ +components([ + AdvancedFileUpload::make('file') + ->label(fn ($get) => $get('is_resubmit') ? 'Ganti Berkas PDF (Opsional)' : 'Lampiran Berkas Tugas') + ->pdfPreviewHeight(400) + ->pdfDisplayPage(1) + ->pdfToolbar(true) + ->pdfZoomLevel(100) + ->pdfFitType(PdfViewFit::FIT) + ->pdfNavPanes(true) + ->disk(config('filesystems.default')) + ->acceptedFileTypes(['application/pdf']) + ->maxSize(1024 * 5) + ->directory('submissions/'.now()->toDateString()) + ->required(fn ($get) => ! $get('is_resubmit')) + ->hiddenLabel() + ->helperText('Hanya file PDF dengan ukuran maksimal 5MB.') + ->columnSpanFull(), + ]); + } +} diff --git a/lang/id/notif.php b/lang/id/notif.php index d9a8de3..98242ec 100644 --- a/lang/id/notif.php +++ b/lang/id/notif.php @@ -121,6 +121,18 @@ 'title' => 'Yeay! Tugas Terkumpul! 🎉', 'body' => 'Berhasil! Tugas Anda telah tercatat dengan aman di sistem. Semangat! 🎈', ], + 'submission_overdue' => [ + 'title' => 'Yah, Batas Waktu Udah Habis! 😭⏳', + 'body' => 'Maaf ya, kamu udah nggak bisa kumpulin tugas ini lagi karena waktunya udah lewat. Tetap semangat buat tugas selanjutnya! 💪', + ], + 'submission_update' => [ + 'title' => 'Kuy Perbarui Tugasmu! 🔄✨', + 'body' => 'Ada yang mau dirubah? Tenang, kamu masih bisa update file tugasmu selama belum lewat deadline kok! 🚀✨', + ], + 'submission_create' => [ + 'title' => 'Kumpulkan Tugas Sekarang! 🚀📚', + 'body' => 'Ayo, jangan sampai telat ya! Segera upload file tugas terbaikmu biar tenang dan nilai maksimal! 💪🎨', + ], 'user_activated' => [ 'title' => 'Pengguna Diaktifkan! ✅✨', 'body' => 'Status akun pengguna berhasil diaktifkan kembali. Siap beraksi! 🚀', @@ -193,6 +205,10 @@ 'title' => 'Daftar Tugas Bikin Lemes! 🤣📒', 'description' => 'Yuk, cek dan kumpulin tugasmu biar tenang hidupnya! Klik aja di tugasnya ya. 🚀👨‍💻', ], + 'empty_assignment' => [ + 'title' => 'Hore, Belum Ada Tugas! 🎊✨', + 'description' => 'Belum ada tugas baru yang perlu dikerjakan. Hidup tenang tanpa beban tugas itu asik ya! 😊🌈', + ], 'changelog' => [ 'title' => 'Catatan Rilis 📜✨', 'description' => 'Kepoin apa aja yang baru, fitur kece, dan perbaikan bug biar nggak kudet! 🚀🔥', @@ -221,6 +237,16 @@ 'title' => 'Belum Ada Mata Kuliah Nih! 📚😴', 'description' => 'Semester ini kayanya masih kosong. Coba hubungi admin kalau ada yang salah ya! ✨', ], + 'assignment_status' => [ + 'not_submitted' => 'Belum Dikumpulkan', + 'submitted' => '✓ Sudah Dikumpulkan', + 'overdue' => '⏰ Waktu Habis', + 'waiting_leader' => 'Menunggu Ketua', + ], + 'group_submission_hint' => [ + 'title' => 'Tugas Kelompok', + 'description' => 'Hanya Ketua Kelompok yang dapat mengumpulkan atau memperbarui tugas ini. Silakan hubungi ketua kelompok Anda.', + ], ], // Icons @@ -359,6 +385,18 @@ 'title' => 'Konfirmasi Pengumpulan Berhasil', 'body' => 'Seluruh berkas tugas Anda telah berhasil diverifikasi dan disimpan oleh sistem.', ], + 'submission_overdue' => [ + 'title' => 'Batas Waktu Telah Berakhir', + 'body' => 'Masa pengumpulan tugas ini telah berakhir. Sistem tidak lagi menerima unggahan baru maupun pembaruan untuk penugasan ini.', + ], + 'submission_update' => [ + 'title' => 'Perbarui Pengumpulan', + 'body' => 'Anda diperbolehkan untuk mengunggah ulang atau memperbarui berkas tugas selama masa pengumpulan masih aktif.', + ], + 'submission_create' => [ + 'title' => 'Kumpulkan Tugas', + 'body' => 'Gunakan formulir ini untuk mengunggah berkas tugas Anda sesuai dengan ketentuan yang berlaku.', + ], 'user_activated' => [ 'title' => 'Aktivasi Akun Berhasil', 'body' => 'Status akun pengguna terpilih telah diubah menjadi aktif.', @@ -431,6 +469,10 @@ 'title' => 'Tugas Saya', 'description' => 'Klik pada tugas untuk melihat detail dan mengumpulkan file.', ], + 'empty_assignment' => [ + 'title' => 'Tidak Ada Tugas Terdaftar', + 'description' => 'Belum terdapat tugas baru yang perlu Anda kerjakan untuk saat ini.', + ], 'changelog' => [ 'title' => 'Catatan Rilis', 'description' => 'Lihat riwayat perbaikan bug, pembaruan aplikasi, dan rilis fitur terbaru secara mendalam.', @@ -459,6 +501,16 @@ 'title' => 'Mata Kuliah Belum Terdaftar', 'description' => 'Belum ada mata kuliah yang terdaftar untuk semester aktif ini.', ], + 'assignment_status' => [ + 'not_submitted' => 'Belum Dikumpulkan', + 'submitted' => 'Sudah Dikumpulkan', + 'overdue' => 'Waktu Habis', + 'waiting_leader' => 'Menunggu Ketua', + ], + 'group_submission_hint' => [ + 'title' => 'Tugas Kelompok', + 'description' => 'Hanya Ketua Kelompok yang memiliki otoritas untuk memperbarui atau mengumpulkan tugas kelompok.', + ], ], // Icons diff --git a/resources/views/filament/resources/learning/assignments/index.blade.php b/resources/views/filament/resources/learning/assignments/index.blade.php index 50131d6..a038dbf 100644 --- a/resources/views/filament/resources/learning/assignments/index.blade.php +++ b/resources/views/filament/resources/learning/assignments/index.blade.php @@ -1,6 +1,6 @@ {{ $this->heading }} {{ $this->description }} @@ -15,10 +15,9 @@ @forelse ($this->assignmentCards as $card) @empty
diff --git a/resources/views/filament/resources/learning/assignments/submit-assignment.blade.php b/resources/views/filament/resources/learning/assignments/submit-assignment.blade.php index 627d8e0..05a17f0 100644 --- a/resources/views/filament/resources/learning/assignments/submit-assignment.blade.php +++ b/resources/views/filament/resources/learning/assignments/submit-assignment.blade.php @@ -32,17 +32,12 @@ class="prose prose-sm dark:prose-invert max-w-none text-gray-600 dark:text-gray- @endif - @if ($this->record->hasMedia('assignments')) - @php - $attachmentUrl = $this->record->getFirstMediaUrl('assignments'); - $googleViewerUrl = - 'https://docs.google.com/viewer?url=' . urlencode($attachmentUrl) . '&embedded=true'; - @endphp + @if ($attachmentUrl = $this->record->getFirstMediaUrl('assignments'))

Lampiran Tugas

-
@@ -50,13 +45,9 @@ class="block border-0">
@if ($this->isOverdue) - - {{ \App\Filament\Support\SystemNotification::getMessage('Yah, Batas Waktu Udah Habis! 😭⏳', 'Batas Waktu Telah Berakhir') }} - {{ \App\Filament\Support\SystemNotification::getMessage('Maaf ya, kamu udah nggak bisa kumpulin tugas ini lagi karena waktunya udah lewat. Tetap semangat buat tugas selanjutnya! 💪', 'Proses pengumpulan tugas ini telah ditutup karena melewati batas waktu.') }} + + {{ $this->overdueHeading }} + {{ $this->overdueDescription }}
-

Tugas Kelompok

+

{{ $this->groupHintTitle }}

- Hanya Ketua Kelompok yang dapat mengumpulkan atau memperbarui tugas ini. - Silakan hubungi ketua kelompok Anda. + {!! $this->groupHintDescription !!}

@@ -106,11 +96,9 @@ class="block border-0"> @endif @else - - {{ $this->isResubmit ? \App\Filament\Support\SystemNotification::getMessage('Kuy Perbarui Tugasmu! 🔄✨', 'Perbarui Pengumpulan') : \App\Filament\Support\SystemNotification::getMessage('Kumpulkan Tugas Sekarang! 🚀📚', 'Kumpulkan Tugas') }} + + {{ $this->submissionHeading }} + {{ $this->submissionDescription }} @if ($this->isResubmit && ($url = $this->getSubmissionFileUrl()))
@@ -124,87 +112,18 @@ class="block border-0"> @endif
-
- - -

- Hanya file PDF. Maksimal 5MB. -

- @error('file') -

- {{ $message }}

- @enderror -
- - -
- -
- - - {{ $this->isResubmit ? 'Perbarui Pengumpulan' : 'Kumpulkan Sekarang' }} - - Mengupload... - + {{ $this->form }}
+ +
+ + + {{ $this->isResubmit ? 'Perbarui' : 'Kumpulkan' }} + + Mengupload... + +
@endif
- -@push('scripts') - -@endpush diff --git a/tests/Feature/AssignmentTest.php b/tests/Feature/AssignmentTest.php index e0f6e79..e52cf6c 100644 --- a/tests/Feature/AssignmentTest.php +++ b/tests/Feature/AssignmentTest.php @@ -142,9 +142,11 @@ $this->actingAs($user); Livewire::test(SubmitAssignmentPage::class, ['record' => $assignment]) ->assertSuccessful() - ->set('file', $file) + ->fillForm([ + 'file' => $file, + ]) ->call('submit') - ->assertHasNoErrors(); + ->assertHasNoFormErrors(); $this->assertDatabaseHas('assignment_submissions', [ 'assignment_id' => $assignment->id,