Compare commits
No commits in common. "7c5b279689a175b37072f8d6aa951551ce6e2021" and "400a0e300181dbb111171b49eed589af82350c06" have entirely different histories.
7c5b279689
...
400a0e3001
@ -6,7 +6,6 @@
|
|||||||
use App\Models\CourseSchedule;
|
use App\Models\CourseSchedule;
|
||||||
use App\Settings\GeneralSettings;
|
use App\Settings\GeneralSettings;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\QueryException;
|
|
||||||
|
|
||||||
class GenerateDailySessions extends Command
|
class GenerateDailySessions extends Command
|
||||||
{
|
{
|
||||||
@ -24,7 +23,7 @@ class GenerateDailySessions extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$dayOfWeek = now()->dayOfWeekIso;
|
$dayOfWeek = now()->dayOfWeekIso; // 1 (Mon) - 7 (Sun)
|
||||||
$currentSemester = app(GeneralSettings::class)->current_semester;
|
$currentSemester = app(GeneralSettings::class)->current_semester;
|
||||||
|
|
||||||
$schedules = CourseSchedule::query()
|
$schedules = CourseSchedule::query()
|
||||||
@ -36,44 +35,24 @@ public function handle()
|
|||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
foreach ($schedules as $schedule) {
|
foreach ($schedules as $schedule) {
|
||||||
$existingSession = ClassSession::withTrashed()
|
$exists = ClassSession::where('course_id', $schedule->course_id)
|
||||||
->where('course_id', $schedule->course_id)
|
->whereDate('date', now())
|
||||||
->whereDate('date', today())
|
->exists();
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($existingSession) {
|
if (! $exists) {
|
||||||
if ($existingSession->trashed()) {
|
$lastSession = ClassSession::where('course_id', $schedule->course_id)->max('session_number');
|
||||||
$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([
|
ClassSession::create([
|
||||||
'course_id' => $schedule->course_id,
|
'course_id' => $schedule->course_id,
|
||||||
'session_number' => ($lastSession ?? 0) + 1,
|
'session_number' => ($lastSession ?? 0) + 1,
|
||||||
'date' => today()->toDateString(),
|
'date' => now()->toDateString(),
|
||||||
'start_time' => $schedule->start_time,
|
'start_time' => $schedule->start_time,
|
||||||
'end_time' => $schedule->end_time,
|
'end_time' => $schedule->end_time,
|
||||||
]);
|
]);
|
||||||
$count++;
|
$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/Restored {$count} sessions for ".today()->toDateString());
|
$this->info("Generated {$count} sessions for ".now()->toDateString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
<?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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -29,7 +29,7 @@ protected function setUp(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = URL::signedRoute('share.assignment', [
|
$url = URL::temporarySignedRoute('share.assignment', now()->addHour(), [
|
||||||
'course' => $course->id,
|
'course' => $course->id,
|
||||||
'assignment_id' => $assignment->id,
|
'assignment_id' => $assignment->id,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -41,7 +41,7 @@ protected function setUp(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = URL::signedRoute('share.assignment', [
|
$url = URL::temporarySignedRoute('share.assignment', now()->addHour(), [
|
||||||
'course' => $course->id,
|
'course' => $course->id,
|
||||||
'assignment_id' => $assignment->id,
|
'assignment_id' => $assignment->id,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
use App\Models\Assignment;
|
use App\Models\Assignment;
|
||||||
use App\Models\AssignmentSubmission;
|
use App\Models\AssignmentSubmission;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
|
||||||
use Filament\Resources\Pages\Page;
|
use Filament\Resources\Pages\Page;
|
||||||
use Filament\Support\Enums\Width;
|
use Filament\Support\Enums\Width;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
@ -101,8 +100,6 @@ public function submissionSummary(): Collection
|
|||||||
'submitted' => $isSubmitted,
|
'submitted' => $isSubmitted,
|
||||||
'submitted_at_formatted' => $isSubmitted ? $submission?->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
|
'submitted_at_formatted' => $isSubmitted ? $submission?->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
|
||||||
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
|
'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([
|
'status_classes' => Arr::toCssClasses([
|
||||||
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
|
'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,
|
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $isSubmitted,
|
||||||
@ -132,8 +129,6 @@ public function submissionSummary(): Collection
|
|||||||
'submitted' => $isSubmitted,
|
'submitted' => $isSubmitted,
|
||||||
'submitted_at_formatted' => $isSubmitted ? $submission->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
|
'submitted_at_formatted' => $isSubmitted ? $submission->submitted_at?->translatedFormat('l, d F Y H:i') : '-',
|
||||||
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
|
'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([
|
'status_classes' => Arr::toCssClasses([
|
||||||
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
|
'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,
|
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $isSubmitted,
|
||||||
@ -200,39 +195,14 @@ public function previewSubmissionAction(): Action
|
|||||||
->modalWidth(Width::SixExtraLarge)
|
->modalWidth(Width::SixExtraLarge)
|
||||||
->infolist([
|
->infolist([
|
||||||
PdfViewerEntry::make('submission')
|
PdfViewerEntry::make('submission')
|
||||||
->visible(fn (AssignmentSubmission $record) => str_ends_with(strtolower($record->getFirstMediaUrl('submission')), '.pdf'))
|
|
||||||
->hiddenLabel()
|
->hiddenLabel()
|
||||||
->fileUrl(fn (AssignmentSubmission $record) => $record->getFirstMediaUrl('submission'))
|
->fileUrl(fn (AssignmentSubmission $record) => $record->getFirstMediaUrl('submission'))
|
||||||
->columnSpanFull(),
|
->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)
|
->modalSubmitAction(false)
|
||||||
->modalCancelActionLabel('Tutup');
|
->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
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -20,10 +20,11 @@
|
|||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Livewire\Attributes\Computed;
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\WithFileUploads;
|
||||||
|
|
||||||
class SubmitAssignmentPage extends Page implements HasForms
|
class SubmitAssignmentPage extends Page implements HasForms
|
||||||
{
|
{
|
||||||
use InteractsWithForms;
|
use InteractsWithForms, WithFileUploads;
|
||||||
|
|
||||||
protected static string $resource = AssignmentResource::class;
|
protected static string $resource = AssignmentResource::class;
|
||||||
|
|
||||||
@ -247,26 +248,6 @@ public function getSubmissionFileUrl(): ?string
|
|||||||
return $this->existingSubmission?->getFirstMediaUrl('submission');
|
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
|
public function submit(): void
|
||||||
{
|
{
|
||||||
$student = $this->student;
|
$student = $this->student;
|
||||||
|
|||||||
@ -111,7 +111,7 @@ public static function configure(Schema $schema): Schema
|
|||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
|
|
||||||
AdvancedFileUpload::make('pdf')
|
AdvancedFileUpload::make('pdf')
|
||||||
->label('Lampiran Berkas')
|
->label('Lampiran PDF')
|
||||||
->pdfPreviewHeight(400)
|
->pdfPreviewHeight(400)
|
||||||
->pdfDisplayPage(1)
|
->pdfDisplayPage(1)
|
||||||
->pdfToolbar(true)
|
->pdfToolbar(true)
|
||||||
@ -119,7 +119,7 @@ public static function configure(Schema $schema): Schema
|
|||||||
->pdfFitType(PdfViewFit::FIT)
|
->pdfFitType(PdfViewFit::FIT)
|
||||||
->pdfNavPanes(true)
|
->pdfNavPanes(true)
|
||||||
->disk(config('filesystems.default'))
|
->disk(config('filesystems.default'))
|
||||||
->acceptedFileTypes(['application/pdf', 'application/zip', 'application/x-zip-compressed', 'application/x-zip', 'application/octet-stream', 'multipart/x-zip', '.zip', '.7z'])
|
->acceptedFileTypes(['application/pdf'])
|
||||||
->maxSize(1024 * 5)
|
->maxSize(1024 * 5)
|
||||||
->directory('assignments-tmp/'.now()->toDateString())
|
->directory('assignments-tmp/'.now()->toDateString())
|
||||||
->nullable()
|
->nullable()
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public static function configure(Schema $schema): Schema
|
|||||||
return $schema
|
return $schema
|
||||||
->components([
|
->components([
|
||||||
AdvancedFileUpload::make('file')
|
AdvancedFileUpload::make('file')
|
||||||
->label(fn ($get) => $get('is_resubmit') ? 'Ganti Berkas Tugas (Opsional)' : 'Lampiran Berkas Tugas')
|
->label(fn ($get) => $get('is_resubmit') ? 'Ganti Berkas PDF (Opsional)' : 'Lampiran Berkas Tugas')
|
||||||
->pdfPreviewHeight(400)
|
->pdfPreviewHeight(400)
|
||||||
->pdfDisplayPage(1)
|
->pdfDisplayPage(1)
|
||||||
->pdfToolbar(true)
|
->pdfToolbar(true)
|
||||||
@ -21,12 +21,12 @@ public static function configure(Schema $schema): Schema
|
|||||||
->pdfFitType(PdfViewFit::FIT)
|
->pdfFitType(PdfViewFit::FIT)
|
||||||
->pdfNavPanes(true)
|
->pdfNavPanes(true)
|
||||||
->disk(config('filesystems.default'))
|
->disk(config('filesystems.default'))
|
||||||
->acceptedFileTypes(['application/pdf', 'application/zip', 'application/x-zip-compressed', 'application/x-zip', 'application/octet-stream', 'multipart/x-zip', '.zip', '.7z'])
|
->acceptedFileTypes(['application/pdf'])
|
||||||
->maxSize(1024 * 40)
|
->maxSize(1024 * 5)
|
||||||
->directory('submissions/'.now()->toDateString())
|
->directory('submissions/'.now()->toDateString())
|
||||||
->required(fn ($get) => ! $get('is_resubmit'))
|
->required(fn ($get) => ! $get('is_resubmit'))
|
||||||
->hiddenLabel()
|
->hiddenLabel()
|
||||||
->helperText('File PDF atau ZIP dengan ukuran maksimal 40MB.')
|
->helperText('Hanya file PDF dengan ukuran maksimal 5MB.')
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ protected function setUp(): void
|
|||||||
$session = ClassSession::find($arguments['session'] ?? null);
|
$session = ClassSession::find($arguments['session'] ?? null);
|
||||||
$date = $session ? $session->date?->toDateString() : null;
|
$date = $session ? $session->date?->toDateString() : null;
|
||||||
|
|
||||||
$url = URL::signedRoute('share.attendance', ['course' => $course->id, 'date' => $date]);
|
$url = URL::temporarySignedRoute('share.attendance', now()->addHour(), ['course' => $course->id, 'date' => $date]);
|
||||||
|
|
||||||
$livewire->js("if (navigator.clipboard) { navigator.clipboard.writeText('{$url}').catch(() => {}); }");
|
$livewire->js("if (navigator.clipboard) { navigator.clipboard.writeText('{$url}').catch(() => {}); }");
|
||||||
|
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
<?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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -36,7 +36,7 @@ protected function setUp(): void
|
|||||||
$session = ClassSession::find($arguments['session'] ?? null);
|
$session = ClassSession::find($arguments['session'] ?? null);
|
||||||
$date = $session ? $session->date?->toDateString() : null;
|
$date = $session ? $session->date?->toDateString() : null;
|
||||||
|
|
||||||
$url = URL::signedRoute('share.attendance', ['course' => $course->id, 'date' => $date]);
|
$url = URL::temporarySignedRoute('share.attendance', now()->addHour(), ['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}";
|
$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}";
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
use App\Filament\Actions\BackAction;
|
use App\Filament\Actions\BackAction;
|
||||||
use App\Filament\Resources\Learning\ClassSessions\Actions\DeleteSessionAction;
|
use App\Filament\Resources\Learning\ClassSessions\Actions\DeleteSessionAction;
|
||||||
use App\Filament\Resources\Learning\ClassSessions\Actions\EditSessionAction;
|
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\GenerateSessionsAction;
|
||||||
use App\Filament\Resources\Learning\ClassSessions\Actions\MarkAsSentAction;
|
use App\Filament\Resources\Learning\ClassSessions\Actions\MarkAsSentAction;
|
||||||
use App\Filament\Resources\Learning\ClassSessions\Actions\ShareAttendanceAction;
|
use App\Filament\Resources\Learning\ClassSessions\Actions\ShareAttendanceAction;
|
||||||
@ -127,11 +126,6 @@ public function deleteSessionAction(): DeleteSessionAction
|
|||||||
return DeleteSessionAction::make();
|
return DeleteSessionAction::make();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportAttendanceAction(): ExportSessionAttendanceAction
|
|
||||||
{
|
|
||||||
return ExportSessionAttendanceAction::make();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Computed]
|
#[Computed]
|
||||||
public function emptyStateHeading(): string
|
public function emptyStateHeading(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
use Illuminate\Http\Middleware\ValidatePostSize;
|
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
@ -12,7 +11,7 @@
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->remove(ValidatePostSize::class);
|
//
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"keywords": ["laravel", "framework"],
|
"keywords": ["laravel", "framework"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.3",
|
||||||
"achyutn/filament-log-viewer": "^2.1",
|
"achyutn/filament-log-viewer": "^2.1",
|
||||||
"asmit/filament-upload": "^2.0",
|
"asmit/filament-upload": "^2.0",
|
||||||
"bezhansalleh/filament-shield": "^4.1",
|
"bezhansalleh/filament-shield": "^4.1",
|
||||||
@ -18,8 +18,6 @@
|
|||||||
"joaopaulolndev/filament-pdf-viewer": "^3.0",
|
"joaopaulolndev/filament-pdf-viewer": "^3.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"maatwebsite/excel": "^3.1",
|
|
||||||
"pxlrbt/filament-excel": "^3.6",
|
|
||||||
"saade/filament-facehash": "^1.0"
|
"saade/filament-facehash": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|||||||
940
composer.lock
generated
940
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -130,7 +130,7 @@
|
|||||||
|
|
||||||
'temporary_file_upload' => [
|
'temporary_file_upload' => [
|
||||||
'disk' => env('LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK'), // Example: 'local', 's3' | Default: 'default'
|
'disk' => env('LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK'), // Example: 'local', 's3' | Default: 'default'
|
||||||
'rules' => ['required', 'file', 'max:40000'], // 40MB
|
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||||
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
||||||
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||||
@ -274,7 +274,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'payload' => [
|
'payload' => [
|
||||||
'max_size' => 1024 * 1024 * 40, // 40MB - maximum request payload size in bytes
|
'max_size' => 1024 * 1024, // 1MB - maximum request payload size in bytes
|
||||||
'max_nesting_depth' => 50, // Maximum depth of dot-notation property paths
|
'max_nesting_depth' => 50, // Maximum depth of dot-notation property paths
|
||||||
'max_calls' => 50, // Maximum method calls per request
|
'max_calls' => 50, // Maximum method calls per request
|
||||||
'max_components' => 20, // Maximum components per batch request
|
'max_components' => 20, // Maximum components per batch request
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
* The maximum file size of an item in bytes.
|
* The maximum file size of an item in bytes.
|
||||||
* Adding a larger file will result in an exception.
|
* Adding a larger file will result in an exception.
|
||||||
*/
|
*/
|
||||||
'max_file_size' => 1024 * 1024 * 40, // 40MB
|
'max_file_size' => 1024 * 1024 * 10, // 10MB
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This queue connection will be used to generate derived and responsive images.
|
* 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
@ -101,21 +101,12 @@
|
|||||||
|
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
@if ($item->has_file)
|
@if ($item->has_file)
|
||||||
@if ($item->is_pdf)
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
wire:click="mountAction('previewSubmission', { submissionId: {{ $item->submission_id }} })"
|
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">
|
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" />
|
<x-filament::icon icon="heroicon-o-eye" class="h-3.5 w-3.5" />
|
||||||
Lihat Tugas
|
Lihat Tugas
|
||||||
</button>
|
</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
|
@else
|
||||||
<span class="text-xs text-gray-400">-</span>
|
<span class="text-xs text-gray-400">-</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@ -35,28 +35,11 @@ class="prose prose-sm dark:prose-invert max-w-none text-gray-600 dark:text-gray-
|
|||||||
@if ($attachmentUrl = $this->record->getFirstMediaUrl('assignments'))
|
@if ($attachmentUrl = $this->record->getFirstMediaUrl('assignments'))
|
||||||
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
<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>
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Lampiran Tugas</p>
|
||||||
@if (str_ends_with(strtolower($attachmentUrl), '.pdf'))
|
|
||||||
<div
|
<div
|
||||||
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
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"
|
<iframe src="{{ $attachmentUrl }}" width="100%" height="500"
|
||||||
class="block border-0"></iframe>
|
class="block border-0"></iframe>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
@ -80,28 +63,11 @@ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-dange
|
|||||||
@if ($url = $this->getSubmissionFileUrl())
|
@if ($url = $this->getSubmissionFileUrl())
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File yang Terakhir Dikumpulkan</p>
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File yang Terakhir Dikumpulkan</p>
|
||||||
@if (str_ends_with(strtolower($url), '.pdf'))
|
|
||||||
<div
|
<div
|
||||||
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
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"
|
<iframe src="{{ $url }}" width="100%" height="400"
|
||||||
class="block border-0"></iframe>
|
class="block border-0"></iframe>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
@ -120,27 +86,11 @@ class="flex flex-col items-center justify-center py-8 text-center text-gray-500
|
|||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2 italic">File yang telah
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2 italic">File yang telah
|
||||||
dikumpulkan oleh kelompok Anda:</p>
|
dikumpulkan oleh kelompok Anda:</p>
|
||||||
@if (str_ends_with(strtolower($url), '.pdf'))
|
|
||||||
<div
|
<div
|
||||||
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
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"
|
<iframe src="{{ $url }}" width="100%" height="400"
|
||||||
class="block border-0"></iframe>
|
class="block border-0"></iframe>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
@ -162,28 +112,11 @@ class="block border-0"></iframe>
|
|||||||
@if ($this->isResubmit && ($url = $this->getSubmissionFileUrl()))
|
@if ($this->isResubmit && ($url = $this->getSubmissionFileUrl()))
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File Terkumpul Saat Ini</p>
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File Terkumpul Saat Ini</p>
|
||||||
@if (str_ends_with(strtolower($url), '.pdf'))
|
|
||||||
<div
|
<div
|
||||||
class="rounded-lg overflow-hidden border border-success-200 dark:border-success-800 bg-gray-50 dark:bg-gray-900">
|
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"
|
<iframe src="{{ $url }}" width="100%" height="400"
|
||||||
class="block border-0"></iframe>
|
class="block border-0"></iframe>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,6 @@ 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">
|
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->viewAttendanceAction)(['session' => $session->id]) }}
|
||||||
{{ ($this->shareAttendanceAction)(['session' => $session->id]) }}
|
{{ ($this->shareAttendanceAction)(['session' => $session->id]) }}
|
||||||
{{ ($this->exportAttendanceAction)(['session' => $session->id]) }}
|
|
||||||
{{ ($this->markAsSentAction)(['record' => $session->id]) }}
|
{{ ($this->markAsSentAction)(['record' => $session->id]) }}
|
||||||
{{ ($this->editSessionAction)(['session' => $session->id]) }}
|
{{ ($this->editSessionAction)(['session' => $session->id]) }}
|
||||||
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user