feat: add download support for non-PDF files in assignment submission views

This commit is contained in:
Yoga Pangestu 2026-04-18 21:51:25 +07:00
parent 9727e8d52f
commit 1f0db96179
6 changed files with 157 additions and 31 deletions

View File

@ -11,6 +11,7 @@
use App\Models\Assignment;
use App\Models\AssignmentSubmission;
use Filament\Actions\Action;
use Filament\Infolists\Components\TextEntry;
use Filament\Resources\Pages\Page;
use Filament\Support\Enums\Width;
use Illuminate\Support\Arr;
@ -100,6 +101,8 @@ public function submissionSummary(): Collection
'submitted' => $isSubmitted,
'submitted_at_formatted' => $isSubmitted ? $submission?->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
'is_pdf' => $isSubmitted && str_ends_with(strtolower($submission->getFirstMediaUrl('submission')), '.pdf'),
'file_url' => $isSubmitted ? $submission->getFirstMediaUrl('submission') : null,
'status_classes' => Arr::toCssClasses([
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $isSubmitted,
@ -129,6 +132,8 @@ public function submissionSummary(): Collection
'submitted' => $isSubmitted,
'submitted_at_formatted' => $isSubmitted ? $submission->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
'is_pdf' => $isSubmitted && str_ends_with(strtolower($submission->getFirstMediaUrl('submission')), '.pdf'),
'file_url' => $isSubmitted ? $submission->getFirstMediaUrl('submission') : null,
'status_classes' => Arr::toCssClasses([
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $isSubmitted,
@ -195,14 +200,39 @@ public function previewSubmissionAction(): Action
->modalWidth(Width::SixExtraLarge)
->infolist([
PdfViewerEntry::make('submission')
->visible(fn (AssignmentSubmission $record) => str_ends_with(strtolower($record->getFirstMediaUrl('submission')), '.pdf'))
->hiddenLabel()
->fileUrl(fn (AssignmentSubmission $record) => $record->getFirstMediaUrl('submission'))
->columnSpanFull(),
TextEntry::make('download_file')
->label('Berkas non-PDF')
->visible(fn (AssignmentSubmission $record) => ! str_ends_with(strtolower($record->getFirstMediaUrl('submission')), '.pdf'))
->hint('Klik ikon unduh untuk melihat isi berkas.')
->default(fn (AssignmentSubmission $record) => $record->getFirstMedia('submission')?->file_name ?? 'Unduh Berkas')
->suffixAction(
Action::make('download')
->label('Unduh')
->icon('heroicon-o-arrow-down-tray')
->url(fn (AssignmentSubmission $record) => $record->getFirstMediaUrl('submission'), true)
),
])
->modalSubmitAction(false)
->modalCancelActionLabel('Tutup');
}
public function downloadMedia(int $submissionId)
{
$submission = AssignmentSubmission::findOrFail($submissionId);
$media = $submission->getFirstMedia('submission');
if (! $media) {
return;
}
return response()->download($media->getPath(), $media->file_name);
}
protected function getHeaderActions(): array
{
return [

View File

@ -248,6 +248,26 @@ public function getSubmissionFileUrl(): ?string
return $this->existingSubmission?->getFirstMediaUrl('submission');
}
public function downloadAttachment()
{
$media = $this->record->getFirstMedia('assignments');
if (! $media) {
return;
}
return response()->download($media->getPath(), $media->file_name);
}
public function downloadSubmission()
{
$media = $this->existingSubmission?->getFirstMedia('submission');
if (! $media) {
return;
}
return response()->download($media->getPath(), $media->file_name);
}
public function submit(): void
{
$student = $this->student;

View File

@ -111,7 +111,7 @@ public static function configure(Schema $schema): Schema
->columnSpanFull(),
AdvancedFileUpload::make('pdf')
->label('Lampiran PDF')
->label('Lampiran Berkas')
->pdfPreviewHeight(400)
->pdfDisplayPage(1)
->pdfToolbar(true)
@ -119,7 +119,7 @@ public static function configure(Schema $schema): Schema
->pdfFitType(PdfViewFit::FIT)
->pdfNavPanes(true)
->disk(config('filesystems.default'))
->acceptedFileTypes(['application/pdf'])
->acceptedFileTypes(['application/pdf', 'application/zip', 'application/x-zip-compressed', 'application/x-zip', 'application/octet-stream', 'multipart/x-zip', '.zip', '.7z'])
->maxSize(1024 * 5)
->directory('assignments-tmp/'.now()->toDateString())
->nullable()

View File

@ -13,7 +13,7 @@ public static function configure(Schema $schema): Schema
return $schema
->components([
AdvancedFileUpload::make('file')
->label(fn ($get) => $get('is_resubmit') ? 'Ganti Berkas PDF (Opsional)' : 'Lampiran Berkas Tugas')
->label(fn ($get) => $get('is_resubmit') ? 'Ganti Berkas Tugas (Opsional)' : 'Lampiran Berkas Tugas')
->pdfPreviewHeight(400)
->pdfDisplayPage(1)
->pdfToolbar(true)
@ -21,12 +21,12 @@ public static function configure(Schema $schema): Schema
->pdfFitType(PdfViewFit::FIT)
->pdfNavPanes(true)
->disk(config('filesystems.default'))
->acceptedFileTypes(['application/pdf'])
->acceptedFileTypes(['application/pdf', 'application/zip', 'application/x-zip-compressed', 'application/x-zip', 'application/octet-stream', 'multipart/x-zip', '.zip', '.7z'])
->maxSize(1024 * 5)
->directory('submissions/'.now()->toDateString())
->required(fn ($get) => ! $get('is_resubmit'))
->hiddenLabel()
->helperText('Hanya file PDF dengan ukuran maksimal 5MB.')
->helperText('File PDF atau ZIP dengan ukuran maksimal 5MB.')
->columnSpanFull(),
]);
}

View File

@ -101,12 +101,21 @@
<td class="px-4 py-3">
@if ($item->has_file)
<button type="button"
wire:click="mountAction('previewSubmission', { submissionId: {{ $item->submission_id }} })"
class="inline-flex items-center gap-1 text-xs text-primary-600 dark:text-primary-400 hover:underline">
<x-filament::icon icon="heroicon-o-eye" class="h-3.5 w-3.5" />
Lihat Tugas
</button>
@if ($item->is_pdf)
<button type="button"
wire:click="mountAction('previewSubmission', { submissionId: {{ $item->submission_id }} })"
class="inline-flex items-center gap-1 text-xs text-primary-600 dark:text-primary-400 hover:underline">
<x-filament::icon icon="heroicon-o-eye" class="h-3.5 w-3.5" />
Lihat Tugas
</button>
@else
<button type="button"
wire:click="downloadMedia({{ $item->submission_id }})"
class="inline-flex items-center gap-1 text-xs text-success-600 dark:text-success-400 hover:underline">
<x-filament::icon icon="heroicon-o-arrow-down-tray" class="h-3.5 w-3.5" />
Unduh ZIP
</button>
@endif
@else
<span class="text-xs text-gray-400">-</span>
@endif

View File

@ -35,11 +35,28 @@ class="prose prose-sm dark:prose-invert max-w-none text-gray-600 dark:text-gray-
@if ($attachmentUrl = $this->record->getFirstMediaUrl('assignments'))
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Lampiran Tugas</p>
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $attachmentUrl }}" width="100%" height="500"
class="block border-0"></iframe>
</div>
@if (str_ends_with(strtolower($attachmentUrl), '.pdf'))
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $attachmentUrl }}" width="100%" height="500"
class="block border-0"></iframe>
</div>
@else
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 group hover:border-primary-500 transition-colors">
<div class="flex items-center gap-4">
<div class="p-2.5 bg-primary-100 dark:bg-primary-900/30 rounded-lg text-primary-600">
<x-filament::icon icon="heroicon-o-document-arrow-down" class="w-6 h-6" />
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $this->record->getFirstMedia('assignments')?->file_name ?? 'Berkas Lampiran' }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Klik tombol di samping untuk mengunduh berkas.</p>
</div>
</div>
<x-filament::button type="button" wire:click="downloadAttachment" color="gray" size="sm" icon="heroicon-o-arrow-down-tray">
Unduh
</x-filament::button>
</div>
@endif
</div>
@endif
</x-filament::section>
@ -63,11 +80,28 @@ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-dange
@if ($url = $this->getSubmissionFileUrl())
<div>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File yang Terakhir Dikumpulkan</p>
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@if (str_ends_with(strtolower($url), '.pdf'))
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@else
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 group hover:border-primary-500 transition-colors">
<div class="flex items-center gap-4">
<div class="p-2.5 bg-primary-100 dark:bg-primary-900/30 rounded-lg text-primary-600">
<x-filament::icon icon="heroicon-o-document-arrow-down" class="w-6 h-6" />
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $this->existingSubmission?->getFirstMedia('submission')?->file_name ?? 'Berkas Tugas' }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Berkas non-PDF (ZIP)</p>
</div>
</div>
<x-filament::button type="button" wire:click="downloadSubmission" color="gray" size="sm" icon="heroicon-o-arrow-down-tray">
Unduh
</x-filament::button>
</div>
@endif
</div>
@endif
</x-filament::section>
@ -86,11 +120,27 @@ class="flex flex-col items-center justify-center py-8 text-center text-gray-500
<div class="mt-6">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2 italic">File yang telah
dikumpulkan oleh kelompok Anda:</p>
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@if (str_ends_with(strtolower($url), '.pdf'))
<div
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@else
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-4">
<div class="p-2.5 bg-primary-100 dark:bg-primary-900/30 rounded-lg text-primary-600">
<x-filament::icon icon="heroicon-o-document-arrow-down" class="w-6 h-6" />
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $this->existingSubmission?->getFirstMedia('submission')?->file_name ?? 'Berkas Tugas' }}</p>
</div>
</div>
<x-filament::button type="button" wire:click="downloadSubmission" color="gray" size="sm" icon="heroicon-o-arrow-down-tray">
Unduh
</x-filament::button>
</div>
@endif
</div>
@endif
</x-filament::section>
@ -112,11 +162,28 @@ class="block border-0"></iframe>
@if ($this->isResubmit && ($url = $this->getSubmissionFileUrl()))
<div class="mb-4">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File Terkumpul Saat Ini</p>
<div
class="rounded-lg overflow-hidden border border-success-200 dark:border-success-800 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@if (str_ends_with(strtolower($url), '.pdf'))
<div
class="rounded-lg overflow-hidden border border-success-200 dark:border-success-800 bg-gray-50 dark:bg-gray-900">
<iframe src="{{ $url }}" width="100%" height="400"
class="block border-0"></iframe>
</div>
@else
<div class="flex items-center justify-between p-4 bg-success-50/50 dark:bg-success-900/10 rounded-xl border border-success-200 dark:border-success-800">
<div class="flex items-center gap-4">
<div class="p-2.5 bg-success-100 dark:bg-success-900/30 rounded-lg text-success-600">
<x-filament::icon icon="heroicon-o-check-badge" class="w-6 h-6" />
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $this->existingSubmission?->getFirstMedia('submission')?->file_name ?? 'Berkas Tugas' }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Berkas Terkumpul (ZIP)</p>
</div>
</div>
<x-filament::button type="button" wire:click="downloadSubmission" color="success" size="sm" icon="heroicon-o-arrow-down-tray" variant="outline">
Unduh
</x-filament::button>
</div>
@endif
</div>
@endif