Compare commits

...

10 Commits

23 changed files with 1153 additions and 287 deletions

View File

@ -6,6 +6,7 @@
use App\Models\CourseSchedule;
use App\Settings\GeneralSettings;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
class GenerateDailySessions extends Command
{
@ -23,7 +24,7 @@ class GenerateDailySessions extends Command
*/
public function handle()
{
$dayOfWeek = now()->dayOfWeekIso; // 1 (Mon) - 7 (Sun)
$dayOfWeek = now()->dayOfWeekIso;
$currentSemester = app(GeneralSettings::class)->current_semester;
$schedules = CourseSchedule::query()
@ -35,24 +36,44 @@ public function handle()
$count = 0;
foreach ($schedules as $schedule) {
$exists = ClassSession::where('course_id', $schedule->course_id)
->whereDate('date', now())
->exists();
$existingSession = ClassSession::withTrashed()
->where('course_id', $schedule->course_id)
->whereDate('date', today())
->first();
if (! $exists) {
$lastSession = ClassSession::where('course_id', $schedule->course_id)->max('session_number');
if ($existingSession) {
if ($existingSession->trashed()) {
$existingSession->restore();
$this->info("Restored session for course_id {$schedule->course_id}");
$count++;
}
continue;
}
$lastSession = ClassSession::withTrashed()
->where('course_id', $schedule->course_id)
->max('session_number');
try {
ClassSession::create([
'course_id' => $schedule->course_id,
'session_number' => ($lastSession ?? 0) + 1,
'date' => now()->toDateString(),
'date' => today()->toDateString(),
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
]);
$count++;
} catch (QueryException $e) {
if ($e->errorInfo[1] === 1062) {
$this->warn("Skipped duplicate session for course_id {$schedule->course_id}");
continue;
}
throw $e;
}
}
$this->info("Generated {$count} sessions for ".now()->toDateString());
$this->info("Generated/Restored {$count} sessions for ".today()->toDateString());
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Filament\Exports;
use App\Models\Attendance;
use App\Models\ClassSession;
use App\Models\Student;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class AttendanceExport implements FromCollection, WithEvents, WithHeadings, WithMapping, WithStyles
{
protected ClassSession $session;
protected Collection $activeStudents;
protected Collection $attendanceRecords;
public function __construct(ClassSession $session)
{
$session->load('course');
$this->session = $session;
$this->activeStudents = Student::whereHas('user', fn ($q) => $q->active())
->orderBy('full_name')
->get();
$this->attendanceRecords = Attendance::query()
->whereHas('courseSchedule', function ($query) {
$query->where('course_id', $this->session->course_id);
})
->whereDate('date', $this->session->date)
->get()
->keyBy('student_id');
}
public function collection()
{
return $this->activeStudents->map(fn ($student) => (object) [
'full_name' => $student->full_name,
'student_number' => $student->student_number,
'status' => $this->attendanceRecords->has($student->id) ? 'Hadir' : 'Alpa',
'attended_at' => $this->attendanceRecords->get($student->id)?->attended_at,
]);
}
public function headings(): array
{
return [
['Data Presensi '.($this->session->course?->name ?? '').' Sesi Ke-'.$this->session->session_number.' ('.$this->session->date?->translatedFormat('d F Y').')'],
['Mahasiswa', 'NIM', 'Status', 'Waktu Presensi'],
];
}
public function map($row): array
{
return [
$row->full_name,
$row->student_number,
$row->status,
$row->attended_at ? $row->attended_at->translatedFormat('d F Y H:i') : '-',
];
}
public function styles(Worksheet $sheet)
{
return [
1 => [
'font' => ['bold' => true, 'size' => 14],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
],
2 => [
'font' => ['bold' => true],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
],
];
}
public function registerEvents(): array
{
return [
AfterSheet::class => function (AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$lastColumn = $sheet->getHighestColumn();
$sheet->mergeCells("A1:{$lastColumn}1");
$sheet->getStyle("A2:{$lastColumn}".$sheet->getHighestRow())
->getAlignment()
->setVertical(Alignment::VERTICAL_TOP)
->setWrapText(true);
foreach (range('A', $lastColumn) as $columnID) {
$sheet->getColumnDimension($columnID)->setAutoSize(true);
}
},
];
}
}

View File

@ -29,7 +29,7 @@ protected function setUp(): void
return;
}
$url = URL::temporarySignedRoute('share.assignment', now()->addHour(), [
$url = URL::signedRoute('share.assignment', [
'course' => $course->id,
'assignment_id' => $assignment->id,
]);

View File

@ -41,7 +41,7 @@ protected function setUp(): void
return;
}
$url = URL::temporarySignedRoute('share.assignment', now()->addHour(), [
$url = URL::signedRoute('share.assignment', [
'course' => $course->id,
'assignment_id' => $assignment->id,
]);

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

@ -20,11 +20,10 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\WithFileUploads;
class SubmitAssignmentPage extends Page implements HasForms
{
use InteractsWithForms, WithFileUploads;
use InteractsWithForms;
protected static string $resource = AssignmentResource::class;
@ -248,6 +247,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'])
->maxSize(1024 * 5)
->acceptedFileTypes(['application/pdf', 'application/zip', 'application/x-zip-compressed', 'application/x-zip', 'application/octet-stream', 'multipart/x-zip', '.zip', '.7z'])
->maxSize(1024 * 40)
->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 40MB.')
->columnSpanFull(),
]);
}

View File

@ -26,7 +26,7 @@ protected function setUp(): void
$session = ClassSession::find($arguments['session'] ?? null);
$date = $session ? $session->date?->toDateString() : null;
$url = URL::temporarySignedRoute('share.attendance', now()->addHour(), ['course' => $course->id, 'date' => $date]);
$url = URL::signedRoute('share.attendance', ['course' => $course->id, 'date' => $date]);
$livewire->js("if (navigator.clipboard) { navigator.clipboard.writeText('{$url}').catch(() => {}); }");

View File

@ -0,0 +1,35 @@
<?php
namespace App\Filament\Resources\Learning\ClassSessions\Actions;
use App\Filament\Exports\AttendanceExport;
use App\Models\ClassSession;
use Filament\Actions\Action;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
class ExportSessionAttendanceAction extends Action
{
public static function getDefaultName(): ?string
{
return 'exportAttendance';
}
protected function setUp(): void
{
parent::setUp();
$this->label('Export Presensi')
->color('success')
->icon('heroicon-o-arrow-down-tray')
->link()
->action(function (array $arguments) {
$session = ClassSession::findOrFail($arguments['session']);
$courseName = $session->course?->name ?? '';
$filename = 'rekap-presensi-'.Str::slug($courseName).'-sesi-'.$session->session_number.'-'.$session->date?->toDateString().'.xlsx';
return Excel::download(new AttendanceExport($session), $filename);
});
}
}

View File

@ -36,7 +36,7 @@ protected function setUp(): void
$session = ClassSession::find($arguments['session'] ?? null);
$date = $session ? $session->date?->toDateString() : null;
$url = URL::temporarySignedRoute('share.attendance', now()->addHour(), ['course' => $course->id, 'date' => $date]);
$url = URL::signedRoute('share.attendance', ['course' => $course->id, 'date' => $date]);
$text = "*Info Kelas {$course->name}*\nSesi ke-".($session->session_number ?? '-').' ('.($session->date?->translatedFormat('d M Y') ?? '').")\n\nSilakan cek detail/rekap kehadiran melalui tautan ini:\n\n{$url}";

View File

@ -5,6 +5,7 @@
use App\Filament\Actions\BackAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\DeleteSessionAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\EditSessionAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\ExportSessionAttendanceAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\GenerateSessionsAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\MarkAsSentAction;
use App\Filament\Resources\Learning\ClassSessions\Actions\ShareAttendanceAction;
@ -126,6 +127,11 @@ public function deleteSessionAction(): DeleteSessionAction
return DeleteSessionAction::make();
}
public function exportAttendanceAction(): ExportSessionAttendanceAction
{
return ExportSessionAttendanceAction::make();
}
#[Computed]
public function emptyStateHeading(): string
{

View File

@ -3,6 +3,7 @@
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Middleware\ValidatePostSize;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
@ -11,7 +12,7 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->remove(ValidatePostSize::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//

View File

@ -6,7 +6,7 @@
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.3",
"php": "^8.2",
"achyutn/filament-log-viewer": "^2.1",
"asmit/filament-upload": "^2.0",
"bezhansalleh/filament-shield": "^4.1",
@ -18,6 +18,8 @@
"joaopaulolndev/filament-pdf-viewer": "^3.0",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"maatwebsite/excel": "^3.1",
"pxlrbt/filament-excel": "^3.6",
"saade/filament-facehash": "^1.0"
},
"require-dev": {

944
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -130,7 +130,7 @@
'temporary_file_upload' => [
'disk' => env('LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK'), // Example: 'local', 's3' | Default: 'default'
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'rules' => ['required', 'file', 'max:40000'], // 40MB
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
@ -274,7 +274,7 @@
*/
'payload' => [
'max_size' => 1024 * 1024, // 1MB - maximum request payload size in bytes
'max_size' => 1024 * 1024 * 40, // 40MB - maximum request payload size in bytes
'max_nesting_depth' => 50, // Maximum depth of dot-notation property paths
'max_calls' => 50, // Maximum method calls per request
'max_components' => 20, // Maximum components per batch request

View File

@ -38,7 +38,7 @@
* The maximum file size of an item in bytes.
* Adding a larger file will result in an exception.
*/
'max_file_size' => 1024 * 1024 * 10, // 10MB
'max_file_size' => 1024 * 1024 * 40, // 40MB
/*
* This queue connection will be used to generate derived and responsive images.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

View File

@ -66,6 +66,7 @@ class="text-[10px] font-bold text-gray-500">{{ $session->attendance_percentage }
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->exportAttendanceAction)(['session' => $session->id]) }}
{{ ($this->markAsSentAction)(['record' => $session->id]) }}
{{ ($this->editSessionAction)(['session' => $session->id]) }}
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}