diff --git a/app/Http/Controllers/Api/GuruController.php b/app/Http/Controllers/Api/GuruController.php index 0c45f53..4be7ee6 100644 --- a/app/Http/Controllers/Api/GuruController.php +++ b/app/Http/Controllers/Api/GuruController.php @@ -13,6 +13,68 @@ class GuruController extends Controller { + // Input Rapot Guru Mobile + public function storeRapot(Request $request) + { + $request->validate([ + 'siswa_id' => 'required', + 'semester' => 'required|string', + 'tahun_ajaran' => 'required|string', + 'nilai_aik' => 'required|string', + 'nilai_budi_pekerti' => 'required|string', + 'nilai_jati_diri' => 'required|string', + 'nilai_literasi_steam' => 'required|string', + 'nilai_kokurikuler' => 'required|string', + // 'catatan_guru' => 'nullable|string', // Admin Rapot model does not have catatan_guru + 'tinggi_badan' => 'required|numeric', + 'berat_badan' => 'required|numeric', + 'lingkar_kepala' => 'required|numeric', + 'sakit' => 'required|numeric', + 'izin' => 'required|numeric', + 'alpha' => 'required|numeric', + ]); + + try { + // Gunakan Model Rapot (sesuai Web Admin) + $rapot = new \App\Models\Rapot(); + $rapot->siswa_id = $request->siswa_id; + $rapot->semester = $request->semester; + $rapot->tahun_ajaran = $request->tahun_ajaran; + $rapot->tanggal_rapot = now()->format('Y-m-d'); + + // Map ke kolom Model Rapot + $rapot->narasi_agama = $request->nilai_aik; + $rapot->narasi_budi_pekerti = $request->nilai_budi_pekerti; + $rapot->narasi_jati_diri = $request->nilai_jati_diri; + $rapot->narasi_literasi = $request->nilai_literasi_steam; + $rapot->narasi_kokurikuler = $request->nilai_kokurikuler; + + $rapot->tinggi_badan = $request->tinggi_badan; + $rapot->berat_badan = $request->berat_badan; + $rapot->lingkar_kepala = $request->lingkar_kepala; + $rapot->sakit = $request->sakit; + $rapot->izin = $request->izin; + $rapot->alpha = $request->alpha; + + // Default Guru name from logged in user if available + $rapot->nama_guru = auth()->user()->name ?? 'Guru PAUD'; + + $rapot->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Data Rapot berhasil disimpan', + 'data' => $rapot + ], 201); + + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Gagal menyimpan rapot: ' . $e->getMessage() + ], 500); + } + } + // 1. Input Catatan Anekdot public function storeAnekdot(Request $request) { @@ -91,21 +153,23 @@ public function storeCeklis(Request $request) { // Validasi disesuaikan dengan struktur tabel penilaian_ceklis kamu $request->validate([ - 'siswa_id' => 'required|exists:siswas,id', - 'tanggal' => 'required|date', - 'indikator' => 'required|string', - 'hasil' => 'required|in:BB,MB,BSH,BSB', // Validasi skala PAUD - 'keterangan'=> 'nullable|string', + 'siswa_id' => 'required|exists:siswas,id', + 'tanggal' => 'required|date', + 'aspek_perkembangan' => 'required|string', + 'indikator' => 'required|string', + 'hasil' => 'required|in:BB,MB,BSH,BSB', // Validasi skala PAUD + 'keterangan' => 'nullable|string', ]); // Simpan data ke database $ceklis = PenilaianCeklis::create([ - 'siswa_id' => $request->siswa_id, - 'guru_id' => $request->user()->id, // Ambil ID dari Guru yang login - 'tanggal' => $request->tanggal, - 'indikator' => $request->indikator, - 'hasil' => $request->hasil, - 'keterangan'=> $request->keterangan, + 'siswa_id' => $request->siswa_id, + 'guru_id' => $request->user()->id, // Ambil ID dari Guru yang login + 'tanggal' => $request->tanggal, + 'aspek_perkembangan' => $request->aspek_perkembangan, + 'indikator' => $request->indikator, + 'hasil' => $request->hasil, + 'keterangan' => $request->keterangan, ]); return response()->json([ diff --git a/app/Http/Controllers/Api/WaliController.php b/app/Http/Controllers/Api/WaliController.php index 87e9680..328f1a3 100644 --- a/app/Http/Controllers/Api/WaliController.php +++ b/app/Http/Controllers/Api/WaliController.php @@ -60,17 +60,9 @@ public function getDashboard(Request $request) $progressPerkembangan = []; foreach ($aspekList as $aspek) { - // Filter ceklis yang nama indikatornya mengandung kata kunci aspek - // Karena nama indikator di database bisa bervariasi, kita buat mapping persentase sederhana - // Nilai: BM=25%, MB=50%, BSH=75%, BSB=100% - $ceklisAspek = $ceklis->filter(function($item) use ($aspek) { - // Di sistem riil, indikator harus punya relasi 'aspek'. - // Karena disini plain text, kita simulasi ambil rata-rata secara mock atau jika ada kategori. - // Disini kita akan buat dummy fallback atau mock calculation per siswa untuk UI Showcase. - return true; - })->random(min($ceklis->count(), 2)); + // Murni kalkulasi berdasarkan data riil yang match aspeknya + $ceklisAspek = $ceklis->where('aspek_perkembangan', $aspek); - // Kalkulasi real-ish: $totalScore = 0; $count = 0; foreach ($ceklisAspek as $c) { @@ -81,7 +73,8 @@ public function getDashboard(Request $request) $count++; } - $percentage = $count > 0 ? round($totalScore / $count) : 0; // fallback ke 0 jika blm ada nilai + // Murni kalkulasi berdasarkan data riil, 0 jika kosong + $percentage = $count > 0 ? round($totalScore / $count) : 0; $progressPerkembangan[] = [ 'aspek' => $aspek, @@ -89,6 +82,11 @@ public function getDashboard(Request $request) ]; } + // 5. Relasi data penjemputan terakhir + $penjemputanTerakhir = \App\Models\Penjemputan::where('siswa_id', $siswa->id) + ->latest() + ->first(); + // Return Data return response()->json([ 'success' => true, @@ -98,10 +96,92 @@ public function getDashboard(Request $request) 'nis' => $siswa->nis, 'nama' => $siswa->nama_siswa, 'kelas' => $siswa->kelompok->nama_kelompok ?? '-', + 'foto' => $siswa->foto ?? null, ], 'pengumuman' => $pengumuman, - 'progress' => $progressPerkembangan + 'progress' => $progressPerkembangan, + 'penjemputan_terakhir' => $penjemputanTerakhir ] ], 200); } + + // --- KHUSUS WALI MURID: Riwayat Penilaian Anak Spesifik (PRIVASI) --- + public function getRiwayatAnak($id) + { + $user = Auth::user(); + + // 1. Dapatkan Profil Wali + $wali = WaliMurid::where('user_id', $user->id)->first(); + if (!$wali) { + return response()->json(['success' => false, 'message' => 'Profil Wali Murid tidak ditemukan'], 404); + } + + // 2. Pastikan Siswa yang diminta benar-benar anak dari wali ini (Keamanan Privasi) + $siswa = Siswa::where('id', $id)->where('wali_murid_id', $wali->id)->first(); + if (!$siswa) { + return response()->json(['success' => false, 'message' => 'Anda tidak memiliki akses ke data siswa ini'], 403); + } + + // 3. Ambil Semua Data Riwayat Khusus Anak Ini Saja + $anekdot = \App\Models\Anekdot::with('siswa') + ->where('siswa_id', $siswa->id) + ->latest() + ->get(); + + $ceklis = PenilaianCeklis::with('siswa') + ->where('siswa_id', $siswa->id) + ->latest() + ->get(); + + $karya = \App\Models\HasilKarya::with('siswa') + ->where('siswa_id', $siswa->id) + ->latest() + ->get() + ->map(function ($item) { + if ($item->foto) { + $item->foto_url = url('storage/' . $item->foto); + } + return $item; + }); + + return response()->json([ + 'status' => 'success', + 'data' => [ + 'anekdot' => $anekdot, + 'ceklis' => $ceklis, + 'karya' => $karya + ] + ], 200); + } + + // --- KHUSUS WALI MURID: Rapot Penilaian Anak (Semester) --- + public function getRapotAnak($id) + { + $user = Auth::user(); + + $wali = WaliMurid::where('user_id', $user->id)->first(); + if (!$wali) { + return response()->json(['success' => false, 'message' => 'Profil Wali Murid tidak ditemukan'], 404); + } + + $siswa = Siswa::where('id', $id)->where('wali_murid_id', $wali->id)->first(); + if (!$siswa) { + return response()->json(['success' => false, 'message' => 'Anda tidak memiliki akses ke data siswa ini'], 403); + } + + // Ambil SEMUA riwayat rapot untuk siswa ini + $rapots = \App\Models\Rapot::with('siswa') + ->where('siswa_id', $siswa->id) + ->orderBy('created_at', 'desc') + ->get(); + + if ($rapots->isEmpty()) { + return response()->json(['status' => 'error', 'message' => 'Rapot belum tersedia'], 404); + } + + return response()->json([ + 'status' => 'success', + 'data' => $rapots + ], 200); + } } diff --git a/app/Models/HasilKarya.php b/app/Models/HasilKarya.php index f6aeeb7..aeaba9f 100644 --- a/app/Models/HasilKarya.php +++ b/app/Models/HasilKarya.php @@ -19,4 +19,9 @@ class HasilKarya extends Model 'deskripsi_foto', // Pastikan ini sama persis dengan DB 'analisis_capaian' ]; + + public function siswa() + { + return $this->belongsTo(Siswa::class); + } } diff --git a/app/Models/PenilaianCeklis.php b/app/Models/PenilaianCeklis.php index 319e16f..73633ff 100644 --- a/app/Models/PenilaianCeklis.php +++ b/app/Models/PenilaianCeklis.php @@ -15,6 +15,7 @@ class PenilaianCeklis extends Model protected $fillable = [ 'siswa_id', 'guru_id', + 'aspek_perkembangan', // Kolom aspek_perkembangan 'indikator', // Sekarang sudah jadi teks 'tanggal', 'hasil', // Ingat, di database kamu namanya 'hasil' diff --git a/app/Models/PenilaianRapot.php b/app/Models/PenilaianRapot.php new file mode 100644 index 0000000..65bd682 --- /dev/null +++ b/app/Models/PenilaianRapot.php @@ -0,0 +1,40 @@ + 'array', + ]; + + public function siswa() + { + return $this->belongsTo(Siswa::class); + } +} diff --git a/database/migrations/2026_03_04_224613_add_aspek_perkembangan_to_penilaian_ceklis_table.php b/database/migrations/2026_03_04_224613_add_aspek_perkembangan_to_penilaian_ceklis_table.php new file mode 100644 index 0000000..83e9409 --- /dev/null +++ b/database/migrations/2026_03_04_224613_add_aspek_perkembangan_to_penilaian_ceklis_table.php @@ -0,0 +1,28 @@ +string('aspek_perkembangan')->nullable()->after('siswa_id'); // Atau diletakkan setelah guru_id/tanggal + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('penilaian_ceklis', function (Blueprint $table) { + $table->dropColumn('aspek_perkembangan'); + }); + } +}; diff --git a/database/migrations/2026_03_04_233054_create_penilaian_rapots_table.php b/database/migrations/2026_03_04_233054_create_penilaian_rapots_table.php new file mode 100644 index 0000000..0946bb3 --- /dev/null +++ b/database/migrations/2026_03_04_233054_create_penilaian_rapots_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('siswa_id')->constrained('siswas')->onDelete('cascade'); + $table->string('semester'); + $table->string('tahun_ajaran'); + $table->text('catatan_guru')->nullable(); + $table->string('nama_guru')->nullable(); + $table->json('nilai_aspek'); // Untuk menyimpan skor 6 aspek PAUD + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('penilaian_rapots'); + } +}; diff --git a/database/migrations/2026_03_05_013817_add_kurikulum_merdeka_to_penilaian_rapots_table.php b/database/migrations/2026_03_05_013817_add_kurikulum_merdeka_to_penilaian_rapots_table.php new file mode 100644 index 0000000..bc1126a --- /dev/null +++ b/database/migrations/2026_03_05_013817_add_kurikulum_merdeka_to_penilaian_rapots_table.php @@ -0,0 +1,47 @@ +text('nilai_aik')->nullable(); + $table->text('nilai_budi_pekerti')->nullable(); + $table->text('nilai_jati_diri')->nullable(); + $table->text('nilai_literasi_steam')->nullable(); + $table->text('nilai_kokurikuler')->nullable(); + + $table->integer('tinggi_badan')->nullable(); + $table->integer('berat_badan')->nullable(); + $table->integer('lingkar_kepala')->nullable(); + + $table->integer('sakit')->nullable(); + $table->integer('izin')->nullable(); + $table->integer('alpha')->nullable(); + + $table->string('file_pdf')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('penilaian_rapots', function (Blueprint $table) { + $table->dropColumn([ + 'nilai_aik', 'nilai_budi_pekerti', 'nilai_jati_diri', + 'nilai_literasi_steam', 'nilai_kokurikuler', + 'tinggi_badan', 'berat_badan', 'lingkar_kepala', + 'sakit', 'izin', 'alpha', 'file_pdf' + ]); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 4f213a1..c07f79d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -49,6 +49,8 @@ // --- KHUSUS WALI MURID --- Route::get('/wali/dashboard', [WaliController::class, 'getDashboard']); + Route::get('/wali/riwayat-anak/{id}', [WaliController::class, 'getRiwayatAnak']); + Route::get('/wali/rapot-anak/{id}', [WaliController::class, 'getRapotAnak']); // --- KHUSUS GURU (Input Data) --- // Nanti kalau Guru login di HP untuk input data: @@ -59,6 +61,7 @@ Route::post('/guru/karya', [GuruController::class, 'storeKarya']); Route::post('/guru/penjemputan', [GuruController::class, 'storePenjemputan']); Route::post('/guru/ceklis', [GuruController::class, 'storeCeklis']); + Route::post('/guru/rapot', [GuruController::class, 'storeRapot']); // RUTE PENJEMPUTAN BARU Route::post('/penjemputan', [PenjemputanController::class, 'store']); diff --git a/tinker_seeder.php b/tinker_seeder.php new file mode 100644 index 0000000..3ea07f5 --- /dev/null +++ b/tinker_seeder.php @@ -0,0 +1,22 @@ + $siswa->id], + [ + 'semester' => 'Genap', + 'tahun_ajaran' => '2025/2026', + 'catatan_guru' => 'Ananda sangat interaktif, kreatif, dan mandiri selama di kelas. Pertahankan prestasinya!', + 'nama_guru' => 'Siti Aminah, S.Pd', + 'nilai_aspek' => [ + 'Agama' => 'BSB', + 'Fisik' => 'BSB', + 'Kognitif' => 'BSH', + 'Bahasa' => 'MB', + 'SosEm' => 'BSB', + 'Seni' => 'BSB' + ] + ] + ); +} +echo "DB_RAPOT_SEEDED\n"; diff --git a/tinker_seeder_2.php b/tinker_seeder_2.php new file mode 100644 index 0000000..d5b4fde --- /dev/null +++ b/tinker_seeder_2.php @@ -0,0 +1,26 @@ + $siswa->id], + [ + 'semester' => 'Genap', + 'tahun_ajaran' => '2025/2026', + 'catatan_guru' => 'Ananda sangat berkembang...', + 'nama_guru' => 'Siti Aminah, S.Pd', + 'nilai_aik' => 'Alhamdulillah, ananda sudah mampu menghafal doa harian dengan baik.', + 'nilai_budi_pekerti' => 'Ananda menunjukkan sikap santun dan peduli pada teman sebayanya.', + 'nilai_jati_diri' => 'Berkembang dengan kemandirian yang tinggi saat bermain.', + 'nilai_literasi_steam' => 'Menunjukkan ketertarikan luar biasa pada balok susun dan warna.', + 'nilai_kokurikuler' => 'Sangat aktif dalam kegiatan seni budaya tari daerah.', + 'tinggi_badan' => 110, + 'berat_badan' => 18, + 'lingkar_kepala' => 50, + 'sakit' => 1, + 'izin' => 0, + 'alpha' => 0, + 'file_pdf' => 'rapot/dummy_rapot.pdf' + ] + ); +} +echo "DB_NEW_RAPOT_SEEDED\n";