Compare commits

..

10 Commits

Author SHA1 Message Date
Reihan Renaldi
c8bb62e9f2
Merge pull request #6 from reihanrere/add-import-student
add import student
2025-05-25 17:10:05 +07:00
b98c57d565 add import student 2025-05-25 17:09:31 +07:00
Reihan Renaldi
f057b0c283
Merge pull request #5 from reihanrere/add-role-parent
add-page-parent
2025-05-25 11:14:23 +07:00
15bab26439 add-page-parent 2025-05-25 11:13:50 +07:00
Reihan Renaldi
38d381b388
Merge pull request #4 from reihanrere/add-settings
add-setting
2025-05-24 21:09:33 +07:00
babc8b2bd3 add-setting 2025-05-24 21:08:04 +07:00
Reihan Renaldi
f6efb0d61e
Merge pull request #3 from reihanrere/add-generate-pdf-library
add generate pdf & add some field on users
2025-05-18 22:07:07 +07:00
6b0e5bb040 add generate pdf & add some field on users 2025-05-18 22:06:45 +07:00
Reihan Renaldi
d65bf05e47
Merge pull request #2 from reihanrere/add-report-student
add-report-student-preview
2025-05-16 21:14:33 +07:00
017a49350c add-report-student-preview 2025-05-16 21:14:06 +07:00
47 changed files with 5235 additions and 250 deletions

View File

@ -0,0 +1,81 @@
<?php
namespace App\Filament\Imports;
use App\Models\Student;
use App\Models\User;
use Filament\Actions\Imports\ImportColumn;
use Filament\Actions\Imports\Importer;
use Filament\Actions\Imports\Models\Import;
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
class StudentImporter extends Importer
{
protected static ?string $model = Student::class;
public static function getColumns(): array
{
return [
ImportColumn::make('full_name')
->requiredMapping()
->rules(['required', 'max:255']),
ImportColumn::make('nis')
->rules(['required','max:255']),
ImportColumn::make('nisn')
->rules(['required','max:255']),
ImportColumn::make('gender')
->requiredMapping()
->rules(['required', 'max:255']),
ImportColumn::make('birth_date')
->rules(['date']),
ImportColumn::make('birth_place')
->rules(['max:255']),
ImportColumn::make('address'),
ImportColumn::make('religion'),
ImportColumn::make('phone')
->rules(['required','max:255']),
ImportColumn::make('email')
->rules(['email', 'max:255']),
ImportColumn::make('parent_name')
->rules(['required','max:255']),
ImportColumn::make('parent_phone')
->rules(['required','max:255']),
];
}
public function resolveRecord(): ?Student
{
// Cek duplikat NIS
if (Student::where('nis', $this->data['nis'])->exists()) {
throw new RowImportFailedException('NIS sudah terdaftar');
}
// Cek duplikat NISN
if (Student::where('nisn', $this->data['nisn'])->exists()) {
throw new RowImportFailedException('NISN sudah terdaftar');
}
// Cek duplikat phone
if (Student::where('phone', $this->data['phone'])->exists()) {
throw new RowImportFailedException('Nomor telepon sudah terdaftar');
}
// Cek duplikat email di tabel users
if (User::where('email', $this->data['email'])->exists()) {
throw new RowImportFailedException('Email sudah digunakan');
}
return new Student();
}
public static function getCompletedNotificationBody(Import $import): string
{
$body = 'Your student import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.';
if ($failedRowsCount = $import->getFailedRowsCount()) {
$body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.';
}
return $body;
}
}

View File

@ -3,14 +3,22 @@
namespace App\Filament\Pages;
use App\Models\AcademicYear;
use App\Models\Assessment;
use App\Models\ClassRoom;
use App\Models\ClassSubject;
use App\Models\CompetencyAchievement;
use App\Models\ExtracurricularAssessment;
use App\Models\HomeRoomTeacher;
use App\Models\Student;
use App\Models\SchoolInformation;
use App\Models\Subject;
use App\Models\TeacherSubject;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Carbon\Carbon;
use function PHPUnit\Framework\isEmpty;
use function Symfony\Component\String\s;
use Barryvdh\DomPDF\Facade\Pdf;
Carbon::setLocale('id');
@ -30,12 +38,33 @@ class ReportPreview extends Page
public $table = [];
public $sakit = 0;
public $izin = 0;
public $tanpa_keterangan = 0;
public $home_room_teacher;
public function mount()
{
$this->loadData();
$this->loadAssessment();
}
public function saveAttendance()
{
$this->validate([
'sakit' => 'required|integer|min:0',
'izin' => 'required|integer|min:0',
'tanpa_keterangan' => 'required|integer|min:0',
]);
Notification::make()
->title('Data Kehadiran Disimpan')
->success()
->send();
}
protected function loadData() : void
{
$this->student = Student::find(request()->query('studentId'));
@ -68,29 +97,81 @@ class ReportPreview extends Page
$semester = request()->query('semester');
$year_id = request()->query('yearId');
// Class Student Mapping
$classSubjects = ClassSubject::with(['subject', 'class', 'academicYear'])
->where('class_room_id', $class_id)
->where('academic_year_id', $year_id)
// Assessment Mapping
$assessments = Assessment::where('semester', $this->semester)
->whereHas('teacherSubject', function($query) use ($class_id, $year_id) {
$query->where('academic_year_id', $year_id)
->where('class_id', $class_id);
})
->where('student_id', $this->student->id)
->with('teacherSubject.subject', 'student')
->get()
->sortByDesc(function ($item) {
return $item->subject->name;
->map(function ($as) use ($class_id) {
$subject = $as->teacherSubject->subject ?? null;
if (!$subject) {
return null;
}
$competencyAchievements = CompetencyAchievement::where('class_room_id', $class_id)
->where('subject_id', $subject->id)
->where('min_score', '<=', $as->score)
->where('max_score', '>=', $as->score)
->first();
return [
"score" => $as->score,
"subject" => $subject->name,
"category" => $subject->category,
"competency_achievement" => $competencyAchievements ? $this->student->full_name . '' . $competencyAchievements->description : '-',
];
})
->filter() // Hapus null jika ada
->sortByDesc('subject')
->sortBy(function ($item) {
return $item['category'] === 'umum' ? 0 : 1;
})
->values()
->toArray();
$this->table["assessments"]['umum'] = array_filter($assessments, function ($item) {
return $item["category"] === "umum";
});
$this->table["assessments"]['muatan lokal'] = array_filter($assessments, function ($item) {
return $item["category"] === "muatan lokal";
});
$this->table["assessments"]['seni'] = array_filter($assessments, function ($item) {
return $item["category"] === "seni";
});
$extracurricular = ExtracurricularAssessment::with('classStudent','extracurricular')
->where('semester', $this->semester)
->whereHas('classStudent', function($query) use ($class_id, $year_id) {
$query->where('academic_year_id', $year_id)
->where('class_room_id', $class_id)
->where('student_id', $this->student->id);
})
->get()
->map(function ($as) {
return [
"name" => $as->extracurricular->name,
"predicate" => $as->predicate,
"description" => $as->description,
];
})
->toArray();
$this->table["extracurricular"] = $extracurricular;
$subjects = [];
$homeRoom = HomeRoomTeacher::with(['teacher'])
->where('class_room_id', $this->class->id)
->where('academic_year_id', $this->academic_year->id)
->firstOrFail()
->toArray();
foreach ($classSubjects as $classSubject) {
$category = strtolower($classSubject['subject']['category']);
$subjectName = $classSubject['subject']['name'];
$subjectId = $classSubject['subject']['id'];
$subjects[$category][$subjectId] = $subjectName;
}
$this->table["subjects"] = $subjects;
// dd($subjects);
$this->home_room_teacher = $homeRoom ?? [];
}
public function extractClassLetter($className)

View File

@ -22,6 +22,16 @@ class SchoolInformation extends Page implements HasForms
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 999; // Biar muncul paling bawah
public static function canAccess(): bool
{
return auth()->user()?->can('page_SchoolInformation');
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()?->can('page_SchoolInformation');
}
public ?array $data = [];
public ?SchoolInformationModel $record = null;

View File

@ -7,6 +7,7 @@ use App\Models\Assessment;
use App\Models\ClassRoom;
use App\Models\ClassStudent;
use App\Models\ClassSubject;
use App\Models\HomeRoomTeacher;
use App\Models\Student;
use App\Models\Subject;
use App\Models\TeacherSubject;
@ -32,12 +33,30 @@ class StudentReport extends Page
public static function canAccess(): bool
{
return auth()->user()->hasAnyRole(['super_admin']);
$user = auth()->user();
if ($user->hasRole('parent')) {
return true;
}
$isHomeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->exists();
return $user->can('page_SchoolInformation') || $isHomeRoomTeacher;
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->hasAnyRole(['super_admin']);
$user = auth()->user();
if ($user->hasRole('parent')) {
return true;
}
// Untuk teacher (wali kelas)
$isHomeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->exists();
// Atau punya permission khusus
return $user->can('page_SchoolInformation') || $isHomeRoomTeacher;
}
public function mount(): void
@ -54,9 +73,33 @@ class StudentReport extends Page
Select::make('class_id')
->label('Class')
->required()
->options(ClassRoom::pluck('class_name', 'id')->toArray())
->options(function () {
$query = ClassRoom::query();
$user = auth()->user();
if ($user->hasAnyRole(['teacher'])) {
$homeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->first();
if ($homeRoomTeacher) {
$query->where('id', $homeRoomTeacher->class_room_id);
}
} else if ($user->hasAnyRole(['parent'])) {
$student = Student::where("email", $user->email)->first(); // Changed from student email to parent_email
if ($student) {
$classStudentIds = ClassStudent::where("student_id", $student->id)
->pluck('class_room_id')
->toArray();
$query->whereIn("id", $classStudentIds);
}
}
return $query->pluck('class_name', 'id')->toArray();
})
->searchable()
->reactive()
->native(false)
->afterStateUpdated(function ($state) {
$this->class_id = $state;
$this->data['class_id'] = $state; // Update data array
@ -98,6 +141,15 @@ class StudentReport extends Page
return;
}
$user = auth()->user();
$isParent = $user->hasRole('parent');
$studentId = null;
if ($isParent) {
$student = Student::where('email', $user->email)->first();
$studentId = $student->id ?? null;
}
$groupedAssessment = [];
$assessments = Assessment::where('semester', $this->semester)
@ -105,6 +157,9 @@ class StudentReport extends Page
$query->where('academic_year_id', $this->academic_year)
->where('class_id', $this->class_id);
})
->when($isParent && $studentId, function($query) use ($studentId) {
$query->where('student_id', $studentId); // Tambahan filter untuk parent
})
->with('teacherSubject', 'student')
->get()
->toArray();
@ -131,6 +186,9 @@ class StudentReport extends Page
$students = ClassStudent::with(['class', 'academicYear', 'student'])
->where('class_room_id', $this->class_id)
->where('academic_year_id', $this->academic_year)
->when($isParent && $studentId, function($query) use ($studentId) {
$query->where('student_id', $studentId); // Tambahan filter untuk parent
})
->get()
->map(function($student) {
return $student['student'];

View File

@ -7,6 +7,7 @@ use App\Filament\Resources\AcademicYearResource\RelationManagers;
use App\Models\AcademicYear;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
@ -41,7 +42,9 @@ class AcademicYearResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
->searchable()
->weight(fn (AcademicYear $record) => $record->is_active ? 'bold' : 'normal')
->color(fn (AcademicYear $record) => $record->is_active ? 'success' : null),
Tables\Columns\IconColumn::make('is_active')
->boolean(),
Tables\Columns\TextColumn::make('start_date')
@ -68,6 +71,22 @@ class AcademicYearResource extends Resource
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('toggleActive')
->label('Toggle Active')
->icon('heroicon-o-power')
->action(function (AcademicYear $record) {
// If setting to active, deactivate all others
if ($record->is_active) {
Notification::make()
->title('Info')
->body('The school year is active')
->info()
->send();
} else {
AcademicYear::where('is_active', true)->update(['is_active' => false]);
$record->update(['is_active' => true]);
}
}),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([

View File

@ -7,9 +7,11 @@ use App\Filament\Resources\AssessmentResource\RelationManagers;
use App\Models\Assessment;
use App\Models\AssessmentComponent;
use App\Models\ClassStudent;
use App\Models\HomeRoomTeacher;
use App\Models\Student;
use App\Models\TeacherSubject;
use Filament\Forms;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
@ -29,14 +31,56 @@ class AssessmentResource extends Resource
{
return $form
->schema([
Forms\Components\Select::make('teacher_subject_id')
// 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->academicYear->name)
// ->searchable()
// ->preload()
// ->afterStateUpdated(function (callable $set, $state) {
// if ($state) {
// $teacherSubject = TeacherSubject::find($state);
// if ($teacherSubject) {
// $set('student_id', null);
// }
// }
// })
// ->required(),
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->academicYear->name)
->options(function () {
$user = auth()->user();
$query = TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear']);
if ($user->hasRole('teacher')) {
// Ambil ID kelas dimana user menjadi wali kelas
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
$query->where(function($q) use ($user, $homeRoomClassIds) {
// Mata pelajaran yang diajarkan oleh guru ini
$q->where('teacher_id', $user->id)
// ATAU kelas dimana dia menjadi wali kelas
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return $query->get()->mapWithKeys(fn ($item) => [
$item->id => sprintf('%s - %s - %s - %s',
$item->teacher->name,
$item->subject->name,
$item->class->class_name,
$item->academicYear->name
)
]);
})
->searchable()
->preload()
->live()
->afterStateUpdated(function (callable $set, $state) {
if ($state) {
$teacherSubject = TeacherSubject::find($state);
@ -44,8 +88,7 @@ class AssessmentResource extends Resource
$set('student_id', null);
}
}
})
->required(),
}),
Forms\Components\Select::make('student_id')
->label('Student')
@ -128,7 +171,7 @@ class AssessmentResource extends Resource
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\DeleteBulkAction::make()->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
]),
])
->emptyStateActions([

View File

@ -3,21 +3,43 @@
namespace App\Filament\Resources\AssessmentResource\Pages;
use App\Filament\Resources\AssessmentResource;
use App\Models\Assessment;
use App\Models\HomeRoomTeacher;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
class ListAssessments extends ListRecords
{
protected static string $resource = AssessmentResource::class;
protected function getTableQuery(): Builder
{
$user = auth()->user();
if ($user->hasRole('teacher')) {
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
return Assessment::with('teacherSubject', 'student')
->whereHas('teacherSubject', function (Builder $query) use ($homeRoomClassIds, $user) {
$query->where('teacher_id', $user->id)
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return Assessment::query();
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
Actions\CreateAction::make()->visible(fn () => auth()->user()->hasRole('admin')),
Actions\Action::make('multiple')
->label('Multiple Assessments')
->url('assessments/multiple')
->icon('heroicon-o-user-group'),
->icon('heroicon-o-user-group')
->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
];
}
}

View File

@ -6,6 +6,7 @@ use App\Filament\Resources\AssessmentResource;
use App\Models\Assessment;
use App\Models\Attendances;
use App\Models\ClassStudent;
use App\Models\HomeRoomTeacher;
use Filament\Actions;
use Filament\Forms\Components\Select;
use Filament\Resources\Pages\page;
@ -43,17 +44,51 @@ class MultipleAssessments extends page
return [
Forms\Components\Grid::make(2)
->schema([
Forms\Components\Select::make('teacherSubjectId')
// Forms\Components\Select::make('teacherSubjectId')
// ->label('Teacher Subject')
// ->options(
// TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear'])->get()->mapWithKeys(function ($item) {
// return [
// $item->id => "{$item->teacher->name} - {$item->subject->name} - {$item->class->class_name} - {$item->academicYear->name}"
// ];
// })->toArray()
// )
// ->searchable()
// ->reactive()
// ->afterStateUpdated(function ($state) {
// $this->teacherSubjectId = $state;
// $this->loadStudents();
// }),
Select::make('teacherSubjectId')
->label('Teacher Subject')
->options(
TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear'])->get()->mapWithKeys(function ($item) {
return [
$item->id => "{$item->teacher->name} - {$item->subject->name} - {$item->class->class_name} - {$item->academicYear->name}"
];
})->toArray()
)
->required()
->options(function () {
$user = auth()->user();
$query = TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear']);
if ($user->hasRole('teacher')) {
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
$query->where(function($q) use ($user, $homeRoomClassIds) {
$q->where('teacher_id', $user->id)
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return $query->get()->mapWithKeys(fn ($item) => [
$item->id => sprintf('%s - %s - %s - %s',
$item->teacher->name,
$item->subject->name,
$item->class->class_name,
$item->academicYear->name
)
]);
})
->searchable()
->reactive()
->live()
->afterStateUpdated(function ($state) {
$this->teacherSubjectId = $state;
$this->loadStudents();

View File

@ -6,11 +6,13 @@ use App\Filament\Resources\AttendancesResource\Pages;
use App\Models\Attendances;
use App\Models\ClassRoom;
use App\Models\ClassStudent;
use App\Models\HomeRoomTeacher;
use App\Models\Student;
use App\Models\Subject;
use App\Models\TeacherSubject;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
@ -29,14 +31,38 @@ class AttendancesResource extends Resource
->schema([
Forms\Components\Section::make('Data Absensi')
->schema([
Forms\Components\Select::make('teacher_subject_id')
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->academicYear->name)
->options(function () {
$user = auth()->user();
$query = TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear']);
if ($user->hasRole('teacher')) {
// Ambil ID kelas dimana user menjadi wali kelas
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
$query->where(function($q) use ($user, $homeRoomClassIds) {
// Mata pelajaran yang diajarkan oleh guru ini
$q->where('teacher_id', $user->id)
// ATAU kelas dimana dia menjadi wali kelas
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return $query->get()->mapWithKeys(fn ($item) => [
$item->id => sprintf('%s - %s - %s - %s',
$item->teacher->name,
$item->subject->name,
$item->class->class_name,
$item->academicYear->name
)
]);
})
->searchable()
->preload()
->live()
->afterStateUpdated(function (callable $set, $state) {
if ($state) {
$teacherSubject = TeacherSubject::find($state);
@ -53,7 +79,7 @@ class AttendancesResource extends Resource
->default(now())
->live(),
Forms\Components\Select::make('student_id')
Select::make('student_id')
->label('Student')
->required()
->searchable()
@ -77,7 +103,7 @@ class AttendancesResource extends Resource
return $student ? $student->full_name . ' (' . $student->nis . ')' : null;
}),
Forms\Components\Select::make('semester')
Select::make('semester')
->label('Semester')
->required()
->options([
@ -85,7 +111,7 @@ class AttendancesResource extends Resource
'second' => 'Second Semester'
]),
Forms\Components\Select::make('status')
Select::make('status')
->label('Status')
->required()
->options([
@ -161,10 +187,6 @@ class AttendancesResource extends Resource
Tables\Columns\TextColumn::make('recorder.name')
->label('Record by')
->toggleable(),
Tables\Columns\TextColumn::make('notes')
->label('Notes')
->toggleable()
])
->filters([
Tables\Filters\SelectFilter::make('status')
@ -218,12 +240,9 @@ class AttendancesResource extends Resource
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\DeleteBulkAction::make()->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
])
->defaultSort('date', 'desc')
->groups([
Tables\Grouping\Group::make('date')

View File

@ -3,21 +3,43 @@
namespace App\Filament\Resources\AttendancesResource\Pages;
use App\Filament\Resources\AttendancesResource;
use App\Models\Attendances;
use App\Models\HomeRoomTeacher;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
class ListAttendances extends ListRecords
{
protected static string $resource = AttendancesResource::class;
protected function getTableQuery(): ?Builder
{
$user = auth()->user();
if ($user->hasRole('teacher')) {
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
return Attendances::with('teacherSubject', 'student')
->whereHas('teacherSubject', function (Builder $query) use ($homeRoomClassIds, $user) {
$query->where('teacher_id', $user->id)
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return Attendances::query();
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
Actions\CreateAction::make()->visible(fn () => auth()->user()->hasRole('admin')),
Actions\Action::make('multiple')
->label('Multiple Attendance')
->url('attendances/multiple')
->icon('heroicon-o-user-group'),
->icon('heroicon-o-user-group')
->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
];
}
}

View File

@ -7,6 +7,7 @@ use App\Models\Assessment;
use App\Models\Attendances;
use App\Models\ClassRoom;
use App\Models\ClassStudent;
use App\Models\HomeRoomTeacher;
use App\Models\Student;
use App\Models\TeacherSubject;
use Filament\Actions;
@ -48,11 +49,33 @@ class MultipleAttendances extends Page
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 . ' - ' . $item->academicYear->name
]))
->options(function () {
$user = auth()->user();
$query = TeacherSubject::with(['teacher', 'subject', 'class', 'academicYear']);
if ($user->hasRole('teacher')) {
// Ambil ID kelas dimana user menjadi wali kelas
$homeRoomClassIds = HomeRoomTeacher::where('teacher_id', $user->id)
->pluck('class_room_id')
->toArray();
$query->where(function($q) use ($user, $homeRoomClassIds) {
// Mata pelajaran yang diajarkan oleh guru ini
$q->where('teacher_id', $user->id)
// ATAU kelas dimana dia menjadi wali kelas
->orWhereIn('class_id', $homeRoomClassIds);
});
}
return $query->get()->mapWithKeys(fn ($item) => [
$item->id => sprintf('%s - %s - %s - %s',
$item->teacher->name,
$item->subject->name,
$item->class->class_name,
$item->academicYear->name
)
]);
})
->searchable()
->live()
->afterStateUpdated(function ($state) {
@ -135,28 +158,6 @@ class MultipleAttendances extends Page
}
$this->students = $result;
// $this->students = ClassStudent::where('class_room_id', $this->teacherSubjectId)
// ->where('academic_year_id', $this->teacherSubject->academic_year_id)
// ->with('student')
// ->get()
// ->map(function ($student) {
// $existingAttendance = Attendances::where('student_id', $student->student_id)
// ->where('teacher_subject_id', $this->teacherSubjectId)
// ->whereDate('date', $this->attendanceDate)
// ->where('semester', $this->semester)
// ->first();
//
// return [
// 'id' => $student->student->id,
// 'name' => $student->student->full_name,
// 'nis' => $student->student->nis,
// 'status' => $existingAttendance ? $existingAttendance->status : null,
// 'attendance_id' => $existingAttendance ? $existingAttendance->id : null,
// ];
// })
// ->values()
// ->toArray();
}
public function markAll($status): void

View File

@ -6,6 +6,7 @@ use App\Filament\Resources\CompetencyAchievementResource\Pages;
use App\Filament\Resources\CompetencyAchievementResource\RelationManagers;
use App\Models\ClassRoom;
use App\Models\CompetencyAchievement;
use App\Models\HomeRoomTeacher;
use App\Models\Subject;
use Filament\Forms;
use Filament\Forms\Form;
@ -35,7 +36,20 @@ class CompetencyAchievementResource extends Resource
Forms\Components\Select::make('class_room_id')
->label('Class')
->required()
->options(ClassRoom::pluck('class_name', 'id')->toArray())
->options(function () {
$query = ClassRoom::query();
$user = auth()->user();
if ($user->hasAnyRole(['teacher'])) {
$homeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->first();
if ($homeRoomTeacher) {
$query->where('id', $homeRoomTeacher->class_room_id);
}
}
return $query->pluck('class_name', 'id')->toArray();
})
->searchable()
->native(false),
@ -87,7 +101,7 @@ class CompetencyAchievementResource extends Resource
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\DeleteBulkAction::make()->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
]),
])
->emptyStateActions([

View File

@ -3,13 +3,30 @@
namespace App\Filament\Resources\CompetencyAchievementResource\Pages;
use App\Filament\Resources\CompetencyAchievementResource;
use App\Models\CompetencyAchievement;
use App\Models\HomeRoomTeacher;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
class ListCompetencyAchievements extends ListRecords
{
protected static string $resource = CompetencyAchievementResource::class;
protected function getTableQuery(): Builder
{
$user = auth()->user();
if ($user->hasRole('teacher')) {
$homeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->firstOrFail();
if ($homeRoomTeacher) {
return CompetencyAchievement::where('class_room_id', $homeRoomTeacher->class_room_id);
}
}
return CompetencyAchievement::query();
}
protected function getHeaderActions(): array
{
return [

View File

@ -7,14 +7,12 @@ use App\Filament\Resources\ExtracurricularAssessmentResource\RelationManagers;
use App\Models\ClassStudent;
use App\Models\Extracurricular;
use App\Models\ExtracurricularAssessment;
use App\Models\TeacherSubject;
use App\Models\HomeRoomTeacher;
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 ExtracurricularAssessmentResource extends Resource
{
@ -27,14 +25,36 @@ class ExtracurricularAssessmentResource extends Resource
public static function form(Form $form): Form
{
return $form
->schema([
->schema(components: [
Forms\Components\Select::make('class_student_id')
->relationship('classStudent', 'id') // atau gunakan relasi yang lebih deskriptif jika ada
->getOptionLabelFromRecordUsing(fn (ClassStudent $record) =>
$record->class->class_name . ' - ' . $record->student->full_name . ' - ' . $record->academicYear->name)
->label('Siswa & Kelas')
->options(function () {
$user = auth()->user();
$query = ClassStudent::with(['student', 'class']);
if ($user->hasAnyRole(['admin']))
{
return $query->get()->mapWithKeys(function ($cs) {
return [
$cs->id => $cs->student->full_name . ' - ' . $cs->class->class_name . ' - ' . $cs->academicYear->name,
];
});
} else if ($user->hasAnyRole(['teacher'])) {
$homeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->firstOrFail();
$query->whereHas('class', function ($query) use ($homeRoomTeacher) {
$query->where('id', $homeRoomTeacher->class_room_id);
});
return $query->get()->mapWithKeys(function ($cs) {
return [
$cs->id => $cs->student->full_name . ' - ' . $cs->class->class_name . ' - ' . $cs->academicYear->name,
];
});
} else {
return [];
}
})
->searchable()
->preload()
->required(),
->preload(),
Forms\Components\Select::make('extracurricular_id')
->relationship('extracurricular', 'name')
@ -50,8 +70,19 @@ class ExtracurricularAssessmentResource extends Resource
])
->required(),
Forms\Components\TextInput::make('score')
->numeric()
Forms\Components\Select::make('predicate')
->label('Predicate')
->options([
'A' => 'A',
'B' => 'B',
'C' => 'C',
'D' => 'D',
'E' => 'E',
])
->required(),
Forms\Components\Textarea::make('description')
->label('Description')
->required(),
]);
}
@ -74,10 +105,17 @@ class ExtracurricularAssessmentResource extends Resource
Tables\Columns\TextColumn::make('extracurricular.name')
->label('Extracurricular'),
Tables\Columns\TextColumn::make('semester'),
Tables\Columns\TextColumn::make('semester')
->formatStateUsing(function ($state) {
return $state === 'first' ? 'Ganjil' : 'Genap';
}),
Tables\Columns\TextColumn::make('score')
Tables\Columns\TextColumn::make('predicate')
->label('Predicate')
->sortable(),
Tables\Columns\TextColumn::make('description')
->label('Description'),
])
->filters([
//
@ -87,8 +125,11 @@ class ExtracurricularAssessmentResource extends Resource
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\DeleteBulkAction::make()->visible(fn () => auth()->user()->hasRole('admin') || auth()->user()->hasRole('teacher')),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]);
}

View File

@ -3,13 +3,32 @@
namespace App\Filament\Resources\ExtracurricularAssessmentResource\Pages;
use App\Filament\Resources\ExtracurricularAssessmentResource;
use App\Models\ExtracurricularAssessment;
use App\Models\HomeRoomTeacher;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
class ListExtracurricularAssessments extends ListRecords
{
protected static string $resource = ExtracurricularAssessmentResource::class;
protected function getTableQuery() : Builder
{
$user = auth()->user();
if ($user->hasRole('teacher')) {
$homeRoomTeacher = HomeRoomTeacher::where('teacher_id', $user->id)->firstOrFail();
return ExtracurricularAssessment::with('classStudent', 'extracurricular')
->whereHas('classStudent', function (Builder $query) use ($homeRoomTeacher) {
$query->where('class_room_id', $homeRoomTeacher->class_room_id)
->where('academic_year_id', $homeRoomTeacher->academic_year_id);
});
} else {
return ExtracurricularAssessment::query();
}
}
protected function getHeaderActions(): array
{
return [

View File

@ -62,8 +62,9 @@ class StudentResource extends Resource
->maxLength(15),
Forms\Components\TextInput::make('email')
->label('Email')
->label("Parent's Email")
->email()
->required()
->maxLength(100),
Forms\Components\TextInput::make('parent_name')
@ -73,6 +74,7 @@ class StudentResource extends Resource
Forms\Components\TextInput::make('parent_phone')
->label("Parent's Phone")
->required()
->maxLength(15),
Forms\Components\Select::make('religion')

View File

@ -3,10 +3,36 @@
namespace App\Filament\Resources\StudentResource\Pages;
use App\Filament\Resources\StudentResource;
use App\Models\Student;
use App\Models\User;
use Carbon\Carbon;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Spatie\Permission\Models\Role;
class CreateStudent extends CreateRecord
{
protected static string $resource = StudentResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
$parentRole = Role::where('name', 'parent')->first();
$birthDate = Carbon::parse($data['birth_date']);
$password = $birthDate->format('dmY') . $data['nis'];
$parentUser = User::updateOrCreate(
['email' => $data['email']],
[
'name' => $data['parent_name'],
'password' => bcrypt($password),
'phone' => $data['parent_phone'],
]
);
$parentUser->assignRole($parentRole);
return $data;
}
}

View File

@ -3,17 +3,47 @@
namespace App\Filament\Resources\StudentResource\Pages;
use App\Filament\Resources\StudentResource;
use App\Models\User;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Spatie\Permission\Models\Role;
class EditStudent extends EditRecord
{
protected static string $resource = StudentResource::class;
protected function mutateFormDataBeforeSave(array $data): array
{
$parentRole = Role::where('name', 'parent')->first();
$parentUser = User::updateOrCreate(
['email' => $data['email']],
[
'name' => $data['parent_name'],
'phone' => $data['parent_phone'],
]
);
// Pastikan user memiliki role parent
if (!$parentUser->hasRole('parent')) {
$parentUser->assignRole($parentRole);
}
return $data;
}
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
Actions\DeleteAction::make()
->before(function (Actions\DeleteAction $action) {
$student = $this->record;
if ($student->email) {
User::where('email', $student->email)->delete();
}
}),
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Filament\Resources\StudentResource\Pages;
use App\Filament\Imports\StudentImporter;
use App\Filament\Resources\StudentResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,6 +15,8 @@ class ListStudents extends ListRecords
{
return [
Actions\CreateAction::make(),
Actions\ImportAction::make('importBrands')
->importer(StudentImporter::class),
];
}
}

View File

@ -29,21 +29,43 @@ class UserResource extends Resource
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\TextInput::make('password')
->password()
->required(fn (string $context): bool => $context === 'create')
->dehydrateStateUsing(fn ($state) => Hash::make($state))
->dehydrated(fn ($state) => filled($state))
->maxLength(255),
Forms\Components\Select::make('role')
->relationship('roles', 'name')
->preload()
->searchable()
->required(),
Forms\Components\TextInput::make('nip')
->label('NIP')
->maxLength(255),
Forms\Components\Select::make('gender')
->options([
'L' => 'L',
'P' => 'P',
]),
Forms\Components\DatePicker::make('birth_date'),
Forms\Components\TextInput::make('phone')
->tel()
->maxLength(255),
Forms\Components\Textarea::make('address')
->columnSpanFull(),
]);
}
@ -55,6 +77,18 @@ class UserResource extends Resource
->searchable(),
Tables\Columns\TextColumn::make('email')
->searchable(),
Tables\Columns\TextColumn::make('nip')
->label('NIP')
->searchable(),
Tables\Columns\TextColumn::make('gender')
->formatStateUsing(fn (string $state): string => ucfirst($state)),
Tables\Columns\TextColumn::make('birth_date')
->date(),
Tables\Columns\TextColumn::make('phone')
->searchable(),
Tables\Columns\TextColumn::make('roles.name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
@ -63,17 +97,16 @@ class UserResource extends Resource
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('roles.name')->searchable(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\EditAction::make()->visible(fn () => auth()->user()->hasRole('admin')),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
// Tables\Actions\DeleteBulkAction::make(),
// Tables\Actions\DeleteBulkAction::make()->visible(fn () => auth()->user()->hasRole('admin')),
]),
])
->emptyStateActions([

View File

@ -0,0 +1,229 @@
<?php
namespace App\Http\Controllers;
use App\Models\AcademicYear;
use App\Models\Assessment;
use App\Models\ClassRoom;
use App\Models\CompetencyAchievement;
use App\Models\ExtracurricularAssessment;
use App\Models\HomeRoomTeacher;
use App\Models\SchoolInformation;
use App\Models\Student;
use Barryvdh\DomPDF\Facade\Pdf;
use Filament\Notifications\Notification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ReportPDFController extends Controller
{
public $student;
public $class;
public $class_name;
public $academic_year;
public $semester;
public $school_information;
public $table = [];
public $home_room_teacher;
public function reportPDF(Request $request)
{
$params = $request->query();
$defaultParams = ["studentId", "classId", "yearId", "semester", "sakit", "izin", "tanpa_keterangan"];
try {
$this->validateParameters($params, $defaultParams);
} catch (\Illuminate\Validation\ValidationException $e) {
Notification::make()
->title('Parameter tidak valid: ' . $e->getMessage())
->danger()
->send();
return back();
}
$this->student = Student::findOrFail($params["studentId"]);
$this->class = ClassRoom::findOrFail($params["classId"]);
$this->academic_year = AcademicYear::findOrFail($params["yearId"]);
$this->semester = $params["semester"];
$this->school_information = SchoolInformation::firstOrFail();
$this->class_name = $this->toRoman($this->class->class_level) . ' ' . $this->extractClassLetter($this->class->class_name);
$this->loadAssessment();
$data = [
'student' => $this->student,
'school_information' => $this->school_information,
'table' => $this->table,
'sakit' => $params['sakit'],
'izin' => $params['izin'],
'tanpa_keterangan' => $params['tanpa_keterangan'],
'class_name' => $this->class_name,
'semester' => $params['semester'],
'academic_year' => $this->academic_year,
'home_room_teacher' => $this->home_room_teacher,
];
$pdf = Pdf::loadView('print.report', $data)
->setPaper('a4', 'portrait')
->setOption('isRemoteEnabled', true)
->setOption('isHtml5ParserEnabled', true);
return response()->streamDownload(
function () use ($pdf) {
echo $pdf->stream();
},
'Rapor_' . $this->student->full_name . '.pdf'
);
}
protected function loadAssessment(): void
{
$student_id = $this->student->id;
$class_id = $this->class->id;
$semester = $this->semester;
$year_id = $this->academic_year->id;
// Assessment Mapping
$assessments = Assessment::where('semester', $this->semester)
->whereHas('teacherSubject', function($query) use ($class_id, $year_id) {
$query->where('academic_year_id', $year_id)
->where('class_id', $class_id);
})
->where('student_id', $this->student->id)
->with('teacherSubject.subject', 'student')
->get()
->map(function ($as) use ($class_id) {
$subject = $as->teacherSubject->subject ?? null;
if (!$subject) {
return null;
}
$competencyAchievements = CompetencyAchievement::where('class_room_id', $class_id)
->where('subject_id', $subject->id)
->where('min_score', '<=', $as->score)
->where('max_score', '>=', $as->score)
->first();
return [
"score" => $as->score,
"subject" => $subject->name,
"category" => $subject->category,
"competency_achievement" => $competencyAchievements ? $this->student->full_name . ' ' . $competencyAchievements->description : '-',
];
})
->filter() // Hapus null jika ada
->sortByDesc('subject')
->sortBy(function ($item) {
return $item['category'] === 'umum' ? 0 : 1;
})
->values()
->toArray();
$this->table["assessments"]['umum'] = array_filter($assessments, function ($item) {
return $item["category"] === "umum";
});
$this->table["assessments"]['muatan lokal'] = array_filter($assessments, function ($item) {
return $item["category"] === "muatan lokal";
});
$this->table["assessments"]['seni'] = array_filter($assessments, function ($item) {
return $item["category"] === "seni";
});
$extracurricular = ExtracurricularAssessment::with('classStudent','extracurricular')
->where('semester', $this->semester)
->whereHas('classStudent', function($query) use ($class_id, $year_id) {
$query->where('academic_year_id', $year_id)
->where('class_room_id', $class_id)
->where('student_id', $this->student->id);
})
->get()
->map(function ($as) {
return [
"name" => $as->extracurricular->name,
"predicate" => $as->predicate,
"description" => $as->description,
];
})
->toArray();
$this->table["extracurricular"] = $extracurricular;
$homeRoom = HomeRoomTeacher::with(['teacher'])
->where('class_room_id', $this->class->id)
->where('academic_year_id', $this->academic_year->id)
->firstOrFail()
->toArray();
$this->home_room_teacher = $homeRoom ?? [];
}
protected function validateParameters(array $params, array $allowedParams): void
{
$validator = Validator::make($params, [
'studentId' => 'required|integer',
'classId' => 'required|integer',
'yearId' => 'required|integer',
'semester' => 'required|in:first,second',
'sakit' => 'nullable|integer|min:0',
'izin' => 'nullable|integer|min:0',
'tanpa_keterangan' => 'nullable|integer|min:0',
]);
if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException($validator);
}
foreach ($params as $key => $value) {
if (!in_array($key, $allowedParams)) {
throw new \InvalidArgumentException("Parameter {$key} tidak valid");
}
}
}
public function extractClassLetter($className): string
{
preg_match('/[A-Z]+$/i', trim($className), $matches);
return $matches[0] ?? '';
}
public function toRoman($number): string
{
$map = [
'M' => 1000,
'CM' => 900,
'D' => 500,
'CD' => 400,
'C' => 100,
'XC' => 90,
'L' => 50,
'XL' => 40,
'X' => 10,
'IX' => 9,
'V' => 5,
'IV' => 4,
'I' => 1,
];
$returnValue = '';
while ($number > 0) {
foreach ($map as $roman => $int) {
if ($number >= $int) {
$number -= $int;
$returnValue .= $roman;
break;
}
}
}
return $returnValue;
}
}

View File

@ -9,7 +9,8 @@ class ExtracurricularAssessment extends Model
protected $fillable = [
'class_student_id',
'extracurricular_id',
'score',
'predicate',
'description',
'semester',
];

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\AcademicYear;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class AcademicYearPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_academic::year');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AcademicYear $academicYear): bool
{
return $user->can('view_academic::year');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_academic::year');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AcademicYear $academicYear): bool
{
return $user->can('update_academic::year');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AcademicYear $academicYear): bool
{
return $user->can('delete_academic::year');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, AcademicYear $academicYear): bool
{
return $user->can('restore_academic::year');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, AcademicYear $academicYear): bool
{
return $user->can('force_delete_academic::year');
}
}

View File

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

View File

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

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\CompetencyAchievement;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class CompetencyAchievementPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_competency::achievement');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, CompetencyAchievement $competencyAchievement): bool
{
return $user->can('view_competency::achievement');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_competency::achievement');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, CompetencyAchievement $competencyAchievement): bool
{
return $user->can('update_competency::achievement');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, CompetencyAchievement $competencyAchievement): bool
{
return $user->can('delete_competency::achievement');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, CompetencyAchievement $competencyAchievement): bool
{
return $user->can('restore_competency::achievement');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, CompetencyAchievement $competencyAchievement): bool
{
return $user->can('force_delete_competency::achievement');
}
}

View File

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

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\HomeRoomTeacher;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class HomeRoomTeacherPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_home::room::teacher');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, HomeRoomTeacher $homeRomeTeacher): bool
{
return $user->can('view_home::room::teacher');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_home::room::teacher');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, HomeRoomTeacher $homeRomeTeacher): bool
{
return $user->can('update_home::room::teacher');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, HomeRoomTeacher $homeRomeTeacher): bool
{
return $user->can('delete_home::room::teacher');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, HomeRoomTeacher $homeRomeTeacher): bool
{
return $user->can('restore_home::room::teacher');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, HomeRoomTeacher $homeRomeTeacher): bool
{
return $user->can('force_delete_home::room::teacher');
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\SchoolInformation;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class SchoolInformationPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SchoolInformation $schoolInformation): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SchoolInformation $schoolInformation): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SchoolInformation $schoolInformation): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, SchoolInformation $schoolInformation): bool
{
return $user->can('page_SchoolInformation');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, SchoolInformation $schoolInformation): bool
{
return $user->can('page_SchoolInformation');
}
}

View File

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

@ -56,6 +56,7 @@ class AdminPanelProvider extends PanelProvider
])
->plugins([
\BezhanSalleh\FilamentShield\FilamentShieldPlugin::make(),
]);
])
->brandName('Sistem Akademik Sekolah');
}
}

View File

@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"bezhansalleh/filament-shield": "^3.3",
"laravel/framework": "^11.31",
"laravel/tinker": "^2.9"

299
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6ab55b3e872edca1f0857b78d520564b",
"content-hash": "c3a721ae1c529499b996247d90300571",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@ -72,6 +72,83 @@
},
"time": "2025-04-06T06:54:34+00:00"
},
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9|^10",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2025-02-13T15:07:54+00:00"
},
{
"name": "bezhansalleh/filament-shield",
"version": "3.3.5",
@ -940,6 +1017,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "a51bd7a063a65499446919286fb18b518177155a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/a51bd7a063a65499446919286fb18b518177155a",
"reference": "a51bd7a063a65499446919286fb18b518177155a",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.0"
},
"time": "2025-01-15T14:09:04+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.1"
},
"time": "2024-12-02T14:37:59+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0"
},
"time": "2024-04-29T13:26:35+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.4.0",
@ -4797,6 +5029,71 @@
],
"time": "2025-02-25T09:09:36+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v8.8.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "3de493bdddfd1f051249af725c7e0d2c38fed740"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/3de493bdddfd1f051249af725c7e0d2c38fed740",
"reference": "3de493bdddfd1f051249af725c7e0d2c38fed740",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
"phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.8.0"
},
"time": "2025-03-23T17:59:05+00:00"
},
{
"name": "spatie/color",
"version": "1.8.0",

View File

@ -21,7 +21,7 @@ return [
'super_admin' => [
'enabled' => true,
'name' => 'super_admin',
'name' => 'admin',
'define_via_gate' => false,
'intercept_gate' => 'before', // after
],

View File

@ -15,7 +15,8 @@ return new class extends Migration
$table->id();
$table->foreignId('class_student_id')->constrained('class_students');
$table->foreignId('extracurricular_id')->constrained('extracurriculars');
$table->float('score');
$table->string('predicate');
$table->text('description');
$table->string('semester');
$table->timestamps();

View File

@ -0,0 +1,31 @@
<?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('notifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('notifications');
}
};

View File

@ -0,0 +1,35 @@
<?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('imports', function (Blueprint $table) {
$table->id();
$table->timestamp('completed_at')->nullable();
$table->string('file_name');
$table->string('file_path');
$table->string('importer');
$table->unsignedInteger('processed_rows')->default(0);
$table->unsignedInteger('total_rows');
$table->unsignedInteger('successful_rows')->default(0);
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('imports');
}
};

View File

@ -0,0 +1,35 @@
<?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('exports', function (Blueprint $table) {
$table->id();
$table->timestamp('completed_at')->nullable();
$table->string('file_disk');
$table->string('file_name')->nullable();
$table->string('exporter');
$table->unsignedInteger('processed_rows')->default(0);
$table->unsignedInteger('total_rows');
$table->unsignedInteger('successful_rows')->default(0);
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('exports');
}
};

View File

@ -0,0 +1,30 @@
<?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('failed_import_rows', function (Blueprint $table) {
$table->id();
$table->json('data');
$table->foreignId('import_id')->constrained()->cascadeOnDelete();
$table->text('validation_error')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('failed_import_rows');
}
};

View File

@ -6,34 +6,45 @@ use App\Models\AcademicYear;
use App\Models\ClassRoom;
use App\Models\ClassStudent;
use App\Models\Student;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ClassStudentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pastikan data ClassRoom, Student, dan AcademicYear sudah ada
$academicYears = AcademicYear::all();
$classRooms = ClassRoom::all();
$students = Student::all();
$academicYears = AcademicYear::all();
// Hanya melanjutkan jika data tersedia
if ($classRooms->isNotEmpty() && $students->isNotEmpty() && $academicYears->isNotEmpty()) {
foreach ($classRooms as $classRoom) {
foreach ($students as $student) {
foreach ($academicYears as $academicYear) {
// Buat ClassStudent baru untuk setiap kombinasi
ClassStudent::create([
'class_room_id' => $classRoom->id,
'student_id' => $student->id,
'academic_year_id' => $academicYear->id,
]);
}
// Pastikan ada data yang tersedia
if ($academicYears->isEmpty() || $classRooms->isEmpty() || $students->isEmpty()) {
return;
}
// Distribusikan siswa ke kelas secara merata
$studentsPerClass = ceil($students->count() / $classRooms->count());
$studentChunks = $students->chunk($studentsPerClass);
foreach ($academicYears as $academicYear) {
$classIndex = 0;
foreach ($studentChunks as $studentGroup) {
// Pastikan kita tidak melebihi jumlah kelas yang tersedia
if ($classIndex >= $classRooms->count()) {
break;
}
$classRoom = $classRooms[$classIndex];
foreach ($studentGroup as $student) {
ClassStudent::firstOrCreate([
'class_room_id' => $classRoom->id,
'student_id' => $student->id,
'academic_year_id' => $academicYear->id,
]);
}
$classIndex++;
}
}
}

View File

@ -2,38 +2,55 @@
namespace Database\Seeders;
use App\Models\ClassRoom;
use App\Models\Student;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class StudentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$religions = ['islam', 'kristen', 'katolik', 'hindu', 'buddha'];
$parentRole = Role::where('name', 'parent')->firstOrFail();
for ($i = 1; $i <= 20; $i++) {
$gender = $i % 2 == 0 ? 'L' : 'P';
$religion = $religions[array_rand($religions)];
$nis = '24' . str_pad($i, 4, '0', STR_PAD_LEFT); // NIS unik, misal 240001
$nis = '24' . str_pad($i, 4, '0', STR_PAD_LEFT);
$birthDate = Carbon::now()->subYears(rand(15, 18))->subMonths(rand(1, 12));
$parentName = 'Orang Tua Siswa ' . $i;
$parentEmail = 'parent' . $i . '@example.com';
$parentPhone = '0813' . rand(1000000, 9999999);
$birthDateParse = Carbon::parse($birthDate);
$password = $birthDateParse->format('dmY') . $nis;
$parentUser = User::firstOrCreate(
['email' => $parentEmail],
[
'name' => $parentName,
'password' => bcrypt($password),
'phone' => $parentPhone,
]
);
$parentUser->assignRole($parentRole);
Student::firstOrCreate(
['nis' => $nis], // Cek berdasarkan NIS
['nis' => $nis],
[
'full_name' => 'Siswa ' . $i,
'gender' => $gender,
'birth_date' => Carbon::now()->subYears(rand(15, 18))->subMonths(rand(1, 12)),
'birth_date' => $birthDate,
'birth_place' => 'Kota ' . chr(65 + rand(0, 25)),
'address' => 'Jl. Contoh No.' . $i,
'phone' => '0812' . rand(1000000, 9999999),
'parent_name' => 'Orang Tua Siswa ' . $i,
'parent_name' => $parentName,
'religion' => $religion,
'parent_phone' => '0813' . rand(1000000, 9999999),
'parent_phone' => $parentPhone,
'email' => $parentEmail,
'created_at' => now(),
'updated_at' => now(),
]

View File

@ -51,7 +51,7 @@ class UserSeeder extends Seeder
// 4. Buat role super_admin jika belum ada
$role = Role::firstOrCreate([
'name' => 'super_admin',
'name' => 'admin',
'guard_name' => 'web',
]);
@ -60,6 +60,16 @@ class UserSeeder extends Seeder
'guard_name' => 'web',
]);
$headmaster = Role::firstOrCreate([
'name' => 'headmaster',
'guard_name' => 'web',
]);
$parent = Role::firstOrCreate([
'name' => 'parent',
'guard_name' => 'web',
]);
$teacher->syncPermissions($rolePermissions);
$role->syncPermissions($rolePermissions);

3007
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,6 @@
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"vite": "^6.0.11"
"vite": "^6.3.5"
}
}

View File

@ -1,7 +1,7 @@
<x-filament-panels::page >
<div class="w-full flex gap-8">
{{-- Report Preview --}}
<div class="w-full" style="font-family: 'Times New Roman', Times, serif;">
<div class="w-full" style="font-family: 'Times New Roman', Times, serif;" id="print">
<div class="text-center mb-6">
<h2 class="text-lg font-bold uppercase">Laporan Hasil Belajar</h2>
<h3 class="text-sm font-bold uppercase">(RAPOR)</h3>
@ -44,30 +44,30 @@
$i = 1;
@endphp
<tbody>
@if(!empty($this->table['subjects']['umum']))
@foreach($this->table['subjects']['umum'] as $subjectId => $subjectName)
@if(!empty($this->table['assessments']['umum']))
@foreach($this->table['assessments']['umum'] as $subjects => $subject)
<tr>
<td class="border border-black text-center">{{ $i }}</td>
<td class="border border-black">{{ $subjectName }}</td>
<td class="border border-black text-center">90</td>
<td class="border border-black">Ananda sangat memahami materi ajaran dan menunjukkan perilaku religius dalam keseharian.</td>
<td class="border border-black">{{ $subject["subject"] }}</td>
<td class="border border-black text-center">{{ $subject["score"] }}</td>
<td class="border border-black">{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
@if(!empty($this->table['subjects']['seni']))
@if(!empty($this->table['assessments']['seni']))
<tr>
<td class="border border-black text-center">{{ $i }}</td>
<td class="border border-black" colspan="3">Seni</td>
</tr>
@foreach($this->table['subjects']['seni'] as $subjectId => $subjectName)
@foreach($this->table['assessments']['seni'] as $subjects => $subject)
<tr>
<td class="border border-black text-center">a</td>
<td class="border border-black">{{ $subjectName }}</td>
<td class="border border-black text-center">90</td>
<td class="border border-black">Ananda sangat memahami materi ajaran dan menunjukkan perilaku religius dalam keseharian.</td>
<td class="border border-black">{{ $subject["subject"] }}</td>
<td class="border border-black text-center">{{ $subject["score"] }}</td>
<td class="border border-black">{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@ -90,19 +90,19 @@
</thead>
<tbody>
@if(!empty($this->table['subjects']['muatan lokal']))
@foreach($this->table['subjects']['muatan lokal'] as $subjectId => $subjectName)
<tr>
<td class="border border-black text-center">{{ $i }}</td>
<td class="border border-black">{{ $subjectName }}</td>
<td class="border border-black text-center">90</td>
<td class="border border-black">Ananda sangat memahami materi ajaran dan menunjukkan perilaku religius dalam keseharian.</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
@if(!empty($this->table['assessments']['muatan lokal']))
@foreach($this->table['assessments']['muatan lokal'] as $subjects => $subject)
<tr>
<td class="border border-black text-center">{{ $i }}</td>
<td class="border border-black">{{ $subject["subject"] }}</td>
<td class="border border-black text-center">{{ $subject["score"] }}</td>
<td class="border border-black">{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
</tbody>
</table>
@ -116,28 +116,47 @@
</tr>
</thead>
@php
$i = 1;
@endphp
<tbody>
<tr>
<td class="border border-black">1</td>
<td class="border border-black">Pramuka</td>
<td class="border border-black text-center">A</td>
<td class="border border-black">Sangat Berkembang</td>
</tr>
@if(!empty($this->table["extracurricular"]))
@foreach($this->table['extracurricular'] as $subjects => $subject)
<tr>
<td class="border border-black text-center">{{ $i }}</td>
<td class="border border-black">{{ $subject["name"] }}</td>
<td class="border border-black text-center">{{ $subject["predicate"] }}</td>
<td class="border border-black">{{ $subject["description"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@else
<tr>
<td class="border border-black"></td>
<td class="border border-black"></td>
<td class="border border-black text-center"></td>
<td class="border border-black"></td>
</tr>
@endif
</tbody>
</table>
{{-- Table Ketidakhadiran --}}
<table class="border text-sm mt-6">
<tr>
<td class="border text-center" colspan="2">Ketidakhadiran</td>
</tr>
<tr>
<td>Sakit</td><td>: 2 hari</td>
<td>Sakit</td><td>: {{ $sakit }} hari</td>
</tr>
<tr>
<td>Izin</td><td>: 1 hari</td>
<td>Izin</td><td>: {{ $izin }} hari</td>
</tr>
<tr>
<td>Tanpa Keterangan</td><td>: 0 hari</td>
<td>Tanpa Keterangan</td><td>: {{ $tanpa_keterangan }} hari</td>
</tr>
</table>
@ -154,8 +173,8 @@
Depok, {{ \Carbon\Carbon::now()->translatedFormat('d F Y') }} <br>
Wali Kelas {{ $this->class_name ?? "-" }} <br><br><br>
<strong>Siti Mawarni, S.Pd</strong><br>
NIP. 197502021990121001
<strong>{{ $home_room_teacher["teacher"]["name"] ?? '-' }}</strong><br>
NIP. {{ $home_room_teacher["teacher"]["nip"] ?? '-' }}
</div>
</div>
@ -173,21 +192,51 @@
<div>
<h4 class="font-semibold mb-1">Input Absensi</h4>
<label class="block text-sm">Sakit:</label>
<input type="number" class="w-full rounded border-gray-300" placeholder="0">
<input
type="number"
wire:model="sakit"
class="w-full rounded border-gray-300"
placeholder="0"
min="0">
<label class="block text-sm mt-2">Izin:</label>
<input type="number" class="w-full rounded border-gray-300" placeholder="0">
<input
type="number"
wire:model="izin"
class="w-full rounded border-gray-300"
placeholder="0"
min="0">
<label class="block text-sm mt-2">Tanpa Keterangan:</label>
<input type="number" class="w-full rounded border-gray-300" placeholder="0">
<input
type="number"
wire:model="tanpa_keterangan"
class="w-full rounded border-gray-300"
placeholder="0"
min="0">
</div>
<div>
<h4 class="font-semibold mb-1">Catatan Wali Kelas</h4>
<textarea class="w-full rounded border-gray-300" rows="4" placeholder="Tulis catatan di sini..."></textarea>
</div>
<x-filament::button
wire:click="saveAttendance"
class="w-full">
Simpan
</x-filament::button>
<x-filament::button class="w-full">Simpan</x-filament::button>
<x-filament::button
tag="a"
href="{{ route('report.pdf', [
'studentId' => $this->student->id,
'classId' => $this->class->id,
'yearId' => $this->academic_year->id,
'semester' => $this->semester,
'sakit' => $this->sakit,
'izin' => $this->izin,
'tanpa_keterangan' => $this->tanpa_keterangan,
]) }}"
target="_blank"
class="w-full">
Download PDF
</x-filament::button>
</div>
</div>
</div>

View File

@ -0,0 +1,254 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Laporan Hasil Belajar</title>
<style>
body {
font-family: 'Times New Roman', Times, serif;
font-size: 14px;
margin: 40px;
}
.text-center {
text-align: center;
}
.uppercase {
text-transform: uppercase;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.no-full {
width: 256px;
border-collapse: collapse;
margin-top: 10px;
}
table, th, td {
border: 1px solid #000;
}
td, th {
padding: 6px;
vertical-align: top;
}
.no-border {
border: none !important;
text-align: left;
}
.signature-section {
margin-top: 60px;
}
.flex {
display: flex;
justify-content: space-between;
}
.w-50 {
width: 48%;
}
.mt-20 {
margin-top: 80px;
}
.text-right {
text-align: right;
}
.mb-4 {
margin-bottom: 1rem;
}
.underline {
display: inline-block;
border-bottom: 1px solid #000;
width: 200px;
margin-top: 30px;
}
</style>
</head>
<body>
<div class="text-center mb-4">
<h2 class="uppercase">Laporan Hasil Belajar</h2>
<h3 class="uppercase">(RAPOR)</h3>
</div>
<table class="no-border">
<tr class="no-border">
<td class="no-border">Nama Peserta Didik</td>
<td class="no-border">: {{ $student->full_name }}</td>
<td class="no-border">Kelas</td>
<td class="no-border">: {{ $class_name }}</td>
</tr>
<tr class="no-border">
<td class="no-border">NISN/NIS</td>
<td class="no-border">: {{ $student->nisn }} / {{ $student->nis }}</td>
<td class="no-border">Fase</td>
<td class="no-border">: -</td>
</tr>
<tr class="no-border">
<td class="no-border">Sekolah</td>
<td class="no-border">: {{ $school_information->school_name }}</td>
<td class="no-border">Semester</td>
<td class="no-border">: {{ $semester === 'first' ? "I" : "II"}}</td>
</tr>
<tr class="no-border">
<td class="no-border">Alamat</td>
<td class="no-border">: {{ $school_information->address }}</td>
<td class="no-border">Tahun Ajaran</td>
<td class="no-border">: {{ $academic_year->name }}</td>
</tr>
</table>
@php
$i = 1;
@endphp
<table>
<tr>
<th style="width: 2rem">No</th>
<th style="width: 185px">Muatan Pelajaran</th>
<th style="width: 100px" class="text-center">Nilai Akhir</th>
<th>Capaian Kompetensi</th>
</tr>
@if(!empty($table['assessments']['umum']))
@foreach($table['assessments']['umum'] as $subjects => $subject)
<tr>
<td>{{ $i }}</td>
<td>{{ $subject["subject"] }}</td>
<td class="text-center">{{ $subject["score"] }}</td>
<td>{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
@if(!empty($table['assessments']['seni']))
<tr>
<td>{{ $i }}</td>
<td colspan="3">Seni</td>
</tr>
@php $charIndex = 0; @endphp
@foreach($table['assessments']['seni'] as $subjects => $subject)
<tr>
<td>{{ chr(97 + $charIndex) }}</td>
<td>{{ $subject["subject"] }}</td>
<td class="text-center">{{ $subject["score"] }}</td>
<td>{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
</table>
<br><strong>Muatan Lokal</strong>
<table>
<tr>
<th style="width: 2rem">No</th>
<th style="width: 185px">Muatan Pelajaran</th>
<th style="width: 100px">Nilai Akhir</th>
<th>Capaian Kompetensi</th>
</tr>
@if(!empty($table['assessments']['muatan lokal']))
@foreach($table['assessments']['muatan lokal'] as $subjects => $subject)
<tr>
<td>{{ $i }}</td>
<td>{{ $subject["subject"] }}</td>
<td class="text-center">{{ $subject["score"] }}</td>
<td>{{ $subject["competency_achievement"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@endif
</table>
<table>
<tr>
<th style="width: 2rem">No</th>
<th style="width: 185px">Ekstrakurikuler</th>
<th style="width: 100px">Predikat</th>
<th>Keterangan</th>
</tr>
@php
$i = 1;
@endphp
@if(!empty($table["extracurricular"]))
@foreach($table['extracurricular'] as $subjects => $subject)
<tr>
<td class=>{{ $i }}</td>
<td class=>{{ $subject["name"] }}</td>
<td class='text-center'>{{ $subject["predicate"] }}</td>
<td class=>{{ $subject["description"] }}</td>
</tr>
@php
$i++;
@endphp
@endforeach
@else
<tr>
<td class=></td>
<td class=></td>
<td class=></td>
<td class=></td>
</tr>
@endif
</table>
<table class="no-full">
<tr>
<td colspan="2" style="text-align: center">
Ketidakhadiran
</td>
</tr>
<tr><td>Sakit</td><td>: {{ $sakit ?? 0 }} hari</td></tr>
<tr><td>Izin</td><td>: {{ $izin ?? 0 }} hari</td></tr>
<tr><td>Tanpa Keterangan</td><td>: {{ $tanpa_keterangan ?? 0 }} hari</td></tr>
</table>
<br>
<table class="no-border" style="margin-top: 60px;">
<tr class="no-border">
<td class="no-border" style="text-align: center;">
Orang Tua / Wali<br><br><br><br>
<span class="underline"></span>
</td>
<td class="no-border" style="text-align: center;">
@php
\Carbon\Carbon::setLocale('id');
@endphp
Depok, {{ \Carbon\Carbon::now()->translatedFormat('d F Y') }}<br>
Wali Kelas {{ $class_name }}<br><br><br>
<strong>{{ $home_room_teacher["teacher"]["name"] ?? '-' }}</strong><br>
NIP. {{ $home_room_teacher["teacher"]["nip"] ?? '-' }}
</td>
</tr>
</table>
<div class="text-center mt-20">
Mengetahui,<br>
Kepala Sekolah<br><br><br>
<strong>{{ $school_information->headmaster_name }}</strong><br>
NIP: {{ $school_information->headmaster_nip }}
</div>
</body>
</html>

View File

@ -1,7 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ReportPDFController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/report-pdf', [ReportPDFController::class, 'reportPDF'])->name('report.pdf');