MIF_E31230892/sim-pkpps/app/Http/Controllers/Api/ApiCapaianController.php

812 lines
32 KiB
PHP

<?php
// app/Http/Controllers/Api/ApiCapaianController.php
// UPDATED: Support sistem kelas baru (kelompok_kelas, kelas, santri_kelas)
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Capaian;
use App\Models\Santri;
use App\Models\SantriKelas;
use App\Models\Semester;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ApiCapaianController extends Controller
{
/**
* Helper: Build kelas info dari Santri model (sistem kelas baru)
* Returns kelas_primary & all_kelas arrays
*/
private function buildKelasInfo(Santri $santri): array
{
// Eager load relasi kelas jika belum loaded
if (!$santri->relationLoaded('kelasPrimary')) {
$santri->load('kelasPrimary.kelas.kelompok');
}
if (!$santri->relationLoaded('kelasSantri')) {
$santri->load('kelasSantri.kelas.kelompok');
}
// Kelas primary
$kelasPrimary = null;
$primaryRelation = $santri->kelasPrimary;
if ($primaryRelation && $primaryRelation->kelas) {
$kelas = $primaryRelation->kelas;
$kelompok = $kelas->kelompok;
$kelasPrimary = [
'id_kelas' => $kelas->id ?? null,
'kode_kelas' => $kelas->kode_kelas ?? null,
'nama_kelas' => $kelas->nama_kelas ?? 'Belum Ada Kelas',
'kelompok' => $kelompok ? $kelompok->nama_kelompok : null,
'id_kelompok' => $kelompok ? $kelompok->id_kelompok : null,
'tahun_ajaran' => $primaryRelation->tahun_ajaran ?? null,
'is_primary' => true,
];
}
// All kelas
$allKelas = $santri->kelasSantri
->filter(fn($sk) => $sk->kelas !== null)
->map(function ($sk) {
$kelas = $sk->kelas;
$kelompok = $kelas->kelompok;
return [
'id_kelas' => $kelas->id ?? null,
'kode_kelas' => $kelas->kode_kelas ?? null,
'nama_kelas' => $kelas->nama_kelas ?? '-',
'kelompok' => $kelompok ? $kelompok->nama_kelompok : null,
'id_kelompok' => $kelompok ? $kelompok->id_kelompok : null,
'tahun_ajaran' => $sk->tahun_ajaran ?? null,
'is_primary' => (bool) $sk->is_primary,
];
})->values()->toArray();
return [
'kelas_primary' => $kelasPrimary,
'all_kelas' => $allKelas,
];
}
/**
* Helper: Build santri info array with kelas baru
*/
private function buildSantriInfo(Santri $santri): array
{
$kelasData = $this->buildKelasInfo($santri);
return [
'id_santri' => $santri->id_santri,
'nama_lengkap' => $santri->nama_lengkap,
'kelas' => $santri->kelas_name, // backward compatible string
'kelas_primary' => $kelasData['kelas_primary'],
'all_kelas' => $kelasData['all_kelas'],
];
}
/**
* Helper: Get peer santri IDs yang sekelas (via santri_kelas pivot)
*/
private function getPeerSantriIds(Santri $santri, ?string $idSemester = null): array
{
$primaryKelasId = $santri->primary_kelas_id;
if (!$primaryKelasId) {
return [$santri->id_santri]; // hanya diri sendiri jika tidak punya kelas
}
return SantriKelas::where('id_kelas', $primaryKelasId)
->pluck('id_santri')
->unique()
->toArray();
}
/**
* GET OVERVIEW CAPAIAN SANTRI
* Endpoint: GET /api/v1/capaian/overview
*/
public function overview(Request $request)
{
try {
$user = $request->user();
// Validasi role
if (!in_array($user->role, ['santri', 'wali'])) {
return response()->json([
'success' => false,
'message' => 'Akses ditolak. Role: ' . $user->role,
], 403);
}
$idSantri = $user->role_id;
$santri = Santri::with(['kelasPrimary.kelas.kelompok', 'kelasSantri.kelas.kelompok'])
->where('id_santri', $idSantri)
->first();
if (!$santri) {
return response()->json([
'success' => false,
'message' => 'Data santri tidak ditemukan. ID: ' . $idSantri,
], 404);
}
$semesterAktif = Semester::aktif()->first();
$idSemester = $request->input('id_semester', $semesterAktif?->id_semester);
$query = Capaian::where('id_santri', $idSantri)
->with(['materi', 'semester']);
if ($idSemester) {
$query->where('id_semester', $idSemester);
}
$capaians = $query->get();
$capaiansBerisi = $capaians->where('persentase', '>', 0);
$totalMateri = $capaiansBerisi->count();
$rataRataProgress = $capaiansBerisi->isEmpty() ? 0 : $capaiansBerisi->avg('persentase');
$materiSelesai = $capaians->where('persentase', '>=', 100)->count();
$perKategori = [];
$kategoriList = ['Al-Qur\'an', 'Hadist', 'Materi Tambahan'];
foreach ($kategoriList as $kategori) {
$capaianKategori = $capaians->filter(function($c) use ($kategori) {
return $c->materi && $c->materi->kategori === $kategori;
});
$capaianKategoriBerisi = $capaianKategori->where('persentase', '>', 0);
$perKategori[] = [
'kategori' => $kategori,
'icon' => $this->getKategoriIcon($kategori),
'color' => $this->getKategoriColor($kategori),
'total_materi' => $capaianKategoriBerisi->count(),
'rata_rata_progress' => round($capaianKategoriBerisi->isEmpty() ? 0 : $capaianKategoriBerisi->avg('persentase'), 1),
'materi_selesai' => $capaianKategori->where('persentase', '>=', 100)->count(),
];
}
$semesters = Semester::select('id_semester', 'nama_semester', 'tahun_ajaran', 'periode', 'is_active')
->orderBy('tahun_ajaran', 'desc')
->orderBy('periode', 'desc')
->get()
->map(function($s) {
return [
'id_semester' => $s->id_semester,
'nama_semester' => $s->nama_semester,
'is_aktif' => $s->is_active == 1,
];
});
$response = [
'success' => true,
'data' => [
'santri' => $this->buildSantriInfo($santri),
'semester' => [
'id_semester' => $idSemester,
'nama_semester' => $semesterAktif?->nama_semester ?? 'Semua Semester',
'list_semester' => $semesters,
],
'statistik_umum' => [
'total_materi' => $totalMateri,
'rata_rata_progress' => round($rataRataProgress, 1),
'materi_selesai' => $materiSelesai,
],
'per_kategori' => $perKategori,
],
];
return response()->json($response, 200);
} catch (\Exception $e) {
Log::error('Error di Capaian Overview', [
'message' => $e->getMessage(),
'line' => $e->getLine(),
'file' => $e->getFile(),
]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
/**
* GET LIST MATERI PER KATEGORI
* Endpoint: GET /api/v1/capaian/kategori/{kategori}
*/
public function listMateriByKategori(Request $request, $kategori)
{
try {
$user = $request->user();
$idSantri = $user->role_id;
$validKategori = ['Al-Qur\'an', 'Hadist', 'Materi Tambahan'];
if (!in_array($kategori, $validKategori)) {
return response()->json([
'success' => false,
'message' => 'Kategori tidak valid: ' . $kategori,
], 400);
}
$santri = Santri::with(['kelasPrimary.kelas.kelompok'])->where('id_santri', $idSantri)->first();
if (!$santri) {
return response()->json(['success' => false, 'message' => 'Data santri tidak ditemukan'], 404);
}
$semesterAktif = Semester::aktif()->first();
$idSemester = $request->input('id_semester', $semesterAktif?->id_semester);
$query = Capaian::where('id_santri', $idSantri)
->whereHas('materi', function($q) use ($kategori) {
$q->where('kategori', $kategori);
})
->with(['materi', 'semester']);
if ($idSemester) {
$query->where('id_semester', $idSemester);
}
$capaians = $query->get();
$materiList = $capaians->map(function($capaian) {
return [
'id_capaian' => $capaian->id_capaian,
'materi' => [
'id_materi' => $capaian->materi->id_materi,
'nama_kitab' => $capaian->materi->nama_kitab,
'total_halaman' => $capaian->materi->total_halaman,
'halaman_mulai' => $capaian->materi->halaman_mulai,
'halaman_akhir' => $capaian->materi->halaman_akhir,
],
'progress' => [
'halaman_selesai' => $capaian->jumlah_halaman_selesai,
'persentase' => round($capaian->persentase, 1),
'status' => $this->getStatusCapaian($capaian->persentase),
'status_color' => $this->getStatusColor($capaian->persentase),
],
'tanggal_input' => $capaian->tanggal_input->format('d M Y'),
];
});
return response()->json([
'success' => true,
'data' => [
'kategori' => $kategori,
'icon' => $this->getKategoriIcon($kategori),
'color' => $this->getKategoriColor($kategori),
'total_materi' => $materiList->count(),
'materi_list' => $materiList,
],
], 200);
} catch (\Exception $e) {
Log::error('Error di List Materi by Kategori', [
'message' => $e->getMessage(),
'kategori' => $kategori,
]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
/**
* GET DETAIL CAPAIAN PER MATERI
* Endpoint: GET /api/v1/capaian/detail/{idCapaian}
* Now includes kelas_primary in santri info
*/
public function detailCapaian(Request $request, $idCapaian)
{
try {
$user = $request->user();
$idSantri = $user->role_id;
$capaian = Capaian::where('id_capaian', $idCapaian)
->where('id_santri', $idSantri)
->with(['materi', 'semester', 'santri.kelasPrimary.kelas.kelompok'])
->first();
if (!$capaian) {
return response()->json([
'success' => false,
'message' => 'Data capaian tidak ditemukan',
], 404);
}
$halamanArray = $capaian->pages_array;
$breakdown = [
'halaman_selesai_list' => $halamanArray,
'jumlah_halaman_selesai' => count($halamanArray),
'halaman_belum_selesai' => $capaian->materi->total_halaman - count($halamanArray),
'halaman_selesai_text' => $capaian->halaman_selesai,
];
// Build kelas_primary info
$kelasPrimary = null;
if ($capaian->santri) {
$kelasData = $this->buildKelasInfo($capaian->santri);
$kelasPrimary = $kelasData['kelas_primary'];
}
return response()->json([
'success' => true,
'data' => [
'id_capaian' => $capaian->id_capaian,
'santri_info' => $capaian->santri ? $this->buildSantriInfo($capaian->santri) : null,
'materi' => [
'id_materi' => $capaian->materi->id_materi,
'kategori' => $capaian->materi->kategori,
'nama_kitab' => $capaian->materi->nama_kitab,
'kelas' => $capaian->materi->kelas,
'total_halaman' => $capaian->materi->total_halaman,
'halaman_mulai' => $capaian->materi->halaman_mulai,
'halaman_akhir' => $capaian->materi->halaman_akhir,
'deskripsi' => $capaian->materi->deskripsi,
],
'semester' => [
'id_semester' => $capaian->semester->id_semester,
'nama_semester' => $capaian->semester->nama_semester,
],
'progress' => [
'persentase' => round($capaian->persentase, 1),
'status' => $this->getStatusCapaian($capaian->persentase),
'status_color' => $this->getStatusColor($capaian->persentase),
],
'breakdown' => $breakdown,
'catatan' => $capaian->catatan,
'tanggal_input' => $capaian->tanggal_input->format('d F Y'),
'last_updated' => $capaian->updated_at->diffForHumans(),
],
], 200);
} catch (\Exception $e) {
Log::error('Error di Detail Capaian', [
'message' => $e->getMessage(),
'id_capaian' => $idCapaian,
]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
/**
* GET GRAFIK PROGRESS HISTORIS
* Endpoint: GET /api/v1/capaian/grafik-progress
*/
public function grafikProgress(Request $request)
{
try {
$user = $request->user();
$idSantri = $user->role_id;
$semesters = Semester::orderBy('tahun_ajaran')
->orderBy('periode')
->get();
$dataGrafik = [];
foreach ($semesters as $semester) {
$capaians = Capaian::where('id_santri', $idSantri)
->where('id_semester', $semester->id_semester)
->where('persentase', '>', 0)
->get();
if ($capaians->count() > 0) {
$dataGrafik[] = [
'semester' => $semester->nama_semester,
'id_semester' => $semester->id_semester,
'rata_rata_progress' => round($capaians->avg('persentase'), 1),
'total_materi' => $capaians->count(),
'materi_selesai' => $capaians->where('persentase', '>=', 100)->count(),
];
}
}
return response()->json([
'success' => true,
'data' => $dataGrafik,
], 200);
} catch (\Exception $e) {
Log::error('Error di Grafik Progress', ['message' => $e->getMessage()]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
/**
* GET TREND SEMESTER
* Endpoint: GET /api/v1/capaian/trend-semester
* Returns progress per semester for line chart visualization
*/
public function trendSemester(Request $request)
{
try {
$user = $request->user();
if (!in_array($user->role, ['santri', 'wali'])) {
return response()->json(['success' => false, 'message' => 'Akses ditolak'], 403);
}
$idSantri = $user->role_id;
$santri = Santri::with(['kelasPrimary.kelas.kelompok'])->where('id_santri', $idSantri)->first();
if (!$santri) {
return response()->json(['success' => false, 'message' => 'Data santri tidak ditemukan'], 404);
}
// Load all capaian grouped by semester
$allCapaian = Capaian::where('id_santri', $idSantri)
->with(['materi', 'semester'])
->where('persentase', '>', 0)
->get();
$semesters = Semester::orderBy('tahun_ajaran')->orderBy('periode')->get();
$kategoriList = ['Al-Qur\'an', 'Hadist', 'Materi Tambahan'];
$trendData = [];
foreach ($semesters as $sem) {
$semCapaian = $allCapaian->where('id_semester', $sem->id_semester);
if ($semCapaian->isEmpty()) continue;
$perKat = [];
foreach ($kategoriList as $kat) {
$katCapaian = $semCapaian->filter(fn($c) => $c->materi && $c->materi->kategori === $kat);
if ($katCapaian->isNotEmpty()) {
$perKat[] = [
'kategori' => $kat,
'rata_rata' => round($katCapaian->avg('persentase'), 1),
'total_materi' => $katCapaian->count(),
'materi_selesai' => $katCapaian->where('persentase', '>=', 100)->count(),
];
}
}
$trendData[] = [
'id_semester' => $sem->id_semester,
'nama_semester' => $sem->nama_semester,
'tahun_ajaran' => $sem->tahun_ajaran,
'rata_rata_progress' => round($semCapaian->avg('persentase'), 1),
'total_materi' => $semCapaian->count(),
'materi_selesai' => $semCapaian->where('persentase', '>=', 100)->count(),
'per_kategori' => $perKat,
'is_aktif' => $sem->is_active == 1,
];
}
return response()->json([
'success' => true,
'data' => [
'santri' => $this->buildSantriInfo($santri),
'trend' => $trendData,
],
], 200);
} catch (\Exception $e) {
Log::error('Error di Trend Semester', ['message' => $e->getMessage()]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
/**
* GET DASHBOARD CAPAIAN (COMPREHENSIVE)
* Endpoint: GET /api/v1/capaian/dashboard
* Single endpoint returning all data for enhanced mobile capaian page
* UPDATED: Uses new kelas system (santri_kelas pivot table)
*/
public function dashboard(Request $request)
{
try {
$user = $request->user();
if (!in_array($user->role, ['santri', 'wali'])) {
return response()->json(['success' => false, 'message' => 'Akses ditolak'], 403);
}
$idSantri = $user->role_id;
$santri = Santri::with(['kelasPrimary.kelas.kelompok', 'kelasSantri.kelas.kelompok'])
->where('id_santri', $idSantri)
->first();
if (!$santri) {
return response()->json(['success' => false, 'message' => 'Data santri tidak ditemukan'], 404);
}
$semesterAktif = Semester::aktif()->first();
$idSemester = $request->input('id_semester', $semesterAktif?->id_semester);
$selectedSemester = $idSemester
? Semester::where('id_semester', $idSemester)->first()
: $semesterAktif;
$allSemesters = Semester::orderBy('tahun_ajaran')->orderBy('periode')->get();
$listSemester = $allSemesters->map(fn($s) => [
'id_semester' => $s->id_semester,
'nama_semester' => $s->nama_semester,
'tahun_ajaran' => $s->tahun_ajaran,
'periode' => $s->periode,
'is_aktif' => $s->is_active == 1,
])->values();
// ===== Load ALL capaian santri in one query =====
$allCapaianSantri = Capaian::where('id_santri', $idSantri)
->with(['materi', 'semester'])
->get();
// Current semester capaians
$currentCapaians = $allCapaianSantri->where('id_semester', $idSemester);
$currentBerisi = $currentCapaians->where('persentase', '>', 0);
$totalProgress = $currentBerisi->isEmpty() ? 0 : round($currentBerisi->avg('persentase'), 1);
$materiSelesaiSemIni = $currentCapaians->where('persentase', '>=', 100)->count();
// ===== Per Kategori =====
$kategoriList = ['Al-Qur\'an', 'Hadist', 'Materi Tambahan'];
$perKategori = [];
foreach ($kategoriList as $kategori) {
$capKat = $currentCapaians->filter(fn($c) => $c->materi && $c->materi->kategori === $kategori);
$capKatBerisi = $capKat->where('persentase', '>', 0);
$perKategori[] = [
'kategori' => $kategori,
'icon' => $this->getKategoriIcon($kategori),
'color' => $this->getKategoriColor($kategori),
'total_materi' => $capKatBerisi->count(),
'rata_rata_progress' => round($capKatBerisi->isEmpty() ? 0 : $capKatBerisi->avg('persentase'), 1),
'materi_selesai' => $capKat->where('persentase', '>=', 100)->count(),
];
}
// ===== Semester History =====
$bySemester = $allCapaianSantri->where('persentase', '>', 0)->groupBy('id_semester');
$semesterHistory = [];
foreach ($allSemesters as $sem) {
if ($bySemester->has($sem->id_semester)) {
$semCaps = $bySemester[$sem->id_semester];
$semesterHistory[] = [
'id_semester' => $sem->id_semester,
'nama_semester' => $sem->nama_semester,
'rata_rata_progress' => round($semCaps->avg('persentase'), 1),
'total_materi' => $semCaps->count(),
'materi_selesai' => $semCaps->where('persentase', '>=', 100)->count(),
'is_current' => $sem->id_semester === $idSemester,
];
}
}
// ===== Achievements =====
$achievements = [];
if ($materiSelesaiSemIni > 0) {
$achievements[] = ['icon' => 'trophy', 'text' => "Khatam $materiSelesaiSemIni Materi Semester Ini", 'type' => 'khatam'];
}
$currentIdx = -1;
for ($i = 0; $i < count($semesterHistory); $i++) {
if ($semesterHistory[$i]['id_semester'] === $idSemester) {
$currentIdx = $i;
break;
}
}
if ($currentIdx > 0) {
$prevProgress = $semesterHistory[$currentIdx - 1]['rata_rata_progress'];
$curProgress = $semesterHistory[$currentIdx]['rata_rata_progress'];
$change = round($curProgress - $prevProgress, 1);
if ($change > 0) {
$achievements[] = ['icon' => 'trending_up', 'text' => "Kenaikan {$change}% dari Semester Lalu", 'type' => 'growth'];
} elseif ($change < 0) {
$achievements[] = ['icon' => 'trending_down', 'text' => "Penurunan " . abs($change) . "% dari Semester Lalu", 'type' => 'decline'];
}
}
// ===== Ranking & Peer Comparison (NEW: via santri_kelas pivot) =====
$peerSantriIds = $this->getPeerSantriIds($santri, $idSemester);
$rankings = collect();
if ($idSemester && count($peerSantriIds) > 1) {
$rankings = Capaian::whereIn('id_santri', $peerSantriIds)
->where('id_semester', $idSemester)
->where('persentase', '>', 0)
->select('id_santri', DB::raw('AVG(persentase) as avg_progress'))
->groupBy('id_santri')
->orderByDesc('avg_progress')
->get();
}
$rank = 0;
$totalRanked = $rankings->count();
foreach ($rankings as $i => $r) {
if ($r->id_santri === $idSantri) {
$rank = $i + 1;
break;
}
}
if ($rank > 0 && $totalRanked > 1) {
$achievements[] = [
'icon' => $rank <= 3 ? 'star' : 'emoji_events',
'text' => "Peringkat $rank dari $totalRanked di Kelas",
'type' => 'rank',
];
}
// Peer comparison per kategori (NEW: via santri_kelas pivot)
$peerComparison = [];
if ($idSemester && count($peerSantriIds) > 1) {
$peerData = Capaian::whereIn('id_santri', $peerSantriIds)
->join('materi', 'capaian.id_materi', '=', 'materi.id_materi')
->where('capaian.id_semester', $idSemester)
->where('capaian.persentase', '>', 0)
->groupBy('materi.kategori')
->select('materi.kategori', DB::raw('AVG(capaian.persentase) as kelas_avg'))
->get()
->keyBy('kategori');
foreach ($kategoriList as $kategori) {
$santriKatBerisi = $currentCapaians->filter(fn($c) => $c->materi && $c->materi->kategori === $kategori && $c->persentase > 0);
$santriAvg = $santriKatBerisi->isEmpty() ? 0 : round($santriKatBerisi->avg('persentase'), 1);
$kelasAvg = isset($peerData[$kategori]) ? round($peerData[$kategori]->kelas_avg, 1) : 0;
$peerComparison[] = [
'kategori' => $kategori,
'icon' => $this->getKategoriIcon($kategori),
'color' => $this->getKategoriColor($kategori),
'santri_progress' => $santriAvg,
'kelas_avg' => $kelasAvg,
];
}
} else {
// No peers, just show santri data
foreach ($kategoriList as $kategori) {
$santriKatBerisi = $currentCapaians->filter(fn($c) => $c->materi && $c->materi->kategori === $kategori && $c->persentase > 0);
$santriAvg = $santriKatBerisi->isEmpty() ? 0 : round($santriKatBerisi->avg('persentase'), 1);
$peerComparison[] = [
'kategori' => $kategori,
'icon' => $this->getKategoriIcon($kategori),
'color' => $this->getKategoriColor($kategori),
'santri_progress' => $santriAvg,
'kelas_avg' => 0,
];
}
}
// ===== Materi Status =====
$materiStatus = $currentCapaians->map(function ($c) {
$status = 'belum_mulai';
if ($c->persentase >= 100) $status = 'selesai';
elseif ($c->persentase > 0) $status = 'progres';
return [
'id_capaian' => $c->id_capaian,
'nama_kitab' => $c->materi->nama_kitab ?? '-',
'kategori' => $c->materi->kategori ?? '-',
'persentase' => round($c->persentase, 1),
'status' => $status,
'status_label' => $this->getStatusCapaian($c->persentase),
'status_color' => $this->getStatusColor($c->persentase),
'icon' => $this->getKategoriIcon($c->materi->kategori ?? ''),
'color' => $this->getKategoriColor($c->materi->kategori ?? ''),
];
})->sortByDesc('persentase')->values();
// ===== Rapor Summary =====
$raporSummary = [
'total_progress' => $totalProgress,
'total_materi' => $currentBerisi->count(),
'materi_selesai' => $materiSelesaiSemIni,
'perubahan' => 0,
'trend' => 'tetap',
'predikat' => $this->getPredikat($totalProgress),
];
if ($currentIdx > 0) {
$prevProg = $semesterHistory[$currentIdx - 1]['rata_rata_progress'];
$curProg = $semesterHistory[$currentIdx]['rata_rata_progress'];
$raporSummary['perubahan'] = round($curProg - $prevProg, 1);
$raporSummary['trend'] = $curProg > $prevProg ? 'naik' : ($curProg < $prevProg ? 'turun' : 'tetap');
}
return response()->json([
'success' => true,
'data' => [
'role' => $user->role,
'santri' => $this->buildSantriInfo($santri),
'semester' => [
'id_semester' => $selectedSemester?->id_semester,
'nama_semester' => $selectedSemester?->nama_semester ?? 'Tidak Diketahui',
'tahun_ajaran' => $selectedSemester?->tahun_ajaran,
],
'list_semester' => $listSemester,
'current_progress' => [
'total_progress' => $totalProgress,
'total_materi' => $currentBerisi->count(),
'materi_selesai' => $materiSelesaiSemIni,
'per_kategori' => $perKategori,
],
'semester_history' => array_values($semesterHistory),
'achievements' => $achievements,
'materi_status' => $materiStatus,
'peer_comparison' => $peerComparison,
'rapor_summary' => $raporSummary,
'rank' => $rank > 0 ? ['position' => $rank, 'total' => $totalRanked] : null,
],
], 200);
} catch (\Exception $e) {
Log::error('Error di Capaian Dashboard', [
'message' => $e->getMessage(),
'line' => $e->getLine(),
'file' => $e->getFile(),
]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
], 500);
}
}
// ==================== HELPER METHODS ====================
private function getPredikat($progress)
{
if ($progress >= 90) return 'Baik Sekali';
if ($progress >= 75) return 'Baik';
if ($progress >= 50) return 'Cukup';
return 'Perlu Perhatian';
}
private function getKategoriIcon($kategori)
{
$icons = [
'Al-Qur\'an' => 'book_quran',
'Hadist' => 'scroll',
'Materi Tambahan' => 'book',
];
return $icons[$kategori] ?? 'book';
}
private function getKategoriColor($kategori)
{
$colors = [
'Al-Qur\'an' => '#6FBAA5',
'Hadist' => '#81C6E8',
'Materi Tambahan' => '#FFD56B',
];
return $colors[$kategori] ?? '#6B7280';
}
private function getStatusCapaian($persentase)
{
if ($persentase >= 100) return 'Selesai';
if ($persentase >= 75) return 'Hampir Selesai';
if ($persentase >= 50) return 'Dalam Progress';
if ($persentase >= 25) return 'Baru Mulai';
if ($persentase > 0) return 'Mulai';
return 'Belum Mulai';
}
private function getStatusColor($persentase)
{
if ($persentase >= 100) return '#10B981';
if ($persentase >= 75) return '#3B82F6';
if ($persentase >= 50) return '#F59E0B';
if ($persentase >= 25) return '#EF4444';
return '#6B7280';
}
}