# DUMP PROYEK: 1 BACKEND CONTROLLERS
> Diekstrak pada: 2026-10-04 21:50:52

## File: `app/Http/Controllers/Public/KontakController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\PesanKontak;
use App\Models\ProfilSekolah;

class KontakController extends Controller
{
    public function index()
    {
        $profil = ProfilSekolah::first();
        return view('public.contact', compact('profil'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama' => 'required|string|max:255',
            'email' => 'required|email|max:255',
            'no_hp' => 'nullable|string|max:20',
            'subjek' => 'required|string|max:255',
            'pesan' => 'required|string',
        ]);

        PesanKontak::create($request->all());

        return back()->with('success', 'Terima kasih! Pesan Anda telah berhasil dikirim dan akan segera kami proses.');
    }
}
```

## File: `app/Http/Controllers/Public/PpdbController.php`
```php
<?php

namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\Ppdb;
use App\Models\Kelas;
use App\Models\TahunAjaran;
use App\Models\ProfilSekolah;
use App\Models\PpdbSetting;
use Illuminate\Http\Request;
use Carbon\Carbon;

class PpdbController extends Controller
{
    public function landing()
    {
        $profil = ProfilSekolah::first();
        
        $setting = PpdbSetting::first(); 
        
        return view('public.ppdb.ppdb', compact('profil', 'setting'));
    }

    public function index()
    {
        $profil = ProfilSekolah::first();
        $taAktif = TahunAjaran::where('status_ppdb', 'buka')->first();
        
        return view('public.ppdb.index', compact('profil', 'taAktif'));
    }

    public function create()
    {
        $profil = ProfilSekolah::first();
        $taAktif = TahunAjaran::where('status_ppdb', 'buka')->first();

        if (!$taAktif) {
            return redirect()->route('public.ppdb.info')->with('error', 'Mohon maaf, pendaftaran PPDB saat ini sedang ditutup.');
        }

        $jenjangs = \App\Models\JenjangKelas::orderBy('id', 'asc')->get();

        return view('public.ppdb.create', compact('profil', 'taAktif', 'jenjangs'));
    }

    public function store(Request $request)
    {
        $taAktif = TahunAjaran::where('status_ppdb', 'buka')->first();
        if (!$taAktif) {
            return redirect()->route('public.ppdb.info')->with('error', 'Pendaftaran gagal karena gelombang PPDB sudah ditutup.');
        }

        $request->validate([
            'jenjang_id'      => 'required|exists:jenjang_kelas,id', 
            'nama_lengkap'    => 'required|string|max:255',
            'jenis_kelamin'   => 'required|in:L,P',
            'nisn'            => 'nullable|numeric|unique:ppdbs,nisn',
            'no_hp'           => 'required|string|max:20',
            'asal_sekolah'    => 'required|string|max:255',
        ]);

        $data = $request->all();
        $jenjang = \App\Models\JenjangKelas::findOrFail($request->jenjang_id);

        $hijri2Digit = substr($taAktif->tahun_ppdb_hijriyah, -2);
        $jenjangKode = str_pad($jenjang->kode, 2, '0', STR_PAD_LEFT);
        
        $prefix = 'PPDB' . $hijri2Digit . $jenjangKode;
        $count = Ppdb::where('no_pendaftaran', 'LIKE', $prefix . '%')->count() + 1;
        
        $noPendaftaran = $prefix . str_pad($count, 3, '0', STR_PAD_LEFT);

        $data['no_pendaftaran'] = $noPendaftaran;
        $data['tahun_ajaran_id'] = $taAktif->id;
        $data['tanggal_daftar'] = \Carbon\Carbon::now()->format('Y-m-d');
        $data['status'] = 'Verifikasi'; 
        $data['jenis_pendaftaran'] = 'Baru';
        $data['kelas_id'] = null;

        Ppdb::create($data);

        return redirect()->route('public.ppdb.success', $noPendaftaran)->with('success', 'Pendaftaran Berhasil!');
    }

    public function success($no_pendaftaran)
    {
        $profil = ProfilSekolah::first();
        $ppdb = Ppdb::where('no_pendaftaran', $no_pendaftaran)->firstOrFail();
        
        return view('public.ppdb.success', compact('profil', 'ppdb'));
    }
}
```

## File: `app/Http/Controllers/Public/GaleriController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\Galeri;
use App\Models\ProfilSekolah;

class GaleriController extends Controller
{
    public function index()
    {
        $profil = ProfilSekolah::first();
        
        $fotos = Galeri::where('tipe', 'foto')
            ->orderBy('created_at', 'desc')
            ->get()
            ->groupBy(function($item) {
                return $item->batch_id ?? $item->id;
            });

        $videos = Galeri::where('tipe', 'video')
            ->orderBy('created_at', 'desc')
            ->get();

        return view('public.gallery.index', compact('profil', 'fotos', 'videos'));
    }
}
```

## File: `app/Http/Controllers/Public/BeritaController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\Berita;
use App\Models\KategoriBerita;
use App\Models\ProfilSekolah;
use Illuminate\Http\Request;

class BeritaController extends Controller
{
    public function index(Request $request)
    {
        $profil = ProfilSekolah::first();
        $kategori_slug = $request->query('kategori');

        $query = Berita::with(['author', 'kategori'])->where('status', 'publish')->orderBy('created_at', 'desc');

        if ($kategori_slug) {
            $query->whereHas('kategori', function($q) use ($kategori_slug) {
                $q->where('slug', $kategori_slug);
            });
        }

        $beritas = $query->paginate(9);
        $kategoris = KategoriBerita::withCount('beritas')->get();

        return view('public.berita.index', compact('profil', 'beritas', 'kategoris'));
    }

    public function show($slug)
    {
        $profil = ProfilSekolah::first();
        $berita = Berita::with(['author', 'kategori'])->where('slug', $slug)->where('status', 'publish')->firstOrFail();
        
        $berita->increment('views');

        $beritaTerkait = Berita::where('kategori_berita_id', $berita->kategori_berita_id)
                               ->where('id', '!=', $berita->id)
                               ->where('status', 'publish')
                               ->latest()->take(3)->get();

        return view('public.berita.show', compact('profil', 'berita', 'beritaTerkait'));
    }
}
```

## File: `app/Http/Controllers/Public/EkstrakurikulerController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\Ekstrakurikuler;
use App\Models\ProfilSekolah;

class EkstrakurikulerController extends Controller
{
    public function index()
    {
        $profil = ProfilSekolah::first();
        $ekskuls = Ekstrakurikuler::orderBy('nama', 'asc')->get();

        return view('public.ekskul', compact('profil', 'ekskuls'));
    }
}
```

## File: `app/Http/Controllers/Public/HomeController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\ProfilSekolah;
use App\Models\BerandaSetting;
use App\Models\Testimoni;
use App\Models\Galeri;
use App\Models\Berita;
use App\Models\PpdbSetting;

class HomeController extends Controller
{
    public function index()
    {
        $profil = ProfilSekolah::first();
        $beranda = BerandaSetting::first();
        $setting = PpdbSetting::first(); 
        
        $testimonis = Testimoni::orderBy('created_at', 'desc')->take(3)->get();
        
        $fotos = Galeri::where('tipe', 'foto')->orderBy('created_at', 'desc')->take(6)->get();
        
        $beritas = Berita::with('kategori')->where('status', 'publish')->orderBy('created_at', 'desc')->take(3)->get();

        return view('public.main', compact('profil', 'beranda', 'setting', 'testimonis', 'fotos', 'beritas'));
    }
}
```

## File: `app/Http/Controllers/Public/DirektoriController.php`
```php
<?php
namespace App\Http\Controllers\Public;

use App\Http\Controllers\Controller;
use App\Models\Guru;
use App\Models\Staff;
use App\Models\ProfilSekolah;
use App\Models\JenjangKelas; 

class DirektoriController extends Controller
{
    public function index()
    {
        $profil = ProfilSekolah::first();
        $gurus = Guru::with('jabatan')->where('tampil_publik', true)->orderBy('nama_lengkap', 'asc')->get();
        $staffs = Staff::with('jabatan')->where('tampil_publik', true)->orderBy('nama_lengkap', 'asc')->get();

        return view('public.guru', compact('profil', 'gurus', 'staffs'));
    }

    public function siswa()
    {
        $profil = ProfilSekolah::first();
        $jenjangs = JenjangKelas::with(['tingkats.kelases.siswa' => function($query) {
            $query->where('status_siswa', 'Aktif')->orderBy('nama_lengkap', 'asc');
        }])->orderBy('id', 'asc')->get();

        return view('public.siswa', compact('profil', 'jenjangs'));
    }
}
```

## File: `app/Http/Controllers/Controller.php`
```php
<?php

namespace App\Http\Controllers;

abstract class Controller
{
    //
}

```

## File: `app/Http/Controllers/Siswa/DashboardController.php`
```php
<?php

namespace App\Http\Controllers\Siswa;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Hash;
use App\Models\TahunAjaran;
use App\Models\Semester;
use App\Models\TarifKeuangan;
use App\Models\PembayaranKeuangan;
use App\Models\Testimoni;

class DashboardController extends Controller
{
    public function index(Request $request)
    {
        $siswa = Auth::user()->siswa;
        
        if(!$siswa) {
            abort(403, 'Profil akademik Anda tidak ditemukan di sistem. Silakan hubungi Admin Madrasah.');
        }

        $siswa->load(['kelas.tingkat.jenjang', 'kelas.details.gender', 'tahunAjaran']);

        $activeMenu = $request->query('tab', 'edit');
        
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $semAktif = Semester::where('is_active', true)->first();
        
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $selectedTaId = $request->input('tahun_ajaran_id', $taAktif ? $taAktif->id : null);
        
        $jadwalSiswa = collect();
        $absensiMapel = collect();
        $tarifs = collect();
        $historiPembayaran = collect();
        $testimoni = Testimoni::where('siswa_id', $siswa->id)->first();
        
        $totalTagihan = 0;
        $totalDibayar = 0;
        $sisaTagihan = 0;
        
        if ($activeMenu == 'jadwal' && $siswa->kelas_id && $taAktif && $semAktif) {
            $jadwalSiswa = \App\Models\JadwalMapel::with(['guruMapel.guru', 'guruMapel.mapel'])
                ->where('kelas_id', $siswa->kelas_id)
                ->where('tahun_ajaran_id', $taAktif->id)
                ->where('semester_id', $semAktif->id)
                ->orderBy('jam_mulai', 'asc')
                ->get()->groupBy('hari'); 
        } 
        elseif ($activeMenu == 'absensi' && $siswa->kelas_id && $taAktif && $semAktif) {
            $dataAbsensi = \App\Models\Absensi::with(['guruMapel.mapel', 'guruMapel.guru'])
                ->where('siswa_id', $siswa->id)
                ->where('tahun_ajaran_id', $taAktif->id)
                ->where('semester_id', $semAktif->id)
                ->orderBy('tanggal', 'desc')->get();
                
            $absensiMapel = $dataAbsensi->groupBy('guru_mapel_id');
            
            $jadwalSiswa = \App\Models\JadwalMapel::with(['guruMapel.mapel', 'guruMapel.guru'])
                ->where('kelas_id', $siswa->kelas_id)
                ->where('tahun_ajaran_id', $taAktif->id)
                ->where('semester_id', $semAktif->id)
                ->whereNotNull('guru_mapel_id')->get()->unique('guru_mapel_id');
        }
        elseif ($activeMenu == 'keuangan') {
            $jenjangId = $siswa->kelas->tingkat->jenjang_id ?? 1;
            
            $keuanganTaId = $request->input('keuangan_ta_id', $taAktif->id ?? null);

            if ($keuanganTaId) {
                $tarifs = TarifKeuangan::with(['kategori', 'semester', 'tahunAjaran'])
                    ->where('jenjang_id', $jenjangId)
                    ->where('tahun_ajaran_id', $keuanganTaId)
                    ->get()
                    ->map(function($tarif) use ($siswa) {
                        $terbayar = PembayaranKeuangan::where('tarif_keuangan_id', $tarif->id)
                                    ->where('siswa_id', $siswa->id)->sum('nominal_bayar');
                        $tarif->terbayar = $terbayar;
                        $tarif->sisa = $tarif->nominal - $terbayar;
                        return $tarif;
                    });
                    
                $totalTagihan = $tarifs->sum('nominal');
                $totalDibayar = $tarifs->sum('terbayar');
                $sisaTagihan  = $totalTagihan - $totalDibayar;
            }

            $historiPembayaran = PembayaranKeuangan::with(['tarif.kategori', 'tarif.tahunAjaran'])
                ->where('siswa_id', $siswa->id)
                ->orderBy('tanggal_bayar', 'desc')
                ->paginate(10);
        }

        return view('siswa.dashboard', compact(
            'siswa', 'activeMenu', 'taAktif', 'semAktif', 
            'jadwalSiswa', 'absensiMapel', 'tarifs', 'historiPembayaran', 'testimoni',
            'totalTagihan', 'totalDibayar', 'sisaTagihan',
            'tahunAjarans', 'selectedTaId' // <--- Variabel dikirim ke View di sini
        ));
    }

    public function updateProfil(Request $request)
    {
        $siswa = Auth::user()->siswa;

        $request->validate([
            'password'      => 'nullable|string|min:8|confirmed',
            'no_hp'         => 'nullable|string|max:20',
            'hobi'          => 'nullable|string|max:255',
            'cita_cita'     => 'nullable|string|max:255',
            'file_ktp'      => 'nullable|file|mimes:pdf,jpg,jpeg,png,webp|max:3048',
            'file_kk'       => 'nullable|file|mimes:pdf,jpg,jpeg,png,webp|max:3048',
            'file_kip'      => 'nullable|file|mimes:pdf,jpg,jpeg,png,webp|max:3048',
            'file_pkh'      => 'nullable|file|mimes:pdf,jpg,jpeg,png,webp|max:3048',
            'file_kks'      => 'nullable|file|mimes:pdf,jpg,jpeg,png,webp|max:3048',
        ]);

        $data = $request->only([
            'no_hp', 'hobi', 'cita_cita', 'nama_jalan', 'dusun', 'rt', 'rw', 'desa', 'kecamatan', 'kabupaten', 'propinsi', 'kode_pos',
            'nama_ayah', 'pekerjaan_ayah', 'penghasilan_ayah', 'pendidikan_akhir_ayah', 'tahun_lahir_ayah',
            'nama_ibu', 'pekerjaan_ibu', 'penghasilan_ibu', 'pendidikan_akhir_ibu', 'tahun_lahir_ibu',
            'no_kk', 'no_kip', 'no_pkh', 'no_kks'
        ]);

        if ($request->filled('foto_base64')) {
            $image_parts = explode(";base64,", $request->foto_base64);
            $image_base64 = base64_decode($image_parts[1]);
            $fileName = 'siswa_' . time() . '_' . uniqid() . '.webp';

            if ($siswa->foto && Storage::disk('public')->exists('siswa/' . $siswa->foto)) {
                Storage::disk('public')->delete('siswa/' . $siswa->foto);
            }
            Storage::disk('public')->put('siswa/' . $fileName, $image_base64);
            $data['foto'] = $fileName;
        }

        $dokumens = ['file_ktp', 'file_kk', 'file_kip', 'file_pkh', 'file_kks'];
        foreach ($dokumens as $dokumen) {
            if ($request->hasFile($dokumen)) {
                $file = $request->file($dokumen);
                $ext = $file->getClientOriginalExtension();
                $newDocName = $dokumen . '_' . $siswa->id . '_' . time() . '.' . $ext;

                if ($siswa->$dokumen && Storage::disk('public')->exists('dokumen_siswa/' . $siswa->$dokumen)) {
                    Storage::disk('public')->delete('dokumen_siswa/' . $siswa->$dokumen);
                }
                $file->storeAs('dokumen_siswa', $newDocName, 'public');
                $data[$dokumen] = $newDocName;
            }
        }

        if ($request->filled('password')) {
            Auth::user()->update([
                'password' => Hash::make($request->password)
            ]);
        }

        $siswa->update($data);

        return redirect()->back()->with('success', 'Data profil Anda berhasil diperbarui!');
    }

    public function updateTestimoni(Request $request)
    {
        $siswa = Auth::user()->siswa;

        $request->validate([
            'nama'  => 'required|string|max:255',
            'peran' => 'required|string|max:255',
            'pesan' => 'required|string',
            'foto'  => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048'
        ]);

        $data = $request->only(['nama', 'peran', 'pesan']);
        $data['siswa_id'] = $siswa->id;

        $testimoni = Testimoni::where('siswa_id', $siswa->id)->first();

        if ($request->hasFile('foto')) {
            if ($testimoni && $testimoni->foto && Storage::disk('public')->exists('testimoni/' . $testimoni->foto)) {
                Storage::disk('public')->delete('testimoni/' . $testimoni->foto);
            }
            
            $file = $request->file('foto');
            $filename = 'testi_siswa_' . $siswa->id . '_' . time() . '.' . $file->getClientOriginalExtension();
            $file->storeAs('testimoni', $filename, 'public');
            $data['foto'] = $filename;
        }

        if ($testimoni) {
            $testimoni->update($data);
        } else {
            Testimoni::create($data);
        }

        return redirect()->back()->with('success', 'Testimoni Anda berhasil diterbitkan!');
    }
}
```

## File: `app/Http/Controllers/Admin/NilaiController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Nilai;

class NilaiController extends Controller
{
    public function storeOrUpdate(Request $request)
    {
        $request->validate([
            'siswa_id'        => 'required|exists:siswas,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id'     => 'required|exists:semesters,id',
            'guru_mapel_id'   => 'required|exists:guru_mapels,id',
            'nilai_kehadiran' => 'required|numeric|min:0|max:100',
            'nilai_tugas'     => 'required|numeric|min:0|max:100',
            'nilai_uts'       => 'required|numeric|min:0|max:100',
            'nilai_uas'       => 'required|numeric|min:0|max:100',
        ]);

        $total = ($request->nilai_kehadiran * 0.10) + 
                 ($request->nilai_tugas * 0.20) + 
                 ($request->nilai_uts * 0.30) + 
                 ($request->nilai_uas * 0.40);
        
        $grade = 'E';
        $ket = 'Sangat Kurang';
        
        if ($total >= 85) { $grade = 'A'; $ket = 'Sangat Baik (Lulus)'; }
        elseif ($total >= 70) { $grade = 'B'; $ket = 'Baik (Lulus)'; }
        elseif ($total >= 55) { $grade = 'C'; $ket = 'Cukup (Lulus)'; }
        elseif ($total >= 40) { $grade = 'D'; $ket = 'Kurang (Remedial)'; }

        Nilai::updateOrCreate(
            [
                'siswa_id'        => $request->siswa_id,
                'guru_mapel_id'   => $request->guru_mapel_id,
                'tahun_ajaran_id' => $request->tahun_ajaran_id,
                'semester_id'     => $request->semester_id,
            ],
            [
                'nilai_kehadiran' => $request->nilai_kehadiran,
                'nilai_tugas'     => $request->nilai_tugas,
                'nilai_uts'       => $request->nilai_uts,
                'nilai_uas'       => $request->nilai_uas,
                'total_nilai'     => $total,
                'grade'           => $grade,
                'keterangan'      => $ket,
            ]
        );

        return back()->with('success', 'Data Nilai Akademik berhasil disimpan secara permanen!');
    }

    public function destroy($id)
    {
        Nilai::findOrFail($id)->delete();
        return back()->with('success', 'Data nilai berhasil dihapus dari database.');
    }
}
```

## File: `app/Http/Controllers/Admin/PpdbController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Ppdb;
use App\Models\Kelas;
use App\Models\Gender;
use App\Models\TahunAjaran; 
use App\Models\JenjangKelas;
use Illuminate\Http\Request;
use Carbon\Carbon;

class PpdbController extends Controller
{
    public function index(Request $request)
    {
        $search = $request->get('search');

        $ppdbs = Ppdb::with(['kelas.tingkat.jenjang', 'tahunAjaran'])
            ->when($search, function($query) use ($search) {
                $query->where(function($q) use ($search) {
                    $q->where('no_pendaftaran', 'LIKE', "%{$search}%")
                      ->orWhere('nama_lengkap', 'LIKE', "%{$search}%");
                });
            })
            ->orderBy('created_at', 'desc')
            ->paginate(30)
            ->withQueryString(); 

        return view('admin.ppdb.index', compact('ppdbs'));
    }

    public function create()
    {
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])
                    ->orderBy('tingkat_kelas_id', 'asc')
                    ->orderBy('nama_kelas', 'asc')
                    ->get();
        $genders = Gender::all();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get(); 

        return view('admin.ppdb.create', compact('kelas', 'genders', 'tahunAjarans'));
    }

    public function getNoPendaftaran($tahunAjaranId, $jenjangId)
    {
        $ta = TahunAjaran::findOrFail($tahunAjaranId);
        $jenjang = JenjangKelas::findOrFail($jenjangId);
        
        $hijri2Digit = substr($ta->tahun_ppdb_hijriyah, -2);
        $jenjangKode = str_pad($jenjang->kode, 2, '0', STR_PAD_LEFT); 
        
        $prefix = 'PPDB' . $hijri2Digit . $jenjangKode;
        
        $count = Ppdb::where('no_pendaftaran', 'LIKE', $prefix . '%')->count() + 1;
        $noPendaftaran = $prefix . str_pad($count, 3, '0', STR_PAD_LEFT); 

        return response()->json([
            'success' => true,
            'no_pendaftaran' => $noPendaftaran
        ]);
    }

    public function store(Request $request)
    {
        $request->validate([
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'nama_lengkap'    => 'required|string|max:255',
            'kelas_id'        => 'required|exists:kelas,id',
            'jenis_kelamin'   => 'required|in:L,P',
            'nisn'            => 'nullable|unique:ppdbs,nisn',
            'tanggal_lahir'   => 'nullable|date|before_or_equal:today',
        ], [
            'tanggal_lahir.date' => 'Format tanggal lahir yang dimasukkan tidak valid.',
            'tanggal_lahir.before_or_equal' => 'Tanggal lahir tidak boleh melebihi hari ini.'
        ]);

        $data = $request->all();
        $ta = TahunAjaran::findOrFail($request->tahun_ajaran_id);
        $kelas = Kelas::with('tingkat.jenjang')->findOrFail($request->kelas_id);
        $jenjang = $kelas->tingkat->jenjang;

        $hijri2Digit = substr($ta->tahun_ppdb_hijriyah, -2);
        $jenjangKode = str_pad($jenjang->kode, 2, '0', STR_PAD_LEFT);
        
        $prefix = 'PPDB' . $hijri2Digit . $jenjangKode;
        $count = Ppdb::where('no_pendaftaran', 'LIKE', $prefix . '%')->count() + 1;
        
        $data['no_pendaftaran'] = $prefix . str_pad($count, 3, '0', STR_PAD_LEFT);
        $data['tanggal_daftar'] = Carbon::now()->format('Y-m-d');
        $data['status'] = 'Verifikasi'; 

        Ppdb::create($data);

        return redirect()->route('admin.ppdb')->with('success', 'Formulir PPDB berhasil disimpan!');
    }

    public function edit($id)
    {
        $ppdb = Ppdb::findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])
                    ->orderBy('tingkat_kelas_id', 'asc')
                    ->orderBy('nama_kelas', 'asc')
                    ->get();
        $genders = Gender::all();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();

        return view('admin.ppdb.edit', compact('ppdb', 'kelas', 'genders', 'tahunAjarans'));
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'nama_lengkap'    => 'required|string|max:255',
            'kelas_id'        => 'required|exists:kelas,id',
            'jenis_kelamin'   => 'required|in:L,P',
            'nisn'            => 'nullable|unique:ppdbs,nisn,'.$id,
            'status'          => 'required|in:Verifikasi,Diterima,Ditolak,Cabut Berkas',
            'tanggal_lahir'   => 'nullable|date|before_or_equal:today',
        ], [
            'tanggal_lahir.date' => 'Format tanggal lahir yang dimasukkan tidak valid.',
            'tanggal_lahir.before_or_equal' => 'Tanggal lahir tidak boleh melebihi hari ini.'
        ]);

        $ppdb = Ppdb::findOrFail($id);
        $ppdb->update($request->all());

        return redirect()->route('admin.ppdb')->with('success', 'Data PPDB berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $ppdb = Ppdb::findOrFail($id);
        $ppdb->delete();
        return redirect()->route('admin.ppdb')->with('success', 'Data pendaftar berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/JabatanController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Jabatan;
use Illuminate\Http\Request;

class JabatanController extends Controller
{
    public function index()
    {
        $jabatans = Jabatan::orderBy('urutan_tingkat', 'asc')->get();
        return view('admin.master-data.jabatan.index', compact('jabatans'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama_jabatan' => 'required|string|max:255',
            'urutan_tingkat' => 'required|integer|min:1',
            'keterangan' => 'nullable|string|max:255',
        ]);

        Jabatan::create($request->all());

        return redirect()->route('admin.jabatan')->with('success', 'Data Jabatan berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_jabatan' => 'required|string|max:255',
            'urutan_tingkat' => 'required|integer|min:1',
            'keterangan' => 'nullable|string|max:255',
        ]);

        $jabatan = Jabatan::findOrFail($id);
        $jabatan->update($request->all());

        return redirect()->route('admin.jabatan')->with('success', 'Data Jabatan berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $jabatan = Jabatan::findOrFail($id);
        $jabatan->delete();

        return redirect()->route('admin.jabatan')->with('success', 'Data Jabatan berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/KategoriWaktuController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\KategoriWaktu;
use Illuminate\Http\Request;

class KategoriWaktuController extends Controller
{
    public function index() {
        $kategoris = KategoriWaktu::all();
        return view('admin.alokasi-waktu.kategori', compact('kategoris'));
    }
    public function store(Request $request) {
        $request->validate(['nama_kategori' => 'required|string|max:50']);
        KategoriWaktu::create($request->all());
        return back()->with('success', 'Kategori berhasil ditambahkan.');
    }
    public function update(Request $request, $id) {
        $request->validate(['nama_kategori' => 'required|string|max:50']);
        KategoriWaktu::findOrFail($id)->update($request->all());
        return back()->with('success', 'Kategori berhasil diperbarui.');
    }
    public function destroy($id) {
        KategoriWaktu::findOrFail($id)->delete();
        return back()->with('success', 'Kategori berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/KategoriMapelController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\KategoriMapel;
use Illuminate\Http\Request;

class KategoriMapelController extends Controller
{
    public function index()
    {
        $kategoriMapels = KategoriMapel::orderBy('created_at', 'desc')->get();
        return view('admin.master-data.kategori-mapel.index', compact('kategoriMapels'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama_kategori' => 'required|string|max:255',
        ]);

        KategoriMapel::create([
            'nama_kategori' => $request->nama_kategori,
        ]);

        return redirect()->route('admin.kategori-mapel')->with('success', 'Kategori Mapel berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_kategori' => 'required|string|max:255',
        ]);

        $kategori = KategoriMapel::findOrFail($id);
        $kategori->update([
            'nama_kategori' => $request->nama_kategori,
        ]);

        return redirect()->route('admin.kategori-mapel')->with('success', 'Kategori Mapel berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $kategori = KategoriMapel::findOrFail($id);
        $kategori->delete();

        return redirect()->route('admin.kategori-mapel')->with('success', 'Kategori Mapel berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/PpdbSettingController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\PpdbSetting;

class PpdbSettingController extends Controller
{
    public function edit()
    {
        $setting = PpdbSetting::firstOrCreate(['id' => 1], [
            'judul' => 'Pendaftaran Peserta Didik Baru',
            'deskripsi' => 'Bergabunglah dengan keluarga besar Madrasah Al Idrus Lengkong dan rasakan pengalaman belajar yang berkesan dalam lingkungan Islami yang mendukung.',
            'jadwal' => [
                ['title' => 'Pendaftaran Online', 'date' => '1 - 28 Februari 2024'],
                ['title' => 'Tes Seleksi', 'date' => '1 - 15 Maret 2024'],
                ['title' => 'Pengumuman Hasil', 'date' => '20 Maret 2024'],
                ['title' => 'Registrasi & Orientasi', 'date' => '25 - 31 Maret 2024'],
            ]
        ]);

        return view('admin.ppdb.pengaturan', compact('setting'));
    }

    public function update(Request $request)
    {
        // BYPASS: Mengganti mimes dan image menjadi 'file' agar fileinfo tidak dipanggil
        $request->validate([
            'judul' => 'required|string|max:255',
            'deskripsi' => 'required|string',
            'jadwal' => 'required|array',
            'brosur_file' => 'nullable|file|max:5120', // Maks 5MB
            'gambar_ilustrasi' => 'nullable|file|max:5120', // Maks 5MB
        ]);

        $setting = PpdbSetting::firstOrCreate(['id' => 1]);
        $data = $request->only(['judul', 'deskripsi', 'jadwal']);

        $ppdbFolder = storage_path('app/public/ppdb');
        if (!is_dir($ppdbFolder)) {
            mkdir($ppdbFolder, 0755, true);
        }

        if ($request->hasFile('brosur_file')) {
            // BYPASS: Hapus file lama menggunakan PHP Murni (@unlink)
            if ($setting->brosur_file && file_exists($ppdbFolder . '/' . $setting->brosur_file)) {
                @unlink($ppdbFolder . '/' . $setting->brosur_file);
            }
            
            $file = $request->file('brosur_file');
            $ext = $file->getClientOriginalExtension() ?: 'pdf';
            $filename = 'Brosur_PPDB_' . time() . '.' . $ext;
            
            // BYPASS: Simpan file menggunakan PHP Murni (move_uploaded_file)
            if (move_uploaded_file($file->getPathname(), $ppdbFolder . '/' . $filename)) {
                chmod($ppdbFolder . '/' . $filename, 0644); // Beri izin baca
                $data['brosur_file'] = $filename;
            }
        }

        if ($request->hasFile('gambar_ilustrasi')) {
            // BYPASS: Hapus file lama menggunakan PHP Murni (@unlink)
            if ($setting->gambar_ilustrasi && file_exists($ppdbFolder . '/' . $setting->gambar_ilustrasi)) {
                @unlink($ppdbFolder . '/' . $setting->gambar_ilustrasi);
            }
            
            $fileImg = $request->file('gambar_ilustrasi');
            $extImg = $fileImg->getClientOriginalExtension() ?: 'png';
            $filenameImg = 'Ilustrasi_PPDB_' . time() . '.' . $extImg;
            
            // BYPASS: Simpan file menggunakan PHP Murni (move_uploaded_file)
            if (move_uploaded_file($fileImg->getPathname(), $ppdbFolder . '/' . $filenameImg)) {
                chmod($ppdbFolder . '/' . $filenameImg, 0644); // Beri izin baca
                $data['gambar_ilustrasi'] = $filenameImg;
            }
        }

        $setting->update($data);
        return back()->with('success', 'Pengaturan Halaman PPDB berhasil diperbarui!');
    }
}
```

## File: `app/Http/Controllers/Admin/BerandaController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\BerandaSetting;
use Illuminate\Support\Str;

class BerandaController extends Controller
{
    public function edit()
    {
        $beranda = BerandaSetting::first() ?? new BerandaSetting();
        return view('admin.menu-utama.beranda', compact('beranda'));
    }

    public function update(Request $request)
    {
        $request->validate([
            'hero_title'    => 'required|string|max:255',
            'hero_subtitle' => 'required|string',
            'stat_siswa'    => 'required|string|max:20',
            'stat_guru'     => 'required|string|max:20',
            'stat_lulusan'  => 'required|string|max:20',
            'kepsek_nama'   => 'nullable|string|max:255',
            'kepsek_sambutan' => 'nullable|string',
            'kepsek_foto'   => 'nullable|file|max:2048', 
            'hero_images.*' => 'nullable|file|max:3048',
        ]);

        $beranda = BerandaSetting::first() ?? new BerandaSetting();
        $data = $request->except(['hero_images', 'kepsek_foto']);

        if ($request->hasFile('hero_images')) {
            $images = $beranda->hero_images ?? [];
            foreach ($request->file('hero_images') as $file) {
                $uploaded = $this->uploadManual($file, 'beranda/slider', 'slider');
                if($uploaded) $images[] = $uploaded;
            }
            $data['hero_images'] = $images;
        }

        if ($request->hasFile('kepsek_foto')) {
            // Hapus file lama secara manual jika ada
            if ($beranda->kepsek_foto && file_exists(storage_path('app/public/beranda/' . $beranda->kepsek_foto))) {
                @unlink(storage_path('app/public/beranda/' . $beranda->kepsek_foto));
            }
            $data['kepsek_foto'] = $this->uploadManual($request->file('kepsek_foto'), 'beranda', 'kepsek');
        }

        if (!$beranda->exists) {
            BerandaSetting::create($data);
        } else {
            $beranda->update($data);
        }

        return back()->with('success', 'Konfigurasi Beranda & Publikasi berhasil disimpan');
    }

    public function deleteSlider($index)
    {
        $beranda = BerandaSetting::first();
        if (!$beranda) return back();

        $images = $beranda->hero_images ?? [];
        if (isset($images[$index])) {
            $oldPath = storage_path('app/public/beranda/slider/' . $images[$index]);
            if (file_exists($oldPath)) {
                @unlink($oldPath);
            }
            unset($images[$index]);
            $beranda->update(['hero_images' => array_values($images)]);
        }
        return back()->with('success', 'Gambar slider berhasil dihapus.');
    }

    // Fungsi Upload PHP Murni Tanpa Campur Tangan Laravel FileSystem
    private function uploadManual($file, $folder, $prefix) {
        $path = storage_path('app/public/' . $folder);
        
        if (!is_dir($path)) {
            mkdir($path, 0755, true);
        }
        
        $ext = $file->getClientOriginalExtension() ?: 'jpg';
        $filename = $prefix . '_' . time() . '_' . Str::random(5) . '.' . $ext;
        
        // Pindahkan file menggunakan PHP asli
        if (move_uploaded_file($file->getPathname(), $path . '/' . $filename)) {
            // TAMBAHAN: Paksa agar file bisa dibaca publik (0644)
            chmod($path . '/' . $filename, 0644);
            return $filename;
        }
        
        return null;
    }
}
```

## File: `app/Http/Controllers/Admin/KeuanganKategoriController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\JenjangKelas;
use App\Models\KategoriKeuangan;
use Illuminate\Http\Request;

class KeuanganKategoriController extends Controller
{
    public function index($jenjang_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $kategoris = KategoriKeuangan::where('jenjang_id', $jenjang_id)
                        ->orderBy('created_at', 'asc')
                        ->get();
                        
        return view('admin.keuangan.kategori.index', compact('jenjang', 'kategoris'));
    }

    public function store(Request $request, $jenjang_id)
    {
        $request->validate([
            'nama'       => 'required|string|max:255',
            'tipe'       => 'required|in:Harian,Mingguan,Bulanan,Per Periode,Tahunan,Sekali Bayar,Insidental',
            'nominal'    => 'nullable|numeric|min:0',
            'keterangan' => 'nullable|string'
        ]);

        KategoriKeuangan::create([
            'jenjang_id' => $jenjang_id,
            'nama'       => $request->nama,
            'tipe'       => $request->tipe,
            'nominal'    => $request->nominal ?? 0, 
            'keterangan' => $request->keterangan
        ]);

        return redirect()->route('admin.keuangan.kategori', $jenjang_id)
                         ->with('success', 'Kategori Keuangan berhasil ditambahkan!');
    }

    public function update(Request $request, $id)
    {
        $kategori = KategoriKeuangan::findOrFail($id);
        
        $request->validate([
            'nama'       => 'required|string|max:255',
            'tipe'       => 'required|in:Harian,Mingguan,Bulanan,Per Periode,Tahunan,Sekali Bayar,Insidental',
            'nominal'    => 'nullable|numeric|min:0',
            'keterangan' => 'nullable|string'
        ]);

        $kategori->update([
            'nama'       => $request->nama,
            'tipe'       => $request->tipe,
            'nominal'    => $request->nominal ?? 0,
            'keterangan' => $request->keterangan
        ]);

        return redirect()->back()->with('success', 'Kategori Keuangan berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $kategori = KategoriKeuangan::findOrFail($id);
        $kategori->delete();
        
        return redirect()->back()->with('success', 'Kategori Keuangan berhasil dihapus!');
    }
}
```

## File: `app/Http/Controllers/Admin/TahunAjaranController.php`
```php
<?php
// app/Http/Controllers/Admin/TahunAjaranController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\TahunAjaran;
use Illuminate\Http\Request;

class TahunAjaranController extends Controller
{
    public function index()
    {
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        return view('admin.master-data.tahun-ajaran.index', compact('tahunAjarans'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'tahun_ajaran' => 'required|string|max:255',
            'tahun_ajaran_hijriyah' => 'required|string|max:255',
            'tahun_ppdb' => 'required|integer|min:2000',
            'tahun_ppdb_hijriyah' => 'required|string|max:50',
            'kode' => 'required|string|max:50',
            'status_akademik' => 'required|in:aktif,tidak_aktif,mendatang',
            'status_ppdb' => 'required|in:buka,tutup,persiapan',
            'tgl_mulai' => 'required|date',
            'tgl_akhir' => 'required|date|after_or_equal:tgl_mulai',
            'tgl_mulai_hijriyah' => 'required|string|max:255',
            'tgl_akhir_hijriyah' => 'required|string|max:255',
            'kuota' => 'required|integer|min:1',
        ]);

        if ($request->status_akademik == 'aktif') {
            TahunAjaran::query()->update(['status_akademik' => 'tidak_aktif']);
        }
        if ($request->status_ppdb == 'buka') {
            TahunAjaran::query()->update(['status_ppdb' => 'tutup']);
        }

        TahunAjaran::create($request->all());

        return redirect()->route('admin.tahun-ajaran')->with('success', 'Tahun Ajaran berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'tahun_ajaran' => 'required|string|max:255',
            'tahun_ajaran_hijriyah' => 'required|string|max:255',
            'tahun_ppdb' => 'required|integer|min:2000',
            'tahun_ppdb_hijriyah' => 'required|string|max:50',
            'kode' => 'required|string|max:50',
            'status_akademik' => 'required|in:aktif,tidak_aktif,mendatang',
            'status_ppdb' => 'required|in:buka,tutup,persiapan',
            'tgl_mulai' => 'required|date',
            'tgl_akhir' => 'required|date|after_or_equal:tgl_mulai',
            'tgl_mulai_hijriyah' => 'required|string|max:255',
            'tgl_akhir_hijriyah' => 'required|string|max:255',
            'kuota' => 'required|integer|min:1',
        ]);

        if ($request->status_akademik == 'aktif') {
            TahunAjaran::where('id', '!=', $id)->update(['status_akademik' => 'tidak_aktif']);
        }
        if ($request->status_ppdb == 'buka') {
            TahunAjaran::where('id', '!=', $id)->update(['status_ppdb' => 'tutup']);
        }

        $tahunAjaran = TahunAjaran::findOrFail($id);
        $tahunAjaran->update($request->all());

        return redirect()->route('admin.tahun-ajaran')->with('success', 'Tahun Ajaran berhasil diperbarui.');
    }
}
```

## File: `app/Http/Controllers/Admin/KeuanganTarifController.php`
```php
<?php
// app/Http/Controllers/Admin/KeuanganTarifController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\JenjangKelas;
use App\Models\TingkatKelas;
use App\Models\KategoriKeuangan;
use App\Models\TarifKeuangan;
use App\Models\TahunAjaran;
use App\Models\Semester;
use Illuminate\Http\Request;

class KeuanganTarifController extends Controller
{
    public function index($jenjang_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $kategoris = KategoriKeuangan::where('jenjang_id', $jenjang_id)->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        $semesters = Semester::where('jenis_periode', $jenjang->sistem_akademik)->orderBy('id', 'asc')->get();
        $tingkats = TingkatKelas::where('jenjang_kelas_id', $jenjang_id)->orderBy('nama_tingkat', 'asc')->get();
        
        $tarifs = TarifKeuangan::with(['kategori', 'tahunAjaran', 'semester', 'tingkat'])
                    ->where('jenjang_id', $jenjang_id)
                    ->orderBy('tahun_ajaran_id', 'desc')
                    ->get();
                    
        return view('admin.keuangan.tarif.index', compact('jenjang', 'kategoris', 'tahunAjarans', 'semesters', 'tingkats', 'tarifs'));
    }

    public function store(Request $request, $jenjang_id)
    {
        $request->validate([
            'kategori_id'     => 'required|exists:kategori_keuangans,id',
            'tingkat_kelas_id'=> 'nullable|exists:tingkat_kelas,id', 
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id'     => 'nullable|exists:semesters,id',
            'nominal'         => 'required|numeric|min:0',
            'pengali'         => 'required|numeric|min:1' 
        ]);

        $exists = TarifKeuangan::where('jenjang_id', $jenjang_id)
            ->where('kategori_id', $request->kategori_id)
            ->where('tahun_ajaran_id', $request->tahun_ajaran_id)
            ->where('semester_id', $request->semester_id)
            ->where('tingkat_kelas_id', $request->tingkat_kelas_id) 
            ->first();

        if ($exists) {
            return redirect()->back()->withErrors(['Kategori ini sudah ditetapkan tarifnya untuk Tingkat, Tahun Ajaran, dan Periode tersebut.']);
        }

        TarifKeuangan::create([
            'jenjang_id'      => $jenjang_id,
            'kategori_id'     => $request->kategori_id,
            'tingkat_kelas_id'=> $request->tingkat_kelas_id, 
            'tahun_ajaran_id' => $request->tahun_ajaran_id,
            'semester_id'     => $request->semester_id,
            'nominal'         => $request->nominal,
            'pengali'         => $request->pengali,
            'total_tarif'     => $request->nominal * $request->pengali
        ]);

        return redirect()->back()->with('success', 'Tarif Tagihan berhasil ditetapkan!');
    }

    public function update(Request $request, $id)
    {
        $tarif = TarifKeuangan::findOrFail($id);
        
        $request->validate([
            'kategori_id'     => 'required|exists:kategori_keuangans,id',
            'tingkat_kelas_id'=> 'nullable|exists:tingkat_kelas,id', 
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id'     => 'nullable|exists:semesters,id',
            'nominal'         => 'required|numeric|min:0',
            'pengali'         => 'required|numeric|min:1'
        ]);
        
        $exists = TarifKeuangan::where('id', '!=', $id)
            ->where('jenjang_id', $tarif->jenjang_id)
            ->where('kategori_id', $request->kategori_id)
            ->where('tahun_ajaran_id', $request->tahun_ajaran_id)
            ->where('semester_id', $request->semester_id)
            ->where('tingkat_kelas_id', $request->tingkat_kelas_id)
            ->first();

        if ($exists) {
            return redirect()->back()->withErrors(['Penetapan tarif gagal! Kategori ini sudah ada di Tingkat, Tahun Ajaran, dan Periode yang sama.']);
        }
        
        $tarif->update([
            'kategori_id'     => $request->kategori_id,
            'tingkat_kelas_id'=> $request->tingkat_kelas_id, 
            'tahun_ajaran_id' => $request->tahun_ajaran_id,
            'semester_id'     => $request->semester_id,
            'nominal'         => $request->nominal,
            'pengali'         => $request->pengali,
            'total_tarif'     => $request->nominal * $request->pengali 
        ]);
        
        return redirect()->back()->with('success', 'Penetapan Tarif berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $tarif = TarifKeuangan::findOrFail($id);
        $tarif->delete();
        
        return redirect()->back()->with('success', 'Penetapan Tarif berhasil dihapus!');
    }
}
```

## File: `app/Http/Controllers/Admin/SiswaController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Siswa;
use App\Models\Kelas;
use App\Models\Gender;
use App\Models\TahunAjaran;
use App\Models\JenjangKelas;
use App\Models\TingkatKelas;
use App\Models\JadwalMapel;
use App\Models\Semester;
use App\Models\TarifKeuangan;
use App\Models\PembayaranKeuangan;
use App\Models\Testimoni; 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; 

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date;

class SiswaController extends Controller
{
    public function index()
    {
        $firstJenjang = JenjangKelas::with('tingkat')->orderBy('id', 'asc')->first();
        
        if ($firstJenjang && $firstJenjang->tingkat->isNotEmpty()) {
            return redirect()->route('admin.siswa.tingkat', [
                'jenjang_id' => $firstJenjang->id,
                'tingkat_id' => $firstJenjang->tingkat->first()->id
            ]);
        }

        $siswas = collect();
        $jenjang = null;
        $tingkat = null;
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $selectedTaId = null;

        session(['url_kembali_siswa' => url()->full()]);

        return view('admin.siswa.index', compact('siswas', 'jenjang', 'tingkat', 'tahunAjarans', 'selectedTaId'));
    }

    public function indexTingkat(Request $request, $jenjang_id, $tingkat_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $tingkat = TingkatKelas::findOrFail($tingkat_id);
        
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : null);
        
        $siswas = Siswa::with(['kelas.tingkat.jenjang', 'kelas.details.gender', 'tahunAjaran', 'akun'])
                    ->whereHas('kelas', function($query) use ($tingkat_id) {
                        $query->where('tingkat_kelas_id', $tingkat_id);
                    })
                    ->when($selectedTaId, function($query) use ($selectedTaId) {
                        $query->where('tahun_ajaran_id', $selectedTaId);
                    })
                    ->orderBy('nama_lengkap', 'asc')
                    ->get();

        session(['url_kembali_siswa' => url()->full()]);

        return view('admin.siswa.index', compact('siswas', 'jenjang', 'tingkat', 'tahunAjarans', 'selectedTaId'));
    }

    public function rekap(Request $request)
    {
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : null);

        $jenjangs = JenjangKelas::with(['tingkat' => function($q) {
            $q->orderBy('nama_tingkat', 'asc');
        }, 'tingkat.kelas' => function($q) {
            $q->orderBy('nama_kelas', 'asc');
        }, 'tingkat.kelas.siswa' => function($q) use ($selectedTaId) {
            $q->where('status_siswa', 'Aktif') 
              ->when($selectedTaId, function($subQ) use ($selectedTaId) {
                  $subQ->where('tahun_ajaran_id', $selectedTaId);
              });
        }])->orderBy('id', 'asc')->get();

        $grandTotalJenjang = $jenjangs->count();
        $grandTotalTingkat = 0;
        $grandTotalKelas = 0;
        $grandTotalSiswa = 0;

        foreach($jenjangs as $jenjang) {
            $grandTotalTingkat += $jenjang->tingkat->count();
            foreach($jenjang->tingkat as $tingkat) {
                $grandTotalKelas += $tingkat->kelas->count();
                foreach($tingkat->kelas as $kelas) {
                    $grandTotalSiswa += $kelas->siswa->count();
                }
            }
        }

        return view('admin.siswa.rekap.index', compact(
            'tahunAjarans', 'selectedTaId', 'jenjangs', 
            'grandTotalJenjang', 'grandTotalTingkat', 'grandTotalKelas', 'grandTotalSiswa'
        ));
    }

    public function create()
    {
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])
                    ->orderBy('tingkat_kelas_id', 'asc')
                    ->orderBy('nama_kelas', 'asc')
                    ->get();

        $genders = Gender::all();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();

        return view('admin.siswa.create', compact('kelas', 'genders', 'tahunAjarans'));
    }

    public function getNisLokal($tahunAjaranId, $jenjangId)
    {
        $ta = TahunAjaran::findOrFail($tahunAjaranId);
        $jenjang = JenjangKelas::findOrFail($jenjangId);
        
        $hijri2Digit = substr(preg_replace('/[^0-9]/', '', $ta->tahun_ppdb_hijriyah), -2);
        $madrasahDefault = '448'; 
        $jenjangKode = str_pad($jenjang->kode, 2, '0', STR_PAD_LEFT); 
        
        $prefix = $hijri2Digit . $madrasahDefault . $jenjangKode;
        
        $lastSiswa = Siswa::where('nis', 'LIKE', $prefix . '%')->orderBy('nis', 'desc')->first();
        $lastUser = \App\Models\User::where('username', 'LIKE', $prefix . '%')->orderBy('username', 'desc')->first();
        
        $countSiswa = $lastSiswa ? ((int) substr($lastSiswa->nis, -3)) : 0;
        $countUser = $lastUser ? ((int) substr($lastUser->username, -3)) : 0;
        
        $count = max($countSiswa, $countUser) + 1;
        $nisLokal = $prefix . str_pad($count, 3, '0', STR_PAD_LEFT); 

        return response()->json([
            'success' => true,
            'nis' => $nisLokal
        ]);
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama_lengkap'    => 'required|string|max:255',
            'kelas_id'        => 'required|exists:kelas,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'jenis_kelamin'   => 'required|in:L,P',
            'nis'             => 'required|unique:siswas,nis',
            'nisn'            => 'nullable|unique:siswas,nisn',
        ]);

        try {
            DB::beginTransaction();

            $data = $request->all();
            $data['status_siswa'] = 'Aktif'; 

            $userSiswa = \App\Models\User::create([
                'name' => $data['nama_lengkap'],
                'username' => $data['nis'], 
                'password' => \Illuminate\Support\Facades\Hash::make('12345678'), 
                'role' => 'siswa',
                'is_active' => true
            ]);

            $data['user_id'] = $userSiswa->id;
            Siswa::create($data);

            DB::commit();
            return redirect()->route('admin.siswa')->with('success', 'Data Siswa Baru dan Akun Login berhasil dibuat!');
        } catch (\Exception $e) {
            DB::rollBack();
            return back()->withErrors(['Terjadi kesalahan: ' . $e->getMessage()]);
        }
    }

    public function templateImport(Request $request)
    {
        try {
            $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
            $dummyHijriyah = $taAktif ? preg_replace('/[^0-9]/', '', $taAktif->tahun_ppdb_hijriyah) : '1446';
            $dummyMasehi = $taAktif ? $taAktif->tahun_ppdb : '2024';

            $tingkatStr = 'VII';
            $tingkatId = $request->query('tingkat_id');
            if ($tingkatId) {
                $tingkatObj = TingkatKelas::find($tingkatId);
                if ($tingkatObj) $tingkatStr = $tingkatObj->nama_tingkat;
            }

            $filePath = \App\Exports\TemplateSiswaExport::createTemplate($tingkatStr, $dummyHijriyah, $dummyMasehi);
            $fileName = basename($filePath);

            return response()->download($filePath, $fileName, [
                'Content-Type' => 'application/vnd.ms-excel',
            ])->deleteFileAfterSend(true);

        } catch (\Exception $e) {
            return back()->withErrors(['Gagal mengunduh template Excel. Error: ' . $e->getMessage()]);
        }
    }

    public function importData(Request $request)
    {
        $request->validate([
            'file_import' => 'required|file|max:5120',
            'tingkat_id' => 'required|exists:tingkat_kelas,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id'
        ]);

        try {
            $file = $request->file('file_import');
            $ext = strtolower($file->getClientOriginalExtension());
            
            if ($ext !== 'xls') {
                return back()->withErrors([
                    'SERVER MENOLAK FILE .xlsx! Server Anda mematikan fitur ZipArchive. Anda WAJIB mengunggah format Excel 97-2003 (.xls).'
                ]);
            }

            $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xls');
            $spreadsheet = $reader->load($file->getPathname());
            $sheet = $spreadsheet->getActiveSheet();
            $rows = $sheet->toArray();
            
            $successCount = 0;
            $errorCount = 0;

            $ta = TahunAjaran::findOrFail($request->tahun_ajaran_id);
            $tingkat = TingkatKelas::with('jenjang')->findOrFail($request->tingkat_id);
            $jenjang = $tingkat->jenjang;

            $madrasahDefault = '448'; 
            $jenjangKode = str_pad($jenjang->kode, 2, '0', STR_PAD_LEFT); 

            for ($i = 1; $i < count($rows); $i++) {
                $row = $rows[$i];
                
                if (empty(array_filter($row))) continue; 

                if(count($row) < 11) { 
                    $errorCount++; 
                    continue; 
                }
                
                $nama = trim($row[0] ?? '');
                $jk = strtoupper(trim($row[1] ?? ''));
                $tempat = trim($row[2] ?? '');
                
                $tglRaw = $row[3] ?? '';
                $tgl = null;
                if (is_numeric($tglRaw)) {
                    $tgl = Date::excelToDateTimeObject($tglRaw)->format('Y-m-d');
                } elseif (!empty($tglRaw)) {
                    $tgl = date('Y-m-d', strtotime(str_replace('/', '-', $tglRaw)));
                }

                $nisn = trim($row[4] ?? '');
                $nisn = $nisn === '' ? null : $nisn;
                
                $asal_sekolah = trim($row[5] ?? '');
                $hp = isset($row[6]) ? trim($row[6]) : null;
                $jenis_pendaftaran = trim($row[7] ?? 'Baru');
                
                $tingkat_excel = trim($row[8] ?? '');
                $nama_kelas = trim($row[9] ?? '');
                $slot_gender = strtolower(trim($row[10] ?? ''));

                $tahunHijriExcel = trim($row[11] ?? '');
                $hijriClean = preg_replace('/[^0-9]/', '', $tahunHijriExcel); 
                
                if (!empty($hijriClean)) {
                    $hijri2Digit = substr($hijriClean, -2);
                } else {
                    $hijri2Digit = substr(preg_replace('/[^0-9]/', '', $ta->tahun_ppdb_hijriyah), -2);
                }

                if(empty($nama) || !in_array($jk, ['L', 'P'])) {
                    $errorCount++; 
                    continue;
                }

                if(strcasecmp($tingkat_excel, $tingkat->nama_tingkat) !== 0) {
                    $errorCount++; 
                    continue;
                }

                $kelas = Kelas::where('tingkat_kelas_id', $request->tingkat_id)
                              ->where('nama_kelas', $nama_kelas)
                              ->first();

                if(!$kelas) {
                    $errorCount++; 
                    continue; 
                }

                if (str_contains($slot_gender, 'putra') && $jk === 'P') { 
                    $errorCount++; 
                    continue; 
                }
                if ((str_contains($slot_gender, 'putri') || str_contains($slot_gender, 'perempuan')) && $jk === 'L') { 
                    $errorCount++; 
                    continue; 
                }

                if(!empty($nisn) && Siswa::where('nisn', $nisn)->exists()) {
                    $errorCount++; 
                    continue;
                }

                $prefixNIS = $hijri2Digit . $madrasahDefault . $jenjangKode;
                
                $lastSiswaImport = Siswa::where('nis', 'LIKE', $prefixNIS . '%')->orderBy('nis', 'desc')->first();
                $lastUserImport = \App\Models\User::where('username', 'LIKE', $prefixNIS . '%')->orderBy('username', 'desc')->first();
                
                $countSiswaImport = $lastSiswaImport ? ((int) substr($lastSiswaImport->nis, -3)) : 0;
                $countUserImport = $lastUserImport ? ((int) substr($lastUserImport->username, -3)) : 0;
                
                $countNis = max($countSiswaImport, $countUserImport) + 1;
                $nisLokalOtomatis = $prefixNIS . str_pad($countNis, 3, '0', STR_PAD_LEFT); 

                try {
                    DB::beginTransaction();

                    $userSiswa = \App\Models\User::create([
                        'name' => $nama,
                        'username' => $nisLokalOtomatis, 
                        'password' => \Illuminate\Support\Facades\Hash::make('12345678'), 
                        'role' => 'siswa',
                        'is_active' => true
                    ]);

                    Siswa::create([
                        'user_id' => $userSiswa->id, 
                        'nama_lengkap' => $nama,
                        'nis' => $nisLokalOtomatis,
                        'nisn' => $nisn,
                        'jenis_kelamin' => $jk,
                        'tempat_lahir' => $tempat,
                        'tanggal_lahir' => $tgl,
                        'asal_sekolah' => $asal_sekolah,
                        'no_hp' => $hp,
                        'jenis_pendaftaran' => $jenis_pendaftaran,
                        'agama' => 'Islam', 
                        'kelas_id' => $kelas->id,
                        'tahun_ajaran_id' => $request->tahun_ajaran_id,
                        'status_siswa' => 'Aktif',
                    ]);

                    DB::commit();
                    $successCount++;
                } catch (\Exception $e) {
                    DB::rollBack();
                    $errorCount++;
                    continue; 
                }
            }

            if ($successCount > 0) {
                return back()->with('success', "Import selesai! Berhasil: $successCount siswa. Gagal/Dilewati: $errorCount baris (karena duplikasi/data tidak valid).");
            } else {
                return back()->withErrors(["Gagal melakukan import. Pastikan file Excel Anda sesuai dengan template."]);
            }

        } catch (\Exception $e) {
            return back()->withErrors(["Terjadi kesalahan saat membaca file Excel: " . $e->getMessage()]);
        }
    }

    public function edit($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])
                    ->orderBy('tingkat_kelas_id', 'asc')
                    ->orderBy('nama_kelas', 'asc')
                    ->get();
        
        $genders = Gender::all();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        
        $activeMenu = 'edit';
        
        return view('admin.siswa.views', compact('siswa', 'kelas', 'genders', 'tahunAjarans', 'activeMenu'));
    }

    public function jadwal($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();

        $activeMenu = 'jadwal';
        
        return view('admin.siswa.views', compact('siswa', 'kelas', 'tahunAjarans', 'activeMenu'));
    }

    public function absensi($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        
        $activeMenu = 'absensi';
        
        return view('admin.siswa.views', compact('siswa', 'kelas', 'tahunAjarans', 'activeMenu'));
    }

    public function nilai($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        
        $activeMenu = 'nilai';
        
        return view('admin.siswa.views', compact('siswa', 'kelas', 'tahunAjarans', 'activeMenu'));
    }
    
    // PEMBARUAN PADA FUNGSI KEUANGAN: FILTER TINGKAT KELAS KETAT
    public function keuangan($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        
        // Kita tidak butuh activeMenu karena ini halaman mandiri (bukan tabs profil siswa)
        // Namun kita biarkan agar view tidak error jika variabel ini dipakai secara global
        $activeMenu = 'keuangan';

        $jenjangId = $siswa->kelas->tingkat->jenjang_id ?? 1;
        $tingkatId = $siswa->kelas->tingkat_id ?? null;
        
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = request('keuangan_ta_id', $defaultTa->id ?? null);
        $activeTa = $selectedTaId ? TahunAjaran::find($selectedTaId) : null;

        // FILTER TINGKAT YANG KETAT (Cegah bocor)
        $tarifs = TarifKeuangan::with(['kategori', 'semester', 'tahunAjaran', 'tingkat'])
            ->where('jenjang_id', $jenjangId)
            ->where(function($query) use ($tingkatId) {
                // Tampilkan JIKA tingkatnya benar-benar NULL atau kosong ATAU sama persis dengan tingkat siswa
                $query->whereNull('tingkat_kelas_id')
                      ->orWhere('tingkat_kelas_id', '')
                      ->orWhere('tingkat_kelas_id', 0)
                      ->orWhere('tingkat_kelas_id', $tingkatId);
            })
            ->when($selectedTaId, function($query) use ($selectedTaId) {
                return $query->where('tahun_ajaran_id', $selectedTaId);
            })
            ->get()
            ->map(function($tarif) use ($siswa) {
                $terbayar = PembayaranKeuangan::where('tarif_keuangan_id', $tarif->id)
                            ->where('siswa_id', $siswa->id)
                            ->sum('nominal_bayar');
                
                $tarif->terbayar = $terbayar;
                $tarif->sisa = $tarif->nominal - $terbayar;
                return $tarif;
            });

        $totalTagihan = $tarifs->sum('nominal');
        $totalDibayar = $tarifs->sum('terbayar');
        $sisaTagihan  = $totalTagihan - $totalDibayar;

        $historiPembayaran = PembayaranKeuangan::with(['tarif.kategori', 'tarif.tahunAjaran'])
            ->where('siswa_id', $siswa->id)
            ->orderBy('tanggal_bayar', 'desc')
            ->orderBy('id', 'desc')
            ->paginate(10);

        return view('admin.keuangan.siswa.transaksi', compact(
            'siswa', 'kelas', 'tahunAjarans', 'activeMenu',
            'activeTa', 'selectedTaId', 'totalTagihan', 'totalDibayar', 'sisaTagihan', 'tarifs', 'historiPembayaran'
        ));
    }

    public function testimoni($id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'akun'])->findOrFail($id);
        $kelas = Kelas::with(['tingkat.jenjang', 'details.gender'])->get();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ppdb', 'desc')->get();
        
        $testimoni = Testimoni::where('siswa_id', $id)->first();
        
        $activeMenu = 'testimoni';
        
        return view('admin.siswa.views', compact('siswa', 'kelas', 'tahunAjarans', 'activeMenu', 'testimoni'));
    }

    public function storeTestimoni(Request $request, $id)
    {
        $siswa = Siswa::findOrFail($id);

        $request->validate([
            'nama'  => 'required|string|max:255',
            'peran' => 'required|string|max:255',
            'pesan' => 'required|string',
            'foto'  => 'nullable|file|max:2048' 
        ]);

        $data = $request->only(['nama', 'peran', 'pesan']);
        $data['siswa_id'] = $siswa->id;

        $testimoni = Testimoni::where('siswa_id', $id)->first();

        if ($request->hasFile('foto')) {
            $testimoniFolder = storage_path('app/public/testimoni');
            if (!is_dir($testimoniFolder)) mkdir($testimoniFolder, 0755, true);

            if ($testimoni && $testimoni->foto && file_exists($testimoniFolder . '/' . $testimoni->foto)) {
                @unlink($testimoniFolder . '/' . $testimoni->foto);
            }
            
            $file = $request->file('foto');
            $ext = $file->getClientOriginalExtension() ?: 'jpg';
            $filename = 'testi_siswa_' . $siswa->id . '_' . time() . '.' . $ext;
            
            if (move_uploaded_file($file->getPathname(), $testimoniFolder . '/' . $filename)) {
                chmod($testimoniFolder . '/' . $filename, 0644);
                $data['foto'] = $filename;
            }
        }

        if ($testimoni) {
            $testimoni->update($data);
            $msg = 'Data Testimoni Orang Tua berhasil diperbarui!';
        } else {
            Testimoni::create($data);
            $msg = 'Data Testimoni Orang Tua berhasil ditambahkan!';
        }

        return redirect()->back()->with('success', $msg);
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_lengkap'  => 'required|string|max:255',
            'kelas_id'      => 'required|exists:kelas,id',
            'jenis_kelamin' => 'required|in:L,P',
            'nis'           => 'required|unique:siswas,nis,'.$id,
            'nisn'          => 'nullable|unique:siswas,nisn,'.$id, 
            'password'      => 'nullable|string|min:8|confirmed', 
            'file_ktp'      => 'nullable|file|max:3048',
            'file_kk'       => 'nullable|file|max:3048',
            'file_kip'      => 'nullable|file|max:3048',
            'file_pkh'      => 'nullable|file|max:3048',
            'file_kks'      => 'nullable|file|max:3048',
        ]);

        $siswa = Siswa::findOrFail($id);
        
        $data = $request->except(['foto_base64', '_token', '_method', 'password', 'password_confirmation', 'file_ktp', 'file_kk', 'file_kip', 'file_pkh', 'file_kks']);

        if ($request->filled('foto_base64')) {
            $siswaFolder = storage_path('app/public/siswa');
            if (!is_dir($siswaFolder)) mkdir($siswaFolder, 0755, true);

            $image_parts = explode(";base64,", $request->foto_base64);
            $image_base64 = base64_decode($image_parts[1]);
            $fileName = 'siswa_' . time() . '_' . uniqid() . '.webp';

            if ($siswa->foto && file_exists($siswaFolder . '/' . $siswa->foto)) {
                @unlink($siswaFolder . '/' . $siswa->foto);
            }

            file_put_contents($siswaFolder . '/' . $fileName, $image_base64);
            chmod($siswaFolder . '/' . $fileName, 0644);
            $data['foto'] = $fileName;
        }

        $dokumenFolder = storage_path('app/public/dokumen_siswa');
        if (!is_dir($dokumenFolder)) mkdir($dokumenFolder, 0755, true);

        $dokumens = ['file_ktp', 'file_kk', 'file_kip', 'file_pkh', 'file_kks'];
        foreach ($dokumens as $dokumen) {
            if ($request->hasFile($dokumen)) {
                $file = $request->file($dokumen);
                $ext = $file->getClientOriginalExtension() ?: 'pdf';
                $newDocName = $dokumen . '_' . $siswa->id . '_' . time() . '.' . $ext;

                if ($siswa->$dokumen && file_exists($dokumenFolder . '/' . $siswa->$dokumen)) {
                    @unlink($dokumenFolder . '/' . $siswa->$dokumen);
                }

                if (move_uploaded_file($file->getPathname(), $dokumenFolder . '/' . $newDocName)) {
                    chmod($dokumenFolder . '/' . $newDocName, 0644);
                    $data[$dokumen] = $newDocName;
                }
            }
        }

        if ($request->filled('password') && $siswa->user_id) {
            \App\Models\User::where('id', $siswa->user_id)->update([
                'password' => \Illuminate\Support\Facades\Hash::make($request->password)
            ]);
        }

        if ($siswa->user_id && $siswa->nis !== $request->nis) {
            \App\Models\User::where('id', $siswa->user_id)->update([
                'username' => $request->nis
            ]);
        }

        $siswa->update($data);

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json([
                'status' => 'success',
                'message' => 'Data profil siswa dan file dokumen berhasil diperbarui!'
            ]);
        }

        return redirect()->back()->with('success', 'Data profil siswa dan file dokumen berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $siswa = Siswa::findOrFail($id);
        
        if ($siswa->foto && file_exists(storage_path('app/public/siswa/' . $siswa->foto))) {
            @unlink(storage_path('app/public/siswa/' . $siswa->foto));
        }

        $dokumens = ['file_ktp', 'file_kk', 'file_kip', 'file_pkh', 'file_kks'];
        foreach ($dokumens as $dokumen) {
            if ($siswa->$dokumen && file_exists(storage_path('app/public/dokumen_siswa/' . $siswa->$dokumen))) {
                @unlink(storage_path('app/public/dokumen_siswa/' . $siswa->$dokumen));
            }
        }

        if ($siswa->user_id) {
            \App\Models\User::where('id', $siswa->user_id)->delete();
        }

        $siswa->delete();
        
        return redirect()->route('admin.siswa')->with('success', 'Data siswa berhasil dihapus.');
    }

    public function toggleAkun($id)
    {
        $siswa = Siswa::with('akun')->findOrFail($id);
        
        if ($siswa->akun) {
            $siswa->akun->update(['is_active' => !$siswa->akun->is_active]);
            $status = $siswa->akun->is_active ? 'diaktifkan' : 'diblokir';
            return back()->with('success', "Akses login untuk siswa ini berhasil $status.");
        }

        return back()->withErrors(['Siswa ini belum memiliki akun login di sistem.']);
    }

    public function generateAkun($id)
    {
        $siswa = Siswa::findOrFail($id);
        
        if ($siswa->user_id) {
            return back()->withErrors(['Siswa ini sudah memiliki akun login.']);
        }

        $userSiswa = \App\Models\User::create([
            'name' => $siswa->nama_lengkap,
            'username' => $siswa->nis, 
            'password' => \Illuminate\Support\Facades\Hash::make('12345678'), 
            'role' => 'siswa',
            'is_active' => true
        ]);

        $siswa->update(['user_id' => $userSiswa->id]);

        return back()->with('success', 'Akun login berhasil di-generate! (Username: NIS, Password default: 12345678)');
    }
}
```

## File: `app/Http/Controllers/Admin/DashboardController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

class DashboardController extends Controller
{
    public function index()
    {
        // Data dummy — nanti ambil dari database
        $stats = [
            'siswa' => 1524,
            'guru' => 156,
            'ppdb' => 89,
            'pengunjung' => 8652,
        ];

        return view('admin.dashboard', compact('stats'));
    }
}
```

## File: `app/Http/Controllers/Admin/GaleriController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Galeri;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class GaleriController extends Controller
{
    public function index()
    {
        $fotos = Galeri::where('tipe', 'foto')
            ->orderBy('created_at', 'desc')
            ->get()
            ->groupBy(function($item) {
                return $item->batch_id ?? $item->id;
            });

        $videos = Galeri::where('tipe', 'video')
            ->orderBy('created_at', 'desc')
            ->get();

        return view('admin.galeri.index', compact('fotos', 'videos'));
    }

    public function create()
    {
        return view('admin.galeri.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'judul' => 'required|string|max:255',
            'tipe' => 'required|in:foto,video',
            'file_foto' => 'required_if:tipe,foto|array',
            'file_foto.*' => 'file|max:5120', 
            'video_url' => 'required_if:tipe,video|nullable|url|max:255',
        ]);

        $baseData = $request->only(['judul', 'deskripsi', 'tipe']);
        
        $batchId = Str::uuid()->toString();
        $baseData['batch_id'] = $batchId;

        if ($request->tipe == 'foto' && $request->hasFile('file_foto')) {
            $files = $request->file('file_foto');
            
            foreach ($files as $file) {
                $data = $baseData;
                $data['file_path'] = $this->handleUpload($file);
                $data['video_url'] = null;
                
                Galeri::create($data); 
            }
        } else {
            $baseData['file_path'] = null;
            $baseData['video_url'] = $request->video_url;
            Galeri::create($baseData);
        }

        return redirect()->route('admin.galeri.index')->with('success', 'Media berhasil ditambahkan ke Galeri.');
    }

    public function edit($id)
    {
        $galeri = Galeri::findOrFail($id);
        $isAlbum = ($galeri->tipe == 'foto' && Galeri::where('batch_id', $galeri->batch_id)->count() > 1);
        
        return view('admin.galeri.edit', compact('galeri', 'isAlbum'));
    }

    public function update(Request $request, $id)
    {
        $galeri = Galeri::findOrFail($id);

        $request->validate([
            'judul' => 'required|string|max:255',
            'file_foto' => 'nullable|file|max:5120', // BYPASS
            'video_url' => 'required_if:tipe,video|nullable|url|max:255',
        ]);

        $data = $request->only(['judul', 'deskripsi']);

        if ($galeri->tipe == 'foto') {
            if ($galeri->batch_id) {
                Galeri::where('batch_id', $galeri->batch_id)->update($data);
            } else {
                $galeri->update($data);
            }

            $isAlbum = Galeri::where('batch_id', $galeri->batch_id)->count() > 1;
            if (!$isAlbum && $request->hasFile('file_foto')) {
                // BYPASS Hapus file menggunakan PHP Murni
                $oldPath = storage_path('app/public/galeri/' . $galeri->file_path);
                if ($galeri->file_path && file_exists($oldPath)) {
                    @unlink($oldPath);
                }
                $galeri->file_path = $this->handleUpload($request->file('file_foto'));
                $galeri->save();
            }
        } else {
            $data['video_url'] = $request->video_url;
            $galeri->update($data);
        }

        return redirect()->route('admin.galeri.index')->with('success', 'Media Galeri berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $galeri = Galeri::findOrFail($id);

        if ($galeri->tipe == 'foto' && $galeri->batch_id) {
            $items = Galeri::where('batch_id', $galeri->batch_id)->get();
            foreach($items as $item) {
                // BYPASS Hapus file menggunakan PHP Murni
                $path = storage_path('app/public/galeri/' . $item->file_path);
                if ($item->file_path && file_exists($path)) {
                    @unlink($path);
                }
                $item->delete();
            }
        } else {
            $path = storage_path('app/public/galeri/' . $galeri->file_path);
            if ($galeri->file_path && file_exists($path)) {
                @unlink($path);
            }
            $galeri->delete();
        }

        return redirect()->route('admin.galeri.index')->with('success', 'Media berhasil dihapus.');
    }

    private function handleUpload($file)
    {
        $folder = storage_path('app/public/galeri');
        if (!is_dir($folder)) {
            mkdir($folder, 0755, true);
        }

        $ext = $file->getClientOriginalExtension() ?: 'jpg';
        $filename = 'galeri_' . time() . '_' . Str::random(5) . '.' . $ext;

        if (move_uploaded_file($file->getPathname(), $folder . '/' . $filename)) {
            chmod($folder . '/' . $filename, 0644); // Izin baca publik
            return $filename;
        }

        return null;
    }
}
```

## File: `app/Http/Controllers/Admin/WaliKelasController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\JenjangKelas;
use App\Models\TahunAjaran;
use App\Models\Semester;
use App\Models\Guru;
use App\Models\Kelas;
use App\Models\WaliKelas;

class WaliKelasController extends Controller
{
    public function jenjang(Request $request, $jenjang_id)
    {
        $jenjang = JenjangKelas::with(['tingkats.kelases.details.gender'])->findOrFail($jenjang_id);
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $semesterAktif = Semester::where('is_active', true)->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $semesters = Semester::orderBy('nama', 'asc')->get();

        $currentTaId = $request->query('tahun_ajaran_id', $taAktif ? $taAktif->id : null);
        $currentSemId = $request->query('semester_id', $semesterAktif ? $semesterAktif->id : null);

        $gurus = Guru::orderBy('nama_lengkap', 'asc')->get();

        $waliKelasList = WaliKelas::with('guru')
            ->where('tahun_ajaran_id', $currentTaId)
            ->where('semester_id', $currentSemId)
            ->get();

        $assignedGuruIds = $waliKelasList->pluck('guru_id')->toArray();

        return view('admin.wali-kelas.index', compact(
            'jenjang', 'taAktif', 'semesterAktif', 
            'tahunAjarans', 'semesters', 
            'currentTaId', 'currentSemId', 'gurus', 'waliKelasList', 'assignedGuruIds'
        ));
    }

    public function storeOrUpdate(Request $request)
    {
        $request->validate([
            'kelas_id'        => 'required|exists:kelas,id',
            'kelas_detail_id' => 'nullable|exists:kelas_details,id',
            'guru_id'         => 'required|exists:gurus,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id'     => 'required|exists:semesters,id',
        ]);

        WaliKelas::updateOrCreate(
            [
                'tahun_ajaran_id' => $request->tahun_ajaran_id,
                'semester_id'     => $request->semester_id,
                'kelas_id'        => $request->kelas_id,
                'kelas_detail_id' => $request->kelas_detail_id,
            ],
            [
                'guru_id' => $request->guru_id
            ]
        );

        return back()->with('success', 'Wali Kelas berhasil ditugaskan untuk Ruang Kelas ini.');
    }
}
```

## File: `app/Http/Controllers/Admin/BeritaController.php`
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Berita;
use App\Models\KategoriBerita;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;

class BeritaController extends Controller
{
    public function index()
    {
        $beritas = Berita::with(['kategori', 'author'])->orderBy('created_at', 'desc')->get();
        return view('admin.berita.index', compact('beritas'));
    }

    public function create()
    {
        $kategoris = KategoriBerita::orderBy('nama_kategori', 'asc')->get();
        return view('admin.berita.create', compact('kategoris'));
    }

    public function store(Request $request)
    {
        // BYPASS: Fileinfo validation
        $request->validate([
            'judul' => 'required|string|max:255',
            'kategori_berita_id' => 'required|exists:kategori_beritas,id',
            'konten' => 'required',
            'status' => 'required|in:draft,publish',
            'thumbnail' => 'required|file|max:3048', 
        ]);

        $data = $request->all();
        $data['slug'] = Str::slug($request->judul) . '-' . Str::random(5);
        $data['user_id'] = Auth::id();
        
        // BYPASS: mbstring (Mengganti Str::limit dengan fungsi murni PHP)
        $clean_konten = strip_tags($request->konten);
        $data['excerpt'] = strlen($clean_konten) > 150 ? substr($clean_konten, 0, 150) . '...' : $clean_konten;

        if ($request->hasFile('thumbnail')) {
            $data['thumbnail'] = $this->handleThumbnail($request->file('thumbnail'));
        }

        Berita::create($data);
        return redirect()->route('admin.berita.index')->with('success', 'Berita berhasil dipublikasikan!');
    }

    public function edit($id)
    {
        $berita = Berita::findOrFail($id);
        $kategoris = KategoriBerita::orderBy('nama_kategori', 'asc')->get();
        return view('admin.berita.edit', compact('berita', 'kategoris'));
    }

    public function update(Request $request, $id)
    {
        $berita = Berita::findOrFail($id);
        
        // BYPASS: Fileinfo validation
        $request->validate([
            'judul' => 'required|string|max:255',
            'kategori_berita_id' => 'required|exists:kategori_beritas,id',
            'konten' => 'required',
            'status' => 'required|in:draft,publish',
            'thumbnail' => 'nullable|file|max:3048',
        ]);

        $data = $request->all();
        $data['slug'] = Str::slug($request->judul) . '-' . Str::random(5);
        
        // BYPASS: mbstring 
        $clean_konten = strip_tags($request->konten);
        $data['excerpt'] = strlen($clean_konten) > 150 ? substr($clean_konten, 0, 150) . '...' : $clean_konten;

        if ($request->hasFile('thumbnail')) {
            // BYPASS: Storage::delete
            $oldPath = storage_path('app/public/berita/' . $berita->thumbnail);
            if ($berita->thumbnail && file_exists($oldPath)) {
                @unlink($oldPath);
            }
            $data['thumbnail'] = $this->handleThumbnail($request->file('thumbnail'));
        }

        $berita->update($data);
        return redirect()->route('admin.berita.index')->with('success', 'Berita berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $berita = Berita::findOrFail($id);
        
        // BYPASS: Storage::delete
        $path = storage_path('app/public/berita/' . $berita->thumbnail);
        if ($berita->thumbnail && file_exists($path)) {
            @unlink($path);
        }
        
        $berita->delete();
        return redirect()->route('admin.berita.index')->with('success', 'Berita berhasil dihapus.');
    }

    public function uploadImage(Request $request)
    {
        // BYPASS: Upload gambar dalam konten (Editor)
        if ($request->hasFile('file')) {
            $file = $request->file('file');
            $ext = $file->getClientOriginalExtension() ?: 'jpg';
            $filename = 'konten_' . time() . '_' . Str::random(5) . '.' . $ext;
            $destinationPath = storage_path('app/public/berita/konten');
            
            if (!is_dir($destinationPath)) mkdir($destinationPath, 0755, true);
            
            if (move_uploaded_file($file->getPathname(), $destinationPath . '/' . $filename)) {
                chmod($destinationPath . '/' . $filename, 0644);
                return response()->json(['url' => asset('storage/berita/konten/' . $filename)]);
            }
        }
        return response()->json(['error' => 'No file uploaded'], 400);
    }

    public function deleteImage(Request $request)
    {
        // BYPASS: Hapus gambar dalam konten
        $src = $request->src;
        $filename = basename($src);
        $path = storage_path('app/public/berita/konten/' . $filename);
        
        if (file_exists($path)) {
            @unlink($path);
        }
        return response()->json(['success' => true]);
    }

    private function handleThumbnail($file)
    {
        // BYPASS: Upload thumbnail manual PHP
        $destinationPath = storage_path('app/public/berita');
        if (!is_dir($destinationPath)) mkdir($destinationPath, 0755, true);

        $ext = $file->getClientOriginalExtension() ?: 'jpg';
        $filename = 'thumb_' . time() . '_' . Str::random(5) . '.' . $ext;
        
        if (move_uploaded_file($file->getPathname(), $destinationPath . '/' . $filename)) {
            chmod($destinationPath . '/' . $filename, 0644); // Beri izin baca
            return $filename;
        }
        
        return null;
    }
}
```

## File: `app/Http/Controllers/Admin/JadwalMapelController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\JadwalMapel;
use App\Models\JenjangKelas;
use App\Models\Kelas;
use App\Models\GuruMapel;
use App\Models\TahunAjaran;
use App\Models\Semester;
use App\Models\AlokasiWaktu; 

class JadwalMapelController extends Controller
{
    public function jenjang(Request $request, $jenjang_id)
    {
        $jenjang = JenjangKelas::with(['tingkats.kelases'])->findOrFail($jenjang_id);
        $kelas_id = $request->query('kelas_id'); 
        
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();

        $semesters = Semester::where('jenis_periode', $jenjang->sistem_akademik)
                             ->orderBy('id', 'asc')
                             ->get();
                             
        $semesterAktif = $semesters->where('is_active', true)->first() ?? $semesters->first();

        $currentTaId = $request->query('tahun_ajaran_id', $taAktif ? $taAktif->id : null);
        $currentSemId = $request->query('semester_id', $semesterAktif ? $semesterAktif->id : null);

        $guruMapels = GuruMapel::with(['guru', 'mapel'])
            ->whereHas('mapel', function($q) use ($jenjang_id) {
                $q->where('jenjang_kelas_id', $jenjang_id);
            })->get();

        $gurusUnik = $guruMapels->pluck('guru')->filter()->unique('id')->sortBy('nama_lengkap')->values();

        $alokasiWaktus = AlokasiWaktu::with(['hari', 'durasi'])
            ->where('jenjang_id', $jenjang_id)
            ->get()
            ->sortBy(function ($item) {
                $hariId = $item->hari_id ?? 999;
                $jamMulai = $item->durasi->jam_mulai ?? '00:00:00';
                return sprintf('%04d-%s', $hariId, $jamMulai);
            });
            
        $hariArr = $alokasiWaktus->map(function($item) {
            return $item->hari->nama_hari ?? null;
        })->filter()->unique()->values();
        
        if ($hariArr->isEmpty()) {
            $hariArr = collect(['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']);
        }

        $jadwals = collect();
        $kelasTerpilih = null;

        if ($kelas_id && $currentTaId && $currentSemId) {
            $kelasTerpilih = Kelas::with('details.gender')->findOrFail($kelas_id);
            $jadwals = JadwalMapel::with(['guruMapel.guru', 'guruMapel.mapel'])
                ->where('kelas_id', $kelas_id)
                ->where('tahun_ajaran_id', $currentTaId)
                ->where('semester_id', $currentSemId)
                ->orderBy('jam_mulai', 'asc')
                ->get()
                ->groupBy('hari'); 
        }

        return view('admin.jadwal-mapel.index', compact(
            'jenjang', 'guruMapels', 'gurusUnik', 'jadwals', 'kelas_id', 'kelasTerpilih', 
            'taAktif', 'semesterAktif', 'tahunAjarans', 'semesters', 
            'currentTaId', 'currentSemId', 'hariArr'
        ));
    }

    public function store(Request $request)
    {
        $request->validate([
            'kelas_id' => 'required|exists:kelas,id',
            'guru_mapel_id' => 'nullable|exists:guru_mapels,id', 
            'hari' => 'required',
            'jam_mulai' => 'required',
            'jam_selesai' => 'required|after:jam_mulai',
            'target_gender' => 'required|string', 
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id' => 'required|exists:semesters,id',
            'keterangan' => 'nullable|string' 
        ]);

        $targetTaId = $request->tahun_ajaran_id;
        $targetSemId = $request->semester_id;
        $targetGender = $request->target_gender;
        $guruMapel = $request->guru_mapel_id ? GuruMapel::with(['guru', 'mapel'])->find($request->guru_mapel_id) : null;
        
        $conflictSchedule = JadwalMapel::where('tahun_ajaran_id', $targetTaId)
            ->where('semester_id', $targetSemId)
            ->where('hari', $request->hari)
            ->where(function($query) use ($request) {
                $query->where('jam_mulai', '<', $request->jam_selesai)
                      ->where('jam_selesai', '>', $request->jam_mulai);
            })
            ->where(function($query) use ($request, $guruMapel, $targetGender) {
                $query->where(function($q) use ($request, $targetGender) {
                    $q->where('kelas_id', $request->kelas_id)
                      ->where(function($subQ) use ($targetGender) {
                          if ($targetGender !== 'Campur') {
                              $subQ->whereIn('target_gender', ['Campur', $targetGender]);
                          }
                      });
                });

                if ($guruMapel) {
                    $query->orWhereHas('guruMapel', function($q) use ($guruMapel) {
                        $q->where('guru_id', $guruMapel->guru_id);
                    });
                }
            })
            ->first();

        if ($conflictSchedule) {
            $jamB = substr($conflictSchedule->jam_mulai, 0, 5) . ' - ' . substr($conflictSchedule->jam_selesai, 0, 5);
            $jenisBentrok = "";

            if ($conflictSchedule->kelas_id == $request->kelas_id) {
                $jenisBentrok = "Siswa/Ruangan ini sudah memiliki jadwal pada jam {$jamB}.";
            } else {
                $namaGuru = $guruMapel->guru->nama_lengkap ?? 'Guru';
                $namaKelasBentrok = $conflictSchedule->kelas->nama_kelas ?? 'kelas lain';
                $jenisBentrok = "Guru <b>{$namaGuru}</b> tidak dapat dipilih karena beliau sudah menjadwalkan mengajar di <b>Kelas {$namaKelasBentrok}</b> pada jam {$jamB}.";
            }

            return back()->withErrors(["Terjadi BENTROK JADWAL! {$jenisBentrok}"])->withInput();
        }

        JadwalMapel::create([
            'tahun_ajaran_id' => $targetTaId,
            'semester_id' => $targetSemId,
            'kelas_id' => $request->kelas_id,
            'guru_mapel_id' => $request->guru_mapel_id ? $request->guru_mapel_id : null,
            'hari' => $request->hari,
            'jam_mulai' => $request->jam_mulai,
            'jam_selesai' => $request->jam_selesai,
            'target_gender' => $targetGender,
            'keterangan' => $request->keterangan, 
        ]);

        return back()->with('success', 'Jadwal Mata Pelajaran berhasil ditambahkan tanpa bentrok.');
    }

    public function copy(Request $request)
    {
        $request->validate([
            'target_kelas_id' => 'required|exists:kelas,id',
            'target_tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'target_semester_id' => 'required|exists:semesters,id',
            'target_gender' => 'required|string',
            'source_tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'source_semester_id' => 'required|exists:semesters,id',
        ]);

        $sourceJadwals = JadwalMapel::where('kelas_id', $request->target_kelas_id)
            ->where('tahun_ajaran_id', $request->source_tahun_ajaran_id)
            ->where('semester_id', $request->source_semester_id)
            ->where(function($query) use ($request) {
                if ($request->target_gender !== 'Campur') {
                    $query->whereIn('target_gender', ['Campur', $request->target_gender]);
                }
            })
            ->get();

        if ($sourceJadwals->isEmpty()) {
            return back()->with('info', 'Tidak ada data jadwal pada periode sumber yang dipilih untuk disalin.');
        }

        $berhasilDicopy = 0;
        $gagalBentrok = 0;

        foreach ($sourceJadwals as $jadwal) {
            
            $queryConflict = JadwalMapel::where('tahun_ajaran_id', $request->target_tahun_ajaran_id)
                ->where('semester_id', $request->target_semester_id)
                ->where('hari', $jadwal->hari)
                ->where(function($q) use ($jadwal) {
                    $q->where('jam_mulai', '<', $jadwal->jam_selesai)
                      ->where('jam_selesai', '>', $jadwal->jam_mulai);
                });

            $queryConflict->where(function($q) use ($request, $jadwal) {
                $q->where(function($subQ) use ($request, $jadwal) {
                    $subQ->where('kelas_id', $request->target_kelas_id)
                         ->where(function($subQ2) use ($jadwal, $request) {
                              if ($request->target_gender !== 'Campur' and $jadwal->target_gender !== 'Campur') {
                                  $subQ2->whereIn('target_gender', ['Campur', $request->target_gender]);
                              }
                         });
                });

                if ($jadwal->guru_mapel_id) {
                    $guruId = GuruMapel::find($jadwal->guru_mapel_id)->guru_id ?? null;
                    if ($guruId) {
                        $q->orWhereHas('guruMapel', function($subQ) use ($guruId) {
                            $subQ->where('guru_id', $guruId);
                        });
                    }
                }
            });

            $check = $queryConflict->first();

            if (!$check) {
                JadwalMapel::create([
                    'tahun_ajaran_id' => $request->target_tahun_ajaran_id,
                    'semester_id' => $request->target_semester_id,
                    'kelas_id' => $request->target_kelas_id,
                    'guru_mapel_id' => $jadwal->guru_mapel_id ? $jadwal->guru_mapel_id : null,
                    'hari' => $jadwal->hari,
                    'jam_mulai' => $jadwal->jam_mulai,
                    'jam_selesai' => $jadwal->jam_selesai,
                    'target_gender' => $jadwal->target_gender,
                    'keterangan' => $jadwal->keterangan, 
                ]);
                $berhasilDicopy++;
            } else {
                $gagalBentrok++;
            }
        }

        $pesan = "Proses Salin Selesai. {$berhasilDicopy} jadwal berhasil disalin.";
        if ($gagalBentrok > 0) {
            $pesan .= " Namun, ada {$gagalBentrok} jadwal yang otomatis ditolak karena mengalami bentrok waktu/guru di periode aktif saat ini.";
        }

        return back()->with('success', $pesan);
    }

    public function destroy($id)
    {
        JadwalMapel::findOrFail($id)->delete();
        return back()->with('success', 'Jadwal berhasil dihapus dari sistem.');
    }

    // FUNGSI REKAP JAM MAPEL GURU (Telah Diperbarui Logikanya)
    public function rekap(Request $request)
    {
        // 1. Ambil Parameter Default (Tahun Ajaran & Jenjang)
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $currentTaId = $request->query('tahun_ajaran_id', $taAktif ? $taAktif->id : null);

        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        $currentJenjangId = $request->query('jenjang_id', $jenjangs->first()->id ?? null);
        $currentJenjang = JenjangKelas::find($currentJenjangId);

        // 2. Tentukan Semester berdasarkan Jenjang
        $semesters = collect();
        $currentSemId = null;

        if ($currentJenjang) {
            $semesters = Semester::where('jenis_periode', $currentJenjang->sistem_akademik)->orderBy('id', 'asc')->get();
            $semesterAktif = $semesters->where('is_active', true)->first() ?? $semesters->first();
            $currentSemId = $request->query('semester_id', $semesterAktif ? $semesterAktif->id : null);
        }

        // 3. Kalkulasi Data Rekap (Logika Baru tanpa withCount)
        $rekapGurus = [];

        if ($currentJenjangId && $currentTaId && $currentSemId) {
            
            // Ambil SEMUA jadwal mapel (KBM) pada jenjang, tahun ajaran, dan semester terkait
            $semuaJadwal = JadwalMapel::with(['guruMapel.guru', 'guruMapel.mapel'])
                ->where('tahun_ajaran_id', $currentTaId)
                ->where('semester_id', $currentSemId)
                ->whereNotNull('guru_mapel_id') // Hanya ambil yang ada gurunya (KBM)
                ->whereHas('guruMapel.mapel', function($q) use ($currentJenjangId) {
                    $q->where('jenjang_kelas_id', $currentJenjangId); // Pastikan sesuai jenjang
                })
                ->get();

            // Kelompokkan jadwal-jadwal tersebut berdasarkan ID Guru
            $groupedByGuru = $semuaJadwal->groupBy(function($item) {
                return $item->guruMapel->guru_id ?? 'Unknown';
            });

            foreach ($groupedByGuru as $guruId => $jadwalsGuru) {
                if ($guruId === 'Unknown') continue; // Lewati jika tidak ada guru valid
                
                $guru = $jadwalsGuru->first()->guruMapel->guru;
                $totalJamSeluruh = $jadwalsGuru->count(); // Total seluruh blok jam guru tersebut
                
                // Kelompokkan lagi berdasarkan mata pelajaran (jika guru tsb mengajar > 1 mapel)
                $rincianMapel = $jadwalsGuru->groupBy(function($item) {
                    return $item->guruMapel->mapel->nama_mapel ?? 'Mapel Tidak Diketahui';
                })->map(function($jadwalsPerMapel, $namaMapel) {
                    return [
                        'nama_mapel' => $namaMapel,
                        'total_jam' => $jadwalsPerMapel->count()
                    ];
                });

                if ($totalJamSeluruh > 0) { 
                    $rekapGurus[] = [
                        'guru' => $guru,
                        'total_jam' => $totalJamSeluruh,
                        'detail_mapels' => $rincianMapel // Array berisikan nama mapel & total jamnya
                    ];
                }
            }

            // Urutkan Array dari total jam mengajar terbanyak ke terdikit
            usort($rekapGurus, function($a, $b) {
                return $b['total_jam'] <=> $a['total_jam'];
            });
        }

        return view('admin.jadwal-mapel.rekap', compact(
            'jenjangs', 'currentJenjang', 'tahunAjarans', 'currentTaId', 
            'semesters', 'currentSemId', 'rekapGurus'
        ));
    }
}
```

## File: `app/Http/Controllers/Admin/SemesterController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Semester;
use Illuminate\Http\Request;

class SemesterController extends Controller
{
    public function index()
    {
        $semesters = Semester::orderBy('id', 'asc')->get();
        return view('admin.master-data.semester.index', compact('semesters'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama' => 'required|string|max:100|unique:semesters,nama',
            'jenis_periode' => 'required|in:semester,caturwulan' // Validasi Baru
        ], [
            'nama.unique' => 'Nama semester/periode ini sudah ada di dalam database.'
        ]);

        Semester::create([
            'nama' => $request->nama,
            'jenis_periode' => $request->jenis_periode, // Data Baru
            'is_active' => false 
        ]);

        return redirect()->route('admin.semester')->with('success', 'Periode akademik baru berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $semester = Semester::findOrFail($id);

        $request->validate([
            'nama' => 'required|string|max:100|unique:semesters,nama,' . $id,
            'jenis_periode' => 'required|in:semester,caturwulan' // Validasi Baru
        ]);

        $semester->update([
            'nama' => $request->nama,
            'jenis_periode' => $request->jenis_periode // Data Baru
        ]);

        return redirect()->route('admin.semester')->with('success', 'Data periode berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $semester = Semester::findOrFail($id);

        if ($semester->is_active) {
            return redirect()->route('admin.semester')->withErrors(['Gagal menghapus! Periode yang sedang Aktif tidak boleh dihapus. Nonaktifkan terlebih dahulu.']);
        }

        $semester->delete();

        return redirect()->route('admin.semester')->with('success', 'Data periode berhasil dihapus.');
    }

    public function toggleAktif($id)
    {
        $semester = Semester::findOrFail($id);
    
        $semester->update(['is_active' => !$semester->is_active]);

        $pesanStatus = $semester->is_active ? 'diaktifkan' : 'dinonaktifkan';

        return redirect()->route('admin.semester')->with('success', 'Periode ' . $semester->nama . ' berhasil ' . $pesanStatus . '.');
    }
}
```

## File: `app/Http/Controllers/Admin/GuruMapelController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\GuruMapel;
use App\Models\Guru;
use App\Models\Mapel;
use App\Models\JenjangKelas;
use App\Models\TahunAjaran; 
use Illuminate\Http\Request;

class GuruMapelController extends Controller
{
    private function getTahunAktif(Request $request)
    {
        $tahunAjarans = TahunAjaran::orderBy('id', 'desc')->get();
        $tahunAktif = null;

        if ($request->has('ta_id') && $request->ta_id != '') {
            $tahunAktif = $tahunAjarans->firstWhere('id', $request->ta_id);
        }

        if (!$tahunAktif) {
            $tahunAktif = $tahunAjarans->firstWhere('status_akademik', 'aktif') ?? $tahunAjarans->first();
        }

        return [$tahunAjarans, $tahunAktif];
    }

    public function index(Request $request)
    {
        list($tahunAjarans, $tahunAktif) = $this->getTahunAktif($request);
        
        $gurus = Guru::with(['jenjangKelas'])->orderBy('nama_lengkap', 'asc')->get();
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        
        $guruMapelsGrouped = collect();
        if ($tahunAktif) {
            $guruMapelsGrouped = GuruMapel::with('mapel.jenjang')
                ->where('tahun_ajaran_id', $tahunAktif->id)
                ->get()
                ->groupBy('guru_id');
        }

        $totalGuru = $gurus->count();
        $guruTerploting = $guruMapelsGrouped->count();
        $guruBelumPloting = $totalGuru - $guruTerploting;
                        
        return view('admin.guru-mapel.index', compact('gurus', 'guruMapelsGrouped', 'jenjangs', 'totalGuru', 'guruTerploting', 'guruBelumPloting', 'tahunAjarans', 'tahunAktif'));
    }

    public function byJenjang(Request $request, $id)
    {
        list($tahunAjarans, $tahunAktif) = $this->getTahunAktif($request);
        $jenjang_aktif = JenjangKelas::findOrFail($id);
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        
        $gurus = Guru::with(['jenjangKelas'])
                     ->whereHas('jenjangKelas', function($q) use ($id) {
                         $q->where('jenjang_kelas_id', $id);
                     })->orderBy('nama_lengkap', 'asc')->get();

        $guruMapelsGrouped = collect();
        if ($tahunAktif) {
            $guruMapelsGrouped = GuruMapel::with(['mapel.jenjang'])
                ->where('tahun_ajaran_id', $tahunAktif->id)
                ->whereHas('mapel', function($q) use ($id) {
                    $q->where('jenjang_kelas_id', $id);
                })->get()->groupBy('guru_id');
        }

        $totalGuruJenjang = $gurus->count();
        $guruTerplotingJenjang = $guruMapelsGrouped->count();
        $guruBelumPlotingJenjang = $totalGuruJenjang - $guruTerplotingJenjang;
        
        if($guruBelumPlotingJenjang < 0) $guruBelumPlotingJenjang = 0;

        return view('admin.guru-mapel.jenjang', compact('gurus', 'guruMapelsGrouped', 'jenjangs', 'jenjang_aktif', 'totalGuruJenjang', 'guruTerplotingJenjang', 'guruBelumPlotingJenjang', 'tahunAjarans', 'tahunAktif'));
    }

    public function getGuruMapelData(Request $request, $guru_id)
    {
        $tahun_ajaran_id = $request->query('ta_id');
        $jenjangIdParam = $request->query('jenjang_id');
        
        $guru = Guru::with(['jenjangKelas'])->findOrFail($guru_id);
        
        $assignedMapelIds = GuruMapel::where('guru_id', $guru_id)
                                     ->where('tahun_ajaran_id', $tahun_ajaran_id)
                                     ->pluck('mapel_id')->toArray();
        
        $jenjangsToProcess = $guru->jenjangKelas;
        if ($jenjangIdParam) {
            $jenjangsToProcess = $jenjangsToProcess->where('id', $jenjangIdParam);
        }
        
        $jenjangIds = $jenjangsToProcess->pluck('id');
        
        $mapelsPerJenjang = Mapel::with(['jenjang', 'kategori'])->whereIn('jenjang_kelas_id', $jenjangIds)->get()->groupBy('jenjang_kelas_id');
        
        $allMapels = Mapel::with('kategori')->whereIn('jenjang_kelas_id', $jenjangIds)->get();
        $kategoris = $allMapels->pluck('kategori')->filter()->unique('id')->values();
        
        $html = '';
        if($jenjangsToProcess->isEmpty()) {
            if ($jenjangIdParam) {
                $html = '<div class="alert alert-warning border-0 shadow-sm"><i class="bi bi-exclamation-triangle me-2"></i>Guru ini belum ditugaskan ke Jenjang ini.</div>';
            } else {
                $html = '<div class="alert alert-warning border-0 shadow-sm"><i class="bi bi-exclamation-triangle me-2"></i>Guru ini belum ditugaskan ke Jenjang manapun. Harap daftarkan ke Jenjang terlebih dahulu di menu <b>Data Guru</b>.</div>';
            }
        } else {
            
            if ($kategoris->isNotEmpty() || $allMapels->whereNull('kategori_mapel_id')->count() > 0) {
                $html .= '<div class="mb-4 bg-white p-3 rounded border border-primary border-opacity-25 shadow-sm">';
                $html .= '<label class="form-label fw-bold text-dark mb-2" style="font-size: 13px;"><i class="bi bi-funnel text-primary me-1"></i> FILTER KATEGORI MAPEL:</label>';
                $html .= '<select class="form-select border-primary fw-medium text-dark" id="kategoriFilter" onchange="filterMapelByCategory(this.value)">';
                $html .= '<option value="all">-- Tampilkan Semua Kategori Mapel --</option>';
                foreach ($kategoris as $kat) {
                    $html .= '<option value="'.$kat->id.'">'.$kat->nama_kategori.'</option>';
                }
                if ($allMapels->whereNull('kategori_mapel_id')->count() > 0) {
                    $html .= '<option value="none">Lainnya (Tanpa Kategori)</option>';
                }
                $html .= '</select>';
                $html .= '</div>';
            }

            foreach($jenjangsToProcess as $jk) {
                $html .= '<div class="mb-4 jenjang-section">';
                $html .= '<h6 class="fw-bold border-bottom pb-2 text-primary"><i class="bi bi-bookmark-star-fill me-2"></i>Mapel Jenjang: '.$jk->nama_jenjang.'</h6>';
                
                $mapels = $mapelsPerJenjang->get($jk->id, collect());
                if($mapels->isEmpty()) {
                    $html .= '<p class="text-muted small fst-italic">Belum ada data mata pelajaran yang dibuat untuk jenjang ini.</p>';
                } else {
                    $html .= '<div class="table-responsive mt-3">';
                    $html .= '<table class="table table-bordered table-hover align-middle mb-0 mapel-table" style="font-size: 13px;">';
                    $html .= '<thead class="table-light"><tr><th class="text-center" style="width: 10%">NO</th><th style="width: 70%">MATA PELAJARAN</th><th class="text-center" style="width: 20%">AKSI</th></tr></thead><tbody>';
                    
                    $no = 1;
                    foreach($mapels as $m) {
                        $checked = in_array($m->id, $assignedMapelIds) ? 'checked' : '';
                        $kategoriId = $m->kategori_mapel_id ?? 'none';
                        $namaKategori = $m->kategori->nama_kategori ?? 'Tanpa Kategori';

                        $html .= '<tr class="mapel-row" data-kategori="'.$kategoriId.'">';
                        $html .= '<td class="text-center text-muted col-no">'.$no++.'</td>';
                        $html .= '<td>';
                        $html .= '<label class="d-block w-100 m-0 fw-bold text-dark" style="cursor:pointer;" for="mapel_'.$m->id.'">'.$m->nama_mapel.'</label>';
                        $html .= '<span class="badge bg-secondary mt-1" style="font-size: 10px;">'.$namaKategori.'</span>';
                        $html .= '</td>';
                        $html .= '<td class="text-center"><div class="form-check d-flex justify-content-center m-0 p-0">';
                        $html .= '<input class="form-check-input border-secondary m-0" style="width: 1.3em; height: 1.3em; cursor:pointer;" type="checkbox" name="mapel_ids[]" value="'.$m->id.'" id="mapel_'.$m->id.'" '.$checked.'>';
                        $html .= '</div></td></tr>';
                    }
                    $html .= '</tbody></table></div>';
                }
                $html .= '</div>';
            }
        }
        
        return response()->json([
            'guru_nama' => $guru->nama_lengkap,
            'html' => $html,
            'has_jenjang' => !$jenjangIds->isEmpty()
        ]);
    }

    public function syncMapel(Request $request)
    {
        $request->validate([
            'guru_id' => 'required|exists:gurus,id',
            'jenjang_id' => 'nullable|exists:jenjang_kelas,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id', 
            'mapel_ids' => 'nullable|array',
            'mapel_ids.*' => 'exists:mapels,id'
        ]);
        
        $guru_id = $request->guru_id;
        $jenjang_id = $request->jenjang_id;
        $tahun_ajaran_id = $request->tahun_ajaran_id;
        $mapel_ids = $request->mapel_ids ?? [];
        
        if ($jenjang_id) {
            $mapelIdsDiJenjangIni = Mapel::where('jenjang_kelas_id', $jenjang_id)->pluck('id')->toArray();
            GuruMapel::where('guru_id', $guru_id)
                     ->where('tahun_ajaran_id', $tahun_ajaran_id)
                     ->whereIn('mapel_id', $mapelIdsDiJenjangIni)
                     ->delete();
        } else {
            $guru = Guru::with('jenjangKelas')->find($guru_id);
            $jenjangIds = $guru->jenjangKelas->pluck('id')->toArray();
            $mapelIdsDiSemuaJenjangGuru = Mapel::whereIn('jenjang_kelas_id', $jenjangIds)->pluck('id')->toArray();
            
            GuruMapel::where('guru_id', $guru_id)
                     ->where('tahun_ajaran_id', $tahun_ajaran_id)
                     ->whereIn('mapel_id', $mapelIdsDiSemuaJenjangGuru)
                     ->delete();
        }
        
        $dataToInsert = [];
        $now = now();
        foreach($mapel_ids as $m_id) {
            $dataToInsert[] = [
                'guru_id' => $guru_id,
                'mapel_id' => $m_id,
                'tahun_ajaran_id' => $tahun_ajaran_id,
                'created_at' => $now,
                'updated_at' => $now
            ];
        }

        if(count($dataToInsert) > 0) {
            GuruMapel::insert($dataToInsert);
        }
        
        return back()->with('success', 'Data Mata Pelajaran berhasil diperbarui untuk Tahun Ajaran tersebut.');
    }

    public function copyPenugasan(Request $request)
    {
        $request->validate([
            'dari_tahun_id' => 'required|exists:tahun_ajarans,id',
            'ke_tahun_id' => 'required|exists:tahun_ajarans,id|different:dari_tahun_id',
        ], [
            'ke_tahun_id.different' => 'Tahun asal dan tujuan tidak boleh sama!'
        ]);

        $dariTahun = $request->dari_tahun_id;
        $keTahun = $request->ke_tahun_id;

        GuruMapel::where('tahun_ajaran_id', $keTahun)->delete();

        $dataLama = GuruMapel::where('tahun_ajaran_id', $dariTahun)->get();
        if ($dataLama->isEmpty()) {
            return back()->with('error', 'Tidak ada data penugasan di Tahun Ajaran Sumber yang bisa disalin.');
        }

        $dataBaru = [];
        $now = now();
        foreach ($dataLama as $lama) {
            $dataBaru[] = [
                'guru_id' => $lama->guru_id,
                'mapel_id' => $lama->mapel_id,
                'tahun_ajaran_id' => $keTahun,
                'created_at' => $now,
                'updated_at' => $now
            ];
        }

        GuruMapel::insert($dataBaru);

        return redirect()->route('admin.guru-mapel', ['ta_id' => $keTahun])
                         ->with('success', 'Berhasil menyalin '.count($dataBaru).' penugasan guru dari tahun lalu. Silakan lakukan penyesuaian jika ada perubahan mapel.');
    }
}
```

## File: `app/Http/Controllers/Admin/KategoriBeritaController.php`
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\KategoriBerita;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class KategoriBeritaController extends Controller
{
    public function index()
    {
        $kategoris = KategoriBerita::withCount('beritas')->orderBy('nama_kategori', 'asc')->get();
        return view('admin.berita.kategori', compact('kategoris'));
    }

    public function store(Request $request)
    {
        $request->validate(['nama_kategori' => 'required|string|max:255|unique:kategori_beritas']);
        KategoriBerita::create([
            'nama_kategori' => $request->nama_kategori,
            'slug' => Str::slug($request->nama_kategori)
        ]);
        return back()->with('success', 'Kategori Berita berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate(['nama_kategori' => 'required|string|max:255|unique:kategori_beritas,nama_kategori,'.$id]);
        $kategori = KategoriBerita::findOrFail($id);
        $kategori->update([
            'nama_kategori' => $request->nama_kategori,
            'slug' => Str::slug($request->nama_kategori)
        ]);
        return back()->with('success', 'Kategori Berita berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $kategori = KategoriBerita::findOrFail($id);
        if($kategori->beritas()->count() > 0) {
            return back()->withErrors(['Gagal menghapus! Kategori ini sedang digunakan pada berita/artikel.']);
        }
        $kategori->delete();
        return back()->with('success', 'Kategori Berita berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/HariController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Hari;
use Illuminate\Http\Request;

class HariController extends Controller
{
    public function index() {
        $haris = Hari::all();
        return view('admin.alokasi-waktu.hari', compact('haris'));
    }
    public function store(Request $request) {
        $request->validate(['nama_hari' => 'required|string|max:50']);
        Hari::create($request->all());
        return back()->with('success', 'Hari berhasil ditambahkan.');
    }
    public function update(Request $request, $id) {
        $request->validate(['nama_hari' => 'required|string|max:50']);
        Hari::findOrFail($id)->update($request->all());
        return back()->with('success', 'Hari berhasil diperbarui.');
    }
    public function destroy($id) {
        Hari::findOrFail($id)->delete();
        return back()->with('success', 'Hari berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/KelasDetailController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\KelasDetail;
use App\Models\Kelas;
use App\Models\Gender;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class KelasDetailController extends Controller
{
    public function index($kelas_id)
    {
        $kelas = Kelas::with('tingkat.jenjang')->findOrFail($kelas_id);
        $details = KelasDetail::with('gender')->where('kelas_id', $kelas_id)->get();
        $genders = Gender::all();
        return view('admin.kelas-detail.index', compact('kelas', 'details', 'genders'));
    }

    public function store(Request $request, $kelas_id)
    {
        $request->validate([
            'gender_id' => [
                'required', 'exists:genders,id',
                Rule::unique('kelas_details')->where('kelas_id', $kelas_id) // Cegah double gender
            ],
            'kapasitas' => 'required|numeric|min:1',
        ]);

        KelasDetail::create([
            'kelas_id' => $kelas_id,
            'gender_id' => $request->gender_id,
            'kapasitas' => $request->kapasitas,
        ]);
        return redirect()->route('admin.kelas-detail.index', $kelas_id)->with('success', 'Kuota Gender berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $detail = KelasDetail::findOrFail($id);
        $request->validate([
            'gender_id' => [
                'required', 'exists:genders,id',
                Rule::unique('kelas_details')->where('kelas_id', $detail->kelas_id)->ignore($id)
            ],
            'kapasitas' => 'required|numeric|min:1',
        ]);

        $detail->update($request->only(['gender_id', 'kapasitas']));
        return redirect()->route('admin.kelas-detail.index', $detail->kelas_id)->with('success', 'Kuota Gender diperbarui.');
    }

    public function destroy($id)
    {
        $detail = KelasDetail::findOrFail($id);
        $kelas_id = $detail->kelas_id;
        $detail->delete();
        return redirect()->route('admin.kelas-detail.index', $kelas_id)->with('success', 'Kuota Gender dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/AbsensiController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Absensi;

class AbsensiController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'siswa_id'        => 'required|exists:siswas,id',
            'guru_mapel_id'   => 'required|exists:guru_mapels,id',
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'semester_id'     => 'required|exists:semesters,id',
            'tanggal'         => 'required|date',
            'status'          => 'required|in:Hadir,Sakit,Izin,Alpa',
            'keterangan'      => 'nullable|string'
        ]);

        $cekAbsen = Absensi::where('siswa_id', $request->siswa_id)
            ->where('guru_mapel_id', $request->guru_mapel_id)
            ->where('tanggal', $request->tanggal)
            ->first();

        if ($cekAbsen) {
            return back()->with('error', 'Siswa ini sudah memiliki data absensi untuk mata pelajaran tersebut pada tanggal yang dipilih!');
        }

        $data = $request->all();
        if ($data['status'] == 'Hadir') {
            $data['keterangan'] = null;
        }

        Absensi::create($data);

        return back()->with('success', 'Data absensi berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'tanggal'    => 'required|date',
            'status'     => 'required|in:Hadir,Sakit,Izin,Alpa',
            'keterangan' => 'nullable|string'
        ]);

        $absensi = Absensi::findOrFail($id);

        if ($absensi->tanggal != $request->tanggal) {
            $cekAbsen = Absensi::where('siswa_id', $absensi->siswa_id)
                ->where('guru_mapel_id', $absensi->guru_mapel_id)
                ->where('tanggal', $request->tanggal)
                ->first();

            if ($cekAbsen) {
                return back()->with('error', 'Tanggal absensi ini sudah terdata untuk mata pelajaran tersebut!');
            }
        }

        $absensi->tanggal = $request->tanggal;
        $absensi->status = $request->status;
        $absensi->keterangan = ($request->status == 'Hadir') ? null : $request->keterangan;
        $absensi->save();

        return back()->with('success', 'Data absensi terakhir berhasil diperbarui.');
    }

    public function destroy($id)
    {
        Absensi::findOrFail($id)->delete();
        return back()->with('success', 'Data absensi berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/EkstrakurikulerController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Ekstrakurikuler;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class EkstrakurikulerController extends Controller
{
    public function index() {
        $ekskuls = Ekstrakurikuler::orderBy('nama', 'asc')->get();
        return view('admin.ekstrakurikuler.index', compact('ekskuls'));
    }

    public function store(Request $request) {
        $request->validate([
            'nama' => 'required',
        ]);
        
        $data = $request->all();
        
        if ($request->hasFile('foto')) {
            $file = $request->file('foto');
            
            $ext = strtolower($file->getClientOriginalExtension());
            if (!in_array($ext, ['jpg', 'jpeg', 'png', 'webp'])) {
                return back()->withErrors(['foto' => 'Format foto harus berupa jpg, png, jpeg, atau webp.']);
            }
            
            $filename = 'ekskul_' . time() . '_' . Str::random(5) . '.' . $ext;
            $path = storage_path('app/public/ekskul');
            
            if (!file_exists($path)) {
                mkdir($path, 0755, true);
            }
            
            $file->move($path, $filename);
            $data['foto'] = $filename;
        }
        
        Ekstrakurikuler::create($data);
        return back()->with('success', 'Ekstrakurikuler berhasil ditambahkan.');
    }

    public function update(Request $request, $id) {
        $ekskul = Ekstrakurikuler::findOrFail($id);
        
        $request->validate([
            'nama' => 'required'
        ]);
        
        $data = $request->all();
        
        if ($request->hasFile('foto')) {
            $file = $request->file('foto');
            
            $ext = strtolower($file->getClientOriginalExtension());
            if (!in_array($ext, ['jpg', 'jpeg', 'png', 'webp'])) {
                return back()->withErrors(['foto' => 'Format foto harus berupa jpg, png, jpeg, atau webp.']);
            }
            
            $oldPath = storage_path('app/public/ekskul/' . $ekskul->foto);
            if ($ekskul->foto && file_exists($oldPath)) {
                @unlink($oldPath);
            }
            
            $filename = 'ekskul_' . time() . '_' . Str::random(5) . '.' . $ext;
            $path = storage_path('app/public/ekskul');
            
            if (!file_exists($path)) {
                mkdir($path, 0755, true);
            }
            
            $file->move($path, $filename);
            $data['foto'] = $filename;
        }
        
        $ekskul->update($data);
        return back()->with('success', 'Data Ekstrakurikuler berhasil diperbarui.');
    }

    public function destroy($id) {
        $ekskul = Ekstrakurikuler::findOrFail($id);
        
        // Hapus foto jika ada menggunakan native PHP
        $oldPath = storage_path('app/public/ekskul/' . $ekskul->foto);
        if ($ekskul->foto && file_exists($oldPath)) {
            @unlink($oldPath);
        }
        
        $ekskul->delete();
        return back()->with('success', 'Ekstrakurikuler berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/AlokasiWaktuController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\AlokasiWaktu;
use App\Models\JenjangKelas;
use App\Models\Hari;
use App\Models\KategoriWaktu;
use App\Models\DurasiWaktu;

class AlokasiWaktuController extends Controller
{
    public function redirectIndex()
    {
        $jenjangPertama = JenjangKelas::orderBy('kode', 'asc')->first();
        if (!$jenjangPertama) {
            return redirect()->back()->with('error', 'Silakan buat data Jenjang Kelas terlebih dahulu.');
        }
        return redirect()->route('admin.alokasi-waktu.jenjang', $jenjangPertama->id);
    }

    public function index($jenjang_id)
    {
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        $currentJenjang = JenjangKelas::findOrFail($jenjang_id);
        
        $haris = Hari::all();
        $kategoris = KategoriWaktu::all();
        $durasis = DurasiWaktu::orderBy('jam_mulai', 'asc')->get();

        $alokasiWaktus = AlokasiWaktu::with(['hari', 'kategori', 'durasi'])
            ->where('jenjang_id', $jenjang_id)
            ->get()
            ->sortBy(function ($item) {
                $hariId = $item->hari_id ?? 999;
                $jamMulai = $item->durasi->jam_mulai ?? '00:00:00';
                return sprintf('%04d-%s', $hariId, $jamMulai);
            });
                                     
        return view('admin.alokasi-waktu.index', compact(
            'jenjangs', 'currentJenjang', 'alokasiWaktus', 'haris', 'kategoris', 'durasis'
        ));
    }

    public function store(Request $request)
    {
        $request->validate([
            'jenjang_id'    => 'required|exists:jenjang_kelas,id',
            'hari_id'       => 'required|array|min:1',
            'hari_id.*'     => 'exists:haris,id',
            'kategori_id'   => 'required|array|min:1',
            'kategori_id.*' => 'exists:kategori_waktus,id',
            'durasi_id'     => 'required|array|min:1',
            'durasi_id.*'   => 'exists:durasi_waktus,id',
            'is_mapel'      => 'required|in:0,1',
            'status_custom' => 'required_if:is_mapel,0|nullable|string|max:100',
            'keterangan'    => 'nullable|string|max:255',
        ]);

        $count = 0;

        foreach ($request->hari_id as $hariId) {
            foreach ($request->kategori_id as $kategoriId) {
                foreach ($request->durasi_id as $durasiId) {
                    AlokasiWaktu::updateOrCreate(
                        [
                            'jenjang_id'  => $request->jenjang_id,
                            'hari_id'     => $hariId,
                            'kategori_id' => $kategoriId,
                            'durasi_id'   => $durasiId,
                        ],
                        [
                            'is_mapel'      => $request->is_mapel,
                            'status_custom' => $request->is_mapel == '0' ? $request->status_custom : null,
                            'keterangan'    => $request->keterangan,
                        ]
                    );
                    $count++;
                }
            }
        }

        return back()->with('success', "Sebanyak {$count} alokasi waktu berhasil disimpan.");
    }

    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            'hari_id'       => 'required|exists:haris,id',
            'kategori_id'   => 'required|exists:kategori_waktus,id',
            'durasi_id'     => 'required|exists:durasi_waktus,id',
            'is_mapel'      => 'required|in:0,1',
            'status_custom' => 'required_if:is_mapel,0|nullable|string|max:100',
            'keterangan'    => 'nullable|string|max:255',
        ]);

        // Null-kan status_custom jika admin merubah dari Lainya ke Mapel
        if ($validated['is_mapel'] == '1') {
            $validated['status_custom'] = null;
        }

        AlokasiWaktu::findOrFail($id)->update($validated);

        return back()->with('success', 'Data alokasi waktu berhasil diperbarui.');
    }

    public function destroy($id)
    {
        AlokasiWaktu::findOrFail($id)->delete();
        return back()->with('success', 'Alokasi waktu berhasil dihapus.');
    }

    // =========================================================================
    // FUNGSI INI WAJIB ADA AGAR JAVASCRIPT BOX WAKTU BISA MUNCUL
    // =========================================================================
    public function getByHari($hari_nama, $jenjang_id)
    {
        $hari = Hari::where('nama_hari', $hari_nama)->first();
        
        if (!$hari) {
            return response()->json([]);
        }

        $data = AlokasiWaktu::with(['kategori', 'durasi'])
            ->where('jenjang_id', $jenjang_id)
            ->where('hari_id', $hari->id)
            ->get()
            ->sortBy(function($item) {
                return $item->durasi->jam_mulai ?? '00:00:00';
            })->values();

        $formattedData = $data->map(function ($item) {
            return [
                'id' => $item->id,
                'jam_mulai' => $item->durasi->jam_mulai ?? '',
                'jam_selesai' => $item->durasi->jam_selesai ?? '',
                'is_mapel' => $item->is_mapel,
                'kategori' => $item->kategori->nama_kategori ?? '',
                'keterangan' => $item->status_custom ?? $item->keterangan,
            ];
        });

        return response()->json($formattedData);
    }
}
```

## File: `app/Http/Controllers/Admin/ProfilController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\ProfilSekolah;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class ProfilController extends Controller {

    public function edit() {
        $profil = ProfilSekolah::firstOrCreate(['id' => 1], [
            'nama_madrasah' => 'MA Al Idrus Lengkong'
        ]);
        return view('admin.menu-utama.profil', compact('profil'));
    }

    public function update(Request $request) {
        try {
            $data = $request->validate([
                'nama' => 'required|string|max:255',
                'nsm' => 'nullable|string|max:50',
                'tahun' => 'nullable|digits:4',
                'logo' => 'nullable|file|max:5120', // Diubah ke 'file' untuk bypass pengecekan finfo server
                'alamat' => 'nullable|string',
                'google_maps_iframe' => 'nullable|string', 
                'telepon' => 'nullable|string|max:30',
                'email' => 'nullable|email',
                'website' => 'nullable|string',
            ], [
                'nama.required' => 'Nama madrasah wajib diisi!',
                'logo.max' => 'Ukuran logo maksimal 5MB',
                'logo.file' => 'File logo tidak valid',
            ]);

            $profil = ProfilSekolah::firstOrCreate(['id' => 1]);
            $logoPathToSave = $profil->logo_path;

            // Proses Upload Logo Menggunakan PHP Murni (Bypass Storage & Fileinfo)
            if ($request->hasFile('logo')) {
                $file = $request->file('logo');

                // Hapus logo lama secara manual jika ada
                if ($profil->logo_path && file_exists(storage_path('app/public/' . $profil->logo_path))) {
                    @unlink(storage_path('app/public/' . $profil->logo_path));
                }

                $folder = storage_path('app/public/logos');
                if (!is_dir($folder)) {
                    mkdir($folder, 0755, true);
                }

                $ext = $file->getClientOriginalExtension() ?: 'png';
                $filename = 'logo_' . time() . '_' . Str::random(5) . '.' . $ext;

                if (move_uploaded_file($file->getPathname(), $folder . '/' . $filename)) {
                    chmod($folder . '/' . $filename, 0644);
                    $logoPathToSave = 'logos/' . $filename;
                }
            }

            $programs = [];
            if ($request->program_judul) {
                foreach ($request->program_judul as $i => $judul) {
                    if ($judul) $programs[] = [
                        'icon' => $request->program_icon[$i] ?? 'bi-book',
                        'judul' => $judul,
                        'desc' => $request->program_desc[$i] ?? '',
                    ];
                }
            }

            $fasilitas = [];
            if ($request->fas_nama) {
                foreach ($request->fas_nama as $i => $nama) {
                    if ($nama) $fasilitas[] = [
                        'nama' => $nama,
                        'desc' => $request->fas_desc[$i] ?? '',
                    ];
                }
            }

            $profil->update([
                'nama_madrasah' => $data['nama'],
                'nsm_npsn' => $data['nsm'],
                'tahun_berdiri' => $data['tahun'],
                'logo_path' => $logoPathToSave,
                'alamat' => $data['alamat'],
                'google_maps_iframe' => $request->google_maps_iframe, 
                'telepon' => $data['telepon'],
                'email' => $data['email'],
                'website' => $data['website'],
                'visi' => $request->visi,
                'misi' => $request->misi,
                'keunggulan' => $request->keunggulan,
                'sejarah' => $request->sejarah,
                'prestasi' => $request->prestasi ?? [],
                'program_unggulan' => $programs,
                'fasilitas' => $fasilitas,
            ]);

            return back()->with('success', 'Profil berhasil disimpan!');

        } catch (\Illuminate\Validation\ValidationException $e) {
            return back()->withErrors($e->validator)->withInput()
                     ->with('warning', 'Periksa kembali data yang diisi!');
        } catch (\Exception $e) {
            Log::error('Profil update error: '.$e->getMessage());
            return back()->with('error', 'Gagal menyimpan: '.$e->getMessage());
        }
    }
}
```

## File: `app/Http/Controllers/Admin/StaffController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Staff;
use App\Models\Jabatan;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Shared\Date;

class StaffController extends Controller
{
    private function konversiMasehiKeHijriyah($tanggal_masehi = null)
    {
        $timestamp = $tanggal_masehi ? strtotime($tanggal_masehi) : time();
        $y = (int)date('Y', $timestamp);
        $m = (int)date('n', $timestamp);
        $d = (int)date('j', $timestamp);

        if (($y > 1582) || (($y == 1582) && ($m > 10)) || (($y == 1582) && ($m == 10) && ($d > 14))) {
            $jd = (int)((1461 * ($y + 4800 + (int)(($m - 14) / 12))) / 4) +
                  (int)((367 * ($m - 2 - 12 * ((int)(($m - 14) / 12)))) / 12) -
                  (int)((3 * ((int)(($y + 4900 + (int)(($m - 14) / 12)) / 100))) / 4) +
                  $d - 32075;
        } else {
            $jd = 367 * $y - (int)((7 * ($y + 5001 + (int)(($m - 9) / 7))) / 4) +
                  (int)((275 * $m) / 9) + $d + 1729777;
        }

        $l = $jd - 1948440 + 10632;
        $n = (int)(($l - 1) / 10631);
        $l = $l - 10631 * $n + 354;
        $j = ((int)((10985 - $l) / 5316)) * ((int)((50 * $l) / 17719)) +
             ((int)($l / 5670)) * ((int)((43 * $l) / 15238));
             
        $l = $l - ((int)((30 - $j) / 15)) * ((int)((17719 * $j) / 50)) - ((int)($j / 16)) * ((int)((15238 * $j) / 43)) + 29;
        
        $bulan = (int)((24 * $l) / 709);
        $tanggal = $l - (int)((709 * $bulan) / 24);
        $tahun = 30 * $n + $j - 30;
        
        $nama_bulan = [
            1 => 'Muharram', 2 => 'Safar', 3 => 'Rabiul Awal', 4 => 'Rabiul Akhir',
            5 => 'Jumadil Awal', 6 => 'Jumadil Akhir', 7 => 'Rajab', 8 => 'Sya\'ban',
            9 => 'Ramadhan', 10 => 'Syawal', 11 => 'Dzulqa\'dah', 12 => 'Dzulhijjah'
        ];

        return [
            'kode_tahun'      => substr((string)$tahun, -3),
            'tanggal_lengkap' => $tanggal . ' ' . $nama_bulan[$bulan] . ' ' . $tahun . ' H'
        ];
    }

    public function index()
    {
        $staffs = Staff::with('jabatans')->orderBy('nama_lengkap', 'asc')->get();
        return view('admin.data-staff.index', compact('staffs'));
    }

    public function create()
    {
        return view('admin.data-staff.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama_lengkap'     => 'required|string|max:255',
            'jenis_kelamin'    => 'required|in:L,P',
            'tempat_lahir'     => 'required|string|max:100',
            'tanggal_lahir'    => 'required|date',
            'tmt_pengangkatan' => 'required|date',
            'hp'               => 'nullable|string|max:20',
            'email'            => 'nullable|email|max:100',
        ]);

        DB::beginTransaction();
        try {
            $data = $request->all();

            $hijriData = $this->konversiMasehiKeHijriyah($request->tmt_pengangkatan);
            $data['tmt_pengangkatan_hijriyah'] = $hijriData['tanggal_lengkap'];

            $kodeMadrasah = "44802";
            $tahunHijriyahKode = $hijriData['kode_tahun'];
            $prefixNip = $kodeMadrasah . $tahunHijriyahKode;

            $lastStaff = Staff::where('nip', 'like', $prefixNip . '%')->orderBy('nip', 'desc')->first();
            $nextUrut = $lastStaff ? ((int) substr($lastStaff->nip, -3)) + 1 : 1;
            
            $data['nip'] = $prefixNip . str_pad($nextUrut, 3, '0', STR_PAD_LEFT);

            $staff = Staff::create($data);

            DB::commit();
            return redirect()->route('admin.staff.edit', $staff->id)->with('success', 'Data Staff Baru berhasil ditambahkan. Silakan lengkapi detail profil di bawah ini jika diperlukan.');
        } catch (\Exception $e) {
            DB::rollback();
            return back()->withErrors('Gagal menyimpan data: ' . $e->getMessage())->withInput();
        }
    }

    public function edit($id)
    {
        $staff = Staff::with('jabatans')->findOrFail($id);
        $jabatans = Jabatan::orderBy('urutan_tingkat', 'asc')->get(); 
        
        return view('admin.data-staff.edit', compact('staff', 'jabatans'));
    }

    public function update(Request $request, $id)
    {
        $staff = Staff::findOrFail($id);
        
        $request->validate([
            'nama_lengkap'  => 'required|string|max:255',
            'jenis_kelamin' => 'required|in:L,P',
            'email'         => 'nullable|email|max:100',
            'nik'           => 'nullable|string|max:20|unique:staffs,nik,'.$id,
            'nip'           => 'nullable|string|max:50|unique:staffs,nip,'.$id,
            'foto_file'     => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
            'file_ijazah'   => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_ktp'      => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_kk'       => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_npwp'     => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'jabatan_ids'   => 'nullable|array', 
            'jabatan_ids.*' => 'exists:jabatans,id'
        ]);

        $data = $request->except(['foto_file', 'foto_base64', 'file_ijazah', 'file_ktp', 'file_kk', 'file_npwp', 'jabatan_ids']);
        $fotoPathToConvert = null;

        if ($request->filled('foto_base64')) {
            $image_parts = explode(";base64,", $request->foto_base64);
            $image_base64 = base64_decode($image_parts[1]);
            $tempPath = sys_get_temp_dir() . '/' . uniqid() . '.jpg';
            file_put_contents($tempPath, $image_base64);
            $fotoPathToConvert = $tempPath;
        } 
        elseif ($request->hasFile('foto_file')) {
            $fotoPathToConvert = $request->file('foto_file')->getPathname();
        }

        if ($fotoPathToConvert) {
            $filename = 'staff_' . time() . '_' . Str::random(5) . '.webp';
            $destinationPath = storage_path('app/public/staff');

            if (!file_exists($destinationPath)) { 
                mkdir($destinationPath, 0755, true); 
            }
            if ($staff->foto && file_exists(storage_path('app/public/staff/' . $staff->foto))) {
                @unlink(storage_path('app/public/staff/' . $staff->foto));
            }

            $this->konversiKeWebp($fotoPathToConvert, $destinationPath . '/' . $filename);
            $data['foto'] = $filename;

            if ($request->filled('foto_base64') && file_exists($fotoPathToConvert)) {
                @unlink($fotoPathToConvert);
            }
        }

        $dokumen_fields = ['file_ijazah', 'file_ktp', 'file_kk', 'file_npwp'];
        
        foreach ($dokumen_fields as $field) {
            if ($request->hasFile($field)) {
                if ($staff->$field) {
                    $oldFilePath = storage_path('app/public/staff/dokumen/' . $staff->$field);
                    if (file_exists($oldFilePath)) {
                        @unlink($oldFilePath);
                    }
                }
                
                $file = $request->file($field);
                $ext = strtolower($file->getClientOriginalExtension());
                $filename = $field . '_' . $staff->id . '_' . time() . '.' . $ext;
                
                $destDocPath = storage_path('app/public/staff/dokumen');
                if (!file_exists($destDocPath)) {
                    mkdir($destDocPath, 0755, true);
                }
                $file->move($destDocPath, $filename);
                
                $data[$field] = $filename;
            }
        }

        $staff->update($data);

        if ($request->has('jabatan_ids')) {
            $staff->jabatans()->sync($request->jabatan_ids);
        } else {
            $staff->jabatans()->detach();
        }

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json([
                'status'  => 'success', 
                'message' => 'Seluruh kelengkapan profil Staff & Jabatan berhasil diperbarui.'
            ]);
        }
        
        return redirect()->back()->with('success', 'Seluruh kelengkapan profil Staff & Jabatan berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $staff = Staff::findOrFail($id);
        
        if ($staff->foto && file_exists(storage_path('app/public/staff/' . $staff->foto))) {
            @unlink(storage_path('app/public/staff/' . $staff->foto));
        }

        $dokumen_fields = ['file_ijazah', 'file_ktp', 'file_kk', 'file_npwp'];
        foreach ($dokumen_fields as $field) {
            if ($staff->$field) { 
                $docPath = storage_path('app/public/staff/dokumen/' . $staff->$field);
                if (file_exists($docPath)) {
                    @unlink($docPath);
                }
            }
        }

        $staff->jabatans()->detach();
        $staff->delete();
        
        return redirect()->route('admin.staff')->with('success', 'Data Staff beserta berkas dan jabatannya berhasil dihapus.');
    }

    public function templateImport()
    {
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        
        $sheet->setCellValue('A1', 'Nama Lengkap');
        $sheet->setCellValue('B1', 'Tempat Lahir');
        $sheet->setCellValue('C1', 'Tanggal Lahir (YYYY-MM-DD)');
        $sheet->setCellValue('D1', 'Jenis Kelamin (L/P)');
        $sheet->setCellValue('E1', 'TMT Pengangkatan (YYYY-MM-DD)');
        $sheet->setCellValue('F1', 'No HP');
        $sheet->setCellValue('G1', 'No Telepon');
        $sheet->setCellValue('H1', 'Email');

        $sheet->getStyle('A1:H1')->getFont()->setBold(true);

        $sheet->setCellValue('A2', 'Budi Santoso, S.Kom');
        $sheet->setCellValue('B2', 'Kuningan');
        $sheet->setCellValue('C2', '1990-05-20');
        $sheet->setCellValue('D2', 'L');
        $sheet->setCellValue('E2', '2023-07-15');
        $sheet->setCellValue('F2', '081234567890');
        $sheet->setCellValue('G2', '');
        $sheet->setCellValue('H2', 'budi@madrasah.com');

        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls($spreadsheet);
        $filename = 'Template_Import_Staff.xls';
        $tempFile = tempnam(sys_get_temp_dir(), 'excel');
        $writer->save($tempFile);

        return response()->download($tempFile, $filename, [
            'Content-Type' => 'application/vnd.ms-excel',
        ])->deleteFileAfterSend(true);
    }

    public function importData(Request $request)
    {
        $request->validate([
            'file_excel' => 'required|file|max:5120',
        ]);

        $file = $request->file('file_excel');
        $extension = strtolower($file->getClientOriginalExtension());

        if (!in_array($extension, ['xls', 'xlsx'])) {
            return redirect()->back()->withErrors('Format file tidak didukung. Harap unggah file berekstensi .xls atau .xlsx');
        }

        try {
            $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file->getPathname());
            $sheet = $spreadsheet->getActiveSheet();
            $rows = $sheet->toArray();

            $countSuccess = 0;
            $errorCount = 0;
            $errorMessages = [];
            
            for ($i = 1; $i < count($rows); $i++) {
                $row = $rows[$i];
                if (empty(array_filter($row))) continue; 

                $nama        = isset($row[0]) && trim((string)$row[0]) !== '' ? trim((string)$row[0]) : null;
                $tempatLahir = isset($row[1]) && trim((string)$row[1]) !== '' ? trim((string)$row[1]) : null;
                $tglLahirRaw = $row[2] ?? '';
                $jk          = isset($row[3]) && trim((string)$row[3]) !== '' ? strtoupper(trim((string)$row[3])) : null;
                $tmtRaw      = $row[4] ?? '';
                $hp          = isset($row[5]) && trim((string)$row[5]) !== '' ? trim((string)$row[5]) : null;
                $telepon     = isset($row[6]) && trim((string)$row[6]) !== '' ? trim((string)$row[6]) : null;
                $email       = isset($row[7]) && trim((string)$row[7]) !== '' ? trim((string)$row[7]) : null;

                if (!$nama || !$tmtRaw || !$tempatLahir || !$tglLahirRaw || !in_array($jk, ['L', 'P'])) {
                    $errorCount++;
                    continue;
                }

                if (is_numeric($tglLahirRaw)) {
                    $tglLahir = Date::excelToDateTimeObject($tglLahirRaw)->format('Y-m-d');
                } else {
                    $tglLahir = date('Y-m-d', strtotime(str_replace('/', '-', $tglLahirRaw)));
                }

                if (is_numeric($tmtRaw)) {
                    $tmt = Date::excelToDateTimeObject($tmtRaw)->format('Y-m-d');
                } else {
                    $tmt = date('Y-m-d', strtotime(str_replace('/', '-', $tmtRaw)));
                }

                $hijriData = $this->konversiMasehiKeHijriyah($tmt);
                
                try {
                    DB::beginTransaction();
                    
                    $prefixNip = "44802" . $hijriData['kode_tahun'];
                    $lastStaff = Staff::where('nip', 'like', $prefixNip . '%')->orderBy('nip', 'desc')->lockForUpdate()->first();
                    $nextUrut = $lastStaff ? ((int) substr($lastStaff->nip, -3)) + 1 : 1;
                    $nipBaru = $prefixNip . str_pad($nextUrut, 3, '0', STR_PAD_LEFT);

                    Staff::create([
                        'nip'                       => $nipBaru,
                        'nama_lengkap'              => $nama,
                        'tempat_lahir'              => $tempatLahir,
                        'tanggal_lahir'             => $tglLahir,
                        'jenis_kelamin'             => $jk,
                        'tmt_pengangkatan'          => $tmt,
                        'hp'                        => $hp,
                        'telepon'                   => $telepon,
                        'email'                     => $email,
                        'tmt_pengangkatan_hijriyah' => $hijriData['tanggal_lengkap'],
                        'agama'                     => 'Islam',
                    ]);

                    DB::commit();
                    $countSuccess++;
                } catch (\Exception $e) {
                    DB::rollback();
                    $errorCount++;
                    $errorMessages[] = "Baris " . ($i + 1) . " gagal: " . $e->getMessage();
                    continue; 
                }
            }

            if ($countSuccess > 0) {
                $msg = "Import selesai! Berhasil: $countSuccess data.";
                if ($errorCount > 0) $msg .= " Gagal/Dilewati: $errorCount baris.";
                return redirect()->back()->with('success', $msg);
            } else {
                $errorsToDisplay = array_merge(['Tidak ada data yang berhasil diimpor. Periksa format.'], array_slice($errorMessages, 0, 5));
                return redirect()->back()->withErrors($errorsToDisplay);
            }

        } catch (\Exception $e) {
            return redirect()->back()->withErrors('Gagal membaca file Excel. Error: ' . $e->getMessage());
        }
    }

    private function konversiKeWebp($source, $destination, $quality = 80)
    {
        $info = getimagesize($source);
        if ($info['mime'] == 'image/jpeg') { 
            $image = imagecreatefromjpeg($source); 
        }
        elseif ($info['mime'] == 'image/gif') { 
            $image = imagecreatefromgif($source); 
        }
        elseif ($info['mime'] == 'image/png') {
            $image = imagecreatefrompng($source);
            imagepalettetotruecolor($image);
            imagealphablending($image, true);
            imagesavealpha($image, true);
        } 
        elseif ($info['mime'] == 'image/webp') { 
            copy($source, $destination); 
            return; 
        }
        else { 
            return false; 
        }
        
        imagewebp($image, $destination, $quality);
        imagedestroy($image);
    }
}
```

## File: `app/Http/Controllers/Admin/PesanKontakController.php`
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\PesanKontak;

class PesanKontakController extends Controller
{
    public function index() {
        $pesans = PesanKontak::orderBy('created_at', 'desc')->get();
        return view('admin.pesan-kontak.index', compact('pesans'));
    }

    public function read($id) {
        $pesan = PesanKontak::findOrFail($id);
        $pesan->update(['is_read' => true]);
        return response()->json(['success' => true]);
    }

    public function destroy($id) {
        PesanKontak::findOrFail($id)->delete();
        return back()->with('success', 'Pesan dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/MapelController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Mapel;
use App\Models\JenjangKelas;
use App\Models\KategoriMapel;
use Illuminate\Http\Request;

class MapelController extends Controller
{
    public function index($jenjang_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $kategoris = KategoriMapel::orderBy('id', 'asc')->get();
        
        $mapels = Mapel::with('kategori')
                    ->where('jenjang_kelas_id', $jenjang_id)
                    ->orderBy('kategori_mapel_id', 'asc')
                    ->orderBy('nama_mapel', 'asc')
                    ->get();

        return view('admin.master-data.mapel.index', compact('jenjang', 'kategoris', 'mapels'));
    }

    public function store(Request $request, $jenjang_id)
    {
        $request->validate([
            'kategori_mapel_id' => 'required|exists:kategori_mapels,id',
            'nama_mapel' => 'required|string|max:255',
        ]);

        $jenjang = JenjangKelas::findOrFail($jenjang_id);

        Mapel::create([
            'jenjang_kelas_id' => $jenjang->id,
            'kategori_mapel_id' => $request->kategori_mapel_id,
            'nama_mapel' => $request->nama_mapel,
        ]);

        return redirect()->route('admin.mapel.index', $jenjang->id)->with('success', 'Mata Pelajaran berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'kategori_mapel_id' => 'required|exists:kategori_mapels,id',
            'nama_mapel' => 'required|string|max:255',
        ]);

        $mapel = Mapel::findOrFail($id);
        $mapel->update([
            'kategori_mapel_id' => $request->kategori_mapel_id,
            'nama_mapel' => $request->nama_mapel,
        ]);

        return redirect()->route('admin.mapel.index', $mapel->jenjang_kelas_id)->with('success', 'Mata Pelajaran berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $mapel = Mapel::findOrFail($id);
        $jenjang_id = $mapel->jenjang_kelas_id;
        $mapel->delete();

        return redirect()->route('admin.mapel.index', $jenjang_id)->with('success', 'Mata Pelajaran berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/DurasiWaktuController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\DurasiWaktu;
use Illuminate\Http\Request;

class DurasiWaktuController extends Controller
{
    public function index() {
        $durasis = DurasiWaktu::orderBy('jam_mulai', 'asc')->get();
        return view('admin.alokasi-waktu.waktu', compact('durasis'));
    }
    public function store(Request $request) {
        $request->validate([
            'jam_mulai' => 'required',
            'jam_selesai' => 'required|after:jam_mulai'
        ]);
        DurasiWaktu::create($request->all());
        return back()->with('success', 'Durasi Waktu berhasil ditambahkan.');
    }
    public function update(Request $request, $id) {
        $request->validate([
            'jam_mulai' => 'required',
            'jam_selesai' => 'required|after:jam_mulai'
        ]);
        DurasiWaktu::findOrFail($id)->update($request->all());
        return back()->with('success', 'Durasi Waktu berhasil diperbarui.');
    }
    public function destroy($id) {
        DurasiWaktu::findOrFail($id)->delete();
        return back()->with('success', 'Durasi Waktu berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/PresensiGuruController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\PresensiGuru;
use App\Models\Guru;
use App\Models\TahunAjaran;
use App\Models\Semester;
use App\Models\JenjangKelas;
use App\Models\Hari;
use App\Models\AlokasiWaktu;
use Carbon\Carbon;

class PresensiGuruController extends Controller
{
    private function getDefaultWaktu($tanggal, $jenjang_id = null)
    {
        Carbon::setLocale('id'); 
        $namaHari = Carbon::parse($tanggal)->translatedFormat('l'); 
        
        $hariRecord = Hari::where('nama_hari', $namaHari)->first();
        
        $defaultJamMasuk = '07:00';
        $defaultJamKeluar = '14:00';

        if ($hariRecord) {
            $query = AlokasiWaktu::with('durasi')->where('hari_id', $hariRecord->id);
            if ($jenjang_id) {
                $query->where('jenjang_id', $jenjang_id);
            }
            
            $alokasis = $query->get();
            
            if ($alokasis->isNotEmpty()) {
                $jamMulais = $alokasis->map(function($item) {
                    return $item->durasi->jam_mulai ?? null;
                })->filter();

                $jamSelesais = $alokasis->map(function($item) {
                    return $item->durasi->jam_selesai ?? null;
                })->filter();

                // Ambil jam paling pagi (min) untuk masuk, dan paling sore (max) untuk keluar
                if ($jamMulais->isNotEmpty()) {
                    $defaultJamMasuk = substr($jamMulais->min(), 0, 5);
                }
                
                if ($jamSelesais->isNotEmpty()) {
                    $defaultJamKeluar = substr($jamSelesais->max(), 0, 5);
                }
            }
        }

        return [$defaultJamMasuk, $defaultJamKeluar];
    }

    public function index(Request $request)
    {
        $tanggal = $request->query('tanggal', date('Y-m-d'));
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $semesters = Semester::orderBy('id', 'asc')->get();
        
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        $jenjangAktif = null;

        $gurus = Guru::select('id', 'nama_lengkap', 'nip', 'jenis_kelamin', 'foto')->orderBy('nama_lengkap', 'asc')->get();
        
        $presensis = PresensiGuru::where('tanggal', $tanggal)->whereIn('guru_id', $gurus->pluck('id'))->get()->keyBy('guru_id');

        $totalGuru = $gurus->count();
        $hadir = $presensis->where('status', 'Hadir')->count();
        $izinSakit = $presensis->whereIn('status', ['Izin', 'Sakit'])->count();
        $alpa = $presensis->where('status', 'Alpa')->count();
        $belumAbsen = $totalGuru - ($hadir + $izinSakit + $alpa);

        list($defaultJamMasuk, $defaultJamKeluar) = $this->getDefaultWaktu($tanggal);

        return view('admin.presensi-guru.index', compact(
            'tanggal', 'taAktif', 'tahunAjarans', 'semesters', 'jenjangs', 'jenjangAktif', 
            'gurus', 'presensis', 'totalGuru', 'hadir', 'izinSakit', 'alpa', 'belumAbsen',
            'defaultJamMasuk', 'defaultJamKeluar'
        ));
    }

    public function jenjang(Request $request, $jenjang_id)
    {
        $tanggal = $request->query('tanggal', date('Y-m-d'));
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $semesters = Semester::orderBy('id', 'asc')->get();
        
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        $jenjangAktif = JenjangKelas::findOrFail($jenjang_id);

        $gurus = Guru::whereHas('jenjangKelas', function($q) use ($jenjang_id) {
            $q->where('jenjang_kelas_id', $jenjang_id);
        })->select('id', 'nama_lengkap', 'nip', 'jenis_kelamin', 'foto')->orderBy('nama_lengkap', 'asc')->get();
        
        $presensis = PresensiGuru::where('tanggal', $tanggal)->whereIn('guru_id', $gurus->pluck('id'))->get()->keyBy('guru_id');

        // Rekap Stats
        $totalGuru = $gurus->count();
        $hadir = $presensis->where('status', 'Hadir')->count();
        $izinSakit = $presensis->whereIn('status', ['Izin', 'Sakit'])->count();
        $alpa = $presensis->where('status', 'Alpa')->count();
        $belumAbsen = $totalGuru - ($hadir + $izinSakit + $alpa);

        list($defaultJamMasuk, $defaultJamKeluar) = $this->getDefaultWaktu($tanggal, $jenjang_id);

        return view('admin.presensi-guru.index', compact(
            'tanggal', 'taAktif', 'tahunAjarans', 'semesters', 'jenjangs', 'jenjangAktif', 
            'gurus', 'presensis', 'totalGuru', 'hadir', 'izinSakit', 'alpa', 'belumAbsen',
            'defaultJamMasuk', 'defaultJamKeluar'
        ));
    }

    public function storeBulk(Request $request)
    {
        $tanggal = $request->tanggal;
        $presensiData = $request->presensi;

        if (!$presensiData) {
            return back()->withErrors(['Tidak ada data presensi yang dikirim.']);
        }

        foreach ($presensiData as $guruId => $data) {
            $status = $data['status'] ?? 'Alpa';
            $jamMasuk = ($status == 'Hadir') ? ($data['jam_masuk'] ?? null) : null;
            $jamKeluar = ($status == 'Hadir') ? ($data['jam_keluar'] ?? null) : null;
            $keterangan = $data['keterangan'] ?? null;

            PresensiGuru::updateOrCreate(
                [
                    'guru_id' => $guruId,
                    'tanggal' => $tanggal,
                ],
                [
                    'status' => $status,
                    'jam_masuk' => $jamMasuk,
                    'jam_keluar' => $jamKeluar,
                    'keterangan' => $keterangan,
                ]
            );
        }

        return back()->with('success', 'Data presensi untuk tanggal ' . Carbon::parse($tanggal)->translatedFormat('d F Y') . ' berhasil disimpan.');
    }

    public function rekap(Request $request)
    {
        $bulan = $request->query('bulan', date('m'));
        $tahun = $request->query('tahun', date('Y'));
        
        $taAktif = TahunAjaran::where('status_akademik', 'aktif')->first();
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $semesters = Semester::orderBy('id', 'asc')->get();
        
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        
        // --- LOGIKA JENJANG DIPINDAHKAN KE SINI ---
        $reqJenjangId = $request->query('jenjang_id');
        
        // Jika tidak ada jenjang_id di URL atau nilainya 'semua', paksa ambil jenjang pertama
        if (empty($reqJenjangId) || $reqJenjangId === 'semua') {
            $jenjangAktifObj = $jenjangs->first();
            $jenjang_id = $jenjangAktifObj ? $jenjangAktifObj->id : null;
        } else {
            $jenjang_id = $reqJenjangId;
            $jenjangAktifObj = $jenjangs->where('id', $jenjang_id)->first();
        }
        // ------------------------------------------
        
        $query = Guru::orderBy('nama_lengkap', 'asc');
        
        if ($jenjang_id) {
            $query->whereHas('jenjangKelas', function($q) use ($jenjang_id) {
                $q->where('jenjang_kelas_id', $jenjang_id);
            });
        }
        
        $gurus = $query->get();
        
        $rekapData = [];
        foreach ($gurus as $guru) {
            $presensis = PresensiGuru::where('guru_id', $guru->id)
                ->whereMonth('tanggal', $bulan)
                ->whereYear('tanggal', $tahun)
                ->orderBy('tanggal', 'asc')
                ->get();
                
            $rekapData[] = [
                'guru' => $guru,
                'detail_presensi' => $presensis, 
                'total_hadir' => $presensis->where('status', 'Hadir')->count(),
                'total_izin' => $presensis->where('status', 'Izin')->count(),
                'total_sakit' => $presensis->where('status', 'Sakit')->count(),
                'total_alpa' => $presensis->where('status', 'Alpa')->count(),
            ];
        }

        // TAMBAHKAN 'jenjangAktifObj' KE COMPACT
        return view('admin.presensi-guru.rekap', compact(
            'bulan', 'tahun', 'jenjangs', 'jenjang_id', 'jenjangAktifObj', 'rekapData', 
            'taAktif', 'tahunAjarans', 'semesters'
        ));
    }
}
```

## File: `app/Http/Controllers/Admin/GuruController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Guru;
use App\Models\Jabatan;
use App\Models\JenjangKelas;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Shared\Date;

class GuruController extends Controller
{
    private function konversiMasehiKeHijriyah($tanggal_masehi = null)
    {
        $timestamp = $tanggal_masehi ? strtotime($tanggal_masehi) : time();
        $y = (int)date('Y', $timestamp);
        $m = (int)date('n', $timestamp);
        $d = (int)date('j', $timestamp);

        if (($y > 1582) || (($y == 1582) && ($m > 10)) || (($y == 1582) && ($m == 10) && ($d > 14))) {
            $jd = (int)((1461 * ($y + 4800 + (int)(($m - 14) / 12))) / 4) +
                  (int)((367 * ($m - 2 - 12 * ((int)(($m - 14) / 12)))) / 12) -
                  (int)((3 * ((int)(($y + 4900 + (int)(($m - 14) / 12)) / 100))) / 4) +
                  $d - 32075;
        } else {
            $jd = 367 * $y - (int)((7 * ($y + 5001 + (int)(($m - 9) / 7))) / 4) +
                  (int)((275 * $m) / 9) + $d + 1729777;
        }

        $l = $jd - 1948440 + 10632;
        $n = (int)(($l - 1) / 10631);
        $l = $l - 10631 * $n + 354;
        $j = ((int)((10985 - $l) / 5316)) * ((int)((50 * $l) / 17719)) +
             ((int)($l / 5670)) * ((int)((43 * $l) / 15238));
             
        $l = $l - ((int)((30 - $j) / 15)) * ((int)((17719 * $j) / 50)) - ((int)($j / 16)) * ((int)((15238 * $j) / 43)) + 29;
        
        $bulan = (int)((24 * $l) / 709);
        $tanggal = $l - (int)((709 * $bulan) / 24);
        $tahun = 30 * $n + $j - 30;
        
        $nama_bulan = [
            1 => 'Muharram', 2 => 'Safar', 3 => 'Rabiul Awal', 4 => 'Rabiul Akhir',
            5 => 'Jumadil Awal', 6 => 'Jumadil Akhir', 7 => 'Rajab', 8 => 'Sya\'ban',
            9 => 'Ramadhan', 10 => 'Syawal', 11 => 'Dzulqa\'dah', 12 => 'Dzulhijjah'
        ];

        return [
            'kode_tahun'      => substr((string)$tahun, -3),
            'tanggal_lengkap' => $tanggal . ' ' . $nama_bulan[$bulan] . ' ' . $tahun . ' H'
        ];
    }

    public function index()
    {
        $gurus = Guru::with('jabatans')->orderBy('nama_lengkap', 'asc')->get();
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get(); 
        return view('admin.data-guru.index', compact('gurus', 'jenjangs'));
    }

    public function byJenjang($id)
    {
        $jenjang_aktif = JenjangKelas::findOrFail($id);
        $jenjangs = JenjangKelas::orderBy('kode', 'asc')->get();
        
        $gurus = Guru::with('jabatans')
                     ->whereHas('jenjangKelas', function($q) use ($id) {
                         $q->where('jenjang_kelas_id', $id);
                     })->orderBy('nama_lengkap', 'asc')->get();
                     
        $semua_guru = Guru::orderBy('nama_lengkap', 'asc')->get();

        return view('admin.data-guru.jenjang', compact('gurus', 'jenjangs', 'jenjang_aktif', 'semua_guru'));
    }

    public function assignJenjang(Request $request, $id)
    {
        $jenjang = JenjangKelas::findOrFail($id);
        
        $request->validate([
            'guru_ids'   => 'required|array',
            'guru_ids.*' => 'exists:gurus,id'
        ], [
            'guru_ids.required' => 'Pilih minimal satu guru untuk ditambahkan.'
        ]);
        
        $jenjang->gurus()->syncWithoutDetaching($request->guru_ids);
        
        return back()->with('success', 'Berhasil menambahkan guru terpilih ke jenjang ' . $jenjang->nama_jenjang);
    }

    public function removeJenjang($jenjang_id, $guru_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $jenjang->gurus()->detach($guru_id);
        return back()->with('success', 'Guru berhasil dikeluarkan dari jenjang ' . $jenjang->nama_jenjang);
    }

    public function create()
    {
        return view('admin.data-guru.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'nama_lengkap'     => 'required|string|max:255',
            'jenis_kelamin'    => 'required|in:L,P',
            'tempat_lahir'     => 'required|string|max:100',
            'tanggal_lahir'    => 'required|date',
            'tmt_pengangkatan' => 'required|date',
            'hp'               => 'nullable|string|max:20',
            'email'            => 'nullable|email|max:100',
        ]);

        DB::beginTransaction();
        try {
            $data = $request->all();
            
            $hijriData = $this->konversiMasehiKeHijriyah($request->tmt_pengangkatan);
            $data['tmt_pengangkatan_hijriyah'] = $hijriData['tanggal_lengkap'];

            $kodeMadrasah = "44801";
            $tahunHijriyahKode = $hijriData['kode_tahun'];
            $prefixNip = $kodeMadrasah . $tahunHijriyahKode;

            $lastGuru = Guru::where('nip', 'like', $prefixNip . '%')->orderBy('nip', 'desc')->first();
            $nextUrut = $lastGuru ? ((int) substr($lastGuru->nip, -3)) + 1 : 1;
            
            $data['nip'] = $prefixNip . str_pad($nextUrut, 3, '0', STR_PAD_LEFT);

            $guru = Guru::create($data);

            DB::commit();
            return redirect()->route('admin.guru.edit', $guru->id)->with('success', 'Data Guru Baru berhasil ditambahkan. Silakan lengkapi detail profil di bawah ini jika diperlukan.');
        } catch (\Exception $e) {
            DB::rollback();
            return back()->withErrors('Gagal menyimpan data: ' . $e->getMessage())->withInput();
        }
    }

    public function edit($id)
    {
        $guru = Guru::with('jabatans')->findOrFail($id);
        $jabatans = Jabatan::orderBy('urutan_tingkat', 'asc')->get(); 
        
        return view('admin.data-guru.edit', compact('guru', 'jabatans'));
    }

    public function update(Request $request, $id)
    {
        $guru = Guru::findOrFail($id);
        
        $request->validate([
            'nama_lengkap'  => 'required|string|max:255',
            'jenis_kelamin' => 'required|in:L,P',
            'email'         => 'nullable|email|max:100',
            'nik'           => 'nullable|string|max:20|unique:gurus,nik,'.$id,
            'nip'           => 'nullable|string|max:50|unique:gurus,nip,'.$id,
            'foto_file'     => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
            'file_ijazah'   => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_ktp'      => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_kk'       => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'file_npwp'     => 'nullable|mimes:pdf,jpg,jpeg,png|max:2048',
            'jabatan_ids'   => 'nullable|array',
            'jabatan_ids.*' => 'exists:jabatans,id'
        ]);

        $data = $request->except(['foto_file', 'foto_base64', 'file_ijazah', 'file_ktp', 'file_kk', 'file_npwp', 'jabatan_ids']);
        $fotoPathToConvert = null;

        if ($request->filled('foto_base64')) {
            $image_parts = explode(";base64,", $request->foto_base64);
            $image_base64 = base64_decode($image_parts[1]);
            $tempPath = sys_get_temp_dir() . '/' . uniqid() . '.jpg';
            file_put_contents($tempPath, $image_base64);
            $fotoPathToConvert = $tempPath;
        } 
        elseif ($request->hasFile('foto_file')) {
            $fotoPathToConvert = $request->file('foto_file')->getPathname();
        }

        if ($fotoPathToConvert) {
            $filename = 'guru_' . time() . '_' . Str::random(5) . '.webp';
            $destinationPath = storage_path('app/public/guru');

            if (!file_exists($destinationPath)) {
                mkdir($destinationPath, 0755, true);
            }

            if ($guru->foto && file_exists(storage_path('app/public/guru/' . $guru->foto))) {
                @unlink(storage_path('app/public/guru/' . $guru->foto));
            }

            $this->konversiKeWebp($fotoPathToConvert, $destinationPath . '/' . $filename);
            $data['foto'] = $filename;

            if ($request->filled('foto_base64') && file_exists($fotoPathToConvert)) {
                @unlink($fotoPathToConvert);
            }
        }

        $dokumen_fields = ['file_ijazah', 'file_ktp', 'file_kk', 'file_npwp'];
        
        foreach ($dokumen_fields as $field) {
            if ($request->hasFile($field)) {
                if ($guru->$field) {
                    $oldFilePath = storage_path('app/public/guru/dokumen/' . $guru->$field);
                    if (file_exists($oldFilePath)) {
                        @unlink($oldFilePath);
                    }
                }

                $file = $request->file($field);
                $ext = strtolower($file->getClientOriginalExtension());
                $filename = $field . '_' . $guru->id . '_' . time() . '.' . $ext;
                $destDocPath = storage_path('app/public/guru/dokumen');
                if (!file_exists($destDocPath)) {
                    mkdir($destDocPath, 0755, true);
                }
                $file->move($destDocPath, $filename);
                
                $data[$field] = $filename;
            }
        }

        $guru->update($data);

        if ($request->has('jabatan_ids')) {
            $guru->jabatans()->sync($request->jabatan_ids);
        } else {
            $guru->jabatans()->detach();
        }

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json([
                'status'  => 'success',
                'message' => 'Seluruh kelengkapan profil Guru & Jabatan berhasil diperbarui.'
            ]);
        }

        return redirect()->back()->with('success', 'Seluruh kelengkapan profil Guru & Jabatan berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $guru = Guru::findOrFail($id);
        
        if ($guru->foto && file_exists(storage_path('app/public/guru/' . $guru->foto))) {
            @unlink(storage_path('app/public/guru/' . $guru->foto));
        }
        
        $dokumen_fields = ['file_ijazah', 'file_ktp', 'file_kk', 'file_npwp'];
        foreach ($dokumen_fields as $field) {
            if ($guru->$field) {
                $docPath = storage_path('app/public/guru/dokumen/' . $guru->$field);
                if (file_exists($docPath)) {
                    @unlink($docPath);
                }
            }
        }

        $guru->jabatans()->detach();
        $guru->jenjangKelas()->detach(); 
        $guru->delete();
        
        return redirect()->route('admin.guru')->with('success', 'Data Guru beserta berkas dan jabatannya berhasil dihapus.');
    }

    public function templateImport()
    {
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        
        $sheet->setCellValue('A1', 'Nama Lengkap');
        $sheet->setCellValue('B1', 'Tempat Lahir');
        $sheet->setCellValue('C1', 'Tanggal Lahir (YYYY-MM-DD)');
        $sheet->setCellValue('D1', 'Jenis Kelamin (L/P)');
        $sheet->setCellValue('E1', 'TMT Pengangkatan (YYYY-MM-DD)');
        $sheet->setCellValue('F1', 'No HP');
        $sheet->setCellValue('G1', 'No Telepon');
        $sheet->setCellValue('H1', 'Email');

        $sheet->getStyle('A1:H1')->getFont()->setBold(true);

        $sheet->setCellValue('A2', 'Ahmad Fadillah, S.Pd');
        $sheet->setCellValue('B2', 'Kuningan');
        $sheet->setCellValue('C2', '1990-05-20');
        $sheet->setCellValue('D2', 'L');
        $sheet->setCellValue('E2', '2023-07-15');
        $sheet->setCellValue('F2', '081234567890');
        $sheet->setCellValue('G2', '');
        $sheet->setCellValue('H2', 'ahmad@madrasah.com');

        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls($spreadsheet);
        $filename = 'Template_Import_Guru.xls';
        $tempFile = tempnam(sys_get_temp_dir(), 'excel');
        $writer->save($tempFile);

        return response()->download($tempFile, $filename, [
            'Content-Type' => 'application/vnd.ms-excel',
        ])->deleteFileAfterSend(true);
    }

    public function importData(Request $request)
    {
        $request->validate([
            'file_excel' => 'required|file|max:5120',
        ]);

        $file = $request->file('file_excel');
        $extension = strtolower($file->getClientOriginalExtension());

        if (!in_array($extension, ['xls', 'xlsx'])) {
            return redirect()->back()->withErrors('Format file tidak didukung. Harap unggah file berekstensi .xls atau .xlsx');
        }

        try {
            $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file->getPathname());
            $sheet = $spreadsheet->getActiveSheet();
            $rows = $sheet->toArray();

            $countSuccess = 0;
            $errorCount = 0;
            $errorMessages = [];
            
            for ($i = 1; $i < count($rows); $i++) {
                $row = $rows[$i];
                if (empty(array_filter($row))) continue; 

                $nama        = trim($row[0] ?? '');
                $tempatLahir = trim($row[1] ?? '');
                $tglLahirRaw = $row[2] ?? '';
                $jk          = strtoupper(trim($row[3] ?? ''));
                $tmtRaw      = $row[4] ?? '';
                $hp          = trim($row[5] ?? '');
                $telepon     = trim($row[6] ?? '');
                $email       = trim($row[7] ?? '');

                if (!$nama || !$tmtRaw || !$tempatLahir || !$tglLahirRaw || !in_array($jk, ['L', 'P'])) {
                    $errorCount++;
                    continue;
                }

                if (is_numeric($tglLahirRaw)) {
                    $tglLahir = Date::excelToDateTimeObject($tglLahirRaw)->format('Y-m-d');
                } else {
                    $tglLahir = date('Y-m-d', strtotime(str_replace('/', '-', $tglLahirRaw)));
                }

                if (is_numeric($tmtRaw)) {
                    $tmt = Date::excelToDateTimeObject($tmtRaw)->format('Y-m-d');
                } else {
                    $tmt = date('Y-m-d', strtotime(str_replace('/', '-', $tmtRaw)));
                }

                $hijriData = $this->konversiMasehiKeHijriyah($tmt);
                
                try {
                    DB::beginTransaction();
                    
                    $prefixNip = "44801" . $hijriData['kode_tahun'];
                    $lastGuru = Guru::where('nip', 'like', $prefixNip . '%')->orderBy('nip', 'desc')->first();
                    $nextUrut = $lastGuru ? ((int) substr($lastGuru->nip, -3)) + 1 : 1;
                    $nipBaru = $prefixNip . str_pad($nextUrut, 3, '0', STR_PAD_LEFT);

                    Guru::create([
                        'nip'                       => $nipBaru,
                        'nama_lengkap'              => $nama,
                        'tempat_lahir'              => $tempatLahir,
                        'tanggal_lahir'             => $tglLahir,
                        'jenis_kelamin'             => $jk,
                        'tmt_pengangkatan'          => $tmt,
                        'hp'                        => $hp,
                        'telepon'                   => $telepon,
                        'email'                     => $email,
                        'tmt_pengangkatan_hijriyah' => $hijriData['tanggal_lengkap'],
                        'agama'                     => 'Islam',
                    ]);

                    DB::commit();
                    $countSuccess++;
                } catch (\Exception $e) {
                    DB::rollback();
                    $errorCount++;
                    $errorMessages[] = "Baris " . ($i + 1) . " gagal: " . $e->getMessage();
                    continue; 
                }
            }

            if ($countSuccess > 0) {
                $msg = "Import selesai! Berhasil: $countSuccess data.";
                if ($errorCount > 0) $msg .= " Gagal/Dilewati: $errorCount baris.";
                return redirect()->back()->with('success', $msg);
            } else {
                $errorsToDisplay = array_merge(['Tidak ada data yang berhasil diimpor. Periksa format.'], array_slice($errorMessages, 0, 5));
                return redirect()->back()->withErrors($errorsToDisplay);
            }

        } catch (\Exception $e) {
            return redirect()->back()->withErrors('Gagal membaca file Excel. Error: ' . $e->getMessage());
        }
    }

    private function konversiKeWebp($source, $destination, $quality = 80)
    {
        $info = getimagesize($source);
        if ($info['mime'] == 'image/jpeg') {
            $image = imagecreatefromjpeg($source);
        } elseif ($info['mime'] == 'image/gif') {
            $image = imagecreatefromgif($source);
        } elseif ($info['mime'] == 'image/png') {
            $image = imagecreatefrompng($source);
            imagepalettetotruecolor($image);
            imagealphablending($image, true);
            imagesavealpha($image, true);
        } elseif ($info['mime'] == 'image/webp') {
            copy($source, $destination);
            return;
        } else {
            return false;
        }

        imagewebp($image, $destination, $quality);
        imagedestroy($image);
    }
}
```

## File: `app/Http/Controllers/Admin/UserController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class UserController extends Controller
{
    public function profile()
    {
        if (Auth::user()->role !== 'admin') {
            abort(403, 'Akses tidak diizinkan.');
        }
        
        return view('admin.user.index');
    }

    public function updateProfile(Request $request)
    {
        $user = User::findOrFail(Auth::id());

        $request->validate([
            'name'     => 'required|string|max:255',
            'username' => 'required|string|max:255|unique:users,username,' . $user->id,
            'password' => 'nullable|string|min:8|confirmed',
        ]);

        $user->name = $request->name;
        $user->username = $request->username;

        if ($request->filled('password')) {
            $user->password = Hash::make($request->password);
        }

        $user->save();

        return redirect()->back()->with('success', 'Profile akun berhasil diperbarui!');
    }
}
```

## File: `app/Http/Controllers/Admin/TingkatKelasController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\TingkatKelas;
use App\Models\JenjangKelas;
use Illuminate\Http\Request;

class TingkatKelasController extends Controller
{
    public function index($jenjang_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $tingkats = TingkatKelas::where('jenjang_kelas_id', $jenjang_id)
                    ->orderBy('id', 'asc')
                    ->get();

        return view('admin.tingkat-kelas.index', compact('jenjang', 'tingkats'));
    }

    public function store(Request $request, $jenjang_id)
    {
        $request->validate([
            'nama_tingkat' => 'required|string|max:255',
        ]);

        $jenjang = JenjangKelas::findOrFail($jenjang_id);

        TingkatKelas::create([
            'jenjang_kelas_id' => $jenjang->id,
            'nama_tingkat' => $request->nama_tingkat,
            'deskripsi' => $request->deskripsi,
        ]);

        return redirect()->route('admin.tingkat-kelas.index', $jenjang->id)->with('success', 'Tingkat Kelas berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_tingkat' => 'required|string|max:255',
        ]);

        $tingkat = TingkatKelas::findOrFail($id);
        $tingkat->update([
            'nama_tingkat' => $request->nama_tingkat,
            'deskripsi' => $request->deskripsi,
        ]);

        return redirect()->route('admin.tingkat-kelas.index', $tingkat->jenjang_kelas_id)->with('success', 'Tingkat Kelas berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $tingkat = TingkatKelas::findOrFail($id);
        $jenjang_id = $tingkat->jenjang_kelas_id; 
        $tingkat->delete();

        return redirect()->route('admin.tingkat-kelas.index', $jenjang_id)->with('success', 'Tingkat Kelas berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/TestimoniController.php`
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Testimoni;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class TestimoniController extends Controller
{
    public function index() {
        $testimonis = Testimoni::orderBy('created_at', 'desc')->get();
        return view('admin.testimoni.index', compact('testimonis'));
    }

    public function store(Request $request) {
        $request->validate(['nama' => 'required', 'peran' => 'required', 'pesan' => 'required', 'foto' => 'nullable|image|max:2048']);
        $data = $request->all();
        if ($request->hasFile('foto')) $data['foto'] = $this->uploadWebp($request->file('foto'));
        Testimoni::create($data);
        return back()->with('success', 'Testimoni ditambahkan.');
    }

    public function update(Request $request, $id) {
        $testi = Testimoni::findOrFail($id);
        $data = $request->all();
        if ($request->hasFile('foto')) {
            if ($testi->foto && Storage::disk('public')->exists('testimoni/' . $testi->foto)) Storage::disk('public')->delete('testimoni/' . $testi->foto);
            $data['foto'] = $this->uploadWebp($request->file('foto'));
        }
        $testi->update($data);
        return back()->with('success', 'Testimoni diperbarui.');
    }

    public function destroy($id) {
        $testi = Testimoni::findOrFail($id);
        if ($testi->foto && Storage::disk('public')->exists('testimoni/' . $testi->foto)) Storage::disk('public')->delete('testimoni/' . $testi->foto);
        $testi->delete();
        return back()->with('success', 'Testimoni dihapus.');
    }

    private function uploadWebp($file) {
        $filename = 'testi_' . time() . '_' . Str::random(5) . '.webp';
        $path = storage_path('app/public/testimoni');
        if (!file_exists($path)) mkdir($path, 0755, true);
        $info = getimagesize($file->getPathname());
        if ($info['mime'] == 'image/jpeg') $image = imagecreatefromjpeg($file->getPathname());
        elseif ($info['mime'] == 'image/png') { $image = imagecreatefrompng($file->getPathname()); imagepalettetotruecolor($image); imagealphablending($image, true); imagesavealpha($image, true); }
        else return null;
        imagewebp($image, $path . '/' . $filename, 80);
        imagedestroy($image);
        return $filename;
    }
}
```

## File: `app/Http/Controllers/Admin/KeuanganSiswaController.php`
```php
<?php
// app/Http/Controllers/Admin/KeuanganSiswaController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Siswa;
use App\Models\JenjangKelas;
use App\Models\TingkatKelas;
use App\Models\TahunAjaran;
use App\Models\TarifKeuangan;
use App\Models\PembayaranKeuangan;
use App\Models\Semester; 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class KeuanganSiswaController extends Controller
{
    private function getTagihanTingkat($jenjang_id, $tingkat_id, $ta_id) {
        return TarifKeuangan::where('jenjang_id', $jenjang_id)
            ->where('tahun_ajaran_id', $ta_id)
            ->where(function($q) use ($tingkat_id) {
                $q->whereNull('tingkat_kelas_id')
                  ->orWhere('tingkat_kelas_id', '')
                  ->orWhere('tingkat_kelas_id', 0)
                  ->orWhere('tingkat_kelas_id', '0');
                if ($tingkat_id !== null) {
                    $q->orWhere('tingkat_kelas_id', $tingkat_id);
                }
            })->sum('total_tarif');
    }

    public function rekap(Request $request)
    {
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : null);
        
        $semesters = Semester::orderBy('id', 'asc')->get();
        $selectedSemId = $request->input('semester_id', 'all');

        $jenjangs = JenjangKelas::with(['tingkat' => function($q) {
            $q->orderBy('nama_tingkat', 'asc');
        }, 'tingkat.kelas' => function($q) {
            $q->orderBy('nama_kelas', 'asc');
        }, 'tingkat.kelas.siswa' => function($q) use ($selectedTaId) {
            $q->where('status_siswa', 'Aktif')
              ->when($selectedTaId, function($subQ) use ($selectedTaId) {
                  $subQ->where('tahun_ajaran_id', $selectedTaId);
              });
        }])->orderBy('id', 'asc')->get();

        $tarifPerTingkat = [];
        foreach($jenjangs as $jenjang) {
            foreach($jenjang->tingkat as $tingkat) {
                $tarifPerTingkat[$tingkat->id] = $this->getTagihanTingkat($jenjang->id, $tingkat->id, $selectedTaId);
            }
        }

        $allTarifs = TarifKeuangan::where('tahun_ajaran_id', $selectedTaId)->get();
        $tarifIdsInTa = $allTarifs->pluck('id');

        $pembayaranBase = PembayaranKeuangan::whereIn('tarif_keuangan_id', $tarifIdsInTa)
                            ->selectRaw('siswa_id, sum(nominal_bayar) as total')
                            ->groupBy('siswa_id')
                            ->pluck('total', 'siswa_id');

        return view('admin.keuangan.rekap.index', compact(
            'tahunAjarans', 'selectedTaId', 'jenjangs', 'pembayaranBase',
            'semesters', 'selectedSemId', 'tarifPerTingkat', 'allTarifs'
        ));
    }

    public function indexTingkat(Request $request, $jenjang_id, $tingkat_id)
    {
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $tingkat = TingkatKelas::findOrFail($tingkat_id);

        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : null);

        $semesters = Semester::where('jenis_periode', $jenjang->sistem_akademik ?? 'semester')->orderBy('id', 'asc')->get();
        $selectedSemId = $request->input('semester_id', 'all');

        $daftarKelas = \App\Models\Kelas::with('details.gender')
                        ->where('tingkat_kelas_id', $tingkat->id)
                        ->orderBy('nama_kelas', 'asc')
                        ->get();

        $siswasReal = Siswa::with(['kelas.tingkat.jenjang', 'kelas.details.gender', 'tahunAjaran'])
                        ->whereHas('kelas', function($q) use ($tingkat_id) {
                            $q->where('tingkat_kelas_id', $tingkat_id);
                        })
                        ->when($selectedTaId, function($q) use ($selectedTaId) {
                            $q->where('tahun_ajaran_id', $selectedTaId);
                        })
                        ->orderBy('nama_lengkap', 'asc')
                        ->get(); 

        $totalTagihanGlobal = $this->getTagihanTingkat($jenjang_id, $tingkat_id, $selectedTaId);

        $siswaIds = $siswasReal->pluck('id');
        $tarifIdsDiTaIni = TarifKeuangan::where('jenjang_id', $jenjang_id)
                                ->where('tahun_ajaran_id', $selectedTaId)
                                ->where(function($q) use ($tingkat_id) {
                                    $q->whereNull('tingkat_kelas_id') 
                                      ->orWhere('tingkat_kelas_id', '') 
                                      ->orWhere('tingkat_kelas_id', 0)
                                      ->orWhere('tingkat_kelas_id', '0')
                                      ->orWhere('tingkat_kelas_id', $tingkat_id); 
                                })->pluck('id');
        
        $allPembayaran = PembayaranKeuangan::whereIn('siswa_id', $siswaIds)
                            ->whereIn('tarif_keuangan_id', $tarifIdsDiTaIni)
                            ->selectRaw('siswa_id, sum(nominal_bayar) as total_bayar')
                            ->groupBy('siswa_id')
                            ->pluck('total_bayar', 'siswa_id');

        foreach ($siswasReal as $siswa) {
            $siswa->total_terbayar = $allPembayaran[$siswa->id] ?? 0;
        }

        $globalJmlSiswa = $siswasReal->count();
        $globalTotalTagihan = $globalJmlSiswa * $totalTagihanGlobal;
                                
        $globalTotalTerbayar = PembayaranKeuangan::whereIn('siswa_id', $siswaIds)
                                ->whereIn('tarif_keuangan_id', $tarifIdsDiTaIni)
                                ->sum('nominal_bayar');

        session(['url_kembali_keuangan_siswa' => url()->full()]);

        return view('admin.keuangan.siswa.index', compact(
            'jenjang', 'tingkat', 'tahunAjarans', 'selectedTaId', 'daftarKelas', 
            'siswasReal', 'totalTagihanGlobal', 
            'globalJmlSiswa', 'globalTotalTagihan', 'globalTotalTerbayar',
            'semesters', 'selectedSemId'
        ));
    }

    public function transaksi(Request $request, $id)
    {
        $siswa = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran'])->findOrFail($id);
        
        $jenjang_id = $siswa->kelas->tingkat->jenjang->id ?? null;
        $tingkat_id = $siswa->kelas->tingkat_kelas_id ?? $siswa->kelas->tingkat->id ?? null; 
        
        $jenjang = JenjangKelas::find($jenjang_id);

        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        
        $semesters = Semester::where('jenis_periode', $jenjang->sistem_akademik ?? 'semester')->orderBy('id', 'asc')->get();

        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : ($siswa->tahun_ajaran_id ?? null));
        $selectedSemId = $request->input('semester_id', 'all');

        $allTarifsDb = TarifKeuangan::with(['kategori', 'semester', 'tahunAjaran', 'tingkat'])
                    ->where('jenjang_id', $jenjang_id)
                    ->where('tahun_ajaran_id', $selectedTaId)
                    ->where(function($q) use ($tingkat_id) {
                        $q->whereNull('tingkat_kelas_id') 
                          ->orWhere('tingkat_kelas_id', '') 
                          ->orWhere('tingkat_kelas_id', 0)
                          ->orWhere('tingkat_kelas_id', '0');
                        if ($tingkat_id !== null) {
                            $q->orWhere('tingkat_kelas_id', $tingkat_id);
                        }
                    })->get();

        $pembayaransRekap = PembayaranKeuangan::where('siswa_id', $id)
                                ->selectRaw('tarif_keuangan_id, sum(nominal_bayar) as total_bayar')
                                ->groupBy('tarif_keuangan_id')
                                ->pluck('total_bayar', 'tarif_keuangan_id');

        $totalTagihanGlobal = $this->getTagihanTingkat($jenjang_id, $tingkat_id, $selectedTaId);
        $totalDibayarGlobal = 0;
        $totalDepositGlobal = 0; 

        foreach ($allTarifsDb as $t) {
            $uangMasukItem = $pembayaransRekap[$t->id] ?? 0;
            $totalDibayarGlobal += min($uangMasukItem, $t->total_tarif); 
            $totalDepositGlobal += max(0, $uangMasukItem - $t->total_tarif); 
        }
        
        $sisaTagihanGlobal = max(0, $totalTagihanGlobal - $totalDibayarGlobal);
        $saldoTitipan = $totalDepositGlobal;

        if ($selectedSemId && $selectedSemId !== 'all') {
            $tarifs = $allTarifsDb->filter(function($t) use ($selectedSemId) {
                return empty($t->semester_id) || $t->semester_id == $selectedSemId;
            });
        } else {
            $tarifs = $allTarifsDb;
        }

        $totalTagihanPeriode = 0;
        $totalDibayarPeriode = 0;

        foreach ($tarifs as $tarif) {
            $uangMasukItem = $pembayaransRekap[$tarif->id] ?? 0;
            $tarif->terbayar = min($uangMasukItem, $tarif->total_tarif); 
            $tarif->sisa = max(0, $tarif->total_tarif - $uangMasukItem);
            $tarif->deposit = max(0, $uangMasukItem - $tarif->total_tarif); 

            $totalTagihanPeriode += $tarif->total_tarif;
            $totalDibayarPeriode += $tarif->terbayar;
        }
        $sisaTagihanPeriode = max(0, $totalTagihanPeriode - $totalDibayarPeriode);

        $totalTagihan = $totalTagihanGlobal; 
        $totalDibayar = $totalDibayarGlobal;
        $sisaTagihan = $sisaTagihanGlobal;
        $totalDeposit = $totalDepositGlobal;

        $historiPembayaran = PembayaranKeuangan::with(['tarif.kategori', 'tarif.tahunAjaran'])
                                ->where('siswa_id', $id)
                                ->orderBy('tanggal_bayar', 'desc')
                                ->orderBy('id', 'desc')
                                ->paginate(30)
                                ->withQueryString();

        $activeTa = $tahunAjarans->firstWhere('id', $selectedTaId);
        $activeSem = $selectedSemId === 'all' ? null : $semesters->firstWhere('id', $selectedSemId);
        $pembayarans = collect(); 

        return view('admin.keuangan.siswa.transaksi', compact(
            'siswa', 'tarifs', 'saldoTitipan',
            'totalTagihanGlobal', 'totalDibayarGlobal', 'sisaTagihanGlobal', 'totalDepositGlobal',
            'totalTagihanPeriode', 'totalDibayarPeriode', 'sisaTagihanPeriode',
            'totalTagihan', 'totalDibayar', 'sisaTagihan', 'totalDeposit', 
            'activeTa', 'activeSem', 'pembayarans', 'historiPembayaran',
            'tahunAjarans', 'semesters', 'selectedTaId', 'selectedSemId', 'jenjang'
        ));
    }

    public function storePembayaran(Request $request, $siswa_id) {
        $request->validate(['tarif_keuangan_id' => 'required|exists:tarif_keuangans,id', 'nominal_bayar' => 'required|numeric|min:1', 'tanggal_bayar' => 'required|date', 'keterangan' => 'nullable|string']);
        PembayaranKeuangan::create(['siswa_id' => $siswa_id, 'tarif_keuangan_id' => $request->tarif_keuangan_id, 'nominal_bayar' => $request->nominal_bayar, 'tanggal_bayar' => $request->tanggal_bayar, 'keterangan' => $request->keterangan]);
        return redirect()->back()->with('success', 'Transaksi pembayaran berhasil dicatat!');
    }
    public function updatePembayaran(Request $request, $id) {
        $request->validate(['nominal_bayar' => 'required|numeric|min:1', 'tanggal_bayar' => 'required|date', 'keterangan' => 'nullable|string']);
        PembayaranKeuangan::findOrFail($id)->update(['nominal_bayar' => $request->nominal_bayar, 'tanggal_bayar' => $request->tanggal_bayar, 'keterangan' => $request->keterangan]);
        return redirect()->back()->with('success', 'Data transaksi pembayaran berhasil diperbarui!');
    }
    public function destroyPembayaran($id) {
        PembayaranKeuangan::findOrFail($id)->delete();
        return redirect()->back()->with('success', 'Data transaksi pembayaran berhasil dihapus!');
    }
}
```

## File: `app/Http/Controllers/Admin/GenderController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Gender;
use Illuminate\Http\Request;

class GenderController extends Controller
{
    public function index()
    {
        if (Gender::count() == 0) {
            Gender::insert([
                ['nama_gender' => 'Putra', 'created_at' => now(), 'updated_at' => now()],
                ['nama_gender' => 'Putri', 'created_at' => now(), 'updated_at' => now()]
            ]);
        }

        $genders = Gender::orderBy('id', 'asc')->get();
        return view('admin.gender.index', compact('genders'));
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_gender' => 'required|string|max:255|unique:genders,nama_gender,'.$id,
        ]);

        $gender = Gender::findOrFail($id);
        $gender->update(['nama_gender' => $request->nama_gender]);

        return redirect()->route('admin.gender')->with('success', 'Istilah Gender berhasil diperbarui.');
    }
}
```

## File: `app/Http/Controllers/Admin/KelasController.php`
```php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Kelas;
use App\Models\TingkatKelas;
use Illuminate\Http\Request;

class KelasController extends Controller
{
    public function index($tingkat_id)
    {
        $tingkat = TingkatKelas::with('jenjang')->findOrFail($tingkat_id);
        $kelases = Kelas::where('tingkat_kelas_id', $tingkat_id)->orderBy('nama_kelas', 'asc')->get();
        return view('admin.kelas.index', compact('tingkat', 'kelases'));
    }

    public function store(Request $request, $tingkat_id)
    {
        $request->validate([
            'nama_kelas' => 'required|string|max:255',
            'kapasitas' => 'nullable|numeric|min:1',
        ]);

        $tingkat = TingkatKelas::findOrFail($tingkat_id);
        Kelas::create([
            'tingkat_kelas_id' => $tingkat->id,
            'nama_kelas' => $request->nama_kelas,
            'kapasitas' => $request->kapasitas,
        ]);
        return redirect()->route('admin.kelas.index', $tingkat->id)->with('success', 'Ruang Kelas berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'nama_kelas' => 'required|string|max:255',
            'kapasitas' => 'nullable|numeric|min:1',
        ]);

        $kelas = Kelas::findOrFail($id);
        $kelas->update([
            'nama_kelas' => $request->nama_kelas,
            'kapasitas' => $request->kapasitas,
        ]);
        return redirect()->route('admin.kelas.index', $kelas->tingkat_kelas_id)->with('success', 'Ruang Kelas diperbarui.');
    }

    public function destroy($id)
    {
        $kelas = Kelas::findOrFail($id);
        $tingkat_id = $kelas->tingkat_kelas_id;
        $kelas->delete();
        return redirect()->route('admin.kelas.index', $tingkat_id)->with('success', 'Ruang Kelas dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/TabunganController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\NasabahTabungan;
use App\Models\TransaksiTabungan;
use App\Models\Siswa;
use App\Models\Guru;
use App\Models\Staff;
use App\Models\JenjangKelas;
use App\Models\TingkatKelas;
use App\Models\Kelas;
use App\Models\TahunAjaran;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class TabunganController extends Controller
{
    private function getTahunHijriyahCode()
    {
        $timestamp = time();
        $y = (int)date('Y', $timestamp);
        $m = (int)date('n', $timestamp);
        $d = (int)date('j', $timestamp);

        if (($y > 1582) || (($y == 1582) && ($m > 10)) || (($y == 1582) && ($m == 10) && ($d > 14))) {
            $jd = (int)((1461 * ($y + 4800 + (int)(($m - 14) / 12))) / 4) +
                  (int)((367 * ($m - 2 - 12 * ((int)(($m - 14) / 12)))) / 12) -
                  (int)((3 * ((int)(($y + 4900 + (int)(($m - 14) / 12)) / 100))) / 4) +
                  $d - 32075;
        } else {
            $jd = 367 * $y - (int)((7 * ($y + 5001 + (int)(($m - 9) / 7))) / 4) +
                  (int)((275 * $m) / 9) + $d + 1729777;
        }

        $l = $jd - 1948440 + 10632;
        $n = (int)(($l - 1) / 10631);
        $l = $l - 10631 * $n + 354;
        $j = ((int)((10985 - $l) / 5316)) * ((int)((50 * $l) / 17719)) +
             ((int)($l / 5670)) * ((int)((43 * $l) / 15238));
        
        $tahunHijri = 30 * $n + $j - 30;
        return substr((string)$tahunHijri, -3);
    }

    private function generateNoRekening($kode_kategori)
    {
        $kode_madrasah = "448";
        $tahun_kode = $this->getTahunHijriyahCode(); 
        $prefix = $kode_madrasah . $kode_kategori . $tahun_kode; 

        $lastNasabah = NasabahTabungan::where('no_rekening', 'like', $prefix . '%')
                        ->orderBy('no_rekening', 'desc')
                        ->first();

        $nextUrut = $lastNasabah ? ((int) substr($lastNasabah->no_rekening, -4)) + 1 : 1;
        return $prefix . str_pad($nextUrut, 4, '0', STR_PAD_LEFT);
    }

    public function rekapTabungan()
    {
        // 1. Ambil semua data Nasabah
        $nasabahs = NasabahTabungan::with('mutasi')->get();
        
        $rekap = [
            'Siswa / Santri' => ['jml' => 0, 'setor_sya' => 0, 'tarik_sya' => 0, 'setor_jar' => 0, 'tarik_jar' => 0],
            'Guru & Staff' => ['jml' => 0, 'setor_sya' => 0, 'tarik_sya' => 0, 'setor_jar' => 0, 'tarik_jar' => 0],
            'Masyarakat Umum' => ['jml' => 0, 'setor_sya' => 0, 'tarik_sya' => 0, 'setor_jar' => 0, 'tarik_jar' => 0],
        ];

        foreach($nasabahs as $n) {
            $kat = $n->entitas_profil;
            $rekap[$kat]['jml'] += 1;
            
            foreach($n->mutasi as $m) {
                if($m->jenis_tabungan == 'Syariah') {
                    $m->jenis_mutasi == 'Setor' ? $rekap[$kat]['setor_sya'] += $m->nominal : $rekap[$kat]['tarik_sya'] += $m->nominal;
                } else {
                    $m->jenis_mutasi == 'Setor' ? $rekap[$kat]['setor_jar'] += $m->nominal : $rekap[$kat]['tarik_jar'] += $m->nominal;
                }
            }
        }

        return view('admin.keuangan.tabungan.rekap.index', compact('rekap'));
    }

    public function indexSiswa($jenjang_id, $tingkat_id, Request $request)
    {
        $tahunAjarans = TahunAjaran::orderBy('id', 'desc')->get();
        $selectedTaId = $request->tahun_ajaran_id ?? ($tahunAjarans->where('status_akademik', 'aktif')->first()->id ?? null);
        $jenjang = JenjangKelas::findOrFail($jenjang_id);
        $tingkat = TingkatKelas::findOrFail($tingkat_id);
        $daftarKelas = Kelas::with(['details.gender'])->where('tingkat_kelas_id', $tingkat_id)->get();
        $kelasIds = $daftarKelas->pluck('id')->toArray();

        $siswasRealQuery = Siswa::with(['kelas.tingkat.jenjang', 'tahunAjaran', 'tabungan'])->whereIn('kelas_id', $kelasIds);
        if ($selectedTaId) $siswasRealQuery->where('tahun_ajaran_id', $selectedTaId);
        $siswasReal = $siswasRealQuery->orderBy('nama_lengkap', 'asc')->get();

        return view('admin.keuangan.tabungan.siswa.index', compact('tahunAjarans', 'selectedTaId', 'jenjang', 'tingkat', 'daftarKelas', 'siswasReal'));
    }

    public function bukaRekeningSiswa(Request $request)
    {
        $request->validate(['siswa_id' => 'required|exists:siswas,id']);
        $siswa = Siswa::findOrFail($request->siswa_id);
        if ($siswa->tabungan) return back()->withErrors('Gagal, siswa ini sudah memiliki buku rekening.');

        NasabahTabungan::create([
            'no_rekening' => $this->generateNoRekening('02'),
            'kode_kategori' => '02',
            'tahun_daftar_hijriyah' => '1' . $this->getTahunHijriyahCode(),
            'siswa_id' => $siswa->id,
            'nama_lengkap' => $siswa->nama_lengkap,
            'nik' => $siswa->nik,
            'no_hp' => $siswa->no_hp,
        ]);
        return back()->with('success', 'Buku Rekening berhasil dibuka.');
    }

    public function indexGuru()
    {
        $gurus = Guru::with(['jabatans', 'tabungan'])->orderBy('nama_lengkap', 'asc')->get();
        return view('admin.keuangan.tabungan.pegawai.guru', compact('gurus'));
    }

    public function bukaRekeningGuru(Request $request)
    {
        $request->validate(['guru_id' => 'required|exists:gurus,id']);
        $guru = Guru::findOrFail($request->guru_id);
        if ($guru->tabungan) return back()->withErrors('Gagal, guru ini sudah memiliki buku rekening.');

        NasabahTabungan::create([
            'no_rekening' => $this->generateNoRekening('01'),
            'kode_kategori' => '01',
            'tahun_daftar_hijriyah' => '1' . $this->getTahunHijriyahCode(),
            'guru_id' => $guru->id,
            'nama_lengkap' => $guru->nama_lengkap,
            'nik' => $guru->nik,
            'no_hp' => $guru->hp ?? '-',
        ]);
        return back()->with('success', 'Buku Rekening berhasil dibuka.');
    }

    public function indexStaff()
    {
        $staffs = Staff::with(['jabatans', 'tabungan'])->orderBy('nama_lengkap', 'asc')->get();
        return view('admin.keuangan.tabungan.pegawai.staff', compact('staffs'));
    }

    public function bukaRekeningStaff(Request $request)
    {
        $request->validate(['staff_id' => 'required|exists:staffs,id']);
        $staff = Staff::findOrFail($request->staff_id);
        if ($staff->tabungan) return back()->withErrors('Gagal, staff ini sudah memiliki buku rekening.');

        NasabahTabungan::create([
            'no_rekening' => $this->generateNoRekening('01'),
            'kode_kategori' => '01',
            'tahun_daftar_hijriyah' => '1' . $this->getTahunHijriyahCode(),
            'staff_id' => $staff->id,
            'nama_lengkap' => $staff->nama_lengkap,
            'nik' => $staff->nik,
            'no_hp' => $staff->hp ?? '-',
        ]);
        return back()->with('success', 'Buku Rekening berhasil dibuka.');
    }

    public function indexUmum()
    {
        $umums = NasabahTabungan::where('kode_kategori', '03')->orderBy('created_at', 'desc')->get();
        return view('admin.keuangan.tabungan.umum.index', compact('umums'));
    }

    public function storeUmum(Request $request)
    {
        $request->validate([
            'nama_lengkap' => 'required|string|max:255',
            'nik' => 'required|string|max:20|unique:nasabah_tabungans,nik',
            'no_hp' => 'required|string|max:20',
        ]);

        $no_rekening = $this->generateNoRekening('03');

        NasabahTabungan::create([
            'no_rekening' => $no_rekening,
            'kode_kategori' => '03',
            'tahun_daftar_hijriyah' => '1' . $this->getTahunHijriyahCode(),
            'nama_lengkap' => $request->nama_lengkap,
            'nik' => $request->nik,
            'no_hp' => $request->no_hp,
        ]);

        return back()->with('success', 'Rekening Umum berhasil dibuat! (No. Rek: ' . $no_rekening . ')');
    }

    public function updateUmum(Request $request, $id)
    {
        $request->validate([
            'nama_lengkap' => 'required|string|max:255',
            'nik' => 'required|string|max:20|unique:nasabah_tabungans,nik,'.$id,
            'no_hp' => 'required|string|max:20',
        ]);

        $nasabah = NasabahTabungan::findOrFail($id);
        $nasabah->update([
            'nama_lengkap' => $request->nama_lengkap,
            'nik' => $request->nik,
            'no_hp' => $request->no_hp,
        ]);

        return back()->with('success', 'Data dasar nasabah umum berhasil diperbarui.');
    }

    public function destroyUmum($id)
    {
        $nasabah = NasabahTabungan::findOrFail($id);
        
        if ($nasabah->file_ktp) {
            $filePath = storage_path('app/public/nasabah_ktp/' . $nasabah->file_ktp);
            if (file_exists($filePath)) {
                @unlink($filePath);
            }
        }
        
        $nasabah->delete();
        return back()->with('success', 'Data nasabah umum berhasil dihapus.');
    }

    public function show($id, Request $request)
    {
        $nasabah = NasabahTabungan::with(['siswa.kelas.tingkat', 'guru', 'staff'])->findOrFail($id);

        $query = TransaksiTabungan::where('nasabah_tabungan_id', $id)
                    ->orderBy('tanggal_transaksi', 'desc')
                    ->orderBy('id', 'desc');

        if ($request->filled('start_date') && $request->filled('end_date')) {
            $query->whereBetween('tanggal_transaksi', [$request->start_date, $request->end_date]);
        }

        $nasabah->setRelation('mutasi', $query->get());

        return view('admin.keuangan.tabungan.show', compact('nasabah'));
    }

    public function cetakRekeningKoran(Request $request, $id)
    {
        $nasabah = NasabahTabungan::with(['siswa.kelas.tingkat', 'guru', 'staff'])->findOrFail($id);
        
        $startDate = $request->start_date ?? now()->subMonth()->format('Y-m-d');
        $endDate = $request->end_date ?? now()->format('Y-m-d');

        $mutasi = TransaksiTabungan::where('nasabah_tabungan_id', $id)
            ->whereBetween('tanggal_transaksi', [$startDate, $endDate])
            ->orderBy('tanggal_transaksi', 'asc')
            ->orderBy('id', 'asc')
            ->get();

        $saldoAwalSyariah = 0;
        $saldoAwalJariyah = 0;
        
        $mutasiSebelumnya = TransaksiTabungan::where('nasabah_tabungan_id', $id)
            ->where('tanggal_transaksi', '<', $startDate)
            ->get();
            
        foreach($mutasiSebelumnya as $m) {
            if ($m->jenis_tabungan == 'Syariah') {
                $m->jenis_mutasi == 'Setor' ? $saldoAwalSyariah += $m->nominal : $saldoAwalSyariah -= $m->nominal;
            } else {
                $m->jenis_mutasi == 'Setor' ? $saldoAwalJariyah += $m->nominal : $saldoAwalJariyah -= $m->nominal;
            }
        }

        return view('admin.keuangan.tabungan.cetak_koran', compact('nasabah', 'mutasi', 'startDate', 'endDate', 'saldoAwalSyariah', 'saldoAwalJariyah'));
    }

    public function cetakBukuTabungan($id)
    {
        $nasabah = NasabahTabungan::with(['siswa.kelas.tingkat', 'guru', 'staff'])->findOrFail($id);
        
        $mutasi = TransaksiTabungan::where('nasabah_tabungan_id', $id)
            ->orderBy('tanggal_transaksi', 'asc')
            ->orderBy('id', 'asc')
            ->get();

        return view('admin.keuangan.tabungan.cetak_buku', compact('nasabah', 'mutasi'));
    }

    public function updateProfilNasabah(Request $request, $id)
    {
        $request->validate([
            'alamat' => 'nullable|string',
        ]);

        $nasabah = NasabahTabungan::findOrFail($id);
        $dataUpdate = ['alamat' => $request->alamat];

        if ($request->hasFile('file_ktp')) {
            $file = $request->file('file_ktp');
            
            $extension = strtolower($file->getClientOriginalExtension());
            $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
            
            if (!in_array($extension, $allowedExtensions)) {
                return back()->withErrors('Gagal! Format file KTP harus berupa: JPG, JPEG, PNG, atau PDF.');
            }
            
            if ($file->getSize() > 5242880) {
                return back()->withErrors('Gagal! Ukuran file KTP tidak boleh lebih dari 5 MB.');
            }

            if ($nasabah->file_ktp) {
                $oldFilePath = storage_path('app/public/nasabah_ktp/' . $nasabah->file_ktp);
                if (file_exists($oldFilePath)) {
                    @unlink($oldFilePath);
                }
            }

            $filename = time() . '_KTP_' . preg_replace('/[^a-zA-Z0-9]/', '_', $nasabah->nama_lengkap) . '.' . $extension;
            $destinationPath = storage_path('app/public/nasabah_ktp');
            
            $file->move($destinationPath, $filename);
            
            $dataUpdate['file_ktp'] = $filename;
        }

        $nasabah->update($dataUpdate);

        return back()->with('success', 'Kelengkapan dokumen profil (Alamat & KTP) berhasil disimpan.');
    }

    public function storeTransaksi(Request $request, $nasabah_id)
    {
        $request->validate([
            'jenis_tabungan' => 'required|in:Syariah,Jariyah',
            'jenis_mutasi' => 'required|in:Setor,Tarik',
            'nominal' => 'required|numeric|min:1',
            'tanggal_transaksi' => 'required|date',
            'keterangan' => 'nullable|string',
        ]);

        DB::beginTransaction();
        try {
            $nasabah = NasabahTabungan::findOrFail($nasabah_id);

            if ($request->jenis_mutasi == 'Tarik') {
                if ($request->jenis_tabungan == 'Syariah' && $nasabah->saldo_syariah < $request->nominal) return back()->withErrors('Saldo Syariah tidak mencukupi.');
                if ($request->jenis_tabungan == 'Jariyah' && $nasabah->saldo_jariyah < $request->nominal) return back()->withErrors('Saldo Jariyah tidak mencukupi.');
                $request->jenis_tabungan == 'Syariah' ? $nasabah->decrement('saldo_syariah', $request->nominal) : $nasabah->decrement('saldo_jariyah', $request->nominal);
            } else {
                $request->jenis_tabungan == 'Syariah' ? $nasabah->increment('saldo_syariah', $request->nominal) : $nasabah->increment('saldo_jariyah', $request->nominal);
            }

            TransaksiTabungan::create([
                'nasabah_tabungan_id' => $nasabah->id,
                'jenis_tabungan' => $request->jenis_tabungan,
                'jenis_mutasi' => $request->jenis_mutasi,
                'nominal' => $request->nominal,
                'tanggal_transaksi' => $request->tanggal_transaksi,
                'keterangan' => $request->keterangan ?? ($request->jenis_mutasi . ' Tabungan ' . $request->jenis_tabungan),
            ]);

            DB::commit();
            return back()->with('success', 'Transaksi ' . $request->jenis_mutasi . ' berhasil.');
        } catch (\Exception $e) {
            DB::rollback();
            return back()->withErrors('Error: ' . $e->getMessage());
        }
    }

    public function updateTransaksi(Request $request, $mutasi_id)
    {
        $request->validate([
            'jenis_tabungan' => 'required|in:Syariah,Jariyah',
            'jenis_mutasi' => 'required|in:Setor,Tarik',
            'nominal' => 'required|numeric|min:1',
            'tanggal_transaksi' => 'required|date',
            'keterangan' => 'nullable|string',
        ]);

        DB::beginTransaction();
        try {
            $mutasiLama = TransaksiTabungan::findOrFail($mutasi_id);
            $nasabah = NasabahTabungan::findOrFail($mutasiLama->nasabah_tabungan_id);

            if ($mutasiLama->jenis_mutasi == 'Setor') {
                $mutasiLama->jenis_tabungan == 'Syariah' ? $nasabah->decrement('saldo_syariah', $mutasiLama->nominal) : $nasabah->decrement('saldo_jariyah', $mutasiLama->nominal);
            } else {
                $mutasiLama->jenis_tabungan == 'Syariah' ? $nasabah->increment('saldo_syariah', $mutasiLama->nominal) : $nasabah->increment('saldo_jariyah', $mutasiLama->nominal);
            }

            if ($request->jenis_mutasi == 'Tarik') {
                if ($request->jenis_tabungan == 'Syariah' && $nasabah->saldo_syariah < $request->nominal) { DB::rollback(); return back()->withErrors('Saldo Syariah tidak cukup.'); }
                if ($request->jenis_tabungan == 'Jariyah' && $nasabah->saldo_jariyah < $request->nominal) { DB::rollback(); return back()->withErrors('Saldo Jariyah tidak cukup.'); }
            }

            if ($request->jenis_mutasi == 'Setor') {
                $request->jenis_tabungan == 'Syariah' ? $nasabah->increment('saldo_syariah', $request->nominal) : $nasabah->increment('saldo_jariyah', $request->nominal);
            } else {
                $request->jenis_tabungan == 'Syariah' ? $nasabah->decrement('saldo_syariah', $request->nominal) : $nasabah->decrement('saldo_jariyah', $request->nominal);
            }

            $mutasiLama->update([
                'jenis_tabungan' => $request->jenis_tabungan,
                'jenis_mutasi' => $request->jenis_mutasi,
                'nominal' => $request->nominal,
                'tanggal_transaksi' => $request->tanggal_transaksi,
                'keterangan' => $request->keterangan ?? ($request->jenis_mutasi . ' Tabungan ' . $request->jenis_tabungan),
            ]);

            DB::commit();
            return back()->with('success', 'Data mutasi diperbarui.');
        } catch (\Exception $e) {
            DB::rollback();
            return back()->withErrors('Error: ' . $e->getMessage());
        }
    }

    public function destroyTransaksi($mutasi_id)
    {
        DB::beginTransaction();
        try {
            $mutasi = TransaksiTabungan::findOrFail($mutasi_id);
            $nasabah = NasabahTabungan::findOrFail($mutasi->nasabah_tabungan_id);

            if ($mutasi->jenis_mutasi == 'Setor') {
                if ($mutasi->jenis_tabungan == 'Syariah' && $nasabah->saldo_syariah < $mutasi->nominal) { DB::rollback(); return back()->withErrors('Saldo Syariah akan minus.'); }
                if ($mutasi->jenis_tabungan == 'Jariyah' && $nasabah->saldo_jariyah < $mutasi->nominal) { DB::rollback(); return back()->withErrors('Saldo Jariyah akan minus.'); }
                $mutasi->jenis_tabungan == 'Syariah' ? $nasabah->decrement('saldo_syariah', $mutasi->nominal) : $nasabah->decrement('saldo_jariyah', $mutasi->nominal);
            } else {
                $mutasi->jenis_tabungan == 'Syariah' ? $nasabah->increment('saldo_syariah', $mutasi->nominal) : $nasabah->increment('saldo_jariyah', $mutasi->nominal);
            }

            $mutasi->delete();
            DB::commit();
            return back()->with('success', 'Histori mutasi dihapus.');
        } catch (\Exception $e) {
            DB::rollback();
            return back()->withErrors('Error: ' . $e->getMessage());
        }
    }
}
```

## File: `app/Http/Controllers/Admin/JenjangKelasController.php`
```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\JenjangKelas;
use Illuminate\Http\Request;

class JenjangKelasController extends Controller
{
    public function index()
    {
        $jenjangs = JenjangKelas::with(['tingkats.kelases.details.gender'])
                    ->orderBy('id', 'asc')
                    ->get();
                    
        return view('admin.jenjang-kelas.index', compact('jenjangs'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'kode' => 'required|string|regex:/^[0-9]+$/|max:10|unique:jenjang_kelas,kode', 
            'nama_jenjang' => 'required|string|max:255|unique:jenjang_kelas,nama_jenjang',
            'sistem_akademik' => 'required|in:semester,caturwulan', // Validasi Baru
        ]);

        JenjangKelas::create($request->all());

        return redirect()->route('admin.jenjang-kelas')->with('success', 'Jenjang Kelas berhasil ditambahkan.');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'kode' => 'required|string|regex:/^[0-9]+$/|max:10|unique:jenjang_kelas,kode,'.$id, 
            'nama_jenjang' => 'required|string|max:255|unique:jenjang_kelas,nama_jenjang,'.$id,
            'sistem_akademik' => 'required|in:semester,caturwulan', // Validasi Baru
        ]);

        $jenjang = JenjangKelas::findOrFail($id);
        $jenjang->update($request->all());

        return redirect()->route('admin.jenjang-kelas')->with('success', 'Jenjang Kelas berhasil diperbarui.');
    }

    public function destroy($id)
    {
        $jenjang = JenjangKelas::findOrFail($id);
        $jenjang->delete();

        return redirect()->route('admin.jenjang-kelas')->with('success', 'Jenjang Kelas berhasil dihapus.');
    }
}
```

## File: `app/Http/Controllers/Admin/GajiKategoriController.php`
```php
<?php
// app/Http/Controllers/Admin/GajiKategoriController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\GajiKategori;
use App\Models\TahunAjaran;
use App\Models\Jabatan; 
use Illuminate\Http\Request;

class GajiKategoriController extends Controller
{
    public function index(Request $request)
    {
        $tahunAjarans = TahunAjaran::orderBy('tahun_ajaran', 'desc')->get();
        $defaultTa = TahunAjaran::where('status_akademik', 'aktif')->first();
        $selectedTaId = $request->input('tahun_ajaran_id', $defaultTa ? $defaultTa->id : null);

        $kategoris = GajiKategori::where('tahun_ajaran_id', $selectedTaId)
                        ->orderBy('sifat', 'asc')
                        ->orderBy('id', 'asc')
                        ->get();

        $jabatans = Jabatan::orderBy('urutan_tingkat', 'asc')->get();

        return view('admin.keuangan.gajih.kategori.index', compact('kategoris', 'tahunAjarans', 'selectedTaId', 'jabatans'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'nama' => 'required|string|max:255',
            'sifat' => 'required|in:pemasukan,potongan',
            'peruntukan' => 'required|in:guru,staff,semua', 
            'tipe_hitungan' => 'required|in:statis,dinamis',
            'sumber_otomasi' => 'required|string|max:50',
            'integration_id' => 'nullable|integer', 
            'nominal_dasar' => 'required|numeric|min:0',
            'keterangan' => 'nullable|string|max:255'
        ]);

        GajiKategori::create($request->all());

        return redirect()->back()->with('success', 'Master Komponen Gaji berhasil ditambahkan!');
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'tahun_ajaran_id' => 'required|exists:tahun_ajarans,id',
            'nama' => 'required|string|max:255',
            'sifat' => 'required|in:pemasukan,potongan',
            'peruntukan' => 'required|in:guru,staff,semua', 
            'tipe_hitungan' => 'required|in:statis,dinamis',
            'sumber_otomasi' => 'required|string|max:50',
            'integration_id' => 'nullable|integer', 
            'nominal_dasar' => 'required|numeric|min:0',
            'keterangan' => 'nullable|string|max:255'
        ]);

        $kategori = GajiKategori::findOrFail($id);
        $kategori->update($request->all());

        return redirect()->back()->with('success', 'Master Komponen Gaji berhasil diperbarui!');
    }

    public function destroy($id)
    {
        $kategori = GajiKategori::findOrFail($id);
        $kategori->delete();

        return redirect()->back()->with('success', 'Master Komponen Gaji berhasil dihapus!');
    }
}
```

## File: `app/Http/Controllers/AuthController.php`
```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function showLoginForm()
    {
        return view('auth.login');
    }

    public function login(Request $request)
    {
        $credentials = $request->validate([
            'username' => 'required',
            'password' => 'required'
        ]);

        if (Auth::attempt($credentials)) {
            if (!Auth::user()->is_active) {
                Auth::logout();
                $request->session()->invalidate();
                return back()->withErrors([
                    'username' => 'Akses ditolak! Akun Anda telah dinonaktifkan oleh Administrator.',
                ])->onlyInput('username');
            }

            $request->session()->regenerate();

            if (Auth::user()->role === 'admin') {
                return redirect()->intended(route('admin.dashboard'))->with('success', 'Selamat datang kembali, Admin!');
            } elseif (Auth::user()->role === 'siswa') {
                return redirect()->intended(route('siswa.dashboard'))->with('success', 'Selamat datang di Portal Siswa!');
            }

            return redirect('/');
        }

        return back()->withErrors([
            'username' => 'Username atau Password yang Anda masukkan salah.',
        ])->onlyInput('username');
    }

    public function logout(Request $request)
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect('/')->with('success', 'Anda telah berhasil logout.');
    }
}
```

