From 43344cb74b04d71cd4d3e39bc5622b5a77eb5f2d Mon Sep 17 00:00:00 2001 From: reihanrere Date: Wed, 30 Apr 2025 17:25:02 +0700 Subject: [PATCH] add-attendaces --- .../Resources/AttendancesResource.php | 235 ++++++++++++++++++ .../Pages/CreateAttendances.php | 12 + .../Pages/EditAttendances.php | 19 ++ .../Pages/ListAttendances.php | 23 ++ .../Pages/MultipleAttendances.php | 167 +++++++++++++ .../Resources/TeacherSubjectResource.php | 143 +++++++++++ .../Pages/CreateTeacherSubject.php | 35 +++ .../Pages/EditTeacherSubject.php | 19 ++ .../Pages/ListTeacherSubjects.php | 19 ++ app/Models/Attendances.php | 26 ++ app/Models/ClassRoom.php | 6 + app/Models/Extracurricular.php | 2 +- app/Models/Student.php | 3 +- app/Models/Subject.php | 7 + app/Models/TeacherSubject.php | 56 +++++ app/Models/User.php | 6 + ...3_010342_create_teacher_subjects_table.php | 33 +++ ..._04_13_010343_create_attendances_table.php | 36 +++ .../seeders/AssessmentComponentSeeder.php | 28 +++ database/seeders/AttendanceSeeder.php | 17 ++ database/seeders/ClassRoomSeeder.php | 32 +++ database/seeders/DatabaseSeeder.php | 46 +--- database/seeders/ExtracurricularSeeder.php | 26 ++ database/seeders/StudentSeeder.php | 44 ++++ database/seeders/SubjectScopeSeeder.php | 17 ++ database/seeders/SubjectSeeder.php | 55 ++++ database/seeders/UserSeeder.php | 75 ++++++ .../pages/multiple-attendances.blade.php | 76 ++++++ 28 files changed, 1223 insertions(+), 40 deletions(-) create mode 100644 app/Filament/Resources/AttendancesResource.php create mode 100644 app/Filament/Resources/AttendancesResource/Pages/CreateAttendances.php create mode 100644 app/Filament/Resources/AttendancesResource/Pages/EditAttendances.php create mode 100644 app/Filament/Resources/AttendancesResource/Pages/ListAttendances.php create mode 100644 app/Filament/Resources/AttendancesResource/Pages/MultipleAttendances.php create mode 100644 app/Filament/Resources/TeacherSubjectResource.php create mode 100644 app/Filament/Resources/TeacherSubjectResource/Pages/CreateTeacherSubject.php create mode 100644 app/Filament/Resources/TeacherSubjectResource/Pages/EditTeacherSubject.php create mode 100644 app/Filament/Resources/TeacherSubjectResource/Pages/ListTeacherSubjects.php create mode 100644 app/Models/Attendances.php create mode 100644 app/Models/TeacherSubject.php create mode 100644 database/migrations/2025_04_13_010342_create_teacher_subjects_table.php create mode 100644 database/migrations/2025_04_13_010343_create_attendances_table.php create mode 100644 database/seeders/AssessmentComponentSeeder.php create mode 100644 database/seeders/AttendanceSeeder.php create mode 100644 database/seeders/ClassRoomSeeder.php create mode 100644 database/seeders/ExtracurricularSeeder.php create mode 100644 database/seeders/StudentSeeder.php create mode 100644 database/seeders/SubjectScopeSeeder.php create mode 100644 database/seeders/SubjectSeeder.php create mode 100644 database/seeders/UserSeeder.php create mode 100644 resources/views/filament/resources/attendances-resource/pages/multiple-attendances.blade.php diff --git a/app/Filament/Resources/AttendancesResource.php b/app/Filament/Resources/AttendancesResource.php new file mode 100644 index 0000000..caa483e --- /dev/null +++ b/app/Filament/Resources/AttendancesResource.php @@ -0,0 +1,235 @@ +schema([ + Forms\Components\Section::make('Informasi Absensi') + ->schema([ + Forms\Components\Select::make('teacher_subject_id') + ->label('Teacher Subject') + ->required() + ->relationship('teacherSubject', 'id') + ->getOptionLabelFromRecordUsing(fn (TeacherSubject $record) => + $record->teacher->name . ' - ' . $record->subject->name . ' - ' . $record->class->class_name . ' - ' . $record->academic_year) + ->searchable() + ->preload() + ->columnSpanFull(), + + Forms\Components\DatePicker::make('date') + ->label('Date') + ->required() + ->default(now()) + ->maxDate(now()), + + Forms\Components\Select::make('student_id') + ->label('Student') + ->required() + ->searchable() + ->options(Student::pluck('full_name', 'id')->toArray()) + ->getOptionLabelUsing(function ($value) { + $student = Student::find($value); + return $student ? $student->full_name . ' (' . $student->nis . ')' : null; + }), + + Forms\Components\Select::make('semester') + ->label('Semester') + ->required() + ->options([ + 'first' => 'First Semester', + 'second' => 'Second Semester' + ]), + + Forms\Components\Select::make('status') + ->label('Status') + ->required() + ->options([ + 'present' => 'Present', + 'absent' => 'Absent', + 'permission' => 'Permission', + 'sick' => 'Sick' + ]) + ->native(false), + + Forms\Components\Hidden::make('recorded_by') + ->default(auth()->id()), + + Forms\Components\Textarea::make('notes') + ->label('Notes') + ->columnSpanFull() + ])->columns(2) + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('student.full_name') + ->label('Student') + ->searchable() + ->sortable(), + + Tables\Columns\TextColumn::make('teacherSubject.class.class_name') + ->label('Class') + ->searchable(), + + Tables\Columns\TextColumn::make('teacherSubject.subject.name') + ->label('Subject') + ->searchable(), + + Tables\Columns\TextColumn::make('teacherSubject.teacher.name') + ->label('Teacher') + ->searchable(), + + Tables\Columns\TextColumn::make('date') + ->label('Date') + ->date('d/m/Y') + ->sortable(), + + Tables\Columns\BadgeColumn::make('status') + ->label('Status') + ->formatStateUsing(fn (string $state): string => match ($state) { + 'present' => 'Present', + 'absent' => 'Absent', + 'permission' => 'Permission', + 'sick' => 'Sick' + }) + ->color(fn (string $state): string => match ($state) { + 'present' => 'success', + 'absent' => 'danger', + 'permission' => 'warning', + 'sick' => 'warning' + }), + + Tables\Columns\TextColumn::make('semester') + ->label('Semester') + ->formatStateUsing(fn (string $state): string => match ($state) { + 'first' => 'First Semester', + 'second' => 'Second Semester' + }), + + Tables\Columns\TextColumn::make('teacherSubject.academic_year') + ->label('Academic Year') + ->searchable(), + + Tables\Columns\TextColumn::make('recorder.name') + ->label('Record by') + ->toggleable(), + + Tables\Columns\TextColumn::make('notes') + ->label('Notes') + ->toggleable() + ]) + ->filters([ + Tables\Filters\SelectFilter::make('status') + ->label('Status') + ->options([ + 'present' => 'Present', + 'absent' => 'Absent', + 'permission' => 'Permission', + 'sick' => 'Sick' + ]), + + Tables\Filters\SelectFilter::make('semester') + ->label('Semester') + ->options([ + 'first' => 'First Semester', + 'second' => 'Second Semester' + ]), + + Tables\Filters\Filter::make('date') + ->form([ + Forms\Components\DatePicker::make('date_from') + ->label('From'), + Forms\Components\DatePicker::make('date_until') + ->label('To'), + ]) + ->query(function (Builder $query, array $data): Builder { + return $query + ->when( + $data['date_from'], + fn (Builder $query, $date): Builder => $query->whereDate('date', '>=', $date), + ) + ->when( + $data['date_until'], + fn (Builder $query, $date): Builder => $query->whereDate('date', '<=', $date), + ); + }), + + Tables\Filters\SelectFilter::make('teacher_subject_id') + ->label('Teacher Subject') + ->relationship('teacherSubject', 'id') + ->searchable() + ->getOptionLabelFromRecordUsing(fn (TeacherSubject $record) => + $record->teacher->name . ' - ' . $record->subject->name) + ->preload(), + ]) + ->actions([ + Tables\Actions\EditAction::make() + ->iconButton(), + Tables\Actions\DeleteAction::make() + ->iconButton(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]) + ->emptyStateActions([ + Tables\Actions\CreateAction::make(), + ]) + ->defaultSort('date', 'desc') + ->groups([ + Tables\Grouping\Group::make('date') + ->label('Date') + ->date() + ->collapsible(), + Tables\Grouping\Group::make('teacherSubject.subject.name') + ->label('Subjects') + ->collapsible(), + Tables\Grouping\Group::make('semester') + ->label('Semester') + ->collapsible(), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListAttendances::route('/'), + 'create' => Pages\CreateAttendances::route('/create'), + 'edit' => Pages\EditAttendances::route('/{record}/edit'), + 'multiple' => Pages\MultipleAttendances::route('/multiple'), + ]; + } +} diff --git a/app/Filament/Resources/AttendancesResource/Pages/CreateAttendances.php b/app/Filament/Resources/AttendancesResource/Pages/CreateAttendances.php new file mode 100644 index 0000000..44e1f36 --- /dev/null +++ b/app/Filament/Resources/AttendancesResource/Pages/CreateAttendances.php @@ -0,0 +1,12 @@ +label('Multiple Attendance') + ->url('attendances/multiple') + ->icon('heroicon-o-user-group'), + ]; + } +} diff --git a/app/Filament/Resources/AttendancesResource/Pages/MultipleAttendances.php b/app/Filament/Resources/AttendancesResource/Pages/MultipleAttendances.php new file mode 100644 index 0000000..ad36c6a --- /dev/null +++ b/app/Filament/Resources/AttendancesResource/Pages/MultipleAttendances.php @@ -0,0 +1,167 @@ +form->fill([ + 'date' => now()->format('Y-m-d'), + 'semester' => 'first', + ]); + $this->attendanceDate = now()->format('Y-m-d'); + } + + public function form(Form $form): Form + { + return $form + ->schema([ + Select::make('teacher_subject_id') + ->label('Teacher Subject') + ->required() + ->options(TeacherSubject::with(['teacher', 'subject', 'class']) + ->get() + ->mapWithKeys(fn ($item) => [ + $item->id => $item->teacher->name . ' - ' . $item->subject->name . ' - ' . $item->class->class_name + ])) + ->searchable() + ->live() + ->afterStateUpdated(function ($state) { + $this->teacherSubjectId = $state; + $this->loadStudents(); + }), + + DatePicker::make('date') + ->label('Attendance Date') + ->required() + ->default(now()) + ->maxDate(now()) + ->live() + ->afterStateUpdated(function ($state) { + $this->attendanceDate = $state; + $this->loadStudents(); + }), + + Select::make('semester') + ->label('Semester') + ->required() + ->options([ + 'first' => 'First Semester', + 'second' => 'Second Semester' + ]) + ->live() + ->afterStateUpdated(function ($state) { + $this->semester = $state; + $this->loadStudents(); + }), + ]) + ->statePath('data'); + } + + protected function loadStudents(): void + { + if (!$this->attendanceDate || !$this->teacherSubjectId) { + $this->students = []; + return; + } + + $this->teacherSubject = TeacherSubject::where('id', $this->teacherSubjectId)->firstOrFail(); + + $this->students = Student::where('class_id', $this->teacherSubject->class_id) + ->orderBy('full_name') + ->get() + ->map(function ($student) { + $existingAttendance = Attendances::where('student_id', $student->id) + ->where('teacher_subject_id', $this->teacherSubjectId) + ->whereDate('date', $this->attendanceDate) + ->where('semester', $this->semester) + ->first(); + + return [ + 'id' => $student->id, + 'name' => $student->full_name, + 'nis' => $student->nis, + 'status' => $existingAttendance ? $existingAttendance->status : null, + 'attendance_id' => $existingAttendance ? $existingAttendance->id : null, + ]; + }) + ->toArray(); + } + + public function markAll($status): void + { + foreach ($this->students as $key => $student) { + $this->students[$key]['status'] = $status; + } + } + + public function submit(): void + { + DB::transaction(function () { + foreach ($this->students as $student) { + if ($student['status']) { + if ($student['attendance_id']) { + // Update existing attendance + Attendances::where('id', $student['attendance_id']) + ->update([ + 'status' => $student['status'], + 'recorded_by' => auth()->id(), + ]); + } else { + // Create new attendance + Attendances::create([ + 'student_id' => $student['id'], + 'teacher_subject_id' => $this->teacherSubject->id, + 'date' => $this->attendanceDate, + 'status' => $student['status'], + 'semester' => $this->semester, + 'recorded_by' => auth()->id(), + ]); + } + } + } + }); + + Notification::make() + ->title('Attendance saved successfully ' . $this->teacherSubject->id) + ->success() + ->send(); + + $this->loadStudents(); + } + + protected function getHeaderActions(): array + { + return [ + Actions\Action::make('back') + ->label('Back to Attendances') + ->url(static::getResource()::getUrl('index')), + ]; + } +} diff --git a/app/Filament/Resources/TeacherSubjectResource.php b/app/Filament/Resources/TeacherSubjectResource.php new file mode 100644 index 0000000..68a3787 --- /dev/null +++ b/app/Filament/Resources/TeacherSubjectResource.php @@ -0,0 +1,143 @@ +schema([ + Forms\Components\Section::make('Teacher Subject Assignment') + ->schema([ + Forms\Components\Select::make('teacher_id') + ->label('Teacher') + ->required() + ->options(User::role('teacher')->pluck('name', 'id')->toArray()) + ->searchable() + ->native(false), + + Forms\Components\Select::make('subject_id') + ->label('Subject') + ->required() + ->options(Subject::pluck('name', 'id')->toArray()) + ->searchable() + ->native(false), + + Forms\Components\Select::make('class_id') + ->label('Class') + ->required() + ->options(ClassRoom::pluck('class_name', 'id')->toArray()) + ->searchable() + ->native(false), + + Forms\Components\TextInput::make('academic_year') + ->label('Academic Year') + ->required() + ->placeholder('e.g. 2023/2024') + ->maxLength(9), + + ]) + ->columns(2), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('teacher.name') + ->label('Teacher') + ->searchable() + ->sortable(), + + Tables\Columns\TextColumn::make('subject.name') + ->label('Subject') + ->searchable() + ->sortable(), + + Tables\Columns\TextColumn::make('class.class_name') + ->label('Class') + ->searchable() + ->sortable(), + + Tables\Columns\TextColumn::make('academic_year') + ->label('Academic Year') + ->searchable() + ->sortable(), + + Tables\Columns\TextColumn::make('created_at') + ->label('Assigned At') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + Tables\Filters\SelectFilter::make('teacher_id') + ->label('Teacher') + ->relationship('teacher', 'name') + ->searchable() + ->preload(), + + Tables\Filters\SelectFilter::make('subject_id') + ->label('Subject') + ->relationship('subject', 'name') + ->searchable() + ->preload(), + + Tables\Filters\SelectFilter::make('class_id') + ->label('Class') + ->relationship('class', 'class_name') + ->searchable() + ->preload(), + + ]) + ->actions([ + Tables\Actions\EditAction::make(), + Tables\Actions\DeleteAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]) + ->emptyStateActions([ + Tables\Actions\CreateAction::make(), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListTeacherSubjects::route('/'), + 'create' => Pages\CreateTeacherSubject::route('/create'), + 'edit' => Pages\EditTeacherSubject::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/TeacherSubjectResource/Pages/CreateTeacherSubject.php b/app/Filament/Resources/TeacherSubjectResource/Pages/CreateTeacherSubject.php new file mode 100644 index 0000000..ea8ec15 --- /dev/null +++ b/app/Filament/Resources/TeacherSubjectResource/Pages/CreateTeacherSubject.php @@ -0,0 +1,35 @@ +where('subject_id', $data['subject_id']) + ->where('class_id', $data['class_id']) + ->where('academic_year', $data['academic_year']) + ->exists(); + + if ($exists) { + Notification::make() + ->title('Failed to save') + ->body('The combination of teacher, subject, class, and academic year is already registered.') + ->danger() + ->send(); + + $this->halt(); + } + + return $data; + } +} diff --git a/app/Filament/Resources/TeacherSubjectResource/Pages/EditTeacherSubject.php b/app/Filament/Resources/TeacherSubjectResource/Pages/EditTeacherSubject.php new file mode 100644 index 0000000..e46c535 --- /dev/null +++ b/app/Filament/Resources/TeacherSubjectResource/Pages/EditTeacherSubject.php @@ -0,0 +1,19 @@ +belongsTo(Student::class, 'student_id'); + } + + public function teacherSubject() + { + return $this->belongsTo(TeacherSubject::class, 'teacher_subject_id'); + } + + // Relasi dengan guru yang merekam + public function recorder() + { + return $this->belongsTo(User::class, 'recorded_by'); + } +} diff --git a/app/Models/ClassRoom.php b/app/Models/ClassRoom.php index 5c75d8a..f619c36 100644 --- a/app/Models/ClassRoom.php +++ b/app/Models/ClassRoom.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; class ClassRoom extends Model { @@ -22,4 +23,9 @@ class ClassRoom extends Model { return $this->hasMany(Student::class, 'class_id'); } + + public function teacherAssignments(): HasMany + { + return $this->hasMany(TeacherSubject::class, 'class_id'); + } } diff --git a/app/Models/Extracurricular.php b/app/Models/Extracurricular.php index e3847bf..f658ed3 100644 --- a/app/Models/Extracurricular.php +++ b/app/Models/Extracurricular.php @@ -6,5 +6,5 @@ use Illuminate\Database\Eloquent\Model; class Extracurricular extends Model { - protected $fillable = ['name', 'description',]; + protected $fillable = ['name', 'description']; } diff --git a/app/Models/Student.php b/app/Models/Student.php index b85def7..0ef2d6e 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -8,17 +8,18 @@ class Student extends Model { protected $fillable = [ 'nis', + 'nisn', 'full_name', 'gender', 'birth_date', 'birth_place', 'address', + 'religion', 'phone', 'email', 'class_id', 'parent_name', 'parent_phone', - 'profile_picture' ]; protected $casts = [ diff --git a/app/Models/Subject.php b/app/Models/Subject.php index d81186c..31bd242 100644 --- a/app/Models/Subject.php +++ b/app/Models/Subject.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; class Subject extends Model { @@ -13,6 +14,12 @@ class Subject extends Model return $this->hasMany(SubjectScope::class); } + public function teacherAssignments(): HasMany + { + return $this->hasMany(TeacherSubject::class, 'subject_id'); + } + + // public function grades() // { // return $this->hasMany(Grade::class); diff --git a/app/Models/TeacherSubject.php b/app/Models/TeacherSubject.php new file mode 100644 index 0000000..72b5f88 --- /dev/null +++ b/app/Models/TeacherSubject.php @@ -0,0 +1,56 @@ +belongsTo(User::class, 'teacher_id'); + } + + /** + * Get the subject associated with this assignment + */ + public function subject(): BelongsTo + { + return $this->belongsTo(Subject::class, 'subject_id'); + } + + /** + * Get the class associated with this assignment + */ + public function class(): BelongsTo + { + return $this->belongsTo(ClassRoom::class, 'class_id'); + } + + /** + * Scope for current academic year + */ + public function scopeCurrentYear($query) + { + return $query->where('academic_year', now()->format('Y')); + } + + /** + * Scope for specific academic year + */ + public function scopeForYear($query, $year) + { + return $query->where('academic_year', $year); + } + + public function attendances() + { + return $this->hasMany(Attendances::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 33d0647..7f5ed38 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; @@ -51,4 +52,9 @@ class User extends Authenticatable 'password' => 'hashed', ]; } + + public function teacherSubjects(): HasMany + { + return $this->hasMany(TeacherSubject::class, 'teacher_id'); + } } diff --git a/database/migrations/2025_04_13_010342_create_teacher_subjects_table.php b/database/migrations/2025_04_13_010342_create_teacher_subjects_table.php new file mode 100644 index 0000000..16d4b6a --- /dev/null +++ b/database/migrations/2025_04_13_010342_create_teacher_subjects_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('teacher_id')->constrained('users'); + $table->foreignId('subject_id')->constrained('subjects'); + $table->foreignId('class_id')->constrained('class_rooms'); + $table->string('academic_year'); + $table->timestamps(); + + $table->unique(['teacher_id', 'subject_id', 'class_id', 'academic_year']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('teacher_subjects'); + } +}; diff --git a/database/migrations/2025_04_13_010343_create_attendances_table.php b/database/migrations/2025_04_13_010343_create_attendances_table.php new file mode 100644 index 0000000..fa90b23 --- /dev/null +++ b/database/migrations/2025_04_13_010343_create_attendances_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('teacher_subject_id')->nullable()->constrained('teacher_subjects'); + $table->foreignId('student_id')->constrained('students'); + $table->date('date'); + $table->enum('status', ['present', 'absent', 'permission', 'sick']); + $table->foreignId('recorded_by')->constrained('users'); + $table->string('semester'); + $table->text('notes')->nullable(); + $table->timestamps(); + + $table->unique(['teacher_subject_id', 'student_id', 'date', 'semester']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('attendances'); + } +}; diff --git a/database/seeders/AssessmentComponentSeeder.php b/database/seeders/AssessmentComponentSeeder.php new file mode 100644 index 0000000..026ea97 --- /dev/null +++ b/database/seeders/AssessmentComponentSeeder.php @@ -0,0 +1,28 @@ + 'Ulangan Harian', 'code' => 'UH', 'weight' => 20, 'is_active' => true], + ['name' => 'Penilaian Tengah Semester', 'code' => 'PTS', 'weight' => 20, 'is_active' => true], + ['name' => 'Penilaian Akhir Semester', 'code' => 'PAS', 'weight' => 30, 'is_active' => true], + ['name' => 'Absensi', 'code' => 'ABS', 'weight' => 20, 'is_active' => true], + ['name' => 'Ekstrakurikuler', 'code' => 'EKS', 'weight' => 10, 'is_active' => true], + ]; + + foreach ($components as $component) { + AssessmentComponent::create($component); + } + } +} diff --git a/database/seeders/AttendanceSeeder.php b/database/seeders/AttendanceSeeder.php new file mode 100644 index 0000000..2605c7b --- /dev/null +++ b/database/seeders/AttendanceSeeder.php @@ -0,0 +1,17 @@ +get(); + + $classes = [ + ['class_name' => '1 A', 'class_level' => '1', 'academic_year' => '2024/2025'], + ['class_name' => '1 B', 'class_level' => '1', 'academic_year' => '2024/2025'], + ['class_name' => '2 A', 'class_level' => '2', 'academic_year' => '2024/2025'], + ['class_name' => '2 B', 'class_level' => '2', 'academic_year' => '2024/2025'], + ]; + + foreach ($classes as $index => $class) { + $class['homeroom_teacher_id'] = $teachers[$index % count($teachers)]->id; + ClassRoom::create($class); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index f89be28..04490f1 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,44 +15,14 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // 1. Buat user admin jika belum ada - $admin = User::firstOrCreate( - ['email' => 'admin@example.com'], - [ - 'name' => 'admin', - 'password' => bcrypt('admin'), - ] - ); - - // 2. Buat permission terkait role satu per satu - Permission::firstOrCreate(['name' => 'view_any_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'view_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'create_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'update_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'delete_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'delete_any_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'force_delete_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'force_delete_any_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'restore_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'restore_any_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'replicate_role', 'guard_name' => 'web']); - Permission::firstOrCreate(['name' => 'reorder_role', 'guard_name' => 'web']); - - // 3. Ambil semua permission yang berhubungan dengan "_role" - $rolePermissions = Permission::where('name', 'like', '%_role')->pluck('name'); - - // 4. Buat role super_admin jika belum ada - $role = Role::firstOrCreate([ - 'name' => 'super_admin', - 'guard_name' => 'web', + $this->call([ + UserSeeder::class, + SubjectSeeder::class, + ClassRoomSeeder::class, + StudentSeeder::class, + ExtracurricularSeeder::class, + AssessmentComponentSeeder::class, + AttendanceSeeder::class, ]); - - // 5. Assign semua permission "_role" ke super_admin - $role->syncPermissions($rolePermissions); - - // 6. Assign role super_admin ke user admin - if (!$admin->hasRole($role)) { - $admin->assignRole($role); - } } } diff --git a/database/seeders/ExtracurricularSeeder.php b/database/seeders/ExtracurricularSeeder.php new file mode 100644 index 0000000..d7322e9 --- /dev/null +++ b/database/seeders/ExtracurricularSeeder.php @@ -0,0 +1,26 @@ + 'Pramuka', 'description' => 'Kegiatan kepramukaan'], + ['name' => 'Basket', 'description' => 'Ekstrakurikuler basket'], + ['name' => 'Futsal', 'description' => 'Ekstrakurikuler futsal'], + ]; + + foreach ($extracurriculars as $extracurricular) { + Extracurricular::create($extracurricular); + } + } +} diff --git a/database/seeders/StudentSeeder.php b/database/seeders/StudentSeeder.php new file mode 100644 index 0000000..5c9e5b1 --- /dev/null +++ b/database/seeders/StudentSeeder.php @@ -0,0 +1,44 @@ +class_level . str_pad($i, 3, '0', STR_PAD_LEFT); + + Student::firstOrCreate( + ['nis' => $nis], // Cek berdasarkan NIS + [ + 'full_name' => 'Siswa ' . $classroom->class_name . ' ' . $i, + 'gender' => $gender, + 'birth_date' => now()->subYears(rand(15, 18))->subMonths(rand(1, 12)), + 'birth_place' => 'Kota ' . chr(65 + rand(0, 25)), + 'address' => 'Jl. Contoh No.' . $i, + 'phone' => '0812' . rand(1000000, 9999999), + 'class_id' => $classroom->id, + 'parent_name' => 'Orang Tua Siswa ' . $i, + 'religion' => $religion, + 'parent_phone' => '0813' . rand(1000000, 9999999), + ] + ); + } + } + } +} diff --git a/database/seeders/SubjectScopeSeeder.php b/database/seeders/SubjectScopeSeeder.php new file mode 100644 index 0000000..5e13ebb --- /dev/null +++ b/database/seeders/SubjectScopeSeeder.php @@ -0,0 +1,17 @@ + 'Matematika', 'is_religious' => false], + ['name' => 'Bahasa Indonesia', 'is_religious' => false], + ['name' => 'Bahasa Inggris', 'is_religious' => false], + ['name' => 'IPA', 'is_religious' => false], + ['name' => 'IPS', 'is_religious' => false], + ['name' => 'Pendidikan Agama Islam', 'is_religious' => true], + ['name' => 'Pendidikan Agama Kristen', 'is_religious' => true], + ['name' => 'Pendidikan Agama Katolik', 'is_religious' => true], + ['name' => 'Pendidikan Agama Hindu', 'is_religious' => true], + ['name' => 'Pendidikan Agama Buddha', 'is_religious' => true], + ]; + + foreach ($subjects as $subject) { + $createdSubject = Subject::create($subject); + + // Untuk mata pelajaran agama, buat scope + if (str_contains($createdSubject->name, 'Agama')) { + $religion = match(true) { + str_contains($createdSubject->name, 'Islam') => 'islam', + str_contains($createdSubject->name, 'Kristen') => 'kristen', + str_contains($createdSubject->name, 'Katolik') => 'katolik', + str_contains($createdSubject->name, 'Hindu') => 'hindu', + str_contains($createdSubject->name, 'Buddha') => 'buddha', + default => null + }; + + if ($religion) { + SubjectScope::create([ + 'subject_id' => $createdSubject->id, + 'religion' => $religion, + 'learning_goal' => 'Memahami prinsip dasar agama ' . $religion, + 'scope' => 'Tingkat dasar' + ]); + } + } + } + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..0664c80 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,75 @@ + 'admin@example.com'], + [ + 'name' => 'admin', + 'password' => bcrypt('admin'), + ] + ); + + $user = User::firstOrCreate( + ['email' => 'teacher@example.com'], + [ + 'name' => 'teacher', + 'password' => bcrypt('teacher'), + ] + ); + + // 2. Buat permission terkait role satu per satu + Permission::firstOrCreate(['name' => 'view_any_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'view_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'create_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'update_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'delete_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'delete_any_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'force_delete_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'force_delete_any_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'restore_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'restore_any_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'replicate_role', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'reorder_role', 'guard_name' => 'web']); + + // 3. Ambil semua permission yang berhubungan dengan "_role" + $rolePermissions = Permission::where('name', 'like', '%_role')->pluck('name'); + + // 4. Buat role super_admin jika belum ada + $role = Role::firstOrCreate([ + 'name' => 'super_admin', + 'guard_name' => 'web', + ]); + + $teacher = Role::firstOrCreate([ + 'name' => 'teacher', + 'guard_name' => 'web', + ]); + + $teacher->syncPermissions($rolePermissions); + + $role->syncPermissions($rolePermissions); + + if (!$admin->hasRole($role)) { + $admin->assignRole($role); + } + + if (!$user->hasRole($teacher)) { + $user->assignRole($teacher); + } + } +} diff --git a/resources/views/filament/resources/attendances-resource/pages/multiple-attendances.blade.php b/resources/views/filament/resources/attendances-resource/pages/multiple-attendances.blade.php new file mode 100644 index 0000000..11cd845 --- /dev/null +++ b/resources/views/filament/resources/attendances-resource/pages/multiple-attendances.blade.php @@ -0,0 +1,76 @@ + + + {{ $this->form }} + + @if($this->attendanceDate && $this->teacherSubjectId) +
+
+

Student Attendance

+
+ + Mark All Present + + + Mark All Absent + + + Mark All Sick + + + Mark All Permission + +
+
+ +
+ @foreach($this->students as $index => $student) +
+
+

{{ $student['name'] }}

+

{{ $student['nis'] }}

+
+
+ + Present + + + Absent + + + Sick + + + Permission + +
+
+ @endforeach +
+ + + Save Attendance + +
+ @endif +
+