add-attendaces

This commit is contained in:
reihanrere 2025-04-30 17:25:02 +07:00
parent 946006cfe6
commit 43344cb74b
28 changed files with 1223 additions and 40 deletions

View File

@ -0,0 +1,235 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\AttendancesResource\Pages;
use App\Models\Attendances;
use App\Models\ClassRoom;
use App\Models\Student;
use App\Models\Subject;
use App\Models\TeacherSubject;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class AttendancesResource extends Resource
{
protected static ?string $model = Attendances::class;
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-check';
protected static ?string $navigationGroup = 'Student Management';
public static function form(Form $form): Form
{
return $form
->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'),
];
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateAttendances extends CreateRecord
{
protected static string $resource = AttendancesResource::class;
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditAttendances extends EditRecord
{
protected static string $resource = AttendancesResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListAttendances extends ListRecords
{
protected static string $resource = AttendancesResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
Actions\Action::make('multiple')
->label('Multiple Attendance')
->url('attendances/multiple')
->icon('heroicon-o-user-group'),
];
}
}

View File

@ -0,0 +1,167 @@
<?php
namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource;
use App\Models\Attendances;
use App\Models\ClassRoom;
use App\Models\Student;
use App\Models\TeacherSubject;
use Filament\Actions;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\Page;
use Illuminate\Support\Facades\DB;
class MultipleAttendances extends Page
{
protected static string $resource = AttendancesResource::class;
protected static string $view = 'filament.resources.attendances-resource.pages.multiple-attendances';
public ?array $data = [];
public $classroomId;
public $teacherSubjectId;
public $teacherSubject;
public $attendanceDate;
public $semester = 'first'; // Default semester
public $students = [];
public function mount(): void
{
$this->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')),
];
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\TeacherSubjectResource\Pages;
use App\Models\ClassRoom;
use App\Models\Subject;
use App\Models\TeacherSubject;
use App\Models\User;
use Closure;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
class TeacherSubjectResource extends Resource
{
protected static ?string $model = TeacherSubject::class;
protected static ?string $navigationIcon = 'heroicon-o-academic-cap';
protected static ?string $navigationGroup = 'Academic Management';
public static function form(Form $form): Form
{
return $form
->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'),
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Filament\Resources\TeacherSubjectResource\Pages;
use App\Filament\Resources\TeacherSubjectResource;
use App\Models\TeacherSubject;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreateTeacherSubject extends CreateRecord
{
protected static string $resource = TeacherSubjectResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
$exists = TeacherSubject::where('teacher_id', $data['teacher_id'])
->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;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\TeacherSubjectResource\Pages;
use App\Filament\Resources\TeacherSubjectResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditTeacherSubject extends EditRecord
{
protected static string $resource = TeacherSubjectResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\TeacherSubjectResource\Pages;
use App\Filament\Resources\TeacherSubjectResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListTeacherSubjects extends ListRecords
{
protected static string $resource = TeacherSubjectResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Attendances extends Model
{
protected $fillable = ['student_id', 'teacher_subject_id', 'date', 'status', 'recorded_by', 'semester', 'notes'];
public function student()
{
return $this->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');
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class ClassRoom extends Model class ClassRoom extends Model
{ {
@ -22,4 +23,9 @@ class ClassRoom extends Model
{ {
return $this->hasMany(Student::class, 'class_id'); return $this->hasMany(Student::class, 'class_id');
} }
public function teacherAssignments(): HasMany
{
return $this->hasMany(TeacherSubject::class, 'class_id');
}
} }

View File

@ -6,5 +6,5 @@ use Illuminate\Database\Eloquent\Model;
class Extracurricular extends Model class Extracurricular extends Model
{ {
protected $fillable = ['name', 'description',]; protected $fillable = ['name', 'description'];
} }

View File

@ -8,17 +8,18 @@ class Student extends Model
{ {
protected $fillable = [ protected $fillable = [
'nis', 'nis',
'nisn',
'full_name', 'full_name',
'gender', 'gender',
'birth_date', 'birth_date',
'birth_place', 'birth_place',
'address', 'address',
'religion',
'phone', 'phone',
'email', 'email',
'class_id', 'class_id',
'parent_name', 'parent_name',
'parent_phone', 'parent_phone',
'profile_picture'
]; ];
protected $casts = [ protected $casts = [

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Subject extends Model class Subject extends Model
{ {
@ -13,6 +14,12 @@ class Subject extends Model
return $this->hasMany(SubjectScope::class); return $this->hasMany(SubjectScope::class);
} }
public function teacherAssignments(): HasMany
{
return $this->hasMany(TeacherSubject::class, 'subject_id');
}
// public function grades() // public function grades()
// { // {
// return $this->hasMany(Grade::class); // return $this->hasMany(Grade::class);

View File

@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TeacherSubject extends Model
{
protected $fillable = ['teacher_id', 'subject_id', 'class_id', 'academic_year'];
/**
* Get the teacher associated with this assignment
*/
public function teacher(): BelongsTo
{
return $this->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);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\HasRoles;
@ -51,4 +52,9 @@ class User extends Authenticatable
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
public function teacherSubjects(): HasMany
{
return $this->hasMany(TeacherSubject::class, 'teacher_id');
}
} }

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('teacher_subjects', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('attendances', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,28 @@
<?php
namespace Database\Seeders;
use App\Models\AssessmentComponent;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class AssessmentComponentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$components = [
['name' => '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);
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class AttendanceSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Database\Seeders;
use App\Models\ClassRoom;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class ClassRoomSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$teachers = User::role('teacher')->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);
}
}
}

View File

@ -15,44 +15,14 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// 1. Buat user admin jika belum ada $this->call([
$admin = User::firstOrCreate( UserSeeder::class,
['email' => 'admin@example.com'], SubjectSeeder::class,
[ ClassRoomSeeder::class,
'name' => 'admin', StudentSeeder::class,
'password' => bcrypt('admin'), ExtracurricularSeeder::class,
] AssessmentComponentSeeder::class,
); AttendanceSeeder::class,
// 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',
]); ]);
// 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);
}
} }
} }

View File

@ -0,0 +1,26 @@
<?php
namespace Database\Seeders;
use App\Models\Extracurricular;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ExtracurricularSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$extracurriculars = [
['name' => 'Pramuka', 'description' => 'Kegiatan kepramukaan'],
['name' => 'Basket', 'description' => 'Ekstrakurikuler basket'],
['name' => 'Futsal', 'description' => 'Ekstrakurikuler futsal'],
];
foreach ($extracurriculars as $extracurricular) {
Extracurricular::create($extracurricular);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Seeders;
use App\Models\ClassRoom;
use App\Models\Student;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class StudentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$classrooms = ClassRoom::all();
$religions = ['islam', 'kristen', 'katolik', 'hindu', 'buddha'];
foreach ($classrooms as $classroom) {
for ($i = 1; $i <= 20; $i++) {
$gender = $i % 2 == 0 ? 'L' : 'P';
$religion = $religions[array_rand($religions)];
$nis = $classroom->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),
]
);
}
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SubjectScopeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Database\Seeders;
use App\Models\Subject;
use App\Models\SubjectScope;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SubjectSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$subjects = [
['name' => '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'
]);
}
}
}
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// 1. Buat user admin jika belum ada
$admin = User::firstOrCreate(
['email' => '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);
}
}
}

View File

@ -0,0 +1,76 @@
<x-filament-panels::page>
<x-filament-panels::form wire:submit="submit">
{{ $this->form }}
@if($this->attendanceDate && $this->teacherSubjectId)
<div class="mt-6 space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium">Student Attendance</h3>
<div class="flex gap-2 space-x-2">
<x-filament::button type="button" wire:click="markAll('present')" color="success">
Mark All Present
</x-filament::button>
<x-filament::button type="button" wire:click="markAll('absent')" color="danger">
Mark All Absent
</x-filament::button>
<x-filament::button type="button" wire:click="markAll('sick')" color="warning">
Mark All Sick
</x-filament::button>
<x-filament::button type="button" wire:click="markAll('permission')" color="warning">
Mark All Permission
</x-filament::button>
</div>
</div>
<div class="space-y-2">
@foreach($this->students as $index => $student)
<div class="flex items-center p-3 border rounded-lg">
<div class="flex-1 flex items-center gap-4">
<p class="font-medium">{{ $student['name'] }}</p>
<p class="text-sm text-gray-500">{{ $student['nis'] }}</p>
</div>
<div class="flex gap-2 space-x-2">
<x-filament::button
type="button"
wire:click="$set('students.{{ $index }}.status', 'present')"
color="{{ $student['status'] === 'present' ? 'success' : 'gray' }}"
size="sm"
>
Present
</x-filament::button>
<x-filament::button
type="button"
wire:click="$set('students.{{ $index }}.status', 'absent')"
color="{{ $student['status'] === 'absent' ? 'danger' : 'gray' }}"
size="sm"
>
Absent
</x-filament::button>
<x-filament::button
type="button"
wire:click="$set('students.{{ $index }}.status', 'sick')"
color="{{ $student['status'] === 'sick' ? 'warning' : 'gray' }}"
size="sm"
>
Sick
</x-filament::button>
<x-filament::button
type="button"
wire:click="$set('students.{{ $index }}.status', 'permission')"
color="{{ $student['status'] === 'permission' ? 'warning' : 'gray' }}"
size="sm"
>
Permission
</x-filament::button>
</div>
</div>
@endforeach
</div>
<x-filament::button type="submit" class="w-full">
Save Attendance
</x-filament::button>
</div>
@endif
</x-filament-panels::form>
</x-filament-panels::page>