refactor: update course schedule and study group views for improved structure and functionality, including renaming views and enhancing data presentation

This commit is contained in:
Yoga Pangestu 2026-03-29 22:51:29 +07:00
parent 59af7bb595
commit 6d58190e28
19 changed files with 582 additions and 641 deletions

View File

@ -23,6 +23,7 @@
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use UnitEnum;
class Index extends Page implements HasActions, HasForms
@ -43,7 +44,7 @@ class Index extends Page implements HasActions, HasForms
protected static ?string $slug = 'information/course-schedules';
protected string $view = 'filament.pages.information.course-schedules';
protected string $view = 'filament.pages.information.course-schedule.index';
public ?string $search = '';
@ -111,23 +112,39 @@ public function getSchedules(): Collection
['day_of_week', 'asc'],
['start_time', 'asc'],
])
->groupBy('day_of_week');
->groupBy('day_of_week')
->map(function ($daySchedules) {
return $daySchedules->map(fn ($schedule) => (object) [
'id' => $schedule->id,
'course_code' => $schedule->course->code ?? 'MATKUL',
'course_name' => $schedule->course->name ?? 'Mata Kuliah Tidak Diketahui',
'lecturer' => $schedule->course->lecturer ?? 'Dosen Belum Ditentukan',
'semester' => $schedule->course->semester,
'credit' => $schedule->course->credit ?? '-',
'room' => $schedule->room,
'mode_label' => $schedule->mode?->getLabel(),
'mode_color' => $schedule->mode?->getColor(),
'time_range' => $schedule->start_time->format('H:i').' '.$schedule->end_time->format('H:i'),
]);
});
}
protected function getViewData(): array
#[Computed]
public function schedulesGrouped(): Collection
{
return [
'schedules' => $this->getSchedules(),
'days' => [
1 => 'Senin',
2 => 'Selasa',
3 => 'Rabu',
4 => 'Kamis',
5 => 'Jumat',
6 => 'Sabtu',
7 => 'Minggu',
],
$days = [
1 => 'Senin',
2 => 'Selasa',
3 => 'Rabu',
4 => 'Kamis',
5 => 'Jumat',
6 => 'Sabtu',
7 => 'Minggu',
];
return $this->getSchedules()->mapWithKeys(function ($schedules, $day) use ($days) {
return [$days[$day] ?? 'Lainnya' => $schedules];
});
}
public function scheduleFormSchema(): array

View File

@ -24,7 +24,9 @@
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use UnitEnum;
class Index extends Page implements HasActions, HasForms
@ -48,7 +50,7 @@ public static function getPagePermission(): string
return 'View:StudyGroup';
}
protected string $view = 'filament.pages.learning.study-groups';
protected string $view = 'filament.pages.learning.study-group.index';
public ?int $course_id = null;
@ -101,7 +103,8 @@ public function deleteStudyGroupAction(): Action
->visible(fn () => auth()->user()->can('Delete:StudyGroup'));
}
public function getStudyGroups(): Collection
#[Computed]
public function studyGroups(): Collection
{
$query = StudyGroup::with(['leader', 'students', 'courses'])
->whereHas('courses', fn ($q) => $q->where('courses.semester', app(GeneralSettings::class)->current_semester));
@ -110,15 +113,35 @@ public function getStudyGroups(): Collection
$query->whereHas('courses', fn ($q) => $q->where('courses.id', $this->course_id));
}
return $query->latest()->get();
}
$studentId = auth()->user()?->student?->id;
protected function getViewData(): array
{
return [
'groups' => $this->getStudyGroups(),
'studentId' => auth()->user()?->student?->id,
];
return $query->latest()->get()->map(function ($record) use ($studentId) {
$isMyGroup = ($studentId && ($record->leader_id === $studentId || $record->students->contains($studentId)));
return (object) [
'id' => $record->id,
'name' => $record->name,
'is_my_group' => $isMyGroup,
'leader_avatar' => $record->leader?->user?->facehash_avatar_url,
'leader_name' => $record->leader->full_name ?? 'Belum Ditentukan',
'is_leader' => $studentId && $record->leader_id === $studentId,
'courses' => $record->courses->map(fn ($c) => (object) [
'name' => $c->name,
]),
'students_count' => $record->students->count(),
'students' => $record->students->map(fn ($s) => (object) [
'id' => $s->id,
'full_name' => $s->full_name,
'is_me' => $studentId && $s->id === $studentId,
]),
// Pre-calculated classes
'card_classes' => Arr::toCssClasses([
'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative',
'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md' => ! $isMyGroup,
'bg-primary-50/30 dark:bg-primary-900/10 border-primary-500 shadow-md ring-1 ring-primary-500' => $isMyGroup,
]),
];
});
}
public function isMyGroup(StudyGroup $record): bool

View File

@ -14,13 +14,14 @@
use Filament\Actions\Action;
use Filament\Resources\Pages\Page;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Livewire\Attributes\Computed;
class ListAssignments extends Page
{
protected static string $resource = AssignmentResource::class;
protected string $view = 'filament.resources.learning.assignments.pages.list-assignments';
protected string $view = 'filament.resources.learning.assignments.index';
protected static ?string $title = 'Tugas';
@ -139,8 +140,33 @@ public function assignmentCards()
'is_new' => $isNew,
'is_pinned' => $isPinned,
'status_label' => $statusLabel,
'status_color' => $statusColor,
'status_icon' => $statusIcon,
// Pre-calculated classes
'card_classes' => Arr::toCssClasses([
'group flex w-full rounded-xl border transition-all overflow-hidden relative',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' => $isPinned,
'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' => ! $isPinned && $canSubmitActual,
'border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/40 opacity-80' => ! $canSubmitActual,
]),
'icon_wrapper_classes' => Arr::toCssClasses([
'flex h-11 w-11 shrink-0 items-center justify-center rounded-lg',
'bg-success-50 dark:bg-success-900/20 text-success-600 dark:text-success-400' => $isSubmitted,
'bg-danger-50 dark:bg-danger-900/20 text-danger-600 dark:text-danger-400' => ! $isSubmitted && $isOverdue,
'bg-warning-50 dark:bg-warning-900/20 text-warning-600 dark:text-warning-400' => ! $isSubmitted && ! $isOverdue,
]),
'status_badge_classes' => Arr::toCssClasses([
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' => $statusColor === 'amber',
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $statusColor === 'success',
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' => $statusColor === 'danger',
'bg-warning-100 text-warning-700 dark:bg-warning-900/30 dark:text-warning-400' => $statusColor === 'warning',
'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400' => $statusColor === 'gray',
]),
'indicator_icon_classes' => Arr::toCssClasses([
'h-5 w-5 opacity-20',
'text-primary-500' => $isLeader,
'text-gray-400' => ! $isLeader,
]),
];
});
}

View File

@ -10,6 +10,7 @@
use Filament\Actions\Action;
use Filament\Resources\Pages\Page;
use Filament\Support\Enums\Width;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Joaopaulolndev\FilamentPdfViewer\Infolists\Components\PdfViewerEntry;
use Livewire\Attributes\Computed;
@ -18,7 +19,7 @@ class SubmissionDetailPage extends Page
{
protected static string $resource = AssignmentResource::class;
protected string $view = 'filament.resources.learning.assignments.pages.submission-detail';
protected string $view = 'filament.resources.learning.assignments.submission-detail';
protected static ?string $title = 'Detail Rekap Pengumpulan';
@ -32,29 +33,35 @@ public function statCards(): array
'label' => $this->record->type === AssignmentType::Individual ? 'Total Mahasiswa' : 'Total Kelompok',
'value' => $this->totalCount,
'icon' => 'heroicon-o-user-group',
'color' => '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',
],
[
'label' => 'Sudah Kumpul',
'value' => $this->doneCount,
'icon' => 'heroicon-o-check-circle',
'color' => 'bg-success-100 dark:bg-success-900/30 text-success-700 dark:text-success-400',
'color_classes' => 'bg-success-100 dark:bg-success-900/30 text-success-700 dark:text-success-400',
],
[
'label' => 'Belum Kumpul',
'value' => $this->totalCount - $this->doneCount,
'icon' => 'heroicon-o-x-circle',
'color' => 'bg-danger-100 dark:bg-danger-900/30 text-danger-700 dark:text-danger-400',
'color_classes' => 'bg-danger-100 dark:bg-danger-900/30 text-danger-700 dark:text-danger-400',
],
[
'label' => 'Persentase',
'value' => $this->percentage.'%',
'icon' => 'heroicon-o-chart-bar',
'color' => 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400',
'color_classes' => 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400',
],
];
}
#[Computed]
public function progressColorClass(): string
{
return $this->percentage === 100 ? 'bg-success-500' : ($this->percentage > 0 ? 'bg-warning-500' : 'bg-gray-300');
}
#[Computed]
public function submissionSummary(): Collection
{
@ -78,10 +85,16 @@ public function submissionSummary(): Collection
'is_individual' => true,
'primary_name' => $student->full_name,
'secondary_info' => $student->student_number,
'submission' => $submission,
'submission_id' => $submission?->id,
'submitted' => $isSubmitted,
'submitted_at_formatted' => $isSubmitted ? $submission->submitted_at?->translatedFormat('l, d F Y, H:i') : '-',
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
'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,
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' => ! $isSubmitted,
]),
'status_label' => $isSubmitted ? '✓ Terkumpul' : 'Belum',
];
})->sortBy('secondary_info')->values();
} else {
@ -101,10 +114,16 @@ public function submissionSummary(): Collection
'is_individual' => false,
'primary_name' => $group->name,
'secondary_info' => $isSubmitted ? $submission->student->full_name : '-',
'submission' => $submission,
'submission_id' => $submission?->id,
'submitted' => $isSubmitted,
'submitted_at_formatted' => $isSubmitted ? $submission->submitted_at?->translatedFormat('l, d F Y, H:i') : '-',
'has_file' => $isSubmitted && $submission->hasMedia('submission'),
'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,
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' => ! $isSubmitted,
]),
'status_label' => $isSubmitted ? '✓ Terkumpul' : 'Belum',
];
})->sortBy('primary_name')->values();
}
@ -136,7 +155,8 @@ public function isOverdue(): bool
return now()->isAfter($this->record->due_date);
}
public function getAssignmentInfo(): array
#[Computed]
public function assignmentSummary(): array
{
return [
'title' => $this->record->title,

View File

@ -11,6 +11,7 @@
use App\Models\Student;
use App\Models\StudyGroup;
use Filament\Resources\Pages\Page;
use Illuminate\Support\Arr;
use Livewire\Attributes\Computed;
use Livewire\WithFileUploads;
@ -20,7 +21,7 @@ class SubmitAssignmentPage extends Page
protected static string $resource = AssignmentResource::class;
protected string $view = 'filament.resources.learning.assignments.pages.submit-assignment';
protected string $view = 'filament.resources.learning.assignments.submit-assignment';
protected static ?string $title = 'Kumpulkan Tugas';
@ -38,6 +39,11 @@ public function statusCards(): array
'icon' => 'heroicon-o-clock',
'is_danger' => $this->isOverdue,
'badge' => $this->isOverdue ? '(Terlewat)' : null,
'icon_classes' => Arr::toCssClasses([
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
'bg-danger-50 dark:bg-danger-900/20 text-danger-600' => $this->isOverdue,
'bg-warning-50 dark:bg-warning-900/20 text-warning-600' => ! $this->isOverdue,
]),
],
[
'label' => 'Status Pengumpulan',
@ -45,6 +51,11 @@ public function statusCards(): array
'icon' => $this->isOverdue ? 'heroicon-o-lock-closed' : 'heroicon-o-check-circle',
'is_danger' => $this->isOverdue,
'is_success' => ! $this->isOverdue,
'icon_classes' => Arr::toCssClasses([
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
'bg-danger-50 dark:bg-danger-900/20 text-danger-600' => $this->isOverdue,
'bg-success-50 dark:bg-success-900/20 text-success-600' => ! $this->isOverdue,
]),
],
];
}

View File

@ -20,6 +20,8 @@
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Livewire\Attributes\Computed;
class ManageAttendances extends Page implements HasForms, HasTable
{
@ -31,7 +33,7 @@ class ManageAttendances extends Page implements HasForms, HasTable
protected static ?string $navigationLabel = 'Presensi';
protected string $view = 'filament.resources.learning.attendances.pages.list-attendances';
protected string $view = 'filament.resources.learning.attendances.index';
public function getSchedules()
{
@ -59,7 +61,8 @@ public function getSchedules()
->get();
}
public function getScheduleCards()
#[Computed]
public function scheduleCards()
{
return $this->getSchedules()
->map(function ($schedule) {
@ -68,7 +71,6 @@ public function getScheduleCards()
$now = now();
$startTime = now()->setTimeFrom($schedule->start_time);
$canAttend = $now->greaterThanOrEqualTo($startTime);
$statusLabel = 'Belum Presensi';
@ -96,17 +98,46 @@ public function getScheduleCards()
'status_color' => $statusColor,
'status_icon' => $statusIcon,
'attended_at' => $isAttended ? $attendance->attended_at->format('H:i') : null,
// Pre-calculated classes
'card_classes' => Arr::toCssClasses([
'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative',
'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md' => ! $isAttended,
'bg-primary-50/30 dark:bg-primary-900/10 border-primary-500 shadow-md ring-1 ring-primary-500' => $isAttended,
]),
'title_classes' => Arr::toCssClasses([
'text-lg font-bold leading-tight flex items-center gap-2 transition-colors',
'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => ! $isAttended,
'text-primary-900 dark:text-primary-100' => $isAttended,
]),
'lecturer_wrapper_classes' => Arr::toCssClasses([
'flex items-center gap-1.5',
'text-gray-500 dark:text-gray-400' => ! $isAttended,
'text-primary-600 dark:text-primary-400' => $isAttended,
]),
'icon_wrapper_classes' => Arr::toCssClasses([
'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border',
'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => ! $isAttended,
'bg-white dark:bg-primary-800 border-primary-300 dark:border-primary-600' => $isAttended,
]),
'time_label_classes' => Arr::toCssClasses([
'truncate font-medium',
'text-gray-100 dark:text-gray-200' => ! $isAttended,
'text-primary-900 dark:text-primary-50' => $isAttended,
]),
'status_badge_classes' => Arr::toCssClasses([
'text-[10px] font-bold px-1.5 py-0.5 rounded ring-1 ring-inset',
'bg-success-50 dark:bg-success-500/10 text-success-600 dark:text-success-400 ring-success-600/20' => $isAttended,
'bg-warning-50 dark:bg-warning-500/10 text-warning-600 dark:text-warning-400 ring-warning-600/20' => ! $isAttended && $canAttend && ! $isAttended,
'bg-gray-50 dark:bg-gray-500/10 text-gray-600 dark:text-gray-400 ring-gray-600/20' => ! $isAttended && ! $canAttend,
]),
'footer_classes' => Arr::toCssClasses([
'flex items-center justify-end gap-2 p-4 pt-0 rounded-b-xl',
'bg-primary-100/30 dark:bg-primary-900/10 pt-4' => $isAttended || ($canAttend && ! $isAttended),
]),
];
});
}
protected function getViewData(): array
{
return [
'scheduleCards' => $this->getScheduleCards(),
];
}
public function attend(int $scheduleId): void
{
/** @var User $user */

View File

@ -16,6 +16,7 @@
use Filament\Resources\Pages\Page;
use Filament\Support\Enums\Width;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
class ListCourseSessions extends Page implements HasActions, HasForms
{
@ -23,7 +24,7 @@ class ListCourseSessions extends Page implements HasActions, HasForms
protected static string $resource = ClassSessionResource::class;
protected string $view = 'filament.resources.learning.class-sessions.pages.list-course-sessions';
protected string $view = 'filament.resources.learning.class-sessions.index';
public $courseId;
@ -32,19 +33,35 @@ public function mount($courseId): void
$this->courseId = $courseId;
}
public function getCourse(): Course
#[Computed]
public function course(): Course
{
return Course::findOrFail($this->courseId);
}
public function getSessions(): Collection
#[Computed]
public function sessions(): Collection
{
return $this->getCourse()->classSessions()->orderBy('session_number', 'asc')->get();
return $this->course->classSessions()
->orderBy('session_number', 'asc')
->get()
->map(fn ($session) => (object) [
'id' => $session->id,
'session_number' => $session->session_number,
'date_formatted' => $session->date->format('l, d F Y'),
'time_range' => $session->start_time->format('H:i').' - '.$session->end_time->format('H:i'),
]);
}
#[Computed]
public function description(): string
{
return 'Data sesi pembelajaran untuk mata kuliah '.$this->course->name;
}
public function getTitle(): string
{
return 'Sesi Kelas - '.$this->getCourse()->name;
return 'Sesi Kelas - '.$this->course->name;
}
protected function getHeaderActions(): array

View File

@ -2,13 +2,11 @@
namespace App\Filament\Resources\Learning\ClassSessions\Pages;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Resources\Learning\ClassSessions\ClassSessionResource;
use App\Models\ClassSession;
use App\Models\Course;
use App\Models\CourseSchedule;
use App\Settings\GeneralSettings;
use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Components\TextInput;
@ -16,7 +14,9 @@
use Filament\Forms\Contracts\HasForms;
use Filament\Resources\Pages\Page;
use Filament\Schemas\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
class ManageClassSessions extends Page implements HasActions, HasForms
{
@ -35,6 +35,12 @@ public function mount(): void
$this->form->fill();
}
#[Computed]
public function todayDate(): string
{
return now()->translatedFormat('l, d F Y');
}
public function form(Schema $schema): Schema
{
return $schema
@ -51,18 +57,8 @@ public function form(Schema $schema): Schema
]);
}
protected function getHeaderActions(): array
{
return [];
}
public function deleteSessionAction(): Action
{
return DeleteAction::make('deleteSession')
->record(fn (array $arguments) => ClassSession::find($arguments['session']));
}
public function getTodaySessions(): Collection
#[Computed]
public function todaySessions(): Collection
{
$dayOfWeek = now()->dayOfWeekIso; // 1 (Mon) - 7 (Sun)
@ -80,21 +76,52 @@ public function getTodaySessions(): Collection
return $schedules->map(function ($schedule) {
$session = $schedule->course->classSessions->first();
$isPending = ! $session;
$sessionNumber = $session?->session_number ?? (ClassSession::where('course_id', $schedule->course_id)->max('session_number') ?? 0) + 1;
return (object) [
'id' => $session?->id,
'course' => $schedule->course,
'session_number' => $session?->session_number ?? (ClassSession::where('course_id', $schedule->course_id)->max('session_number') ?? 0) + 1,
'start_time' => $schedule->start_time,
'end_time' => $schedule->end_time,
'title' => $session?->title,
'is_pending' => ! $session,
'is_pending' => $isPending,
'session_number' => $sessionNumber,
'course_name' => $schedule->course->name,
'course_code' => $schedule->course->code,
'lecturer' => $schedule->course->lecturer ?? '-',
'time_range' => $schedule->start_time->format('H:i').' - '.$schedule->end_time->format('H:i'),
'attendances_count' => $session?->attendances_count ?? 0,
'url' => $isPending ? null : ClassSessionResource::getUrl('course', ['courseId' => $schedule->course->id]),
// Pre-calculated classes
'card_classes' => Arr::toCssClasses([
'group flex w-full rounded-xl border overflow-hidden relative transition-all hover:shadow-md',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' => ! $isPending,
'border-gray-300 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/10 border-dashed' => $isPending,
]),
'session_badge_classes' => Arr::toCssClasses([
'flex h-12 w-12 shrink-0 flex-col items-center justify-center rounded-lg font-bold border',
'bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-300 border-primary-200 dark:border-primary-700' => ! $isPending,
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700' => $isPending,
]),
'title_classes' => Arr::toCssClasses([
'font-bold transition-colors',
'text-gray-900 dark:text-white group-hover:text-primary-600' => ! $isPending,
'text-gray-500 dark:text-gray-400' => $isPending,
]),
'status_badge_classes' => Arr::toCssClasses([
'flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-bold shadow-sm',
'bg-primary-500 text-white animate-pulse' => ! $isPending,
'bg-gray-500 text-white opacity-60' => $isPending,
]),
'attendance_section_classes' => Arr::toCssClasses([
'flex flex-col items-center justify-center border-l p-2 bg-white/30 dark:bg-black/10 transition-colors',
'border-primary-200 dark:border-primary-800 group-hover:bg-primary-500/5' => ! $isPending,
'border-gray-200 dark:border-gray-800' => $isPending,
]),
];
});
}
public function getCourses(): Collection
#[Computed]
public function courses(): Collection
{
$query = Course::query()
->where('semester', app(GeneralSettings::class)->current_semester)
@ -109,6 +136,14 @@ public function getCourses(): Collection
}
return $query->get()
->sortBy('name');
->sortBy('name')
->map(fn ($course) => (object) [
'id' => $course->id,
'name' => $course->name,
'code' => $course->code,
'lecturer' => $course->lecturer ?? 'Dosen Belum Ditentukan',
'sessions_count' => $course->classSessions->count(),
'url' => ClassSessionResource::getUrl('course', ['courseId' => $course->id]),
]);
}
}

View File

@ -0,0 +1,121 @@
<x-filament-panels::page>
<x-filament::section>
<div
class="fi-section-content rounded-xl border border-info-200 bg-info-50 dark:bg-info-500/10 dark:border-info-500/30 p-4 mb-6">
<div class="flex items-start gap-3">
<x-heroicon-o-information-circle class="w-5 h-5 text-info-600 dark:text-info-400 mt-0.5" />
<div class="text-sm text-info-800 dark:text-info-200">
Geser ke samping untuk melihat hari lainnya.
</div>
</div>
</div>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
{{ $this->form }}
</div>
</div>
@if ($this->schedulesGrouped->isNotEmpty())
<div class="overflow-x-auto">
<div class="flex flex-nowrap gap-6 min-w-max pb-4">
@foreach ($this->schedulesGrouped as $dayName => $daySchedules)
<section class="w-80 shrink-0 space-y-4">
<div class="flex items-center gap-2 border-b border-gray-200 dark:border-gray-700 pb-2">
<x-heroicon-o-calendar class="w-5 h-5 text-primary-500" />
<h2 class="text-lg font-semibold tracking-tight text-gray-950 dark:text-white">
{{ $dayName }}
</h2>
</div>
<div class="space-y-4">
@foreach ($daySchedules as $schedule)
<div
class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm hover:shadow-md transition">
<div class="p-5 space-y-4">
<div class="flex items-start justify-between gap-3">
<div class="flex items-center gap-2">
<span
class="inline-flex items-center rounded-md bg-primary-50 dark:bg-primary-500/10 px-2 py-1 text-xs font-semibold text-primary-700 dark:text-primary-300 ring-1 ring-inset ring-primary-600/20">
{{ $schedule->course_code }}
</span>
@if ($schedule->mode_label)
<span
class="inline-flex items-center rounded-md bg-{{ $schedule->mode_color }}-50 dark:bg-{{ $schedule->mode_color }}-500/10 px-2 py-1 text-xs font-semibold text-{{ $schedule->mode_color }}-700 dark:text-{{ $schedule->mode_color }}-300 ring-1 ring-inset ring-{{ $schedule->mode_color }}-600/20">
{{ $schedule->mode_label }}
</span>
@endif
</div>
@if ($schedule->room)
<span
class="inline-flex items-center text-xs text-gray-500 dark:text-gray-400">
<x-heroicon-m-map-pin class="w-4 h-4 mr-1 opacity-70" />
{{ $schedule->room }}
</span>
@endif
</div>
<h3
class="text-base font-semibold leading-tight text-gray-950 dark:text-white">
{{ $schedule->course_name }}
</h3>
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<x-heroicon-m-user class="w-4 h-4 mr-1.5 opacity-70" />
<span class="truncate">{{ $schedule->lecturer }}</span>
</div>
<div class="flex flex-wrap items-center gap-2 mt-1">
@if ($schedule->semester)
<span
class="inline-flex items-center gap-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20">
<x-heroicon-m-academic-cap class="w-3 h-3" />
Semester {{ $schedule->semester }}
</span>
@endif
</div>
<div
class="flex items-center justify-between pt-3 border-t border-gray-100 dark:border-gray-800 text-sm">
<div
class="flex items-center font-medium text-gray-800 dark:text-gray-200">
<x-heroicon-o-clock class="w-4 h-4 mr-1.5 text-primary-500" />
{{ $schedule->time_range }}
</div>
<span
class="text-[11px] font-semibold tracking-wide text-gray-400 uppercase">
{{ $schedule->credit }} SKS
</span>
</div>
@canAny(['Update:CourseSchedule', 'Delete:CourseSchedule'])
<div
class="flex items-center justify-end gap-1 pt-3 border-t border-gray-100 dark:border-gray-800">
{{ $this->editScheduleAction(['schedule' => $schedule->id]) }}
{{ $this->deleteScheduleAction(['schedule' => $schedule->id]) }}
</div>
@endcanAny
</div>
</div>
@endforeach
</div>
</section>
@endforeach
</div>
</div>
@else
<x-filament::empty-state icon="heroicon-o-calendar-days" heading="Tidak ada data yang ditemukan"
description="Setelah Anda membuat data pertama, maka akan muncul disini." iconColor="gray">
</x-filament::empty-state>
@endif
</x-filament::section>
</x-filament-panels::page>

View File

@ -1,136 +0,0 @@
<x-filament-panels::page>
<x-filament::section>
<div
class="fi-section-content rounded-xl border border-info-200 bg-info-50 dark:bg-info-500/10 dark:border-info-500/30 p-4 mb-6">
<div class="flex items-start gap-3">
<x-heroicon-o-information-circle class="w-5 h-5 text-info-600 dark:text-info-400 mt-0.5" />
<div class="text-sm text-info-800 dark:text-info-200">
Geser ke samping untuk melihat hari lainnya.
</div>
</div>
</div>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
{{ $this->form }}
</div>
</div>
@if ($schedules->isNotEmpty())
<div class="overflow-x-auto">
<div class="flex flex-nowrap gap-6 min-w-max pb-4">
@foreach ($days as $dayNumber => $dayName)
@php
$daySchedules = $schedules->get($dayNumber);
@endphp
@if ($daySchedules && $daySchedules->count() > 0)
<section class="w-80 shrink-0 space-y-4">
<div class="flex items-center gap-2 border-b border-gray-200 dark:border-gray-700 pb-2">
<x-heroicon-o-calendar class="w-5 h-5 text-primary-500" />
<h2 class="text-lg font-semibold tracking-tight text-gray-950 dark:text-white">
{{ $dayName }}
</h2>
</div>
<div class="space-y-4">
@foreach ($daySchedules as $schedule)
<div
class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm hover:shadow-md transition">
<div class="p-5 space-y-4">
<div class="flex items-start justify-between gap-3">
<div class="flex items-center gap-2">
<span
class="inline-flex items-center rounded-md bg-primary-50 dark:bg-primary-500/10 px-2 py-1 text-xs font-semibold text-primary-700 dark:text-primary-300 ring-1 ring-inset ring-primary-600/20">
{{ $schedule->course->code ?? 'MATKUL' }}
</span>
@if ($schedule->mode)
@php
$color = $schedule->mode->getColor();
@endphp
<span
class="inline-flex items-center rounded-md bg-{{ $color }}-50 dark:bg-{{ $color }}-500/10 px-2 py-1 text-xs font-semibold text-{{ $color }}-700 dark:text-{{ $color }}-300 ring-1 ring-inset ring-{{ $color }}-600/20">
{{ $schedule->mode->getLabel() }}
</span>
@endif
</div>
@if ($schedule->room)
<span
class="inline-flex items-center text-xs text-gray-500 dark:text-gray-400">
<x-heroicon-m-map-pin class="w-4 h-4 mr-1 opacity-70" />
{{ $schedule->room }}
</span>
@endif
</div>
<h3
class="text-base font-semibold leading-tight text-gray-950 dark:text-white">
{{ $schedule->course->name ?? 'Mata Kuliah Tidak Diketahui' }}
</h3>
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<x-heroicon-m-user class="w-4 h-4 mr-1.5 opacity-70" />
<span class="truncate">
{{ $schedule->course->lecturer ?? 'Dosen Belum Ditentukan' }}
</span>
</div>
<div class="flex flex-wrap items-center gap-2 mt-1">
@if ($schedule->course?->semester)
<span
class="inline-flex items-center gap-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20">
<x-heroicon-m-academic-cap class="w-3 h-3" />
Semester {{ $schedule->course->semester }}
</span>
@endif
</div>
<div
class="flex items-center justify-between pt-3 border-t border-gray-100 dark:border-gray-800 text-sm">
<div
class="flex items-center font-medium text-gray-800 dark:text-gray-200">
<x-heroicon-o-clock class="w-4 h-4 mr-1.5 text-primary-500" />
{{ $schedule->start_time->format('H:i') }}
<span class="mx-1"></span>
{{ $schedule->end_time->format('H:i') }}
</div>
<span
class="text-[11px] font-semibold tracking-wide text-gray-400 uppercase">
{{ $schedule->course->credit ?? '-' }} SKS
</span>
</div>
@canAny(['Update:CourseSchedule', 'Delete:CourseSchedule'])
<div
class="flex items-center justify-end gap-1 pt-3 border-t border-gray-100 dark:border-gray-800">
{{ ($this->editScheduleAction)(['schedule' => $schedule->id]) }}
{{ ($this->deleteScheduleAction)(['schedule' => $schedule->id]) }}
</div>
@endcanAny
</div>
</div>
@endforeach
</div>
</section>
@endif
@endforeach
</div>
</div>
@endif
@if ($schedules->isEmpty())
<x-filament::empty-state icon="heroicon-o-calendar-days" heading="Tidak ada data yang ditemukan"
description="Setelah Anda membuat data pertama, maka akan muncul disini." iconColor="gray">
</x-filament::empty-state>
@endif
</x-filament::section>
</x-filament-panels::page>

View File

@ -9,38 +9,24 @@
</x-filament::section>
<!-- Today's Sessions Section -->
@if ($this->getTodaySessions()->isNotEmpty())
@if ($this->todaySessions->isNotEmpty())
<x-filament::section icon="heroicon-o-bolt" icon-color="primary">
<x-slot name="heading">
Sesi Hari Ini
</x-slot>
<x-slot name="description">
Sesi yang dijadwalkan pada {{ now()->translatedFormat('l, d F Y') }}
Sesi yang dijadwalkan pada {{ $this->today_date }}
</x-slot>
<div class="space-y-4">
@foreach ($this->getTodaySessions() as $session)
@php
$cardClass = \Illuminate\Support\Arr::toCssClasses([
'group flex w-full rounded-xl border overflow-hidden relative transition-all hover:shadow-md',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' => !$session->is_pending,
'border-gray-300 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/10 border-dashed' => $session->is_pending,
]);
@endphp
@foreach ($this->todaySessions as $session)
@if ($session->is_pending)
<div class="{{ $cardClass }}">
<div class="{{ $session->card_classes }}">
@else
<a href="{{ \App\Filament\Resources\Learning\ClassSessions\ClassSessionResource::getUrl('course', ['courseId' => $session->course->id]) }}"
class="{{ $cardClass }}">
<a href="{{ $session->url }}" class="{{ $session->card_classes }}">
@endif
<div class="flex flex-1 items-start gap-4 p-5">
<div @class([
'flex h-12 w-12 shrink-0 flex-col items-center justify-center rounded-lg font-bold border',
'bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-300 border-primary-200 dark:border-primary-700' => !$session->is_pending,
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700' =>
$session->is_pending,
])>
<div class="{{ $session->session_badge_classes }}">
<span class="text-[9px] uppercase leading-none opacity-60 mb-0.5 font-bold">Sesi</span>
<span class="text-xl leading-none italic">#{{ $session->session_number }}</span>
</div>
@ -48,25 +34,16 @@ class="{{ $cardClass }}">
<div class="flex-1 min-w-0">
<div class="flex items-start justify-between gap-3 flex-wrap">
<div>
<h3 @class([
'font-bold transition-colors',
'text-gray-900 dark:text-white group-hover:text-primary-600' => !$session->is_pending,
'text-gray-500 dark:text-gray-400' => $session->is_pending,
])>
{{ $session->course->name }}
<h3 class="{{ $session->title_classes }}">
{{ $session->course_name }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5 font-medium">
{{ $session->course->code }} {{ $session->course->lecturer ?? '-' }}
{{ $session->course_code }} {{ $session->lecturer }}
</p>
</div>
<div @class([
'flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-bold shadow-sm',
'bg-primary-500 text-white animate-pulse' => !$session->is_pending,
'bg-gray-500 text-white opacity-60' => $session->is_pending,
])>
<div class="{{ $session->status_badge_classes }}">
<x-heroicon-s-clock class="w-3.5 h-3.5" />
{{ $session->start_time->format('H:i') }} -
{{ $session->end_time->format('H:i') }}
{{ $session->time_range }}
</div>
@if ($session->is_pending)
<div
@ -78,11 +55,7 @@ class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-5
</div>
</div>
<div @class([
'flex flex-col items-center justify-center border-l p-2 bg-white/30 dark:bg-black/10 transition-colors',
'border-primary-200 dark:border-primary-800 group-hover:bg-primary-500/5' => !$session->is_pending,
'border-gray-200 dark:border-gray-800' => $session->is_pending,
])>
<div class="{{ $session->attendance_section_classes }}">
@if ($session->is_pending)
<div class="flex items-center gap-2 text-gray-400 dark:text-gray-500 p-2 opacity-50">
<x-heroicon-m-clock class="w-5 h-5" />
@ -119,10 +92,10 @@ class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-400 dark:text-gray-5
Pilih mata kuliah untuk melihat dan mengelola semua riwayat sesi.
</x-slot>
@if ($this->getCourses()->isNotEmpty())
@if ($this->courses->isNotEmpty())
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach ($this->getCourses() as $course)
<a href="{{ \App\Filament\Resources\Learning\ClassSessions\ClassSessionResource::getUrl('course', ['courseId' => $course->id]) }}"
@foreach ($this->courses as $course)
<a href="{{ $course->url }}"
class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm overflow-hidden transition hover:shadow-md h-fit block group">
<div class="p-5 space-y-4">
@ -134,7 +107,7 @@ class="inline-flex items-center rounded-md bg-primary-50 dark:bg-primary-500/10
<div class="flex items-center gap-2 text-gray-400 dark:text-gray-500">
<span class="text-xs font-medium">
{{ $course->classSessions->count() }} Sesi
{{ $course->sessions_count }} Sesi
</span>
<x-heroicon-m-chevron-right
class="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
@ -148,7 +121,7 @@ class="text-base font-bold text-gray-950 dark:text-white leading-tight group-hov
</h3>
<div class="flex items-center text-sm text-gray-500 dark:text-gray-400">
<x-heroicon-m-user class="w-4 h-4 mr-1.5 opacity-70" />
<span class="truncate">{{ $course->lecturer ?? 'Dosen Belum Ditentukan' }}</span>
<span class="truncate">{{ $course->lecturer }}</span>
</div>
</div>
</div>

View File

@ -0,0 +1,157 @@
<x-filament-panels::page>
<x-filament::section>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
{{ $this->form }}
</div>
</div>
@if ($this->studyGroups->isNotEmpty())
<div class="columns-1 md:columns-2 lg:columns-3 xl:columns-4 gap-6 space-y-6">
@foreach ($this->studyGroups as $record)
<div class="break-inside-avoid mb-6">
<div class="{{ $record->card_classes }}">
@if ($record->is_my_group)
<div class="absolute top-0 right-0 -translate-x-1 -translate-y-1/2 z-10">
<span
class="inline-flex items-center gap-1 rounded-full bg-primary-600 px-3 py-1 text-[10px] font-bold text-white dark:text-gray-900 shadow-lg ring-2 ring-white dark:ring-gray-900 uppercase tracking-widest">
<x-heroicon-m-check-badge class="w-3.5 h-3.5" />
Kelompok Saya
</span>
</div>
@endif
<div class="p-6 space-y-4 flex-1">
<div class="flex flex-wrap items-center gap-1.5">
@forelse ($record->courses as $course)
<span @class([
'inline-flex items-center rounded-md px-2.5 py-1 text-[10px] font-bold ring-1 ring-inset uppercase tracking-tight',
'bg-primary-50 dark:bg-primary-500/10 text-primary-700 dark:text-primary-300 ring-primary-600/20' => !$record->is_my_group,
'bg-white dark:bg-primary-800/40 text-primary-700 dark:text-primary-200 ring-primary-500/30' => $record->is_my_group,
])>
{{ $course->name }}
</span>
@empty
<span
class="inline-flex items-center rounded-md bg-gray-50 dark:bg-gray-500/10 px-2 py-0.5 text-[10px] font-bold text-gray-500 dark:text-gray-400 ring-1 ring-inset ring-gray-600/20 uppercase tracking-tight italic">
Tanpa Mata Kuliah
</span>
@endforelse
</div>
<div class="space-y-1">
<h3 @class([
'text-lg font-bold leading-tight flex items-center gap-2 transition-colors',
'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => !$record->is_my_group,
'text-primary-900 dark:text-primary-100' => $record->is_my_group,
])>
<x-heroicon-o-users @class([
'w-5 h-5 shrink-0',
'opacity-40' => !$record->is_my_group,
'text-primary-500' => $record->is_my_group,
]) />
{{ $record->name }}
</h3>
</div>
<div class="space-y-4">
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<div @class([
'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border',
'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => !$record->is_my_group,
'bg-white dark:bg-primary-800 border-primary-300 dark:border-primary-600' => $record->is_my_group,
])>
@if ($record->leader_avatar)
<img src="{{ $record->leader_avatar }}"
alt="User avatar" class="w-full h-full rounded-full object-cover" />
@else
<x-heroicon-m-user @class([
'w-4 h-4',
'text-primary-600 dark:text-primary-300' => !$record->is_my_group,
'text-primary-700 dark:text-primary-200' => $record->is_my_group,
]) />
@endif
</div>
<div class="flex flex-col min-w-0">
<span
class="text-[10px] font-bold uppercase text-gray-400 tracking-widest leading-none mb-1">Ketua
Kelompok</span>
<span @class([
'truncate font-medium',
'text-gray-900 dark:text-gray-200' => !$record->is_my_group,
'text-primary-900 dark:text-primary-50' => $record->is_my_group,
])>
{{ $record->leader_name }}
@if ($record->is_leader)
<span
class="text-[10px] text-primary-600 font-bold ml-1">(Anda)</span>
@endif
</span>
</div>
</div>
<div @class([
'pt-4 border-t',
'border-gray-100 dark:border-gray-800' => !$record->is_my_group,
'border-primary-200 dark:border-primary-800' => $record->is_my_group,
])>
<div class="flex items-center justify-between mb-3">
<span
class="text-[10px] font-bold text-gray-400 uppercase tracking-widest leading-none">Anggota
Kelompok</span>
<span
class="text-[10px] font-bold px-1.5 py-0.5 rounded bg-violet-50 dark:bg-violet-500/10 text-violet-600 dark:text-violet-400 ring-1 ring-inset ring-violet-600/20">
{{ $record->students_count }} orang
</span>
</div>
<div class="flex flex-wrap gap-1.5">
@forelse ($record->students as $student)
<span @class([
'inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[10px] font-medium ring-1 ring-inset',
'bg-gray-50 dark:bg-gray-800/50 text-gray-700 dark:text-gray-300 ring-gray-200 dark:ring-gray-700' =>
!$student->is_me,
'bg-primary-600 text-white dark:text-gray-900 ring-primary-700 font-bold shadow-sm' =>
$student->is_me,
])>
{{ $student->full_name }}
@if ($student->is_me)
(Anda)
@endif
</span>
@empty
<span class="text-xs text-gray-400 italic">Belum ada anggota</span>
@endforelse
</div>
</div>
</div>
</div>
@canAny(['Update:StudyGroup', 'Delete:StudyGroup'])
<div @class([
'rounded-b-xl',
'bg-primary-100/30 dark:bg-primary-900/10' => $record->is_my_group,
])>
<div @class([
'mx-6 border-t flex items-center justify-end gap-2 py-4',
'border-gray-100 dark:border-gray-800' => !$record->is_my_group,
'border-primary-200 dark:border-primary-800' => $record->is_my_group,
])>
{{ $this->editStudyGroupAction(['studyGroup' => $record->id]) }}
{{ $this->deleteStudyGroupAction(['studyGroup' => $record->id]) }}
</div>
</div>
@endcanAny
</div>
</div>
@endforeach
</div>
@endif
@if ($this->studyGroups->isEmpty())
<x-filament::empty-state icon="heroicon-o-users" heading="Tidak ada data yang ditemukan"
description="Setelah Anda membuat data pertama, maka akan muncul disini." iconColor="gray">
</x-filament::empty-state>
@endif
</x-filament::section>
</x-filament-panels::page>

View File

@ -1,162 +0,0 @@
<x-filament-panels::page>
<x-filament::section>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
{{ $this->form }}
</div>
</div>
@if ($groups->isNotEmpty())
<div class="columns-1 md:columns-2 lg:columns-3 xl:columns-4 gap-6 space-y-6">
@foreach ($groups as $record)
@php $isMyGroup = $this->isMyGroup($record); @endphp
<div class="break-inside-avoid mb-6">
<div @class([
'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative',
'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md' => !$isMyGroup,
'bg-primary-50/30 dark:bg-primary-900/10 border-primary-500 shadow-md ring-1 ring-primary-500' => $isMyGroup,
])>
@if ($isMyGroup)
<div class="absolute top-0 right-0 -translate-x-1 -translate-y-1/2 z-10">
<span
class="inline-flex items-center gap-1 rounded-full bg-primary-600 px-3 py-1 text-[10px] font-bold text-white dark:text-gray-900 shadow-lg ring-2 ring-white dark:ring-gray-900 uppercase tracking-widest">
<x-heroicon-m-check-badge class="w-3.5 h-3.5" />
Kelompok Saya
</span>
</div>
@endif
<div class="p-6 space-y-4 flex-1">
<div class="flex flex-wrap items-center gap-1.5">
@forelse ($record->courses as $course)
<span @class([
'inline-flex items-center rounded-md px-2.5 py-1 text-[10px] font-bold ring-1 ring-inset uppercase tracking-tight',
'bg-primary-50 dark:bg-primary-500/10 text-primary-700 dark:text-primary-300 ring-primary-600/20' => !$isMyGroup,
'bg-white dark:bg-primary-800/40 text-primary-700 dark:text-primary-200 ring-primary-500/30' => $isMyGroup,
])>
{{ $course->name }}
</span>
@empty
<span
class="inline-flex items-center rounded-md bg-gray-50 dark:bg-gray-500/10 px-2 py-0.5 text-[10px] font-bold text-gray-500 dark:text-gray-400 ring-1 ring-inset ring-gray-600/20 uppercase tracking-tight italic">
Tanpa Mata Kuliah
</span>
@endforelse
</div>
<div class="space-y-1">
<h3 @class([
'text-lg font-bold leading-tight flex items-center gap-2 transition-colors',
'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => !$isMyGroup,
'text-primary-900 dark:text-primary-100' => $isMyGroup,
])>
<x-heroicon-o-users @class([
'w-5 h-5 shrink-0',
'opacity-40' => !$isMyGroup,
'text-primary-500' => $isMyGroup,
]) />
{{ $record->name }}
</h3>
</div>
<div class="space-y-4">
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<div @class([
'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border',
'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => !$isMyGroup,
'bg-white dark:bg-primary-800 border-primary-300 dark:border-primary-600' => $isMyGroup,
])>
@if ($record->leader)
<img src="{{ $record->leader->user->facehash_avatar_url }}"
alt="User avatar" class="w-full h-full rounded-full object-cover" />
@else
<x-heroicon-m-user @class([
'w-4 h-4',
'text-primary-600 dark:text-primary-300' => !$isMyGroup,
'text-primary-700 dark:text-primary-200' => $isMyGroup,
]) />
@endif
</div>
<div class="flex flex-col min-w-0">
<span
class="text-[10px] font-bold uppercase text-gray-400 tracking-widest leading-none mb-1">Ketua
Kelompok</span>
<span @class([
'truncate font-medium',
'text-gray-900 dark:text-gray-200' => !$isMyGroup,
'text-primary-900 dark:text-primary-50' => $isMyGroup,
])>
{{ $record->leader->full_name ?? 'Belum Ditentukan' }}
@if ($studentId && $record->leader_id === $studentId)
<span
class="text-[10px] text-primary-600 font-bold ml-1">(Anda)</span>
@endif
</span>
</div>
</div>
<div @class([
'pt-4 border-t',
'border-gray-100 dark:border-gray-800' => !$isMyGroup,
'border-primary-200 dark:border-primary-800' => $isMyGroup,
])>
<div class="flex items-center justify-between mb-3">
<span
class="text-[10px] font-bold text-gray-400 uppercase tracking-widest leading-none">Anggota
Kelompok</span>
<span
class="text-[10px] font-bold px-1.5 py-0.5 rounded bg-violet-50 dark:bg-violet-500/10 text-violet-600 dark:text-violet-400 ring-1 ring-inset ring-violet-600/20">
{{ $record->students->count() }} orang
</span>
</div>
<div class="flex flex-wrap gap-1.5">
@forelse ($record->students as $student)
<span @class([
'inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[10px] font-medium ring-1 ring-inset',
'bg-gray-50 dark:bg-gray-800/50 text-gray-700 dark:text-gray-300 ring-gray-200 dark:ring-gray-700' =>
$student->id !== $studentId,
'bg-primary-600 text-white dark:text-gray-900 ring-primary-700 font-bold shadow-sm' =>
$student->id === $studentId,
])>
{{ $student->full_name }}
@if ($student->id === $studentId)
(Anda)
@endif
</span>
@empty
<span class="text-xs text-gray-400 italic">Belum ada anggota</span>
@endforelse
</div>
</div>
</div>
</div>
@canAny(['Update:StudyGroup', 'Delete:StudyGroup'])
<div @class([
'rounded-b-xl',
'bg-primary-100/30 dark:bg-primary-900/10' => $isMyGroup,
])>
<div @class([
'mx-6 border-t flex items-center justify-end gap-2 py-4',
'border-gray-100 dark:border-gray-800' => !$isMyGroup,
'border-primary-200 dark:border-primary-800' => $isMyGroup,
])>
{{ ($this->editStudyGroupAction)(['studyGroup' => $record->id]) }}
{{ ($this->deleteStudyGroupAction)(['studyGroup' => $record->id]) }}
</div>
</div>
@endcanAny
</div>
</div>
@endforeach
</div>
@endif
@if ($groups->isEmpty())
<x-filament::empty-state icon="heroicon-o-users" heading="Tidak ada data yang ditemukan"
description="Setelah Anda membuat data pertama, maka akan muncul disini." iconColor="gray">
</x-filament::empty-state>
@endif
</x-filament::section>
</x-filament-panels::page>

View File

@ -10,26 +10,11 @@
@else
<div class="space-y-6">
@foreach ($this->assignmentCards as $card)
<div @class([
'group flex w-full rounded-xl border transition-all overflow-hidden relative',
'border-primary-300 dark:border-primary-700 bg-primary-50/30 dark:bg-primary-900/10 shadow-sm' =>
$card->is_pinned,
'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' =>
!$card->is_pinned && $card->can_submit_actual,
'border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/40 opacity-80' => !$card->can_submit_actual,
])>
<div class="{{ $card->card_classes }}">
<a href="{{ \App\Filament\Resources\Learning\Assignments\AssignmentResource::getUrl('submit', ['record' => $card->id]) }}"
wire:navigate
class="flex flex-1 items-start gap-4 p-5 text-left min-w-0 hover:bg-primary-500/5 transition-colors cursor-pointer">
<div @class([
'flex h-11 w-11 shrink-0 items-center justify-center rounded-lg',
'bg-success-50 dark:bg-success-900/20 text-success-600 dark:text-success-400' =>
$card->is_submitted,
'bg-danger-50 dark:bg-danger-900/20 text-danger-600 dark:text-danger-400' =>
!$card->is_submitted && $card->is_overdue,
'bg-warning-50 dark:bg-warning-900/20 text-warning-600 dark:text-warning-400' =>
!$card->is_submitted && !$card->is_overdue,
])>
<div class="{{ $card->icon_wrapper_classes }}">
<x-filament::icon :icon="$card->status_icon" class="h-6 w-6" />
</div>
@ -64,19 +49,7 @@ class="inline-flex items-center gap-1 rounded-full bg-blue-100 text-blue-700 dar
</span>
@endif
<span @class([
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' =>
$card->status_color === 'amber',
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' =>
$card->status_color === 'success',
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' =>
$card->status_color === 'danger',
'bg-warning-100 text-warning-700 dark:bg-warning-900/30 dark:text-warning-400' =>
$card->status_color === 'warning',
'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400' =>
$card->status_color === 'gray',
])>
<span class="{{ $card->status_badge_classes }}">
@if ($card->status_icon)
<x-filament::icon :icon="$card->status_icon" class="h-3 w-3" />
@endif
@ -132,11 +105,7 @@ class="flex flex-col items-center justify-center border-l border-gray-100 dark:b
<div class="absolute right-12 top-2 pointer-events-none">
<x-filament::icon
icon="{{ $card->is_leader ? 'heroicon-m-sparkles' : 'heroicon-m-user-group' }}"
@class([
'h-5 w-5 opacity-20',
'text-primary-500' => $card->is_leader,
'text-gray-400' => !$card->is_leader,
])
class="{{ $card->indicator_icon_classes }}"
title="{{ $card->is_leader ? 'Anda adalah Ketua Kelompok' : 'Anda adalah Anggota Kelompok' }}" />
</div>
@endif

View File

@ -5,7 +5,7 @@
<div class="flex items-center gap-2 mb-1">
<div class="flex h-7 w-7 items-center justify-center rounded-lg {{ $stat['color'] }}">
<div class="flex h-7 w-7 items-center justify-center rounded-lg {{ $stat['color_classes'] }}">
<x-filament::icon :icon="$stat['icon']" class="h-4 w-4 text-current" />
</div>
@ -22,23 +22,19 @@
</div>
<div class="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-2">
<div class="h-2 rounded-full transition-all {{ $this->percentage === 100 ? 'bg-success-500' : ($this->percentage > 0 ? 'bg-warning-500' : 'bg-gray-300') }}"
<div class="h-2 rounded-full transition-all {{ $this->progress_color_class }}"
style="width: {{ $this->percentage }}%"></div>
</div>
@php
$info = $this->getAssignmentInfo();
@endphp
<x-filament::section>
<x-slot name="heading">{{ $info['title'] }}</x-slot>
<x-slot name="heading">{{ $this->assignmentSummary['title'] }}</x-slot>
<x-slot name="description">
{{ $info['course'] }}
{{ $this->assignmentSummary['course'] }}
&nbsp;·&nbsp;
Batas: {{ $info['due_date'] }}
Batas: {{ $this->assignmentSummary['due_date'] }}
&nbsp;·&nbsp;
Tipe: {{ $info['type'] }}
@if ($info['is_overdue'])
Tipe: {{ $this->assignmentSummary['type'] }}
@if ($this->assignmentSummary['is_overdue'])
<x-filament::badge color="danger" class="ml-2">Terlewat</x-filament::badge>
@endif
</x-slot>
@ -84,17 +80,9 @@
</td>
<td class="px-4 py-3">
@if ($item->submitted)
<span
class="inline-flex items-center gap-1 rounded-full bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400 px-2.5 py-0.5 text-xs font-medium">
Terkumpul
</span>
@else
<span
class="inline-flex items-center rounded-full bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400 px-2.5 py-0.5 text-xs font-medium">
Belum
</span>
@endif
<span class="{{ $item->status_classes }}">
{{ $item->status_label }}
</span>
</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400 text-xs">
@ -104,7 +92,7 @@ class="inline-flex items-center rounded-full bg-danger-100 text-danger-700 dark:
<td class="px-4 py-3">
@if ($item->has_file)
<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">
<x-filament::icon icon="heroicon-o-eye" class="h-3.5 w-3.5" />
Lihat Tugas
@ -113,8 +101,6 @@ class="inline-flex items-center gap-1 text-xs text-primary-600 dark:text-primary
<span class="text-xs text-gray-400">-</span>
@endif
</td>
</tr>
@endforeach
</tbody>

View File

@ -8,15 +8,7 @@
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-4">
@foreach ($this->statusCards as $card)
<div class="flex items-center gap-3 rounded-lg border border-gray-200 dark:border-gray-700 p-3">
<div @class([
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
'bg-danger-50 dark:bg-danger-900/20 text-danger-600' =>
$card['is_danger'] ?? false,
'bg-success-50 dark:bg-success-900/20 text-success-600' =>
$card['is_success'] ?? false,
'bg-warning-50 dark:bg-warning-900/20 text-warning-600' =>
!($card['is_danger'] ?? false) && !($card['is_success'] ?? false),
])>
<div class="{{ $card['icon_classes'] }}">
<x-filament::icon :icon="$card['icon']" class="h-5 w-5" />
</div>
<div>
@ -64,9 +56,8 @@ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-dange
<x-filament::icon icon="heroicon-o-lock-closed" class="h-5 w-5" />
</div>
<div class="flex flex-wrap items-center gap-2">
@php $status = $this->submissionStatus; @endphp
<x-filament::badge :color="$status->badge_color" size="lg">
{{ $status->badge_label }}
<x-filament::badge :color="$this->submissionStatus->badge_color" size="lg">
{{ $this->submissionStatus->badge_label }}
</x-filament::badge>
</div>
</div>

View File

@ -3,24 +3,15 @@
<x-slot name="heading">Sesi Kuliah</x-slot>
<x-slot name="description">Silakan melakukan presensi sesuai dengan waktu kelas.</x-slot>
@if ($scheduleCards->isNotEmpty())
@if ($this->scheduleCards->isNotEmpty())
<div class="columns-1 md:columns-2 lg:columns-3 xl:columns-4 gap-6 space-y-6">
@foreach ($scheduleCards as $card)
@foreach ($this->scheduleCards as $card)
<div class="break-inside-avoid mb-6">
<div @class([
'fi-card flex flex-col justify-between rounded-xl border transition duration-200 group relative',
'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md' => !$card->is_attended,
'bg-primary-50/30 dark:bg-primary-900/10 border-primary-500 shadow-md ring-1 ring-primary-500' =>
$card->is_attended,
])>
<div class="{{ $card->card_classes }}">
<div class="p-6 space-y-4 flex-1">
<div class="space-y-1">
<h3 @class([
'text-lg font-bold leading-tight flex items-center gap-2 transition-colors',
'text-gray-950 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400' => !$card->is_attended,
'text-primary-900 dark:text-primary-100' => $card->is_attended,
])>
<h3 class="{{ $card->title_classes }}">
<x-heroicon-o-book-open @class([
'w-5 h-5 shrink-0',
'opacity-40' => !$card->is_attended,
@ -28,11 +19,7 @@
]) />
{{ $card->course_name }}
</h3>
<div @class([
'flex items-center gap-1.5',
'text-gray-500 dark:text-gray-400' => !$card->is_attended,
'text-primary-600 dark:text-primary-400' => $card->is_attended,
])>
<div class="{{ $card->lecturer_wrapper_classes }}">
<x-heroicon-m-user @class(['w-3.5 h-3.5 shrink-0', 'opacity-50' => !$card->is_attended]) />
<span class="text-[11px] font-medium leading-none">
{{ $card->lecturer_name }}
@ -42,12 +29,7 @@
<div class="space-y-4">
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400 pt-4">
<div @class([
'w-8 h-8 rounded-full flex items-center justify-center mr-3 shrink-0 border',
'bg-primary-100 dark:bg-primary-900/40 border-primary-200 dark:border-primary-800' => !$card->is_attended,
'bg-white dark:bg-primary-800 border-primary-300 dark:border-primary-600' =>
$card->is_attended,
])>
<div class="{{ $card->icon_wrapper_classes }}">
<x-heroicon-m-clock @class([
'w-4 h-4',
'text-primary-600 dark:text-primary-300' => !$card->is_attended,
@ -58,11 +40,7 @@
<span
class="text-[10px] font-bold uppercase text-gray-400 tracking-widest leading-none mb-1">Waktu
Kuliah</span>
<span @class([
'truncate font-medium',
'text-gray-900 dark:text-gray-200' => !$card->is_attended,
'text-primary-900 dark:text-primary-50' => $card->is_attended,
])>
<span class="{{ $card->time_label_classes }}">
{{ $card->time_range }}
</span>
</div>
@ -76,15 +54,7 @@ class="text-[10px] font-bold uppercase text-gray-400 tracking-widest leading-non
<div class="flex items-center justify-between mb-3">
<span
class="text-[10px] font-bold text-gray-400 uppercase tracking-widest leading-none">Status</span>
<span @class([
'text-[10px] font-bold px-1.5 py-0.5 rounded ring-1 ring-inset',
'bg-success-50 dark:bg-success-500/10 text-success-600 dark:text-success-400 ring-success-600/20' =>
$card->is_attended,
'bg-warning-50 dark:bg-warning-500/10 text-warning-600 dark:text-warning-400 ring-warning-600/20' =>
!$card->is_attended && $card->can_attend,
'bg-gray-50 dark:bg-gray-500/10 text-gray-600 dark:text-gray-400 ring-gray-600/20' =>
!$card->is_attended && !$card->can_attend,
])>
<span class="{{ $card->status_badge_classes }}">
{{ $card->status_label }}
</span>
</div>
@ -99,11 +69,7 @@ class="text-primary-600 dark:text-primary-200">{{ $card->attended_at }}</span>
</div>
</div>
<div @class([
'flex items-center justify-end gap-2 p-4 pt-0 rounded-b-xl',
'bg-primary-100/30 dark:bg-primary-900/10 pt-4' =>
$card->is_attended || $card->can_attend,
])>
<div class="{{ $card->footer_classes }}">
@if ($card->can_attend)
<x-filament::button wire:click="attend({{ $card->id }})" color="primary"
size="xs"

View File

@ -9,15 +9,15 @@ class="fi-btn fi-btn-size-md relative grid-flow-col items-center justify-center
{{ $this->getTitle() }}
</h1>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">
Data sesi pembelajaran untuk mata kuliah {{ $this->getCourse()->name }}
{{ $this->description }}
</p>
</div>
</div>
<x-filament::section>
@if ($this->getSessions()->isNotEmpty())
@if ($this->sessions->isNotEmpty())
<div class="space-y-4">
@foreach ($this->getSessions() as $session)
@foreach ($this->sessions as $session)
<div
class="fi-card p-5 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm flex items-center justify-between hover:shadow-md transition">
<div class="flex items-start gap-4">
@ -31,25 +31,24 @@ class="flex flex-col items-center justify-center w-12 h-12 rounded-lg bg-primary
class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-1.5 font-bold text-gray-900 dark:text-white">
<x-heroicon-o-calendar class="w-4 h-4 text-primary-500" />
{{ $session->date->format('l, d F Y') }}
{{ $session->date_formatted }}
</div>
<div class="flex items-center gap-1.5 font-medium">
<x-heroicon-o-clock class="w-4 h-4 text-primary-500" />
{{ $session->start_time->format('H:i') }} -
{{ $session->end_time->format('H:i') }}
{{ $session->time_range }}
</div>
</div>
</div>
</div>
<div class="flex items-center gap-1">
{{ ($this->editSessionAction)(['session' => $session->id]) }}
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
{{ $this->editSessionAction(['session' => $session->id]) }}
{{ $this->deleteSessionAction(['session' => $session->id]) }}
</div>
</div>
@endforeach
</div>
@else
@else
<x-filament::empty-state icon="heroicon-o-presentation-chart-bar" heading="Tidak ada data yang ditemukan"
description="Setelah Anda membuat data pertama, maka akan muncul disini." iconColor="gray">
</x-filament::empty-state>

View File

@ -1,103 +0,0 @@
<x-filament-panels::page>
<x-filament::section>
<div class="flex items-center justify-between gap-4 mb-6">
<div class="w-full max-w-xl">
{{ $this->form }}
</div>
</div>
@if($this->getCourses()->isNotEmpty())
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
@foreach($this->getCourses() as $course)
<div
x-data="{ open: false }"
class="fi-card rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm overflow-hidden transition hover:shadow-md h-fit"
>
<!-- Course Header Card -->
<div
@click="open = !open"
class="p-5 cursor-pointer space-y-4 group"
>
<div class="flex items-center justify-between">
<span class="inline-flex items-center rounded-md bg-primary-50 dark:bg-primary-500/10 px-2 py-1 text-xs font-semibold text-primary-700 dark:text-primary-300 ring-1 ring-inset ring-primary-600/20">
{{ $course->code }}
</span>
<div class="flex items-center gap-2 text-gray-400 dark:text-gray-500">
<span class="text-xs font-medium">
{{ $course->classSessions->count() }} Sesi
</span>
<x-heroicon-m-chevron-down
class="w-4 h-4 transition-transform duration-300"
x-bind:class="open ? 'rotate-180' : ''"
/>
</div>
</div>
<div class="space-y-1">
<h3 class="text-base font-bold text-gray-950 dark:text-white leading-tight group-hover:text-primary-600 dark:group-hover:text-primary-400 transition">
{{ $course->name }}
</h3>
<div class="flex items-center text-sm text-gray-500 dark:text-gray-400">
<x-heroicon-m-user class="w-4 h-4 mr-1.5 opacity-70" />
<span class="truncate">{{ $course->lecturer ?? 'Dosen Belum Ditentukan' }}</span>
</div>
</div>
</div>
<!-- Sessions List (Collapsible) -->
<div
x-show="open"
x-collapse
x-cloak
class="border-t border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/20"
>
@if($course->classSessions->isNotEmpty())
<div class="divide-y divide-gray-100 dark:divide-gray-800 max-h-80 overflow-y-auto">
@foreach($course->classSessions as $session)
<div class="p-4 flex items-center justify-between hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition">
<div class="space-y-1">
<div class="flex items-center gap-2">
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-0.5 text-[10px] font-bold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
#{{ $session->session_number }}
</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white">
{{ $session->date->format('l, d/m/Y') }}
</span>
</div>
<p class="text-[12px] text-gray-600 dark:text-gray-400 line-clamp-1">
{{ $session->title ?? 'Materi belum diisi' }}
</p>
<div class="flex items-center gap-1.5 text-[11px] text-gray-500 dark:text-gray-400">
<x-heroicon-o-clock class="w-3.5 h-3.5 text-primary-500" />
{{ $session->start_time->format('H:i') }} - {{ $session->end_time->format('H:i') }}
</div>
</div>
<div class="flex items-center gap-1">
{{ ($this->editSessionAction)(['session' => $session->id]) }}
{{ ($this->deleteSessionAction)(['session' => $session->id]) }}
</div>
</div>
@endforeach
</div>
@else
<div class="p-6 text-center">
<x-heroicon-o-chat-bubble-bottom-center-text class="w-8 h-8 text-gray-300 dark:text-gray-600 mx-auto mb-2" />
<p class="text-xs text-gray-500 dark:text-gray-400 italic">
Belum ada laporan sesi untuk mata kuliah ini.
</p>
</div>
@endif
</div>
</div>
@endforeach
</div>
@else
<x-filament::empty-state
icon="heroicon-o-presentation-chart-bar"
heading="Tidak ada mata kuliah"
description="Mata kuliah untuk semester ini belum tersedia atau tidak sesuai dengan pencarian."
/>
@endif
</x-filament::section>
</x-filament-panels::page>