diff --git a/app/Http/Controllers/admin/LaporanAkademikController.php b/app/Http/Controllers/admin/LaporanAkademikController.php new file mode 100644 index 0000000..b15e40f --- /dev/null +++ b/app/Http/Controllers/admin/LaporanAkademikController.php @@ -0,0 +1,42 @@ +orderBy('id_siswa') + ->get() + ->groupBy('id_siswa'); + + return view('admin.laporan-akademik.index', compact('nilai')); + } + + public function cetak($idSiswa) + { + $nilai = Nilai::with(['siswa', 'mapel']) + ->where('id_siswa', $idSiswa) + ->get(); + + if ($nilai->isEmpty()) { + abort(404, 'Data nilai tidak ditemukan.'); + } + + $siswa = $nilai->first()->siswa; + $rataRata = round($nilai->avg('nilai_akhir'), 2); + + $pdf = Pdf::loadView('admin.laporan-akademik.pdf', compact( + 'nilai', + 'siswa', + 'rataRata' + ))->setPaper('A4', 'portrait'); + + return $pdf->stream('laporan-akademik-' . $siswa->nama . '.pdf'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/guru/NilaiController.php b/app/Http/Controllers/guru/NilaiController.php index 1ef8724..91805e0 100644 --- a/app/Http/Controllers/guru/NilaiController.php +++ b/app/Http/Controllers/guru/NilaiController.php @@ -1,6 +1,6 @@ user(); + { + $user = auth()->user(); + $mapel_id = $user->mapel_id; + $kelas = Kelas::all(); - // ambil mapel guru - $mapel_id = $user->mapel_id; + // Filter kelas + $query = Nilai::with(['siswa.kelas', 'mapel']) + ->where('mapel_id', $mapel_id); - // ambil semua kelas (buat dropdown filter) - $kelas = \App\Models\Kelas::all(); + if ($request->kelas_id) { + $query->whereHas('siswa', function ($q) use ($request) { + $q->where('kelas_id', $request->kelas_id); + }); + } - // query nilai + relasi siswa & mapel - $query = \App\Models\Nilai::with(['siswa.kelas', 'mapel']) - ->where('mapel_id', $mapel_id); + // Filter siswa (opsional) + if ($request->siswa_id) { + $query->where('siswa_id', $request->siswa_id); + } - // π₯ FILTER KELAS - if ($request->kelas_id) { - $query->whereHas('siswa', function ($q) use ($request) { - $q->where('kelas_id', $request->kelas_id); - }); + $nilai = $query->get(); + + // Siswa berdasarkan kelas yang dipilih (untuk dropdown filter siswa) + $siswaList = collect(); + if ($request->kelas_id) { + $siswaList = Siswa::where('kelas_id', $request->kelas_id)->get(); + } + + return view('guru.nilai.index', compact('nilai', 'kelas', 'siswaList')); } - $nilai = $query->get(); + // ββ HITUNG RATA-RATA: simpan nilai_akhir ke kolom rata ββββββββββ + public function hitungRata(Request $request) + { + $request->validate([ + 'kelas_id' => 'required', + ]); - return view('guru.nilai.index', compact('nilai', 'kelas')); -} - // π Halaman input nilai per kelas - public function inputNilai() -{ - $kelas = Kelas::all(); + $user = auth()->user(); + $mapel_id = $user->mapel_id; - // π₯ ambil mapel sesuai guru login - $mapel = MataPelajaran::find(auth()->user()->mapel_id); + // Ambil semua nilai siswa di kelas ini untuk mapel guru + $nilaiList = Nilai::with('siswa') + ->where('mapel_id', $mapel_id) + ->whereHas('siswa', fn($q) => $q->where('kelas_id', $request->kelas_id)) + ->get(); - if (!$mapel) { - return back()->with('error', 'Mapel belum disetting!'); + foreach ($nilaiList as $n) { + // Hitung rata-rata dari tugas, uts, uas + $parts = []; + if ($n->nilai_tugas !== null) $parts[] = (float) $n->nilai_tugas; + if ($n->nilai_uts !== null) $parts[] = (float) $n->nilai_uts; + if ($n->nilai_uas !== null) $parts[] = (float) $n->nilai_uas; + + if (count($parts) > 0) { + $rata = round(array_sum($parts) / count($parts), 2); + $n->update(['nilai_akhir' => $rata]); + } + } + + return redirect()->route('guru.nilai.index', ['kelas_id' => $request->kelas_id]) + ->with('success', 'Rata-rata berhasil dihitung!'); } - return view('guru.nilai.input', compact('kelas', 'mapel')); -} + // ββ KIRIM NILAI KE SISWA (per kelas atau semua) βββββββββββββββββ + public function kirimKeSiswa(Request $request) + { + $user = auth()->user(); + $mapel_id = $user->mapel_id; - // π Ambil siswa berdasarkan kelas (AJAX) - public function getSiswaByKelas($id) -{ - $siswa = Siswa::where('kelas_id', $id)->get(); + $query = Nilai::where('mapel_id', $mapel_id) + ->whereNotNull('nilai_akhir'); - return response()->json($siswa); -} + // Kalau ada kelas_id, kirim per kelas saja + if ($request->kelas_id) { + $query->whereHas('siswa', fn($q) => $q->where('kelas_id', $request->kelas_id)); + } - // π Simpan nilai banyak sekaligus + $nilaiList = $query->get(); + + foreach ($nilaiList as $n) { + $n->update(['sudah_kirim' => 1]); + } + + $msg = $request->kelas_id + ? 'Nilai berhasil dikirim ke siswa kelas ini!' + : 'Nilai berhasil dikirim ke semua kelas!'; + + return redirect()->route('guru.nilai.index') + ->with('success', $msg); + } + + // ββ HALAMAN INPUT NILAI PER KELAS ββββββββββββββββββββββββββββββββ + public function inputNilai() + { + $kelas = Kelas::all(); + $mapel = MataPelajaran::find(auth()->user()->mapel_id); + + if (!$mapel) { + return back()->with('error', 'Mapel belum disetting!'); + } + + return view('guru.nilai.input', compact('kelas', 'mapel')); + } + + // ββ AJAX: ambil siswa berdasarkan kelas βββββββββββββββββββββββββ + public function getSiswaByKelas($id) + { + $siswa = Siswa::where('kelas_id', $id)->get(); + return response()->json($siswa); + } + + // ββ SIMPAN NILAI BATCH βββββββββββββββββββββββββββββββββββββββββββ public function storeBatch(Request $request) { $request->validate([ 'mapel_id' => 'required', - 'nilai' => 'required|array' + 'nilai' => 'required|array', ]); foreach ($request->nilai as $siswa_id => $nilai) { - if ($nilai != null) { Nilai::updateOrCreate( - [ - 'siswa_id' => $siswa_id, - 'mapel_id' => $request->mapel_id - ], - [ - 'nilai' => $nilai - ] + ['siswa_id' => $siswa_id, 'mapel_id' => $request->mapel_id], + ['nilai' => $nilai] ); } } return redirect()->route('guru.nilai.index') - ->with('success', 'Nilai berhasil disimpan'); + ->with('success', 'Nilai berhasil disimpan'); } - // ================= CRUD BIASA ================= - - public function create() - { - $siswa = Siswa::all(); - $mapel = MataPelajaran::all(); - - return view('guru.nilai.create', compact('siswa', 'mapel')); - } - - public function store(Request $request) + // ββ MASUKKAN NILAI TUGAS DARI MENU TUGAS βββββββββββββββββββββββββ + // Dipanggil dari tombol di halaman tugas / rekap rata-rata + public function masukkanNilaiTugas(Request $request) { $request->validate([ - 'siswa_id' => 'required', - 'mapel_id' => 'required', - 'nilai' => 'required|numeric|min:0|max:100', + 'siswa_id' => 'required', + 'nilai_tugas' => 'required|numeric|min:0|max:100', ]); - Nilai::create($request->all()); + $user = auth()->user(); + $mapel_id = $user->mapel_id; - return redirect()->route('guru.nilai.index') - ->with('success', 'Data nilai berhasil ditambahkan'); + Nilai::updateOrCreate( + ['siswa_id' => $request->siswa_id, 'mapel_id' => $mapel_id], + ['nilai_tugas' => $request->nilai_tugas] + ); + + return back()->with('success', 'Nilai tugas berhasil dimasukkan ke menu nilai!'); } - public function edit($id) -{ - $nilai = Nilai::with(['siswa', 'mapel'])->findOrFail($id); + // ββ MASUKKAN NILAI UJIAN DARI MENU UJIAN βββββββββββββββββββββββββ + // Dipanggil dari tombol di menu ujian per ujian (UTS/UAS) + public function masukkanNilaiUjian(Request $request) + { + $request->validate([ + 'ujian_id' => 'required', + ]); - return view('guru.nilai.edit', compact('nilai')); -} + $ujian = \App\Models\Ujian::with('kelas')->findOrFail($request->ujian_id); + $mapel_id = $ujian->mapel_id; + $jenis = strtolower($ujian->jenis); // 'uts' atau 'uas' + $kolom = 'nilai_' . $jenis; // 'nilai_uts' atau 'nilai_uas' + + // Ambil semua hasil ujian ini + $hasilList = \App\Models\Hasil::where('ujian_id', $ujian->id)->get(); + + foreach ($hasilList as $h) { + Nilai::updateOrCreate( + ['siswa_id' => $h->siswa_id, 'mapel_id' => $mapel_id], + [$kolom => $h->nilai] + ); + } + + return back()->with('success', 'Nilai ' . strtoupper($jenis) . ' berhasil dikirim ke menu nilai!'); + } + + // ββ EDIT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + public function edit($id) + { + $nilai = Nilai::with(['siswa', 'mapel'])->findOrFail($id); + return view('guru.nilai.edit', compact('nilai')); + } public function update(Request $request, $id) -{ - $request->validate([ - 'nilai' => 'required|numeric|min:0|max:100' - ]); + { + $request->validate([ + 'nilai_tugas' => 'nullable|numeric|min:0|max:100', + 'nilai_uts' => 'nullable|numeric|min:0|max:100', + 'nilai_uas' => 'nullable|numeric|min:0|max:100', + ]); - $nilai = Nilai::findOrFail($id); + $nilai = Nilai::findOrFail($id); + $nilai->update($request->only(['nilai_tugas', 'nilai_uts', 'nilai_uas'])); - $nilai->update([ - 'nilai' => $request->nilai - ]); - - return redirect()->route('guru.nilai.index') - ->with('success', 'Nilai berhasil diupdate'); -} + return redirect()->route('guru.nilai.index') + ->with('success', 'Nilai berhasil diupdate'); + } public function destroy($id) { Nilai::findOrFail($id)->delete(); - return redirect()->route('guru.nilai.index') - ->with('success', 'Data nilai berhasil dihapus'); + ->with('success', 'Data nilai berhasil dihapus'); } } \ No newline at end of file diff --git a/app/Http/Controllers/guru/TugasController.php b/app/Http/Controllers/guru/TugasController.php index 5f17097..1f0ada4 100644 --- a/app/Http/Controllers/guru/TugasController.php +++ b/app/Http/Controllers/guru/TugasController.php @@ -8,91 +8,108 @@ use App\Models\Kelas; use App\Models\MataPelajaran; use App\Models\PengumpulanTugas; -use App\Models\Jadwal; // π₯ WAJIB +use App\Models\Jadwal; use Illuminate\Support\Facades\Storage; use Carbon\Carbon; use App\Models\Siswa; class TugasController extends Controller { - public function index() -{ - $guru = auth()->user()->guru; + public function index() + { + $guru = auth()->user()->guru; - $tugas = Tugas::with(['kelas', 'mapel']) - ->withCount('pengumpulan') - ->where('guru_id', $guru->id) - ->get(); + $tugas = Tugas::with(['kelas', 'mapel']) + ->withCount('pengumpulan') + ->where('guru_id', $guru->id) + ->get(); - foreach ($tugas as $t) { - $t->total_siswa = Siswa::where('kelas_id', $t->kelas_id)->count(); + foreach ($tugas as $t) { + $t->total_siswa = Siswa::where('kelas_id', $t->kelas_id)->count(); + } + + $tugasIds = $tugas->pluck('id'); + + // ----------------------------------------------------------------- + // Rekap nilai dikelompokkan per KELAS (untuk filter JS di view) + // Format: { "X IPA 1": [ {id, nama, inisial, jumlah_tugas, rata_rata}, β¦ ], β¦ } + // ----------------------------------------------------------------- + $rekapPerKelas = \App\Models\PengumpulanTugas::with(['siswa', 'tugas.kelas']) + ->whereIn('tugas_id', $tugasIds) + ->whereNotNull('nilai') + ->get() + ->groupBy(function ($p) { + // Kelompokkan berdasarkan nama kelas siswa + return $p->tugas->kelas->nama_kelas ?? 'Tidak Diketahui'; + }) + ->map(function ($kelasGroup) { + // Di dalam setiap kelas, kelompokkan lagi per siswa + return $kelasGroup + ->groupBy('siswa_id') + ->map(function ($kumpulan) { + $siswa = $kumpulan->first()->siswa; + $nilaiList = $kumpulan->pluck('nilai')->map(fn ($n) => (float) $n); + $rata = round($nilaiList->avg(), 1); + + return [ + 'id' => $siswa->id ?? 0, + 'nama' => $siswa->nama ?? '-', + 'inisial' => strtoupper(substr($siswa->nama ?? 'S', 0, 1)) + . (isset(explode(' ', $siswa->nama ?? '')[1]) + ? strtoupper(substr(explode(' ', $siswa->nama)[1], 0, 1)) + : ''), + 'jumlah_tugas' => $kumpulan->count(), + 'rata_rata' => $rata, + ]; + }) + ->values(); // reset key jadi array numerik + }); + + // Juga tetap sediakan $rekapNilai (flat) kalau masih dipakai view lain + $rekapNilai = $rekapPerKelas->flatten(1)->values(); + + return view('guru.tugas.index', compact('tugas', 'rekapNilai', 'rekapPerKelas')); } - // Ambil semua pengumpulan dari semua tugas guru ini - $tugasIds = $tugas->pluck('id'); + public function create() + { + $kelas = Kelas::all(); + $guru = auth()->user()->guru; + $mapel = $guru->mapelRel; - $rekapNilai = \App\Models\PengumpulanTugas::with('siswa') - ->whereIn('tugas_id', $tugasIds) - ->whereNotNull('nilai') - ->get() - ->groupBy('siswa_id') - ->map(function($kumpulan) { - $siswa = $kumpulan->first()->siswa; - $nilaiList = $kumpulan->pluck('nilai')->map(fn($n) => (float)$n); - $rata = round($nilaiList->avg(), 1); - return [ - 'nama' => $siswa->nama ?? '-', - 'inisial' => strtoupper(substr($siswa->nama ?? 'S', 0, 1)), - 'jumlah_tugas' => $kumpulan->count(), - 'rata_rata' => $rata, - ]; - }) - ->values(); - - return view('guru.tugas.index', compact('tugas', 'rekapNilai')); -} - -public function create() -{ - $kelas = Kelas::all(); - $guru = auth()->user()->guru; - - $mapel = $guru->mapelRel; - - return view('guru.tugas.create', compact('kelas', 'mapel')); -} - - public function store(Request $request) -{ - $guru = auth()->user()->guru; - - $request->validate([ - 'kelas_id' => 'required', - 'judul' => 'required', - 'deadline' => 'required', - 'file' => 'nullable|mimes:pdf,doc,docx,ppt,pptx|max:2048' - ]); - - $filePath = null; - - // π₯ INI BAGIAN PALING PENTING - if ($request->hasFile('file')) { - $filePath = $request->file('file')->store('tugas_file', 'public'); + return view('guru.tugas.create', compact('kelas', 'mapel')); } - Tugas::create([ - 'guru_id' => $guru->id, - 'kelas_id' => $request->kelas_id, - 'mapel_id' => $guru->mapel_id, - 'judul' => $request->judul, - 'deskripsi' => $request->deskripsi, - 'file' => $filePath, - 'deadline' => str_replace('T', ' ', $request->deadline), -]); + public function store(Request $request) + { + $guru = auth()->user()->guru; + + $request->validate([ + 'kelas_id' => 'required', + 'judul' => 'required', + 'deadline' => 'required', + 'file' => 'nullable|mimes:pdf,doc,docx,ppt,pptx|max:2048', + ]); + + $filePath = null; + if ($request->hasFile('file')) { + $filePath = $request->file('file')->store('tugas_file', 'public'); + } + + Tugas::create([ + 'guru_id' => $guru->id, + 'kelas_id' => $request->kelas_id, + 'mapel_id' => $guru->mapel_id, + 'judul' => $request->judul, + 'deskripsi' => $request->deskripsi, + 'file' => $filePath, + 'deadline' => str_replace('T', ' ', $request->deadline), + ]); + + return redirect()->route('guru.tugas.index') + ->with('success', 'Tugas berhasil dibuat'); + } - return redirect()->route('guru.tugas.index') - ->with('success', 'Tugas berhasil dibuat'); -} public function getMapelByKelas($id) { $guruId = auth()->user()->guru->id; @@ -106,94 +123,95 @@ public function getMapelByKelas($id) return response()->json($mapel); } + public function show($id) -{ - $tugas = Tugas::with(['kelas', 'mapel'])->findOrFail($id); + { + $tugas = Tugas::with(['kelas', 'mapel'])->findOrFail($id); - return view('guru.tugas.show', compact('tugas')); -} -public function edit($id) -{ - $tugas = Tugas::findOrFail($id); - $kelas = Kelas::all(); - - return view('guru.tugas.edit', compact('tugas', 'kelas')); -} -public function update(Request $request, $id) -{ - $request->validate([ - 'judul' => 'required', - 'kelas_id' => 'required', - 'deadline' => 'required' - ]); - - $tugas = Tugas::findOrFail($id); - - $tugas->update([ - 'judul' => $request->judul, - 'kelas_id' => $request->kelas_id, - 'deskripsi' => $request->deskripsi, - 'deadline' => $request->deadline, - ]); - - return redirect()->route('guru.tugas.index') - ->with('success', 'Tugas berhasil diupdate'); -} - -public function download($id) -{ - $tugas = Tugas::findOrFail($id); - - if (!$tugas->file) { - abort(404, 'File tidak ditemukan'); + return view('guru.tugas.show', compact('tugas')); } - $path = storage_path('app/public/' . $tugas->file); + public function edit($id) + { + $tugas = Tugas::findOrFail($id); + $kelas = Kelas::all(); - if (!file_exists($path)) { - abort(404, 'File tidak ada di storage'); + return view('guru.tugas.edit', compact('tugas', 'kelas')); } - return response()->download($path); -} + public function update(Request $request, $id) + { + $request->validate([ + 'judul' => 'required', + 'kelas_id' => 'required', + 'deadline' => 'required', + ]); -public function pengumpulan($id) -{ - $tugas = Tugas::with(['pengumpulan.siswa'])->findOrFail($id); + $tugas = Tugas::findOrFail($id); - $deadline = Carbon::parse($tugas->deadline); + $tugas->update([ + 'judul' => $request->judul, + 'kelas_id' => $request->kelas_id, + 'deskripsi' => $request->deskripsi, + 'deadline' => $request->deadline, + ]); - foreach ($tugas->pengumpulan as $p) { - $waktu = Carbon::parse($p->created_at); - $p->status = $waktu->gt($deadline) ? 'telat' : 'tepat'; + return redirect()->route('guru.tugas.index') + ->with('success', 'Tugas berhasil diupdate'); } - // Siapkan data untuk JS rata-rata - $pengumpulanJson = $tugas->pengumpulan->map(function($p) { - return [ - 'nama' => $p->siswa->nama ?? '-', - 'inisial' => strtoupper(substr($p->siswa->nama ?? 'S', 0, 1)), - 'nilai' => $p->nilai, - ]; - }); + public function download($id) + { + $tugas = Tugas::findOrFail($id); - return view('guru.tugas.pengumpulan', compact('tugas', 'pengumpulanJson')); -} + if (!$tugas->file) { + abort(404, 'File tidak ditemukan'); + } -public function nilai(Request $request, $id) -{ - $request->validate([ - 'nilai' => 'required|integer|min:0|max:100', - 'komentar' => 'nullable' - ]); + $path = storage_path('app/public/' . $tugas->file); - $pengumpulan = \App\Models\PengumpulanTugas::findOrFail($id); + if (!file_exists($path)) { + abort(404, 'File tidak ada di storage'); + } - $pengumpulan->update([ - 'nilai' => $request->nilai, - 'komentar' => $request->komentar - ]); + return response()->download($path); + } - return back()->with('success', 'Nilai berhasil disimpan'); -} + public function pengumpulan($id) + { + $tugas = Tugas::with(['pengumpulan.siswa'])->findOrFail($id); + $deadline = Carbon::parse($tugas->deadline); + + foreach ($tugas->pengumpulan as $p) { + $waktu = Carbon::parse($p->created_at); + $p->status = $waktu->gt($deadline) ? 'telat' : 'tepat'; + } + + $pengumpulanJson = $tugas->pengumpulan->map(function ($p) { + return [ + 'nama' => $p->siswa->nama ?? '-', + 'inisial' => strtoupper(substr($p->siswa->nama ?? 'S', 0, 1)), + 'nilai' => $p->nilai, + ]; + }); + + return view('guru.tugas.pengumpulan', compact('tugas', 'pengumpulanJson')); + } + + public function nilai(Request $request, $id) + { + $request->validate([ + 'nilai' => 'required|integer|min:0|max:100', + 'komentar' => 'nullable', + ]); + + $pengumpulan = \App\Models\PengumpulanTugas::findOrFail($id); + + $pengumpulan->update([ + 'nilai' => $request->nilai, + 'komentar' => $request->komentar, + ]); + + return back()->with('success', 'Nilai berhasil disimpan'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/siswa/NilaiController.php b/app/Http/Controllers/siswa/NilaiController.php index 98cf22a..70f6c44 100644 --- a/app/Http/Controllers/siswa/NilaiController.php +++ b/app/Http/Controllers/siswa/NilaiController.php @@ -5,90 +5,115 @@ use App\Http\Controllers\Controller; use App\Models\Nilai; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Schema; class NilaiController extends Controller { public function index() -{ - $siswa_id = auth()->user()->siswa_id; + { + $siswa_id = Auth::user()->siswa_id; - // πΉ Ambil nilai tugas - $tugas = \App\Models\PengumpulanTugas::with('tugas.mapel') - ->where('siswa_id', $siswa_id) - ->get(); + // Cek apakah kolom sudah_kirim sudah ada di tabel nilai + $hasSudahKirim = Schema::hasColumn('nilai', 'sudah_kirim'); - // πΉ Ambil nilai ujian - $ujian = \App\Models\Hasil::with('ujian.mapel') - ->where('siswa_id', $siswa_id) - ->get(); + $query = Nilai::with('mapel') + ->where('siswa_id', $siswa_id); - // π₯ REKAP DATA - $data = []; + // Hanya filter sudah_kirim kalau kolomnya sudah ada + if ($hasSudahKirim) { + $query->where('sudah_kirim', 1); + } - // ======================= - // πΉ PROSES NILAI TUGAS - // ======================= - foreach ($tugas as $t) { - $mapel_id = $t->tugas->mapel->id ?? null; + $nilaiGuru = $query->get(); - if (!$mapel_id) continue; + // Rekap dari nilai guru + $data = []; + foreach ($nilaiGuru as $n) { + $mapel_id = $n->mapel_id; + if (!$mapel_id) continue; + + // Kalau kolom sudah_kirim belum ada, tampilkan yang nilai_akhir-nya sudah terisi saja + if (!$hasSudahKirim && $n->nilai_akhir === null) continue; - if (!isset($data[$mapel_id])) { $data[$mapel_id] = [ - 'mapel' => $t->tugas->mapel->nama_mapel, - 'tugas' => [], - 'uts' => null, - 'uas' => null, + 'mapel' => $n->mapel->nama_mapel ?? 'β', + 'tugas' => $n->nilai_tugas !== null ? round($n->nilai_tugas) : null, + 'uts' => $n->nilai_uts !== null ? round($n->nilai_uts) : null, + 'uas' => $n->nilai_uas !== null ? round($n->nilai_uas) : null, + 'rata' => $n->nilai_akhir !== null ? round($n->nilai_akhir) : null, ]; } - $data[$mapel_id]['tugas'][] = $t->nilai; + // Fallback ke sistem lama kalau belum ada data dari guru + if (empty($data)) { + $data = $this->getRekapsLama($siswa_id); + } + + return view('siswa.nilai.index', compact('data')); } - // ======================= - // πΉ PROSES NILAI UJIAN - // ======================= - foreach ($ujian as $u) { - $mapel_id = $u->ujian->mapel->id ?? null; + /** + * Fallback rekap dari PengumpulanTugas & Hasil (sistem lama) + */ + private function getRekapsLama($siswa_id): array + { + $tugas = \App\Models\PengumpulanTugas::with('tugas.mapel') + ->where('siswa_id', $siswa_id)->get(); - if (!$mapel_id) continue; + $ujian = \App\Models\Hasil::with('ujian.mapel') + ->where('siswa_id', $siswa_id)->get(); - if (!isset($data[$mapel_id])) { - $data[$mapel_id] = [ - 'mapel' => $u->ujian->mapel->nama_mapel, - 'tugas' => [], - 'uts' => null, - 'uas' => null, + $data = []; + + foreach ($tugas as $t) { + $mapel_id = $t->tugas->mapel->id ?? null; + if (!$mapel_id) continue; + if (!isset($data[$mapel_id])) { + $data[$mapel_id] = [ + 'mapel' => $t->tugas->mapel->nama_mapel, + 'tugas_arr' => [], + 'uts' => null, + 'uas' => null, + ]; + } + if ($t->nilai !== null) { + $data[$mapel_id]['tugas_arr'][] = $t->nilai; + } + } + + foreach ($ujian as $u) { + $mapel_id = $u->ujian->mapel->id ?? null; + if (!$mapel_id) continue; + if (!isset($data[$mapel_id])) { + $data[$mapel_id] = [ + 'mapel' => $u->ujian->mapel->nama_mapel, + 'tugas_arr' => [], + 'uts' => null, + 'uas' => null, + ]; + } + if (strtoupper($u->ujian->jenis) === 'UTS') $data[$mapel_id]['uts'] = $u->nilai; + if (strtoupper($u->ujian->jenis) === 'UAS') $data[$mapel_id]['uas'] = $u->nilai; + } + + foreach ($data as $k => $d) { + $rata_tugas = count($d['tugas_arr']) + ? array_sum($d['tugas_arr']) / count($d['tugas_arr']) + : null; + $uts = $d['uts'] ?? null; + $uas = $d['uas'] ?? null; + $parts = array_filter([$rata_tugas, $uts, $uas], fn($v) => $v !== null); + $rata = count($parts) ? round(array_sum($parts) / count($parts)) : null; + + $data[$k] = [ + 'mapel' => $d['mapel'], + 'tugas' => $rata_tugas !== null ? round($rata_tugas) : null, + 'uts' => $uts !== null ? round($uts) : null, + 'uas' => $uas !== null ? round($uas) : null, + 'rata' => $rata, ]; } - if ($u->ujian->jenis == 'UTS') { - $data[$mapel_id]['uts'] = $u->nilai; - } - - if ($u->ujian->jenis == 'UAS') { - $data[$mapel_id]['uas'] = $u->nilai; - } + return $data; } - - // ======================= - // πΉ HITUNG RATA-RATA - // ======================= - foreach ($data as $k => $d) { - - $rata_tugas = count($d['tugas']) - ? array_sum($d['tugas']) / count($d['tugas']) - : 0; - - $uts = $d['uts'] ?? 0; - $uas = $d['uas'] ?? 0; - - $rata = ($rata_tugas + $uts + $uas) / 3; - - $data[$k]['tugas'] = round($rata_tugas); - $data[$k]['rata'] = round($rata); - } - - return view('siswa.nilai.index', compact('data')); -} } \ No newline at end of file diff --git a/app/Models/Nilai.php b/app/Models/Nilai.php index 773aec9..a852dcb 100644 --- a/app/Models/Nilai.php +++ b/app/Models/Nilai.php @@ -11,14 +11,12 @@ class Nilai extends Model protected $fillable = [ 'siswa_id', 'mapel_id', - - // π₯ lama (jangan dihapus dulu) - 'nilai', - - // π₯ baru + 'nilai', // lama, jangan dihapus 'nilai_tugas', 'nilai_uts', - 'nilai_uas' + 'nilai_uas', + 'nilai_akhir', // hasil hitung rata-rata (disimpan permanen) + 'sudah_kirim', // 0 = belum kirim ke siswa, 1 = sudah kirim ]; public function siswa() @@ -31,19 +29,26 @@ public function mapel() return $this->belongsTo(MataPelajaran::class, 'mapel_id'); } - // π₯ helper biar fleksibel (INI KUNCI AMAN) - public function getNilaiAkhirAttribute() + /** + * Accessor: hitung nilai akhir dinamis (kalau kolom nilai_akhir belum diisi) + */ + public function getNilaiAkhirDinamisAttribute() { - // kalau pakai sistem baru - if ($this->nilai_tugas || $this->nilai_uts || $this->nilai_uas) { - return collect([ - $this->nilai_tugas, - $this->nilai_uts, - $this->nilai_uas - ])->filter()->avg(); + if ($this->nilai_akhir !== null) { + return $this->nilai_akhir; } - // fallback ke sistem lama + $parts = array_filter([ + $this->nilai_tugas !== null ? (float) $this->nilai_tugas : null, + $this->nilai_uts !== null ? (float) $this->nilai_uts : null, + $this->nilai_uas !== null ? (float) $this->nilai_uas : null, + ], fn($v) => $v !== null); + + if (count($parts) > 0) { + return round(array_sum($parts) / count($parts), 2); + } + + // Fallback ke nilai lama return $this->nilai; } } \ No newline at end of file diff --git a/composer.json b/composer.json index cedc908..8980ddc 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "2.2", "laravel/framework": "^11.31", "laravel/tinker": "^2.9", "simplesoftwareio/simple-qrcode": "^4.2" diff --git a/composer.lock b/composer.lock index 03f90a7..cc03ae0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba0b871c4e08ecaa364989ce5e6f5138", + "content-hash": "2135f2e9f15e9229e297b7ea4e12f164", "packages": [ { "name": "bacon/bacon-qr-code", @@ -66,6 +66,89 @@ }, "time": "2022-12-07T17:46:57+00:00" }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/c96f90c97666cebec154ca1ffb67afed372114d8", + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dompdf/dompdf": "^2.0.7", + "illuminate/support": "^6|^7|^8|^9|^10|^11", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "larastan/larastan": "^1.0|^2.7.0", + "orchestra/testbench": "^4|^5|^6|^7|^8|^9", + "phpro/grumphp": "^1 || ^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2024-04-25T13:16:04+00:00" + }, { "name": "brick/math", "version": "0.12.3", @@ -493,6 +576,74 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v2.0.8", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "c20247574601700e1f7c8dab39310fca1964dc52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/c20247574601700e1f7c8dab39310fca1964dc52", + "reference": "c20247574601700e1f7c8dab39310fca1964dc52", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "phenx/php-font-lib": ">=0.5.4 <1.0.0", + "phenx/php-svg-lib": ">=0.5.2 <1.0.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v2.0.8" + }, + "time": "2024-04-29T13:06:17+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.4.0", @@ -2121,6 +2272,79 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "masterminds/html5", + "version": "2.8.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" + }, + "time": "2023-05-10T11:58:31+00:00" + }, { "name": "monolog/monolog", "version": "3.9.0", @@ -2625,6 +2849,108 @@ ], "time": "2025-05-08T08:14:37+00:00" }, + { + "name": "phenx/php-font-lib", + "version": "0.5.6", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a1681e9793040740a405ac5b189275059e2a9863" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a1681e9793040740a405ac5b189275059e2a9863", + "reference": "a1681e9793040740a405ac5b189275059e2a9863", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-mbstring": "*" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Fabien MΓ©nager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/0.5.6" + }, + "time": "2024-01-29T14:45:26+00:00" + }, + { + "name": "phenx/php-svg-lib", + "version": "0.5.3", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "0e46722c154726a5f9ac218197ccc28adba16fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/0e46722c154726a5f9ac218197ccc28adba16fcf", + "reference": "0e46722c154726a5f9ac218197ccc28adba16fcf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Fabien MΓ©nager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/PhenX/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.3" + }, + "time": "2024-02-23T20:39:24+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -3388,6 +3714,78 @@ }, "time": "2025-06-25T14:20:11+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.9.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-iconv": "*", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + }, + "time": "2025-07-11T13:20:48+00:00" + }, { "name": "simplesoftwareio/simple-qrcode", "version": "4.2.0", diff --git a/database/migrations/2026_05_09_213247_add_sudah_kirim_to_nilai_table.php b/database/migrations/2026_05_09_213247_add_sudah_kirim_to_nilai_table.php new file mode 100644 index 0000000..dedb9bf --- /dev/null +++ b/database/migrations/2026_05_09_213247_add_sudah_kirim_to_nilai_table.php @@ -0,0 +1,25 @@ +boolean('sudah_kirim')->default(false)->after('nilai_akhir'); + }); +} + +public function down(): void +{ + Schema::table('nilai', function (Blueprint $table) { + $table->dropColumn('sudah_kirim'); + }); +} +}; diff --git a/public/uploads/1778813458.jpg b/public/uploads/1778813458.jpg new file mode 100644 index 0000000..e8ddd9d Binary files /dev/null and b/public/uploads/1778813458.jpg differ diff --git a/public/uploads/1778814685.jpg b/public/uploads/1778814685.jpg new file mode 100644 index 0000000..e8ddd9d Binary files /dev/null and b/public/uploads/1778814685.jpg differ diff --git a/resources/views/guru/nilai/edit.blade.php b/resources/views/guru/nilai/edit.blade.php index 683232d..5e8f3f4 100644 --- a/resources/views/guru/nilai/edit.blade.php +++ b/resources/views/guru/nilai/edit.blade.php @@ -1,173 +1,83 @@ @extends('layouts.app') @section('content') - -
Ubah nilai tugas, UTS, dan UAS
+Perbarui nilai akademik siswa
-Kelola dan pantau nilai akademik siswa
+Rekap nilai tugas, UTS, UAS & hitung rata-rata siswa