add assessment flow

This commit is contained in:
reihanrere 2025-05-01 19:51:32 +07:00
parent 43344cb74b
commit edf02bc09a
27 changed files with 1007 additions and 269 deletions

View File

@ -1,170 +0,0 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\AssessmentComponentResource\Pages;
use App\Models\AssessmentComponent;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\Str;
class AssessmentComponentResource extends Resource
{
protected static ?string $model = AssessmentComponent::class;
protected static ?string $navigationIcon = 'heroicon-o-calculator';
protected static ?string $navigationGroup = 'Data Master';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('Component Information')
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(100)
->live(onBlur: true)
->afterStateUpdated(function ($set, $state) {
$abbreviation = self::generateAbbreviation($state);
$set('code', $abbreviation);
}),
Forms\Components\TextInput::make('code')
->required()
->maxLength(50)
->unique(ignoreRecord: true)
->disabled()
->dehydrated(),
Forms\Components\TextInput::make('weight')
->required()
->numeric()
->minValue(0)
->maxValue(100)
->suffix('%')
->helperText('Bobot dalam persentase (0-100)'),
Forms\Components\Toggle::make('is_active')
->default(true)
->inline(false)
->onColor('success')
->offColor('danger'),
])->columns(2)
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('code')
->searchable()
->sortable()
->badge()
->color('info'),
Tables\Columns\TextColumn::make('weight')
->numeric()
->suffix('%')
->sortable()
->alignCenter(),
Tables\Columns\IconColumn::make('is_active')
->boolean()
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('is_active')
->options([
true => 'Active',
false => 'Inactive',
])
->label('Status'),
])
->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('weight', 'desc');
}
public static function getPages(): array
{
return [
'index' => Pages\ListAssessmentComponents::route('/'),
'create' => Pages\CreateAssessmentComponent::route('/create'),
'edit' => Pages\EditAssessmentComponent::route('/{record}/edit'),
];
}
protected static function generateAbbreviation(string $name): string
{
// Pastikan nama tidak kosong
if (empty(trim($name))) {
return 'DFT'; // Default value jika kosong
}
$commonAbbreviations = [
'ulangan harian' => 'UH',
'penilaian tengah semester' => 'PTS',
'penilaian akhir semester' => 'PAS',
'tugas' => 'TGS',
'praktikum' => 'PRK',
'kehadiran' => 'ABS',
'proyek' => 'PRJ',
];
$lowerName = Str::lower(trim($name));
// Cek apakah nama cocok dengan daftar singkatan umum
if (array_key_exists($lowerName, $commonAbbreviations)) {
return $commonAbbreviations[$lowerName];
}
// Logika untuk membuat singkatan dari kata pertama setiap kata
$words = preg_split('/\s+/', $lowerName);
$abbreviation = '';
foreach ($words as $word) {
// Ambil huruf pertama dari setiap kata yang tidak kosong
if (!empty($word)) {
$abbreviation .= strtoupper($word[0]);
}
}
// Jika hasil singkatan terlalu pendek, ambil 3 huruf pertama (tanpa spasi)
if (strlen($abbreviation) < 2) {
$cleaned = preg_replace('/\s+/', '', $name);
$abbreviation = strtoupper(substr($cleaned, 0, 3));
}
return $abbreviation ?: 'DFT'; // Fallback jika masih kosong
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,144 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\AssessmentResource\Pages;
use App\Filament\Resources\AssessmentResource\RelationManagers;
use App\Models\Assessment;
use App\Models\AssessmentComponent;
use App\Models\Student;
use App\Models\TeacherSubject;
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\Database\Eloquent\SoftDeletingScope;
class AssessmentResource extends Resource
{
protected static ?string $model = Assessment::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Academic Management';
public static function form(Form $form): Form
{
return $form
->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()
->afterStateUpdated(function (callable $set, $state) {
// Filter siswa berdasarkan kelas yang dipilih dalam teacher_subject_id
if ($state) {
$teacherSubject = TeacherSubject::find($state);
if ($teacherSubject) {
$set('student_id', null); // Reset student_id jika teacher_subject_id berubah
}
}
})
->required(),
Forms\Components\Select::make('student_id')
->label('Student')
->required()
->searchable()
// Filter opsi siswa berdasarkan kelas yang terkait dengan teacher_subject_id
->options(function ($get) {
$teacherSubjectId = $get('teacher_subject_id');
if ($teacherSubjectId) {
$teacherSubject = TeacherSubject::find($teacherSubjectId);
// Ambil siswa yang memiliki kelas yang sesuai dengan teacher_subject_id
$students = Student::where('class_id', $teacherSubject->class_id)
->pluck('full_name', 'id')
->toArray();
return $students;
}
return [];
})
->getOptionLabelUsing(function ($value) {
$student = Student::find($value);
return $student ? $student->full_name . ' (' . $student->nis . ')' : null;
})
->required(),
Forms\Components\TextInput::make('score')
->label('Score')
->numeric()
->required()
->minValue(0)
->maxValue(100),
]);
}
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('score')
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListAssessments::route('/'),
'create' => Pages\CreateAssessment::route('/create'),
'edit' => Pages\EditAssessment::route('/{record}/edit'),
'multiple' => Pages\MultipleAssessments::route('/multiple'),
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Filament\Resources\AssessmentResource\Pages;
use App\Filament\Resources\AssessmentResource;
use App\Models\Assessment;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreateAssessment extends CreateRecord
{
protected static string $resource = AssessmentResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
$exists = Assessment::where('teacher_subject_id', $data['teacher_subject_id'])
->where('student_id', $data['student_id'])
->exists();
if ($exists) {
Notification::make()
->title('Failed to save')
->body('A record for this teacher, subject, and student combination already exists.')
->danger()
->send();
$this->halt(); // Stop the save process
}
return $data;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Filament\Resources\AssessmentResource\Pages;
use App\Filament\Resources\AssessmentResource;
use App\Models\Assessment;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
class EditAssessment extends EditRecord
{
protected static string $resource = AssessmentResource::class;
protected function mutateFormDataBeforeSave(array $data): array
{
$exists = Assessment::where('teacher_subject_id', $data['teacher_subject_id'])
->where('student_id', $data['student_id'])
->where('id', '!=', $this->record->id) // ignore current record
->exists();
if ($exists) {
Notification::make()
->title('Failed to save')
->body('An assessment for this teacher, subject, and student combination already exists.')
->danger()
->send();
$this->halt();
}
return $data;
}
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

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

View File

@ -0,0 +1,129 @@
<?php
namespace App\Filament\Resources\AssessmentResource\Pages;
use App\Filament\Resources\AssessmentResource;
use App\Models\Assessment;
use App\Models\Attendances;
use Filament\Actions;
use Filament\Resources\Pages\page;
use App\Models\Student;
use App\Models\TeacherSubject;
use Filament\Forms;
use Filament\Notifications\Notification;
use Illuminate\Support\Collection;
class MultipleAssessments extends page
{
protected static string $resource = AssessmentResource::class;
protected static string $view = 'filament.resources.assessment-resource.pages.multiple-assessment';
use Forms\Concerns\InteractsWithForms;
public ?int $teacherSubjectId = null;
public array $students = [];
public function mount(): void
{
$this->form->fill();
}
protected function getFormSchema(): array
{
return [
Forms\Components\Select::make('teacherSubjectId')
->label('Teacher Subject')
->options(
TeacherSubject::with(['teacher', 'subject', 'class'])->get()->mapWithKeys(function ($item) {
return [
$item->id => "{$item->teacher->name} - {$item->subject->name} - {$item->class->class_name}"
];
})->toArray()
)
->searchable()
->reactive()
->afterStateUpdated(fn ($state) => $this->loadStudents()),
];
}
public function loadStudents(): void
{
if (!$this->teacherSubjectId) {
$this->students = [];
return;
}
$teacherSubject = TeacherSubject::find($this->teacherSubjectId);
if (!$teacherSubject) {
$this->students = [];
Notification::make()
->title('Not Found')
->body('Selected teacher subject not found.')
->danger()
->send();
return;
}
$this->students = Student::where('class_id', $teacherSubject->class_id)
->get()
->map(function ($student) {
$existingAssessment = Assessment::where('student_id', $student->id)
->where('teacher_subject_id', $this->teacherSubjectId)
->first();
return [
'id' => $student->id,
'name' => $student->full_name,
'nis' => $student->nis,
'score' => $existingAssessment ? $existingAssessment->score : null,
];
})->toArray();
}
public function submit(): void
{
if (!$this->teacherSubjectId || empty($this->students)) {
Notification::make()
->title('Error')
->body('Please select a teacher subject and enter student scores.')
->danger()
->send();
return;
}
foreach ($this->students as $student) {
if (!isset($student['score'])) continue;
Assessment::updateOrCreate(
[
'student_id' => $student['id'],
'teacher_subject_id' => $this->teacherSubjectId,
],
[
'score' => $student['score'],
]
);
}
Notification::make()
->title('Success')
->body('Assessments saved successfully.')
->success()
->send();
}
protected function getHeaderActions(): array
{
return [
Actions\Action::make('back')
->label('Back to Assessments')
->url(static::getResource()::getUrl('index')),
];
}
}

View File

@ -20,7 +20,7 @@ class AttendancesResource extends Resource
{ {
protected static ?string $model = Attendances::class; protected static ?string $model = Attendances::class;
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-check'; protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-check';
protected static ?string $navigationGroup = 'Student Management'; protected static ?string $navigationGroup = 'Academic Management';
public static function form(Form $form): Form public static function form(Form $form): Form
{ {
@ -36,6 +36,15 @@ class AttendancesResource extends Resource
$record->teacher->name . ' - ' . $record->subject->name . ' - ' . $record->class->class_name . ' - ' . $record->academic_year) $record->teacher->name . ' - ' . $record->subject->name . ' - ' . $record->class->class_name . ' - ' . $record->academic_year)
->searchable() ->searchable()
->preload() ->preload()
->afterStateUpdated(function (callable $set, $state) {
// Filter siswa berdasarkan kelas yang dipilih dalam teacher_subject_id
if ($state) {
$teacherSubject = TeacherSubject::find($state);
if ($teacherSubject) {
$set('student_id', null); // Reset student_id jika teacher_subject_id berubah
}
}
})
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\DatePicker::make('date') Forms\Components\DatePicker::make('date')
@ -48,7 +57,20 @@ class AttendancesResource extends Resource
->label('Student') ->label('Student')
->required() ->required()
->searchable() ->searchable()
->options(Student::pluck('full_name', 'id')->toArray()) // Filter opsi siswa berdasarkan kelas yang terkait dengan teacher_subject_id
->options(function ($get) {
$teacherSubjectId = $get('teacher_subject_id');
if ($teacherSubjectId) {
$teacherSubject = TeacherSubject::find($teacherSubjectId);
// Ambil siswa yang memiliki kelas yang sesuai dengan teacher_subject_id
$students = Student::where('class_id', $teacherSubject->class_id)
->pluck('full_name', 'id')
->toArray();
return $students;
}
return [];
})
->getOptionLabelUsing(function ($value) { ->getOptionLabelUsing(function ($value) {
$student = Student::find($value); $student = Student::find($value);
return $student ? $student->full_name . ' (' . $student->nis . ')' : null; return $student ? $student->full_name . ' (' . $student->nis . ')' : null;

View File

@ -3,10 +3,31 @@
namespace App\Filament\Resources\AttendancesResource\Pages; namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource; use App\Filament\Resources\AttendancesResource;
use App\Models\Assessment;
use Filament\Actions; use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
class CreateAttendances extends CreateRecord class CreateAttendances extends CreateRecord
{ {
protected static string $resource = AttendancesResource::class; protected static string $resource = AttendancesResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
$exists = Assessment::where('teacher_subject_id', $data['teacher_subject_id'])
->where('student_id', $data['student_id'])
->exists();
if ($exists) {
Notification::make()
->title('Failed to save')
->body('A record for this teacher, subject, and student combination already exists.')
->danger()
->send();
$this->halt(); // Stop the save process
}
return $data;
}
} }

View File

@ -18,7 +18,7 @@ class StudentResource extends Resource
protected static ?string $navigationIcon = 'heroicon-o-users'; protected static ?string $navigationIcon = 'heroicon-o-users';
protected static ?string $navigationGroup = 'Student Management'; protected static ?string $navigationGroup = 'Academic Management';
public static function form(Form $form): Form public static function form(Form $form): Form
{ {

View File

@ -19,7 +19,7 @@ class SubjectScopeResource extends Resource
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Student Management'; protected static ?string $navigationGroup = 'Academic Management';
public static function form(Form $form): Form public static function form(Form $form): Form
{ {

24
app/Models/Assessment.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Assessment extends Model
{
protected $fillable = [
'teacher_subject_id',
'student_id',
'score',
];
public function teacherSubject()
{
return $this->belongsTo(TeacherSubject::class);
}
public function student()
{
return $this->belongsTo(Student::class);
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AssessmentComponent extends Model
{
protected $fillable = ['name','code','weight','is_active'];
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Assessment;
use App\Models\User;
class AssessmentPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_assessment');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Assessment $assessment): bool
{
return $user->can('view_assessment');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_assessment');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Assessment $assessment): bool
{
return $user->can('update_assessment');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Assessment $assessment): bool
{
return $user->can('delete_assessment');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Assessment $assessment): bool
{
return $user->can('restore_assessment');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Assessment $assessment): bool
{
return $user->can('force_delete_assessment');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Attendances;
use App\Models\User;
class AttendancesPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_attendances');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Attendances $attendances): bool
{
return $user->can('view_attendances');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_attendances');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Attendances $attendances): bool
{
return $user->can('update_attendances');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Attendances $attendances): bool
{
return $user->can('delete_attendances');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Attendances $attendances): bool
{
return $user->can('restore_attendances');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Attendances $attendances): bool
{
return $user->can('force_delete_attendances');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\ClassRoom;
use App\Models\User;
class ClassRoomPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_class::room');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, ClassRoom $classRoom): bool
{
return $user->can('view_class::room');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_class::room');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, ClassRoom $classRoom): bool
{
return $user->can('update_class::room');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, ClassRoom $classRoom): bool
{
return $user->can('delete_class::room');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, ClassRoom $classRoom): bool
{
return $user->can('restore_class::room');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, ClassRoom $classRoom): bool
{
return $user->can('force_delete_class::room');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Extracurricular;
use App\Models\User;
class ExtracurricularPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_extracurricular');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Extracurricular $extracurricular): bool
{
return $user->can('view_extracurricular');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_extracurricular');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Extracurricular $extracurricular): bool
{
return $user->can('update_extracurricular');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Extracurricular $extracurricular): bool
{
return $user->can('delete_extracurricular');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Extracurricular $extracurricular): bool
{
return $user->can('restore_extracurricular');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Extracurricular $extracurricular): bool
{
return $user->can('force_delete_extracurricular');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Student;
use App\Models\User;
class StudentPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_student');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Student $student): bool
{
return $user->can('view_student');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_student');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Student $student): bool
{
return $user->can('update_student');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Student $student): bool
{
return $user->can('delete_student');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Student $student): bool
{
return $user->can('restore_student');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Student $student): bool
{
return $user->can('force_delete_student');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Subject;
use App\Models\User;
class SubjectPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_subject');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Subject $subject): bool
{
return $user->can('view_subject');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_subject');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Subject $subject): bool
{
return $user->can('update_subject');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Subject $subject): bool
{
return $user->can('delete_subject');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Subject $subject): bool
{
return $user->can('restore_subject');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Subject $subject): bool
{
return $user->can('force_delete_subject');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\SubjectScope;
use App\Models\User;
class SubjectScopePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_subject_scope');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SubjectScope $subjectScope): bool
{
return $user->can('view_subject_scope');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_subject_scope');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SubjectScope $subjectScope): bool
{
return $user->can('update_subject_scope');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SubjectScope $subjectScope): bool
{
return $user->can('delete_subject_scope');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, SubjectScope $subjectScope): bool
{
return $user->can('restore_subject_scope');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, SubjectScope $subjectScope): bool
{
return $user->can('force_delete_subject_scope');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\TeacherSubject;
use App\Models\User;
class TeacherSubjectPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_teacher_subject');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, TeacherSubject $teacherSubject): bool
{
return $user->can('view_teacher_subject');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_teacher_subject');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, TeacherSubject $teacherSubject): bool
{
return $user->can('update_teacher_subject');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, TeacherSubject $teacherSubject): bool
{
return $user->can('delete_teacher_subject');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, TeacherSubject $teacherSubject): bool
{
return $user->can('restore_teacher_subject');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, TeacherSubject $teacherSubject): bool
{
return $user->can('force_delete_teacher_subject');
}
}

View File

@ -11,13 +11,14 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('assessment_components', function (Blueprint $table) { Schema::create('assessments', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->foreignId('teacher_subject_id')->constrained('teacher_subjects');
$table->string('code')->unique(); $table->foreignId('student_id')->constrained('students');
$table->float('weight')->default(0); $table->float('score');
$table->boolean('is_active')->default(true);
$table->timestamps(); $table->timestamps();
$table->unique(['teacher_subject_id', 'student_id']);
}); });
} }
@ -26,6 +27,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('assessment_components'); Schema::dropIfExists('assessments');
} }
}; };

View File

@ -1,28 +0,0 @@
<?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

@ -21,7 +21,6 @@ class DatabaseSeeder extends Seeder
ClassRoomSeeder::class, ClassRoomSeeder::class,
StudentSeeder::class, StudentSeeder::class,
ExtracurricularSeeder::class, ExtracurricularSeeder::class,
AssessmentComponentSeeder::class,
AttendanceSeeder::class, AttendanceSeeder::class,
]); ]);
} }

View File

@ -0,0 +1,38 @@
<x-filament-panels::page>
<x-filament-panels::form wire:submit="submit">
{{ $this->form }}
@if($this->teacherSubjectId)
<div class="mt-6 space-y-4">
<h3 class="text-lg font-medium">Multiple Assessment</h3>
<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">
<p class="font-medium">{{ $student['name'] }}</p>
<p class="text-sm text-gray-500">{{ $student['nis'] }}</p>
</div>
<div class="w-32">
<x-filament::input
type="number"
:value="$student['score'] ?? null"
wire:model.defer="students.{{ $index }}.score"
placeholder="Score"
min="0"
max="100"
/>
</div>
</div>
@endforeach
</div>
<div class="mt-4">
<x-filament::button type="submit" color="primary">
Save Assessments
</x-filament::button>
</div>
</div>
@endif
</x-filament-panels::form>
</x-filament-panels::page>