add assessment flow
This commit is contained in:
parent
43344cb74b
commit
edf02bc09a
@ -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
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
144
app/Filament/Resources/AssessmentResource.php
Normal file
144
app/Filament/Resources/AssessmentResource.php
Normal 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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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')),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -20,7 +20,7 @@ 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';
|
||||
protected static ?string $navigationGroup = 'Academic Management';
|
||||
|
||||
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)
|
||||
->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
|
||||
}
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Forms\Components\DatePicker::make('date')
|
||||
@ -48,7 +57,20 @@ class AttendancesResource extends Resource
|
||||
->label('Student')
|
||||
->required()
|
||||
->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) {
|
||||
$student = Student::find($value);
|
||||
return $student ? $student->full_name . ' (' . $student->nis . ')' : null;
|
||||
|
||||
@ -3,10 +3,31 @@
|
||||
namespace App\Filament\Resources\AttendancesResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AttendancesResource;
|
||||
use App\Models\Assessment;
|
||||
use Filament\Actions;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAttendances extends CreateRecord
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ class StudentResource extends Resource
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
@ -19,7 +19,7 @@ class SubjectScopeResource extends Resource
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
24
app/Models/Assessment.php
Normal file
24
app/Models/Assessment.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AssessmentComponent extends Model
|
||||
{
|
||||
protected $fillable = ['name','code','weight','is_active'];
|
||||
}
|
||||
65
app/Policies/AssessmentPolicy.php
Normal file
65
app/Policies/AssessmentPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/AttendancesPolicy.php
Normal file
65
app/Policies/AttendancesPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/ClassRoomPolicy.php
Normal file
65
app/Policies/ClassRoomPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/ExtracurricularPolicy.php
Normal file
65
app/Policies/ExtracurricularPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/StudentPolicy.php
Normal file
65
app/Policies/StudentPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/SubjectPolicy.php
Normal file
65
app/Policies/SubjectPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/SubjectScopePolicy.php
Normal file
65
app/Policies/SubjectScopePolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
65
app/Policies/TeacherSubjectPolicy.php
Normal file
65
app/Policies/TeacherSubjectPolicy.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@ -11,13 +11,14 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('assessment_components', function (Blueprint $table) {
|
||||
Schema::create('assessments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('code')->unique();
|
||||
$table->float('weight')->default(0);
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->foreignId('teacher_subject_id')->constrained('teacher_subjects');
|
||||
$table->foreignId('student_id')->constrained('students');
|
||||
$table->float('score');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['teacher_subject_id', 'student_id']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,6 +27,6 @@ return new class extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('assessment_components');
|
||||
Schema::dropIfExists('assessments');
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,6 @@ class DatabaseSeeder extends Seeder
|
||||
ClassRoomSeeder::class,
|
||||
StudentSeeder::class,
|
||||
ExtracurricularSeeder::class,
|
||||
AssessmentComponentSeeder::class,
|
||||
AttendanceSeeder::class,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user