user(); $idSantri = $user->role_id; // Santri atau wali punya role_id = id_santri $tanggal = $request->get('tanggal', now()->format('Y-m-d')); $selectedDate = Carbon::parse($tanggal); // Summary Hari Ini $summary = AbsensiKegiatan::where('id_santri', $idSantri) ->whereDate('tanggal', $selectedDate) ->select( DB::raw('COUNT(*) as total'), DB::raw('SUM(CASE WHEN status = "Hadir" THEN 1 ELSE 0 END) as hadir'), DB::raw('SUM(CASE WHEN status = "Izin" THEN 1 ELSE 0 END) as izin'), DB::raw('SUM(CASE WHEN status = "Sakit" THEN 1 ELSE 0 END) as sakit'), DB::raw('SUM(CASE WHEN status = "Alpa" THEN 1 ELSE 0 END) as alpa') ) ->first(); $percentage = $summary->total > 0 ? round(($summary->hadir / $summary->total) * 100, 1) : 0; // Timeline Absensi Hari Ini $timeline = AbsensiKegiatan::with(['kegiatan.kategori']) ->where('id_santri', $idSantri) ->whereDate('tanggal', $selectedDate) ->orderBy('waktu_absen') ->get() ->map(function($absensi) use ($selectedDate) { $kegiatan = $absensi->kegiatan; // Calculate punctuality (jika RFID) $punctuality = null; if ($absensi->metode_absen === 'RFID' && $absensi->status === 'Hadir') { $waktuMulai = Carbon::parse($selectedDate->format('Y-m-d') . ' ' . $kegiatan->waktu_mulai); $waktuAbsen = Carbon::parse($absensi->waktu_absen); $diffMinutes = $waktuAbsen->diffInMinutes($waktuMulai, false); if ($diffMinutes <= 0) { $punctuality = 'Tepat Waktu'; } else { $punctuality = 'Telat ' . abs($diffMinutes) . ' menit'; } } return [ 'absensi_id' => $absensi->absensi_id, 'kegiatan_id' => $kegiatan->kegiatan_id, 'nama_kegiatan' => $kegiatan->nama_kegiatan, 'kategori' => [ 'nama' => $kegiatan->kategori->nama_kategori, 'icon' => $kegiatan->kategori->icon ?? 'fa-calendar', 'warna' => $kegiatan->kategori->warna ?? '#6FBAA5', ], 'waktu_mulai' => date('H:i', strtotime($kegiatan->waktu_mulai)), 'waktu_selesai' => date('H:i', strtotime($kegiatan->waktu_selesai)), 'status' => $absensi->status, 'waktu_absen' => $absensi->waktu_absen ? date('H:i', strtotime($absensi->waktu_absen)) : null, 'metode_absen' => $absensi->metode_absen, 'punctuality' => $punctuality, 'keterangan' => $absensi->keterangan, ]; }); return response()->json([ 'success' => true, 'data' => [ 'tanggal' => $selectedDate->locale('id')->isoFormat('dddd, D MMMM YYYY'), 'tanggal_raw' => $selectedDate->format('Y-m-d'), 'summary' => [ 'total' => $summary->total, 'hadir' => $summary->hadir, 'izin' => $summary->izin, 'sakit' => $summary->sakit, 'alpa' => $summary->alpa, 'percentage' => $percentage, ], 'timeline' => $timeline, ], ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } /** * ========================================== * 2. SUMMARY MINGGU INI * ========================================== */ public function week(Request $request) { try { $user = $request->user(); $idSantri = $user->role_id; $startDate = Carbon::now()->startOfWeek(); $endDate = Carbon::now()->endOfWeek(); // Summary $summary = AbsensiKegiatan::where('id_santri', $idSantri) ->whereBetween('tanggal', [$startDate, $endDate]) ->select( DB::raw('COUNT(*) as total'), DB::raw('SUM(CASE WHEN status = "Hadir" THEN 1 ELSE 0 END) as hadir'), DB::raw('SUM(CASE WHEN status = "Izin" THEN 1 ELSE 0 END) as izin'), DB::raw('SUM(CASE WHEN status = "Sakit" THEN 1 ELSE 0 END) as sakit'), DB::raw('SUM(CASE WHEN status = "Alpa" THEN 1 ELSE 0 END) as alpa') ) ->first(); $percentage = $summary->total > 0 ? round(($summary->hadir / $summary->total) * 100, 1) : 0; // Trend 7 hari $trend = []; for ($i = 0; $i < 7; $i++) { $date = $startDate->copy()->addDays($i); $dayData = AbsensiKegiatan::where('id_santri', $idSantri) ->whereDate('tanggal', $date) ->selectRaw('COUNT(*) as total, SUM(CASE WHEN status = "Hadir" THEN 1 ELSE 0 END) as hadir') ->first(); $trend[] = [ 'date' => $date->format('Y-m-d'), 'day_name' => $date->locale('id')->isoFormat('ddd'), 'percentage' => $dayData->total > 0 ? round(($dayData->hadir / $dayData->total) * 100, 1) : 0, ]; } // Breakdown per kategori $perKategori = AbsensiKegiatan::where('id_santri', $idSantri) ->whereBetween('tanggal', [$startDate, $endDate]) ->join('kegiatans', 'absensi_kegiatans.kegiatan_id', '=', 'kegiatans.kegiatan_id') ->join('kategori_kegiatans', 'kegiatans.kategori_id', '=', 'kategori_kegiatans.kategori_id') ->select( 'kategori_kegiatans.nama_kategori', 'kategori_kegiatans.warna', DB::raw('COUNT(*) as total'), DB::raw('SUM(CASE WHEN absensi_kegiatans.status = "Hadir" THEN 1 ELSE 0 END) as hadir') ) ->groupBy('kategori_kegiatans.kategori_id', 'kategori_kegiatans.nama_kategori', 'kategori_kegiatans.warna') ->get() ->map(function($item) { return [ 'nama_kategori' => $item->nama_kategori, 'warna' => $item->warna ?? '#6FBAA5', 'total' => $item->total, 'hadir' => $item->hadir, 'percentage' => $item->total > 0 ? round(($item->hadir / $item->total) * 100, 1) : 0, ]; }); return response()->json([ 'success' => true, 'data' => [ 'periode' => $startDate->locale('id')->isoFormat('D MMM') . ' - ' . $endDate->locale('id')->isoFormat('D MMM Y'), 'start_date' => $startDate->format('Y-m-d'), 'end_date' => $endDate->format('Y-m-d'), 'summary' => [ 'total' => $summary->total, 'hadir' => $summary->hadir, 'izin' => $summary->izin, 'sakit' => $summary->sakit, 'alpa' => $summary->alpa, 'percentage' => $percentage, ], 'trend' => $trend, 'per_kategori' => $perKategori, ], ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } /** * ========================================== * 3. RIWAYAT BULAN (dengan Pagination) * ========================================== */ public function month(Request $request) { try { $user = $request->user(); $idSantri = $user->role_id; $bulan = $request->get('bulan', now()->format('Y-m')); $date = Carbon::parse($bulan . '-01'); $startDate = $date->copy()->startOfMonth(); $endDate = $date->copy()->endOfMonth(); // Summary $summary = AbsensiKegiatan::where('id_santri', $idSantri) ->whereBetween('tanggal', [$startDate, $endDate]) ->select( DB::raw('COUNT(*) as total'), DB::raw('SUM(CASE WHEN status = "Hadir" THEN 1 ELSE 0 END) as hadir'), DB::raw('SUM(CASE WHEN status = "Izin" THEN 1 ELSE 0 END) as izin'), DB::raw('SUM(CASE WHEN status = "Sakit" THEN 1 ELSE 0 END) as sakit'), DB::raw('SUM(CASE WHEN status = "Alpa" THEN 1 ELSE 0 END) as alpa') ) ->first(); $percentage = $summary->total > 0 ? round(($summary->hadir / $summary->total) * 100, 1) : 0; // Riwayat per hari (grouped) $riwayat = AbsensiKegiatan::with(['kegiatan.kategori']) ->where('id_santri', $idSantri) ->whereBetween('tanggal', [$startDate, $endDate]) ->orderByDesc('tanggal') ->orderBy('waktu_absen') ->get() ->groupBy(function($item) { return Carbon::parse($item->tanggal)->format('Y-m-d'); }) ->map(function($items, $date) { $hadir = $items->where('status', 'Hadir')->count(); $total = $items->count(); return [ 'tanggal' => Carbon::parse($date)->locale('id')->isoFormat('dddd, D MMMM Y'), 'tanggal_raw' => $date, 'total' => $total, 'hadir' => $hadir, 'percentage' => $total > 0 ? round(($hadir / $total) * 100, 1) : 0, 'items' => $items->map(function($absensi) { return [ 'kegiatan' => $absensi->kegiatan->nama_kegiatan, 'kategori' => $absensi->kegiatan->kategori->nama_kategori, 'status' => $absensi->status, 'waktu_absen' => $absensi->waktu_absen ? date('H:i', strtotime($absensi->waktu_absen)) : null, ]; })->values(), ]; }) ->values(); // Heatmap Calendar (30 hari) $heatmap = $this->generateHeatmap($idSantri, $startDate, $endDate); return response()->json([ 'success' => true, 'data' => [ 'periode' => $date->locale('id')->isoFormat('MMMM YYYY'), 'bulan_raw' => $date->format('Y-m'), 'summary' => [ 'total' => $summary->total, 'hadir' => $summary->hadir, 'izin' => $summary->izin, 'sakit' => $summary->sakit, 'alpa' => $summary->alpa, 'percentage' => $percentage, ], 'heatmap' => $heatmap, 'riwayat' => $riwayat, ], ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } /** * ========================================== * HELPER: Generate Heatmap Data * ========================================== */ private function generateHeatmap($idSantri, $startDate, $endDate) { $heatmap = []; $current = $startDate->copy(); while ($current->lte($endDate)) { $dayData = AbsensiKegiatan::where('id_santri', $idSantri) ->whereDate('tanggal', $current) ->selectRaw('COUNT(*) as total, SUM(CASE WHEN status = "Hadir" THEN 1 ELSE 0 END) as hadir') ->first(); $percentage = $dayData->total > 0 ? round(($dayData->hadir / $dayData->total) * 100, 1) : 0; $level = $this->getHeatmapLevel($percentage); $heatmap[] = [ 'date' => $current->format('Y-m-d'), 'day' => $current->format('j'), 'day_name' => $current->locale('id')->isoFormat('dd'), 'percentage' => $percentage, 'level' => $level, 'is_today' => $current->isToday(), ]; $current->addDay(); } return $heatmap; } /** * Get Heatmap Level (0-4) */ private function getHeatmapLevel($percentage) { if ($percentage >= 90) return 4; // Dark green if ($percentage >= 80) return 3; // Green if ($percentage >= 70) return 2; // Yellow if ($percentage > 0) return 1; // Red return 0; // No data } }