refactor: implement null-safe operator for safer property access across various models and pages

This commit is contained in:
Yoga Pangestu 2026-04-05 23:52:55 +07:00
parent b583e8b49c
commit 57e32fed73
37 changed files with 110 additions and 110 deletions

View File

@ -75,7 +75,7 @@ public function authenticate(): ?LoginResponse
return null; return null;
} }
$data = $this->form->getState(); $data = $this->form?->getState();
$authGuard = Filament::auth(); $authGuard = Filament::auth();
@ -105,7 +105,7 @@ public function authenticate(): ?LoginResponse
filled($this->userUndertakingMultiFactorAuthentication) && filled($this->userUndertakingMultiFactorAuthentication) &&
(decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier()) (decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier())
) { ) {
$this->multiFactorChallengeForm->validate(); $this->multiFactorChallengeForm?->validate();
} else { } else {
foreach (Filament::getMultiFactorAuthenticationProviders() as $multiFactorAuthenticationProvider) { foreach (Filament::getMultiFactorAuthenticationProviders() as $multiFactorAuthenticationProvider) {
if (! $multiFactorAuthenticationProvider->isEnabled($user)) { if (! $multiFactorAuthenticationProvider->isEnabled($user)) {
@ -122,7 +122,7 @@ public function authenticate(): ?LoginResponse
} }
if (filled($this->userUndertakingMultiFactorAuthentication)) { if (filled($this->userUndertakingMultiFactorAuthentication)) {
$this->multiFactorChallengeForm->fill(); $this->multiFactorChallengeForm?->fill();
return null; return null;
} }

View File

@ -71,7 +71,7 @@ public static function getPagePermission(): string
public function mount(): void public function mount(): void
{ {
$this->form->fill(); $this->form?->fill();
} }
public function form(Schema $schema): Schema public function form(Schema $schema): Schema
@ -132,15 +132,15 @@ public function getSchedules(): Collection
->map(function ($daySchedules) { ->map(function ($daySchedules) {
return $daySchedules->map(fn ($schedule) => (object) [ return $daySchedules->map(fn ($schedule) => (object) [
'id' => $schedule->id, 'id' => $schedule->id,
'course_code' => $schedule->course->code ?? 'MATKUL', 'course_code' => $schedule->course?->code ?? 'MATKUL',
'course_name' => $schedule->course->name ?? 'Mata Kuliah Tidak Diketahui', 'course_name' => $schedule->course?->name ?? 'Mata Kuliah Tidak Diketahui',
'lecturer' => $schedule->course->lecturer ?? 'Dosen Belum Ditentukan', 'lecturer' => $schedule->course?->lecturer ?? 'Dosen Belum Ditentukan',
'semester' => $schedule->course->semester, 'semester' => $schedule->course?->semester,
'credit' => $schedule->course->credit ?? '-', 'credit' => $schedule->course?->credit ?? '-',
'room' => $schedule->room, 'room' => $schedule->room,
'mode_label' => $schedule->mode?->getLabel(), 'mode_label' => $schedule->mode?->getLabel(),
'mode_color' => $schedule->mode?->getColor(), 'mode_color' => $schedule->mode?->getColor(),
'time_range' => $schedule->start_time->format('H:i').' '.$schedule->end_time->format('H:i'), 'time_range' => $schedule->start_time?->format('H:i').' '.$schedule->end_time?->format('H:i'),
]); ]);
}); });
} }

View File

@ -33,8 +33,8 @@ protected function setUp(): void
return [ return [
'name' => $record->name, 'name' => $record->name,
'leader_id' => $record->leader_id, 'leader_id' => $record->leader_id,
'course_id' => $record->courses->pluck('id')->toArray(), 'course_id' => $record->courses?->pluck('id')->toArray(),
'students' => $record->students->pluck('id')->toArray(), 'students' => $record->students?->pluck('id')->toArray(),
]; ];
}) })
->after(function (StudyGroup $record, array $data) { ->after(function (StudyGroup $record, array $data) {

View File

@ -81,7 +81,7 @@ public function emptyDescription(): string
public function mount(): void public function mount(): void
{ {
$this->form->fill([ $this->form?->fill([
'course_id' => $this->course_id, 'course_id' => $this->course_id,
]); ]);
} }
@ -141,20 +141,20 @@ public function studyGroups(): Collection
$studentId = auth()->user()?->student?->id; $studentId = auth()->user()?->student?->id;
return $query->orderBy('name', 'asc')->get()->map(function ($record) use ($studentId) { return $query->orderBy('name', 'asc')->get()->map(function ($record) use ($studentId) {
$isMyGroup = ($studentId && ($record->leader_id === $studentId || $record->students->contains($studentId))); $isMyGroup = ($studentId && ($record->leader_id === $studentId || $record->students?->contains($studentId)));
return (object) [ return (object) [
'id' => $record->id, 'id' => $record->id,
'name' => $record->name, 'name' => $record->name,
'is_my_group' => $isMyGroup, 'is_my_group' => $isMyGroup,
'leader_avatar' => $record->leader?->user?->facehash_avatar_url, 'leader_avatar' => $record->leader?->user?->facehash_avatar_url,
'leader_name' => $record->leader->full_name ?? 'Belum Ditentukan', 'leader_name' => $record->leader?->full_name ?? 'Belum Ditentukan',
'is_leader' => $studentId && $record->leader_id === $studentId, 'is_leader' => $studentId && $record->leader_id === $studentId,
'courses' => $record->courses->map(fn ($c) => (object) [ 'courses' => $record->courses?->map(fn ($c) => (object) [
'name' => $c->name, 'name' => $c->name,
]), ]),
'students_count' => $record->students->count(), 'students_count' => $record->students?->count() ?? 0,
'students' => $record->students->map(fn ($s) => (object) [ 'students' => $record->students?->map(fn ($s) => (object) [
'id' => $s->id, 'id' => $s->id,
'full_name' => $s->full_name, 'full_name' => $s->full_name,
'is_me' => $studentId && $s->id === $studentId, 'is_me' => $studentId && $s->id === $studentId,
@ -177,7 +177,7 @@ public function isMyGroup(StudyGroup $record): bool
return false; return false;
} }
return $record->leader_id === $studentId || $record->students->contains($studentId); return $record->leader_id === $studentId || $record->students?->contains($studentId);
} }
public function studyGroupFormSchema(): array public function studyGroupFormSchema(): array

View File

@ -243,7 +243,7 @@ public function save(): void
$this->callHook('beforeValidate'); $this->callHook('beforeValidate');
$data = $this->form->getState(); $data = $this->form?->getState();
$this->callHook('afterValidate'); $this->callHook('afterValidate');
@ -311,13 +311,13 @@ protected function handleRecordUpdate(Model $record, array $data): Model
/** @var User $record */ /** @var User $record */
if ($studentData && $record->student()->exists()) { if ($studentData && $record->student()->exists()) {
$record->student->update($studentData); $record->student?->update($studentData);
} elseif ($studentData) { } elseif ($studentData) {
$record->student()->create($studentData); $record->student()->create($studentData);
} }
if ($settingsData && $record->settings()->exists()) { if ($settingsData && $record->settings()->exists()) {
$record->settings->update($settingsData); $record->settings?->update($settingsData);
} elseif ($settingsData) { } elseif ($settingsData) {
$record->settings()->create($settingsData); $record->settings()->create($settingsData);
} }
@ -330,7 +330,7 @@ protected function afterSave(): void
SystemNotification::send('profile_updated') SystemNotification::send('profile_updated')
->send(); ->send();
if (filled($this->form->getState()['password'] ?? null)) { if (filled($this->form?->getState()['password'] ?? null)) {
filament()->auth()->logout(); filament()->auth()->logout();
session()->invalidate(); session()->invalidate();
session()->regenerateToken(); session()->regenerateToken();

View File

@ -92,7 +92,7 @@ protected function afterSave(): void
$student = Student::find($kosmaId); $student = Student::find($kosmaId);
if ($student?->user) { if ($student?->user) {
$student->user->assignRole(RoleEnum::Kosma); $student->user?->assignRole(RoleEnum::Kosma);
} }
} }
} }

View File

@ -30,7 +30,7 @@ protected function setUp(): void
if ($record->type === AssignmentType::Group) { if ($record->type === AssignmentType::Group) {
return $record->studyGroups() return $record->studyGroups()
->where('leader_id', auth()->user()->student->id) ->where('leader_id', auth()->user()?->student?->id)
->exists(); ->exists();
} }

View File

@ -41,17 +41,17 @@ protected function getSavedNotification(): ?Notification
protected function mutateFormDataBeforeFill(array $data): array protected function mutateFormDataBeforeFill(array $data): array
{ {
$data['student_ids'] = $this->record->assignmentTargets() $data['student_ids'] = $this->record?->assignmentTargets()
->whereNotNull('student_id') ->whereNotNull('student_id')
->pluck('student_id') ->pluck('student_id')
->toArray(); ->toArray();
$data['study_group_ids'] = $this->record->assignmentTargets() $data['study_group_ids'] = $this->record?->assignmentTargets()
->whereNotNull('study_group_id') ->whereNotNull('study_group_id')
->pluck('study_group_id') ->pluck('study_group_id')
->toArray(); ->toArray();
$media = $this->record->getMedia('assignments')->last(); $media = $this->record?->getMedia('assignments')->last();
$relativePath = $media ? $media->getPathRelativeToRoot() : null; $relativePath = $media ? $media->getPathRelativeToRoot() : null;
$data['pdf'] = $relativePath ? [$relativePath] : []; $data['pdf'] = $relativePath ? [$relativePath] : [];

View File

@ -46,7 +46,7 @@ public static function getPagePermission(): string
public function mount(): void public function mount(): void
{ {
$this->form->fill([ $this->form?->fill([
'search' => $this->search, 'search' => $this->search,
'course_id' => $this->course_id, 'course_id' => $this->course_id,
]); ]);
@ -221,7 +221,7 @@ public function assignments(): Collection
private function getAssignmentPriority($assignment): int private function getAssignmentPriority($assignment): int
{ {
$isSubmitted = $assignment->assignmentSubmissions->isNotEmpty(); $isSubmitted = $assignment->assignmentSubmissions?->isNotEmpty();
$isOverdue = now()->isAfter($assignment->due_date); $isOverdue = now()->isAfter($assignment->due_date);
if (! $isOverdue) { if (! $isOverdue) {
@ -244,14 +244,14 @@ public function assignmentCards()
$pinnedIds = $this->pinnedIds; $pinnedIds = $this->pinnedIds;
return $this->assignments()->map(function ($assignment) use ($studentProfile, $pinnedIds) { return $this->assignments()->map(function ($assignment) use ($studentProfile, $pinnedIds) {
$submission = $assignment->assignmentSubmissions->first(); $submission = $assignment->assignmentSubmissions?->first();
$isSubmitted = $submission !== null; $isSubmitted = $submission !== null;
$isGroup = $assignment->type === AssignmentType::Group; $isGroup = $assignment->type === AssignmentType::Group;
$isLeader = false; $isLeader = false;
if ($isGroup) { if ($isGroup) {
$userGroup = $assignment->studyGroups->first(function ($g) use ($studentProfile) { $userGroup = $assignment->studyGroups?->first(function ($g) use ($studentProfile) {
return $g->leader_id === $studentProfile->id || $g->students->contains($studentProfile->id); return $g->leader_id === $studentProfile->id || $g->students?->contains($studentProfile->id);
}); });
$isLeader = $userGroup && $userGroup->leader_id === $studentProfile->id; $isLeader = $userGroup && $userGroup->leader_id === $studentProfile->id;
} }
@ -263,7 +263,7 @@ public function assignmentCards()
$canSubmitActual = $canSubmit && $canSubmitByRole; $canSubmitActual = $canSubmit && $canSubmitByRole;
$isUrgent = ! $isSubmitted && $canSubmitActual && now()->diffInHours($assignment->due_date) <= 48; $isUrgent = ! $isSubmitted && $canSubmitActual && now()->diffInHours($assignment->due_date) <= 48;
$isNew = $assignment->created_at->diffInDays(now()) <= 3; $isNew = $assignment->created_at?->diffInDays(now()) <= 3;
$isPinned = in_array($assignment->id, $pinnedIds); $isPinned = in_array($assignment->id, $pinnedIds);
$statusLabel = SystemNotification::getByKey('labels.assignment_status.not_submitted'); $statusLabel = SystemNotification::getByKey('labels.assignment_status.not_submitted');
@ -288,8 +288,8 @@ public function assignmentCards()
'id' => $assignment->id, 'id' => $assignment->id,
'title' => $assignment->title, 'title' => $assignment->title,
'course_name' => $assignment->course?->name ?? '-', 'course_name' => $assignment->course?->name ?? '-',
'due_date_formatted' => $assignment->due_date->translatedFormat('l, d F Y H:i'), 'due_date_formatted' => $assignment->due_date?->translatedFormat('l, d F Y H:i'),
'submitted_at_formatted' => ($isSubmitted && $submission->submitted_at) ? $submission->submitted_at->translatedFormat('l, d F Y H:i') : null, 'submitted_at_formatted' => ($isSubmitted && $submission->submitted_at) ? $submission->submitted_at?->translatedFormat('l, d F Y H:i') : null,
'is_submitted' => $isSubmitted, 'is_submitted' => $isSubmitted,
'is_group' => $isGroup, 'is_group' => $isGroup,
'is_leader' => $isLeader, 'is_leader' => $isLeader,

View File

@ -30,7 +30,7 @@ public function statCards(): array
{ {
return [ return [
[ [
'label' => $this->record->type === AssignmentType::Individual ? 'Total Mahasiswa' : 'Total Kelompok', 'label' => $this->record?->type === AssignmentType::Individual ? 'Total Mahasiswa' : 'Total Kelompok',
'value' => $this->totalCount, 'value' => $this->totalCount,
'icon' => 'heroicon-o-user-group', 'icon' => 'heroicon-o-user-group',
'color_classes' => 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-900', 'color_classes' => 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-900',
@ -65,11 +65,11 @@ public function progressColorClass(): string
#[Computed] #[Computed]
public function submissionSummary(): Collection public function submissionSummary(): Collection
{ {
$assignment = $this->record->load(['students', 'studyGroups', 'course']); $assignment = $this->record?->load(['students', 'studyGroups', 'course']);
$isIndividual = $assignment->type === AssignmentType::Individual; $isIndividual = $assignment->type === AssignmentType::Individual;
if ($isIndividual) { if ($isIndividual) {
$targets = $assignment->students->keyBy('id'); $targets = $assignment->students?->keyBy('id') ?? collect();
$submissions = AssignmentSubmission::with('student') $submissions = AssignmentSubmission::with('student')
->where('assignment_id', $assignment->id) ->where('assignment_id', $assignment->id)
@ -87,7 +87,7 @@ public function submissionSummary(): Collection
'secondary_info' => $student->student_number, 'secondary_info' => $student->student_number,
'submission_id' => $submission?->id, 'submission_id' => $submission?->id,
'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'),
'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',
@ -98,7 +98,7 @@ public function submissionSummary(): Collection
]; ];
})->sortBy('secondary_info')->values(); })->sortBy('secondary_info')->values();
} else { } else {
$targets = $assignment->studyGroups->keyBy('id'); $targets = $assignment->studyGroups?->keyBy('id') ?? collect();
$submissions = AssignmentSubmission::with(['studyGroup', 'student']) $submissions = AssignmentSubmission::with(['studyGroup', 'student'])
->where('assignment_id', $assignment->id) ->where('assignment_id', $assignment->id)
@ -113,7 +113,7 @@ public function submissionSummary(): Collection
'id' => $group->id, 'id' => $group->id,
'is_individual' => false, 'is_individual' => false,
'primary_name' => $group->name, 'primary_name' => $group->name,
'secondary_info' => $isSubmitted ? $submission->student->full_name : '-', 'secondary_info' => $isSubmitted ? $submission?->student?->full_name : '-',
'submission_id' => $submission?->id, 'submission_id' => $submission?->id,
'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') : '-',
@ -132,13 +132,13 @@ public function submissionSummary(): Collection
#[Computed] #[Computed]
public function totalCount(): int public function totalCount(): int
{ {
return $this->submissionSummary->count(); return $this->submissionSummary?->count();
} }
#[Computed] #[Computed]
public function doneCount(): int public function doneCount(): int
{ {
return $this->submissionSummary->where('submitted', true)->count(); return $this->submissionSummary?->where('submitted', true)->count();
} }
#[Computed] #[Computed]
@ -152,17 +152,17 @@ public function percentage(): int
#[Computed] #[Computed]
public function isOverdue(): bool public function isOverdue(): bool
{ {
return now()->isAfter($this->record->due_date); return now()->isAfter($this->record?->due_date);
} }
#[Computed] #[Computed]
public function assignmentSummary(): array public function assignmentSummary(): array
{ {
return [ return [
'title' => $this->record->title, 'title' => $this->record?->title,
'course' => $this->record->course?->name ?? '-', 'course' => $this->record?->course?->name ?? '-',
'due_date' => $this->record->due_date?->translatedFormat('l, d F Y H:i'), 'due_date' => $this->record?->due_date?->translatedFormat('l, d F Y H:i'),
'type' => $this->record->type->value, 'type' => $this->record?->type->value,
'is_overdue' => $this->isOverdue, 'is_overdue' => $this->isOverdue,
]; ];
} }

View File

@ -42,7 +42,7 @@ public function mount(Assignment $record): void
{ {
$this->record = $record; $this->record = $record;
$this->form->fill([ $this->form?->fill([
'is_resubmit' => $this->isResubmit, 'is_resubmit' => $this->isResubmit,
]); ]);
} }
@ -59,7 +59,7 @@ public function statusCards(): array
return [ return [
[ [
'label' => 'Batas Waktu', 'label' => 'Batas Waktu',
'value' => $this->record->due_date?->translatedFormat('l, d F Y H:i'), 'value' => $this->record?->due_date?->translatedFormat('l, d F Y H:i'),
'icon' => 'heroicon-o-clock', 'icon' => 'heroicon-o-clock',
'is_danger' => $this->isOverdue, 'is_danger' => $this->isOverdue,
'badge' => $this->isOverdue ? '(Terlewat)' : null, 'badge' => $this->isOverdue ? '(Terlewat)' : null,
@ -98,7 +98,7 @@ public function currentGroup(): ?StudyGroup
{ {
$student = $this->student; $student = $this->student;
return $this->record->studyGroups() return $this->record?->studyGroups()
->where(fn ($q) => $q->where('leader_id', $student->id)->orWhereHas('students', fn ($sq) => $sq->whereKey($student->id))) ->where(fn ($q) => $q->where('leader_id', $student->id)->orWhereHas('students', fn ($sq) => $sq->whereKey($student->id)))
->first(); ->first();
} }
@ -108,18 +108,18 @@ public function existingSubmission(): ?AssignmentSubmission
{ {
$student = $this->student; $student = $this->student;
if ($this->record->type === AssignmentType::Group) { if ($this->record?->type === AssignmentType::Group) {
$group = $this->currentGroup; $group = $this->currentGroup;
if (! $group) { if (! $group) {
return null; return null;
} }
return AssignmentSubmission::where('assignment_id', $this->record->id) return AssignmentSubmission::where('assignment_id', $this->record?->id)
->where('study_group_id', $group->id) ->where('study_group_id', $group->id)
->first(); ->first();
} }
return AssignmentSubmission::where('assignment_id', $this->record->id) return AssignmentSubmission::where('assignment_id', $this->record?->id)
->where('student_id', $student->id) ->where('student_id', $student->id)
->first(); ->first();
} }
@ -133,19 +133,19 @@ public function isResubmit(): bool
#[Computed] #[Computed]
public function isOverdue(): bool public function isOverdue(): bool
{ {
return now()->isAfter($this->record->due_date); return now()->isAfter($this->record?->due_date);
} }
#[Computed] #[Computed]
public function canSubmit(): bool public function canSubmit(): bool
{ {
if ($this->record->type === AssignmentType::Individual) { if ($this->record?->type === AssignmentType::Individual) {
return true; return true;
} }
$group = $this->currentGroup; $group = $this->currentGroup;
return $group && $group->leader_id === $this->student->id; return $group && $group->leader_id === $this->student?->id;
} }
#[Computed] #[Computed]
@ -251,7 +251,7 @@ public function submit(): void
} }
$existingSubmission = $this->existingSubmission; $existingSubmission = $this->existingSubmission;
$state = $this->form->getState(); $state = $this->form?->getState();
if (! $existingSubmission && empty($state['file'])) { if (! $existingSubmission && empty($state['file'])) {
SystemNotification::send('submission_file_missing', type: 'warning') SystemNotification::send('submission_file_missing', type: 'warning')
@ -300,7 +300,7 @@ public function submit(): void
->send(); ->send();
} }
$this->form->fill([ $this->form?->fill([
'is_resubmit' => $this->isResubmit, 'is_resubmit' => $this->isResubmit,
]); ]);
unset($this->existingSubmission, $this->isResubmit); unset($this->existingSubmission, $this->isResubmit);

View File

@ -95,7 +95,7 @@ public function scheduleCards()
{ {
return $this->getSchedules() return $this->getSchedules()
->map(function ($schedule) { ->map(function ($schedule) {
$attendance = $schedule->attendances->first(); $attendance = $schedule->attendances?->first();
$isAttended = $attendance !== null; $isAttended = $attendance !== null;
$now = now(); $now = now();
@ -118,15 +118,15 @@ public function scheduleCards()
return (object) [ return (object) [
'id' => $schedule->id, 'id' => $schedule->id,
'course_name' => $schedule->course->name, 'course_name' => $schedule->course?->name,
'lecturer_name' => $schedule->course->lecturer ?? 'Belum Ditentukan', 'lecturer_name' => $schedule->course?->lecturer ?? 'Belum Ditentukan',
'time_range' => $schedule->start_time->format('H:i').' - '.$schedule->end_time->format('H:i'), 'time_range' => $schedule->start_time?->format('H:i').' - '.$schedule->end_time?->format('H:i'),
'is_attended' => $isAttended, 'is_attended' => $isAttended,
'can_attend' => $canAttend && ! $isAttended, 'can_attend' => $canAttend && ! $isAttended,
'status_label' => $statusLabel, 'status_label' => $statusLabel,
'status_color' => $statusColor, 'status_color' => $statusColor,
'status_icon' => $statusIcon, 'status_icon' => $statusIcon,
'attended_at' => $isAttended ? $attendance->attended_at->format('H:i') : null, 'attended_at' => $isAttended ? $attendance?->attended_at?->format('H:i') : null,
// Pre-calculated classes // Pre-calculated classes
'card_classes' => Arr::toCssClasses([ 'card_classes' => Arr::toCssClasses([
'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative', 'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative',
@ -204,14 +204,14 @@ public function attend(int $sessionId): void
// Find schedule for compatibility // Find schedule for compatibility
$schedule = CourseSchedule::where('course_id', $session->course_id) $schedule = CourseSchedule::where('course_id', $session->course_id)
->where('day_of_week', $session->date->dayOfWeekIso) ->where('day_of_week', $session->date?->dayOfWeekIso)
->first(); ->first();
Attendance::create([ Attendance::create([
'student_id' => $student->id, 'student_id' => $student->id,
'class_session_id' => $sessionId, 'class_session_id' => $sessionId,
'course_schedule_id' => $schedule?->id, 'course_schedule_id' => $schedule?->id,
'date' => $session->date->toDateString(), 'date' => $session->date?->toDateString(),
'attended_at' => now(), 'attended_at' => now(),
]); ]);
@ -232,7 +232,7 @@ public function table(Table $table): Table
->label('Tanggal') ->label('Tanggal')
->date('l, d F Y') ->date('l, d F Y')
->sortable() ->sortable()
->description(fn (Attendance $record): string => $record->attended_at->format('H:i').' ') ->description(fn (Attendance $record): string => $record->attended_at?->format('H:i').' ')
->color('gray'), ->color('gray'),
TextColumn::make('courseSchedule.course.name') TextColumn::make('courseSchedule.course.name')
@ -240,7 +240,7 @@ public function table(Table $table): Table
->searchable() ->searchable()
->sortable() ->sortable()
->wrap() ->wrap()
->description(fn (Attendance $record): string => $record->courseSchedule->course->lecturer ?? 'Belum Ditentukan'), ->description(fn (Attendance $record): string => $record->courseSchedule?->course?->lecturer ?? 'Belum Ditentukan'),
]) ])
->defaultSort('date', 'desc') ->defaultSort('date', 'desc')
->filters([ ->filters([

View File

@ -24,7 +24,7 @@ protected function setUp(): void
->action(function (array $arguments, $livewire) { ->action(function (array $arguments, $livewire) {
$course = $livewire->course; $course = $livewire->course;
$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::temporarySignedRoute('share.attendance', now()->addHour(), ['course' => $course->id, 'date' => $date]); $url = URL::temporarySignedRoute('share.attendance', now()->addHour(), ['course' => $course->id, 'date' => $date]);

View File

@ -28,7 +28,7 @@ protected function setUp(): void
->modalHeading('Generate Sesi Pembelajaran') ->modalHeading('Generate Sesi Pembelajaran')
->modalDescription('Sistem akan men-generate atau memperbarui sesi secara otomatis berdasarkan jadwal mata kuliah ini.') ->modalDescription('Sistem akan men-generate atau memperbarui sesi secara otomatis berdasarkan jadwal mata kuliah ini.')
->schema(function ($livewire) { ->schema(function ($livewire) {
$lastSession = $livewire->course->classSessions()->latest('session_number')->first(); $lastSession = $livewire->course?->classSessions()->latest('session_number')->first();
$lastNumber = $lastSession?->session_number ?? 0; $lastNumber = $lastSession?->session_number ?? 0;
$nextNumber = $lastNumber + 1; $nextNumber = $lastNumber + 1;
$lastDate = $lastSession?->date ? Carbon::parse($lastSession->date) : now(); $lastDate = $lastSession?->date ? Carbon::parse($lastSession->date) : now();

View File

@ -34,11 +34,11 @@ protected function setUp(): void
->action(function (array $arguments, $livewire) { ->action(function (array $arguments, $livewire) {
$course = $livewire->course; $course = $livewire->course;
$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::temporarySignedRoute('share.attendance', now()->addHour(), ['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}";
$escapedText = json_encode($text); $escapedText = json_encode($text);

View File

@ -37,7 +37,7 @@ protected function setUp(): void
return ['assignment' => null]; return ['assignment' => null];
} }
$assignment = $session->assignments->first(); $assignment = $session->assignments?->first();
if (! $assignment) { if (! $assignment) {
return ['assignment' => null]; return ['assignment' => null];
@ -48,7 +48,7 @@ protected function setUp(): void
->orderBy('full_name') ->orderBy('full_name')
->get(); ->get();
$submissionMap = $assignment->assignmentSubmissions->keyBy('student_id'); $submissionMap = $assignment->assignmentSubmissions?->keyBy('student_id');
$data = (object) [ $data = (object) [
'id' => $assignment->id, 'id' => $assignment->id,

View File

@ -40,7 +40,7 @@ protected function setUp(): void
->orderBy('full_name') ->orderBy('full_name')
->get(); ->get();
$attendanceMap = $session->attendances->keyBy('student_id'); $attendanceMap = $session->attendances?->keyBy('student_id');
$students = $activeStudents->map(fn ($student) => (object) [ $students = $activeStudents->map(fn ($student) => (object) [
'student' => $student, 'student' => $student,

View File

@ -46,15 +46,15 @@ public function sessions(): Collection
->whereHas('user', fn ($q) => $q->active()) ->whereHas('user', fn ($q) => $q->active())
->count(); ->count();
return $this->course->classSessions() return $this->course?->classSessions()
->withCount(['attendances', 'materials', 'assignments']) ->withCount(['attendances', 'materials', 'assignments'])
->orderByDesc('session_number') ->orderByDesc('session_number')
->get() ->get()
->map(fn ($session) => (object) [ ->map(fn ($session) => (object) [
'id' => $session->id, 'id' => $session->id,
'session_number' => $session->session_number, 'session_number' => $session->session_number,
'date_formatted' => $session->date->translatedFormat('l, d F Y'), 'date_formatted' => $session->date?->translatedFormat('l, d F Y'),
'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'), 'time_range' => $session->start_time?->format('H:i').' - '.$session->end_time?->format('H:i'),
'attendances_count' => $session->attendances_count, 'attendances_count' => $session->attendances_count,
'total_students' => $totalActiveStudents, 'total_students' => $totalActiveStudents,
'attendance_percentage' => $totalActiveStudents > 0 ? round(($session->attendances_count / $totalActiveStudents) * 100) : 0, 'attendance_percentage' => $totalActiveStudents > 0 ? round(($session->attendances_count / $totalActiveStudents) * 100) : 0,
@ -71,12 +71,12 @@ public function form(Schema $schema): Schema
#[Computed] #[Computed]
public function description(): string public function description(): string
{ {
return 'Data sesi pembelajaran untuk mata kuliah '.$this->course->name; return 'Data sesi pembelajaran untuk mata kuliah '.$this->course?->name;
} }
public function getTitle(): string public function getTitle(): string
{ {
return 'Sesi Kelas - '.$this->course->name; return 'Sesi Kelas - '.$this->course?->name;
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array

View File

@ -32,7 +32,7 @@ class ManageClassSessions extends Page implements HasActions, HasForms
public function mount(): void public function mount(): void
{ {
$this->form->fill(); $this->form?->fill();
} }
#[Computed] #[Computed]
@ -76,10 +76,10 @@ public function todaySessions(): Collection
'id' => $session->id, 'id' => $session->id,
'is_pending' => false, 'is_pending' => false,
'session_number' => $session->session_number, 'session_number' => $session->session_number,
'course_name' => $session->course->name, 'course_name' => $session->course?->name,
'course_code' => $session->course->code, 'course_code' => $session->course?->code,
'lecturer' => $session->course->lecturer ?? '-', 'lecturer' => $session->course?->lecturer ?? '-',
'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'), 'time_range' => $session->start_time?->format('H:i').' - '.$session->end_time?->format('H:i'),
'attendances_count' => $session->attendances_count, 'attendances_count' => $session->attendances_count,
'total_students' => $totalActiveStudents, 'total_students' => $totalActiveStudents,
'attendance_percentage' => $percentage, 'attendance_percentage' => $percentage,
@ -124,7 +124,7 @@ public function courses(): Collection
'name' => $course->name, 'name' => $course->name,
'code' => $course->code, 'code' => $course->code,
'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan', 'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan',
'sessions_count' => $course->classSessions->count(), 'sessions_count' => $course->classSessions?->count(),
'total_students' => $totalActiveStudents, 'total_students' => $totalActiveStudents,
'url' => ClassSessionResource::getUrl('course', ['course' => $course]), 'url' => ClassSessionResource::getUrl('course', ['course' => $course]),
]; ];

View File

@ -36,7 +36,7 @@ public static function configure(Table $table): Table
->label('Mata Kuliah') ->label('Mata Kuliah')
->searchable() ->searchable()
->sortable() ->sortable()
->description(fn (Material $record) => $record->classSession?->session_number ? "Sesi Ke-{$record->classSession->session_number}" : ''), ->description(fn (Material $record) => $record->classSession?->session_number ? "Sesi Ke-{$record->classSession?->session_number}" : ''),
TextColumn::make('title') TextColumn::make('title')
->label('Judul') ->label('Judul')

View File

@ -23,7 +23,7 @@ public function show(Request $request, Course $course): View
->latest('date') ->latest('date')
->first(); ->first();
$defaultDate = $latestAttendance ? $latestAttendance->date->toDateString() : now()->toDateString(); $defaultDate = $latestAttendance ? $latestAttendance->date?->toDateString() : now()->toDateString();
$date = $request->query('date', $defaultDate); $date = $request->query('date', $defaultDate);
$attendances = Attendance::with('student') $attendances = Attendance::with('student')

View File

@ -28,7 +28,7 @@ public function handle(Request $request, Closure $next): Response
if ($user && $user->settings) { if ($user && $user->settings) {
// Register Colors // Register Colors
FilamentColor::register([ FilamentColor::register([
'primary' => match ($user->settings->primary_color) { 'primary' => match ($user->settings?->primary_color) {
'blue' => Color::Blue, 'blue' => Color::Blue,
'sky' => Color::Sky, 'sky' => Color::Sky,
'cyan' => Color::Cyan, 'cyan' => Color::Cyan,
@ -48,12 +48,12 @@ public function handle(Request $request, Closure $next): Response
// Register Top Navigation // Register Top Navigation
$panel = Filament::getCurrentOrDefaultPanel(); $panel = Filament::getCurrentOrDefaultPanel();
if ($panel && method_exists($panel, 'topNavigation')) { if ($panel && method_exists($panel, 'topNavigation')) {
$panel->topNavigation((bool) $user->settings->top_navigation); $panel->topNavigation((bool) $user->settings?->top_navigation);
} }
// Register Font & UI Styles // Register Font & UI Styles
$font = $user->settings->font ?? 'Inter'; $font = $user->settings?->font ?? 'Inter';
$radius = match ($user->settings->border_radius ?? 'md') { $radius = match ($user->settings?->border_radius ?? 'md') {
'none' => '0px', 'none' => '0px',
'md' => '0.375rem', 'md' => '0.375rem',
'lg' => '0.5rem', 'lg' => '0.5rem',
@ -61,7 +61,7 @@ public function handle(Request $request, Closure $next): Response
'2xl' => '1rem', '2xl' => '1rem',
default => '0.5rem', default => '0.5rem',
}; };
$maxWidth = match ($user->settings->content_width ?? 'full') { $maxWidth = match ($user->settings?->content_width ?? 'full') {
'centered' => '80rem', 'centered' => '80rem',
default => 'none', default => 'none',
}; };

View File

@ -42,7 +42,7 @@ protected function formattedDueDate(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -16,7 +16,7 @@ class AssignmentPin extends Model
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -37,7 +37,7 @@ protected function formattedSubmittedAt(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -21,7 +21,7 @@ class AssignmentTarget extends Model
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -41,7 +41,7 @@ protected function formattedAttendedAt(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -42,12 +42,12 @@ protected function formattedVersion(): Attribute
protected function formattedReleaseDate(): Attribute protected function formattedReleaseDate(): Attribute
{ {
return Attribute::get(fn () => $this->release_date->translatedFormat('l, d M Y')); return Attribute::get(fn () => $this->release_date?->translatedFormat('l, d M Y'));
} }
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -50,7 +50,7 @@ protected function formattedEndTime(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -23,7 +23,7 @@ class Course extends Model
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -43,7 +43,7 @@ protected function formattedEndTime(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -24,7 +24,7 @@ class Material extends Model implements HasMedia
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -56,7 +56,7 @@ protected function formattedDateOfBirth(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -24,7 +24,7 @@ class StudyGroup extends Model
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -21,7 +21,7 @@ class StudyGroupMember extends Model
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -68,7 +68,7 @@ protected function name(): Attribute
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute

View File

@ -27,7 +27,7 @@ protected function casts(): array
protected function formattedCreatedAt(): Attribute protected function formattedCreatedAt(): Attribute
{ {
return Attribute::get(fn () => $this->created_at->translatedFormat('l, d M Y H:i')); return Attribute::get(fn () => $this->created_at?->translatedFormat('l, d M Y H:i'));
} }
protected function formattedUpdatedAt(): Attribute protected function formattedUpdatedAt(): Attribute