Initial commit: Aplikasi Ketertiban Siswa

- Fitur: input pelanggaran, sistem poin, surat peringatan otomatis, SP progress
- Realtime notifikasi via Laravel Reverb + Echo
- Dashboard statistik, manajemen user, manajemen master data
- Login 2 kolom premium, user dropdown, pengaturan akun
- Tema biru kalem, logo sekolah dinamis, sidebar brand dinamis
- Sinkronisasi siswa dari Database Kesiswaan via API
This commit is contained in:
Bro
2026-07-21 22:43:13 +07:00
commit 1c35734f7b
116 changed files with 19664 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AppNotification extends Model
{
protected $table = 'notifications';
protected $fillable = [
'user_id', 'type', 'title', 'body', 'data',
'icon', 'color', 'action_url', 'is_read',
'read_at', 'violation_id', 'sp_letter_id',
];
protected function casts(): array
{
return [
'data' => 'json',
'is_read' => 'boolean',
'read_at' => 'datetime',
];
}
public function user()
{
return $this->belongsTo(User::class);
}
public function violation()
{
return $this->belongsTo(Violation::class);
}
public function spLetter()
{
return $this->belongsTo(SpLetter::class, 'sp_letter_id');
}
public function scopeUnread($query)
{
return $query->where('is_read', false);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Classes extends Model
{
protected $table = 'classes';
protected $fillable = [
'source_id', 'name', 'level', 'department_code',
'department_name', 'academic_year_name', 'is_active',
'metadata',
];
protected function casts(): array
{
return [
'is_active' => 'boolean',
'metadata' => 'json',
];
}
public function students()
{
return $this->hasMany(Student::class, 'class_id');
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = ['key', 'value', 'group', 'description'];
public static function getValue(string $key, mixed $default = null): mixed
{
$setting = static::where('key', $key)->first();
return $setting ? $setting->value : $default;
}
public static function setValue(string $key, mixed $value, string $group = 'general', ?string $description = null): void
{
static::updateOrCreate(
['key' => $key],
['value' => $value, 'group' => $group, 'description' => $description]
);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SpLetter extends Model
{
protected $fillable = [
'student_id', 'sp_threshold_id', 'generated_by',
'letter_number', 'title', 'body', 'total_points_at_time',
'violations_included', 'status', 'file_path',
'printed_at', 'delivered_at',
];
protected function casts(): array
{
return [
'total_points_at_time' => 'integer',
'violations_included' => 'json',
'printed_at' => 'datetime',
'delivered_at' => 'datetime',
];
}
public function student()
{
return $this->belongsTo(Student::class);
}
public function spThreshold()
{
return $this->belongsTo(SpThreshold::class, 'sp_threshold_id');
}
public function generator()
{
return $this->belongsTo(User::class, 'generated_by');
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SpThreshold extends Model
{
protected $fillable = [
'name', 'slug', 'min_points', 'max_points',
'color', 'default_description', 'is_active',
];
protected function casts(): array
{
return [
'min_points' => 'integer',
'max_points' => 'integer',
'is_active' => 'boolean',
];
}
public function spLetters()
{
return $this->hasMany(SpLetter::class, 'sp_threshold_id');
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Student extends Model
{
protected $fillable = [
'source_id', 'nisn', 'student_number', 'full_name',
'gender', 'place_of_birth', 'date_of_birth', 'address',
'phone_number', 'email', 'class_name', 'class_level',
'department_code', 'department_name', 'academic_year_name',
'status', 'photo_path', 'class_id', 'is_active',
'metadata', 'synced_at',
];
protected function casts(): array
{
return [
'date_of_birth' => 'date',
'is_active' => 'boolean',
'metadata' => 'json',
'synced_at' => 'datetime',
];
}
public function violations(): HasMany
{
return $this->hasMany(Violation::class);
}
public function spLetters(): HasMany
{
return $this->hasMany(SpLetter::class);
}
public function class()
{
return $this->belongsTo(Classes::class, 'class_id');
}
public function getTotalPointsAttribute(): int
{
return $this->violations()
->whereNull('deleted_at')
->sum('points');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name', 'username', 'email', 'password', 'role', 'is_active',
];
protected $hidden = ['password', 'remember_token'];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_active' => 'boolean',
];
}
public function isAdmin(): bool { return $this->role === 'admin'; }
public function isBK(): bool { return $this->role === 'bk'; }
public function isWaliKelas(): bool { return $this->role === 'wali_kelas'; }
public function recordedViolations()
{
return $this->hasMany(Violation::class, 'recorded_by');
}
public function appNotifications()
{
return $this->hasMany(AppNotification::class, 'user_id');
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Violation extends Model
{
use SoftDeletes;
protected $fillable = [
'student_id', 'violation_type_id', 'recorded_by',
'description', 'points', 'sanction', 'location',
'violation_date', 'violation_time',
'is_verified', 'verified_by', 'verified_at',
'notes',
];
protected function casts(): array
{
return [
'violation_date' => 'date',
'violation_time' => 'datetime:H:i',
'is_verified' => 'boolean',
'verified_at' => 'datetime',
'points' => 'integer',
];
}
public function student(): BelongsTo
{
return $this->belongsTo(Student::class);
}
public function violationType(): BelongsTo
{
return $this->belongsTo(ViolationType::class, 'violation_type_id');
}
public function recorder(): BelongsTo
{
return $this->belongsTo(User::class, 'recorded_by');
}
public function verifier(): BelongsTo
{
return $this->belongsTo(User::class, 'verified_by');
}
public function evidences(): HasMany
{
return $this->hasMany(ViolationEvidence::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ViolationCategory extends Model
{
protected $fillable = [
'name', 'slug', 'color', 'description', 'sort_order', 'is_active',
];
protected function casts(): array
{
return [
'is_active' => 'boolean',
'sort_order' => 'integer',
];
}
public function violationTypes()
{
return $this->hasMany(ViolationType::class, 'category_id');
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
class ViolationEvidence extends Model
{
protected $table = 'violation_evidences';
protected $fillable = [
'violation_id', 'file_path', 'original_name', 'file_size', 'mime_type',
];
public function violation()
{
return $this->belongsTo(Violation::class);
}
public function getUrlAttribute(): string
{
return Storage::url($this->file_path);
}
public function getThumbnailUrlAttribute(): string
{
return Storage::url($this->file_path);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ViolationType extends Model
{
protected $fillable = [
'category_id', 'name', 'slug', 'points',
'default_sanction', 'description', 'is_active',
];
protected function casts(): array
{
return [
'is_active' => 'boolean',
'points' => 'integer',
];
}
public function category()
{
return $this->belongsTo(ViolationCategory::class, 'category_id');
}
public function violations()
{
return $this->hasMany(Violation::class, 'violation_type_id');
}
}