get(); // Ambil daftar mapel dari jadwal kelas $mapelList = Schedule::with('subject') ->where('id_class', $classId) ->distinct('id_subject') ->get() ->map(function($schedule) { return $schedule->subject; }) ->unique('id') ->sortBy('name') ->values(); // Default ke hari ini jika tidak ada filter $tanggal = $request->get('tanggal', now()->format('Y-m-d')); $subjectId = $request->get('subject_id'); $absensi = collect(); $belumAbsen = $allStudents; // Jika ada subject filter, ambil data berdasarkan tanggal + day_of_week if ($subjectId) { // Parse tanggal untuk ambil day of week $date = \Carbon\Carbon::createFromFormat('Y-m-d', $tanggal); // Map hari ke format Indonesia $dayMap = [ 0 => 'Minggu', 1 => 'Senin', 2 => 'Selasa', 3 => 'Rabu', 4 => 'Kamis', 5 => 'Jumat', 6 => 'Sabtu' ]; $dayName = $dayMap[$date->dayOfWeek] ?? ''; // Get schedule IDs untuk subject dan day ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $subjectId) ->where('day_of_week', $dayName) ->pluck('id') ->toArray(); // Get attendance history untuk tanggal dan schedules spesifik if (!empty($scheduleIds)) { $absensi = AttendanceHistory::with(['student', 'class']) ->whereIn('id_schedule', $scheduleIds) ->where(function($q) use ($tanggal) { $q->whereDate('created_at', $tanggal) ->orWhereDate('attendance_date', $tanggal); }) ->get(); } // Ambil siswa yang sudah absen untuk tanggal + day ini $sudahAbsenIds = $absensi->pluck('id_student')->unique(); // Siswa yang belum absen untuk tanggal + day ini $belumAbsen = $allStudents->whereNotIn('id', $sudahAbsenIds)->values(); } return view('pages.absensi.absensi_mapel', [ 'absensi' => $absensi, 'kelas' => $kelas, 'belumAbsen' => $belumAbsen, 'mapelList' => $mapelList, 'allStudents' => $allStudents, 'selectedTanggal' => $tanggal, ]); } public function getAbsensiByMapel($classId, $subjectId, Request $request) { try { // Get schedules untuk subject ini di kelas ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $subjectId) ->pluck('id') ->toArray(); // Build query untuk attendance history $query = AttendanceHistory::with(['student', 'class']); if (!empty($scheduleIds)) { $query->whereIn('id_schedule', $scheduleIds); } // Apply date filters jika ada $bulan = $request->get('bulan'); $tahun = $request->get('tahun'); if ($bulan && $tahun) { $query->whereMonth('created_at', $bulan) ->whereYear('created_at', $tahun); } elseif ($bulan) { $query->whereMonth('created_at', $bulan); } elseif ($tahun) { $query->whereYear('created_at', $tahun); } // Get attendance history $absensi = $query->get(); // Get all students $allStudents = Student::where('id_class', $classId)->get(); // Ambil id siswa yang sudah absen untuk mapel ini $sudahAbsenIds = $absensi->pluck('id_student')->unique(); // Siswa yang belum absen untuk mapel ini $belumAbsen = $allStudents->whereNotIn('id', $sudahAbsenIds)->values(); return response()->json([ 'success' => true, 'absensi' => $absensi, 'belumAbsen' => $belumAbsen, ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } public function getSchedules($classId, $subjectId) { try { // Get all schedules untuk subject ini di kelas ini $schedules = Schedule::where('id_class', $classId) ->where('id_subject', $subjectId) ->get() ->map(function($schedule) { return [ 'id' => $schedule->id, 'day' => $schedule->day_of_week ?? 'N/A', 'time' => ($schedule->start_time ? \Carbon\Carbon::parse($schedule->start_time)->format('H:i') : '') . ' - ' . ($schedule->end_time ? \Carbon\Carbon::parse($schedule->end_time)->format('H:i') : '') ]; }); return response()->json([ 'success' => true, 'schedules' => $schedules, ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } public function getAbsensiByMapelAndDate($classId, $subjectId, $tanggal) { try { // Parse tanggal $date = \Carbon\Carbon::createFromFormat('Y-m-d', $tanggal); // Map hari ke format Indonesia $dayMap = [ 0 => 'Minggu', 1 => 'Senin', 2 => 'Selasa', 3 => 'Rabu', 4 => 'Kamis', 5 => 'Jumat', 6 => 'Sabtu' ]; $dayName = $dayMap[$date->dayOfWeek] ?? ''; // Get schedules untuk subject ini yang sesuai dengan hari ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $subjectId) ->where('day_of_week', $dayName) ->pluck('id') ->toArray(); // Get attendance history untuk tanggal spesifik dan schedules tersebut $absensi = collect(); if (!empty($scheduleIds)) { $absensi = AttendanceHistory::with(['student', 'class']) ->whereIn('id_schedule', $scheduleIds) ->where(function($q) use ($tanggal) { $q->whereDate('created_at', $tanggal) ->orWhereDate('attendance_date', $tanggal); }) ->get(); } // Get all students $allStudents = Student::where('id_class', $classId)->get(); // Siswa yang sudah absen untuk tanggal dan schedule ini $sudahAbsenIds = $absensi->pluck('id_student')->unique(); // Siswa yang belum absen $belumAbsen = $allStudents->whereNotIn('id', $sudahAbsenIds)->values(); return response()->json([ 'success' => true, 'absensi' => $absensi, 'belumAbsen' => $belumAbsen, 'scheduleIds' => $scheduleIds, ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'Error: ' . $e->getMessage(), ], 500); } } public function editStatus(Request $request, $id) { try { $request->validate([ 'status' => 'required|string' ]); // Jika $id adalah 'null', create baru if ($id === 'null') { $request->validate([ 'id_student' => 'required|integer', 'id_class' => 'required|integer', 'id_schedule' => 'required|integer', ]); // Create attendance record dengan id_schedule dari request $currentDate = now()->format('Y-m-d'); AttendanceHistory::create([ 'id_student' => $request->id_student, 'id_class' => $request->id_class, 'status' => $request->status, 'period_number' => 1, 'id_schedule' => $request->id_schedule, 'coordinate' => config('coordinate.coordinate', '-7.604032330848524, 112.10176449791652'), 'attendance_date' => $currentDate, 'created_at' => now(), 'updated_at' => now(), ]); return response()->json(['success' => true, 'message' => 'Data absensi berhasil ditambahkan.']); } else { // Update existing $absensi = AttendanceHistory::findOrFail($id); $currentDate = now()->format('Y-m-d'); $absensi->update([ 'status' => $request->status, 'attendance_date' => $currentDate, 'updated_at' => now(), ]); return response()->json(['success' => true, 'message' => 'Status absensi berhasil diupdate.']); } } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => $e->getMessage() ], 500); } } /** * Export absensi mapel ke Excel - Format Grid per Periode (seperti standar absensi) */ public function exportExcel($classId, Request $request) { try { // Get kelas info $kelas = Classes::findOrFail($classId); $kelasName = $kelas->name ?? 'Kelas'; // Get filter info $mapelId = $request->get('mapel'); $bulan = $request->get('bulan'); $tahun = $request->get('tahun'); // Default ke bulan & tahun sekarang jika kedua-duanya kosong if (empty($bulan) && empty($tahun)) { $bulan = date('m'); $tahun = date('Y'); } elseif ($bulan && empty($tahun)) { $tahun = date('Y'); } // Jika bulan kosong tapi tahun ada, lakukan export tahunan if (empty($bulan) && $tahun) { return $this->exportExcelYearly($classId, $mapelId, $tahun); } if (empty($bulan)) { $bulan = date('m'); } // Get mapel/subject name $mapelName = 'Mapel'; if ($mapelId) { $subject = Subject::find($mapelId); $mapelName = $subject ? $subject->name : 'Mapel'; } // Get all students $students = Student::where('id_class', $classId)->orderBy('name', 'ASC')->get(); if ($students->isEmpty()) { return response()->json(['error' => 'Tidak ada siswa di kelas'], 400); } // Get schedules untuk mapel ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $mapelId) ->pluck('id') ->toArray(); // Generate filename $month_name = $this->getMonthName($bulan); $filename = 'Absensi_Mapel_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $mapelName) . '_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $kelasName) . '.xlsx'; // Create spreadsheet $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Absensi Mapel'); // Row 1: Title (Yellow background) $title = 'ABSENSI ' . strtoupper($mapelName) . ' ' . strtoupper($kelasName); $sheet->setCellValue('A1', $title); $sheet->mergeCells('A1:AE1'); $sheet->getRowDimension(1)->setRowHeight(25); $sheet->getStyle('A1')->applyFromArray([ 'font' => ['bold' => true, 'size' => 14, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], ]); // Row 2: Period info $periodText = 'PERIODE ' . strtoupper($month_name) . ' ' . $tahun; $sheet->setCellValue('A2', $periodText); $sheet->mergeCells('A2:AE2'); $sheet->getRowDimension(2)->setRowHeight(20); $sheet->getStyle('A2')->applyFromArray([ 'font' => ['bold' => true, 'size' => 12, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], ]); // Row 3: Empty $sheet->getRowDimension(3)->setRowHeight(10); // Row 4: Headers $sheet->setCellValue('A4', 'Nama'); $sheet->setCellValue('B4', 'Jabatan'); // Days columns (1-31) $daysInMonth = \Carbon\Carbon::createFromDate($tahun, $bulan, 1)->daysInMonth; for ($day = 1; $day <= 31; $day++) { $colLetter = $this->getColumnLetter($day + 2); // +2 untuk offset dari kolom A (Nama) dan B (Jabatan) if ($day <= $daysInMonth) { $sheet->setCellValue($colLetter . '4', $day); } else { $sheet->setCellValue($colLetter . '4', ''); } } // Summary columns $jumlahCol = $this->getColumnLetter(35); // Column after day 31 $keteranganCol = $this->getColumnLetter(36); $sheet->setCellValue($jumlahCol . '4', 'Jumlah\nHari Kerja'); $sheet->setCellValue($keteranganCol . '4', 'Keterangan'); // Style headers (Row 4) - Yellow background $headerRange = 'A4:' . $keteranganCol . '4'; $sheet->getStyle($headerRange)->applyFromArray([ 'font' => ['bold' => true, 'size' => 11, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], ]); $sheet->getRowDimension(4)->setRowHeight(30); // Get attendance data untuk bulan+tahun ini $attendanceData = []; if (!empty($scheduleIds)) { $attendance = AttendanceHistory::where(function($q) use ($bulan, $tahun) { $q->where(function($qq) use ($bulan, $tahun) { $qq->whereMonth('created_at', $bulan)->whereYear('created_at', $tahun); })->orWhere(function($qq) use ($bulan, $tahun) { $qq->whereMonth('attendance_date', $bulan)->whereYear('attendance_date', $tahun); }); })->whereIn('id_schedule', $scheduleIds)->get(); foreach ($attendance as $att) { $date = $att->attendance_date ? \Carbon\Carbon::parse($att->attendance_date) : \Carbon\Carbon::parse($att->created_at); $key = $att->id_student . '_' . $date->day; $attendanceData[$key] = $att->status; } } // Add student rows $dataRow = 5; foreach ($students as $student) { $sheet->setCellValue('A' . $dataRow, $student->name); $sheet->setCellValue('B' . $dataRow, $student->classroom ? $student->classroom->name : '-'); // Add attendance status for each day for ($day = 1; $day <= 31; $day++) { $colLetter = $this->getColumnLetter($day + 2); $key = $student->id . '_' . $day; $status = isset($attendanceData[$key]) ? $attendanceData[$key] : ''; // Convert status to letter code $statusCode = ''; if ($status) { switch($status) { case 'hadir': $statusCode = 'H'; break; case 'izin': $statusCode = 'I'; break; case 'sakit': $statusCode = 'S'; break; case 'alpha': $statusCode = 'A'; break; default: $statusCode = ''; } } $sheet->setCellValue($colLetter . $dataRow, $statusCode); $sheet->getStyle($colLetter . $dataRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); } // Borders untuk semua kolom $dataRange = 'A' . $dataRow . ':' . $keteranganCol . $dataRow; $sheet->getStyle($dataRange)->applyFromArray([ 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER], ]); $dataRow++; } // Add legend $legendRow = $dataRow + 2; $sheet->setCellValue('A' . $legendRow, 'Keterangan:'); $sheet->getStyle('A' . $legendRow)->applyFromArray(['font' => ['bold' => true, 'size' => 11]]); $legendRow++; $legends = [ 'H' => 'Hadir', 'I' => 'Izin', 'S' => 'Sakit', 'A' => 'Alpha' ]; foreach ($legends as $code => $meaning) { $sheet->setCellValue('A' . $legendRow, $code); $sheet->setCellValue('B' . $legendRow, $meaning); $sheet->getStyle('A' . $legendRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $legendRow++; } // Set column widths $sheet->getColumnDimension('A')->setWidth(20); $sheet->getColumnDimension('B')->setWidth(15); // Day columns for ($day = 1; $day <= 31; $day++) { $colLetter = $this->getColumnLetter($day + 2); $sheet->getColumnDimension($colLetter)->setWidth(5); } $sheet->getColumnDimension($jumlahCol)->setWidth(12); $sheet->getColumnDimension($keteranganCol)->setWidth(15); // Save and download $writer = new Xlsx($spreadsheet); $tempPath = storage_path('app/temp/' . $filename); @mkdir(dirname($tempPath), 0755, true); $writer->save($tempPath); return response()->download($tempPath)->deleteFileAfterSend(true); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } } /** * Helper: Get absensi mapel rows for export sesuai dengan filter * Export semua siswa di kelas dengan status absensi mereka (jika ada) */ private function getAbsensiMapelRowsForExport($classId, $mapelId, $tanggal = null, $bulan = null, $tahun = null) { // Get kelas $kelas = Classes::findOrFail($classId); // Get semua siswa di kelas (PENTING: ambil SEMUA siswa) $students = Student::where('id_class', $classId) ->orderBy('name', 'ASC') ->get(); if ($students->isEmpty()) { return []; } // Get schedules untuk mapel ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $mapelId) ->pluck('id') ->toArray(); // Build query untuk attendance history - ambil SEMUA attendance untuk mapel ini $query = AttendanceHistory::with(['student', 'class']); // Filter by schedule if available if (!empty($scheduleIds)) { $query->whereIn('id_schedule', $scheduleIds); } else { // Jika tidak ada schedule, kembalikan semua siswa dengan keterangan "-" return $this->buildEmptyAttendanceRows($students); } // Apply date filters - PENTING: include semua attendance data sesuai filter if ($tanggal) { // Untuk tanggal spesifik $query->where(function($q) use ($tanggal) { $q->whereDate('created_at', $tanggal) ->orWhereDate('attendance_date', $tanggal); }); } elseif ($bulan && $tahun) { // Untuk bulan+tahun spesifik $query->where(function($q) use ($bulan, $tahun) { $q->where(function($qq) use ($bulan, $tahun) { $qq->whereMonth('created_at', $bulan) ->whereYear('created_at', $tahun); })->orWhere(function($qq) use ($bulan, $tahun) { $qq->whereMonth('attendance_date', $bulan) ->whereYear('attendance_date', $tahun); }); }); } elseif ($bulan) { // Untuk bulan spesifik $query->where(function($q) use ($bulan) { $q->whereMonth('created_at', $bulan) ->orWhereMonth('attendance_date', $bulan); }); } elseif ($tahun) { // Untuk tahun spesifik $query->where(function($q) use ($tahun) { $q->whereYear('created_at', $tahun) ->orWhereYear('attendance_date', $tahun); }); } // Get attendance records $absensi = $query->get(); // Build rows untuk export - PENTING: include SEMUA students $rows = []; $no = 1; foreach ($students as $student) { // Cari attendance record untuk student ini // Jika ada multiple records, ambil yang paling recent $absensiItem = $absensi->where('id_student', $student->id)->sortByDesc('created_at')->first(); $keterangan = '-'; $attendanceDate = '-'; $jamPertemuan = '-'; if ($absensiItem) { // Tentukan keterangan berdasarkan status switch($absensiItem->status) { case 'hadir': $keterangan = 'Hadir'; break; case 'izin': $keterangan = 'Izin'; break; case 'sakit': $keterangan = 'Sakit'; break; case 'alpha': $keterangan = 'Alpha'; break; case 'dispen': $keterangan = 'Dispen'; break; default: $keterangan = ucfirst($absensiItem->status); } // Format tanggal dan jam dari attendance record $attendanceDate = $absensiItem->attendance_date ? \Carbon\Carbon::parse($absensiItem->attendance_date)->format('d/m/Y') : ($absensiItem->created_at ? $absensiItem->created_at->format('d/m/Y') : '-'); $jamPertemuan = $absensiItem->created_at ? $absensiItem->created_at->format('H:i') : '-'; } // Add row untuk student ini (terlepas ada attendance atau tidak) $rows[] = [ 'no' => $no, 'student_name' => $student->name, 'nisn' => $student->nisn ?? '-', 'attendance_date' => $attendanceDate, 'jam_pertemuan' => $jamPertemuan, 'keterangan' => $keterangan, ]; $no++; } return $rows; } /** * Helper: Build empty attendance rows jika tidak ada schedule */ private function buildEmptyAttendanceRows($students) { $rows = []; $no = 1; foreach ($students as $student) { $rows[] = [ 'no' => $no, 'student_name' => $student->name, 'nisn' => $student->nisn ?? '-', 'attendance_date' => '-', 'jam_pertemuan' => '-', 'keterangan' => '-', ]; $no++; } return $rows; } /** * Helper: Convert column number to letter */ private function getColumnLetter($col) { $letter = ''; while ($col > 0) { $col--; $letter = chr(65 + ($col % 26)) . $letter; $col = intdiv($col, 26); } return $letter; } /** * Helper: Get month name in Indonesian */ private function getMonthName($bulan) { $months = [ '01' => 'Januari', '02' => 'Februari', '03' => 'Maret', '04' => 'April', '05' => 'Mei', '06' => 'Juni', '07' => 'Juli', '08' => 'Agustus', '09' => 'September', '10' => 'Oktober', '11' => 'November', '12' => 'Desember' ]; return $months[$bulan] ?? 'Bulan'; } /** * Export absensi mapel ke Excel - Format Tahunan (12 Bulan) */ private function exportExcelYearly($classId, $mapelId, $tahun) { // Get kelas info $kelas = Classes::findOrFail($classId); $kelasName = $kelas->name ?? 'Kelas'; // Get mapel/subject name $mapelName = 'Mapel'; if ($mapelId) { $subject = Subject::find($mapelId); $mapelName = $subject ? $subject->name : 'Mapel'; } // Get all students $students = Student::where('id_class', $classId)->orderBy('name', 'ASC')->get(); if ($students->isEmpty()) { return response()->json(['error' => 'Tidak ada siswa di kelas'], 400); } // Get schedules untuk mapel ini $scheduleIds = Schedule::where('id_class', $classId) ->where('id_subject', $mapelId) ->pluck('id') ->toArray(); // Generate filename $filename = 'Laporan_Tahunan_Absensi_Mapel_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $mapelName) . '_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $kelasName) . '_' . $tahun . '.xlsx'; // Create spreadsheet $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Laporan Tahunan'); // Row 1: Title (Yellow background) $title = 'LAPORAN TAHUNAN ABSENSI ' . strtoupper($mapelName) . ' ' . strtoupper($kelasName); $sheet->setCellValue('A1', $title); $sheet->mergeCells('A1:R1'); $sheet->getRowDimension(1)->setRowHeight(25); $sheet->getStyle('A1')->applyFromArray([ 'font' => ['bold' => true, 'size' => 14, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], ]); // Row 2: Period info $periodText = 'TAHUN ' . $tahun; $sheet->setCellValue('A2', $periodText); $sheet->mergeCells('A2:R2'); $sheet->getRowDimension(2)->setRowHeight(20); $sheet->getStyle('A2')->applyFromArray([ 'font' => ['bold' => true, 'size' => 12, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], ]); // Row 3: Empty $sheet->getRowDimension(3)->setRowHeight(10); // Row 4: Headers $sheet->setCellValue('A4', 'Nama'); $sheet->setCellValue('B4', 'Jabatan/Kelas'); // Month Names $months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agt', 'Sep', 'Okt', 'Nov', 'Des']; foreach ($months as $idx => $monthName) { $colLetter = $this->getColumnLetter($idx + 3); // C = 3 $sheet->setCellValue($colLetter . '4', $monthName); } // Totals Headers $sheet->setCellValue('O4', "Total\nHadir"); $sheet->setCellValue('P4', "Total\nIzin"); $sheet->setCellValue('Q4', "Total\nSakit"); $sheet->setCellValue('R4', "Total\nAlpha"); // Style headers (Row 4) - Yellow background $headerRange = 'A4:R4'; $sheet->getStyle($headerRange)->applyFromArray([ 'font' => ['bold' => true, 'size' => 11, 'color' => ['rgb' => '000000']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFCC00']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], ]); $sheet->getRowDimension(4)->setRowHeight(30); // Fetch attendance data for the whole year $attendanceData = []; if (!empty($scheduleIds)) { $attendance = AttendanceHistory::whereIn('id_schedule', $scheduleIds) ->where(function($q) use ($tahun) { $q->whereYear('created_at', $tahun) ->orWhereYear('attendance_date', $tahun); })->get(); foreach ($attendance as $att) { $date = $att->attendance_date ? \Carbon\Carbon::parse($att->attendance_date) : \Carbon\Carbon::parse($att->created_at); $month = $date->month; $studentId = $att->id_student; $status = $att->status; if (!isset($attendanceData[$studentId])) { $attendanceData[$studentId] = []; } if (!isset($attendanceData[$studentId][$month])) { $attendanceData[$studentId][$month] = [ 'hadir' => 0, 'izin' => 0, 'sakit' => 0, 'alpha' => 0, 'dispen' => 0 ]; } if (in_array($status, ['hadir', 'izin', 'sakit', 'alpha', 'dispen'])) { $attendanceData[$studentId][$month][$status]++; } } } // Add student rows $dataRow = 5; foreach ($students as $student) { $sheet->setCellValue('A' . $dataRow, $student->name); $sheet->setCellValue('B' . $dataRow, $kelasName); $totalHadir = 0; $totalIzin = 0; $totalSakit = 0; $totalAlpha = 0; // Populate months (C to N) for ($month = 1; $month <= 12; $month++) { $colLetter = $this->getColumnLetter($month + 2); // C = 3 $h = 0; $i = 0; $s = 0; $a = 0; if (isset($attendanceData[$student->id][$month])) { $h = $attendanceData[$student->id][$month]['hadir'] ?? 0; $i = $attendanceData[$student->id][$month]['izin'] ?? 0; $s = $attendanceData[$student->id][$month]['sakit'] ?? 0; $a = (($attendanceData[$student->id][$month]['alpha'] ?? 0) + ($attendanceData[$student->id][$month]['dispen'] ?? 0)); } $totalHadir += $h; $totalIzin += $i; $totalSakit += $s; $totalAlpha += $a; if ($h > 0 || $i > 0 || $s > 0 || $a > 0) { $displayText = "H:{$h}\nI:{$i}\nS:{$s}\nA:{$a}"; $sheet->setCellValue($colLetter . $dataRow, $displayText); $sheet->getStyle($colLetter . $dataRow)->getAlignment()->setWrapText(true); } else { $sheet->setCellValue($colLetter . $dataRow, '-'); } $sheet->getStyle($colLetter . $dataRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); } // Populate totals $sheet->setCellValue('O' . $dataRow, $totalHadir); $sheet->setCellValue('P' . $dataRow, $totalIzin); $sheet->setCellValue('Q' . $dataRow, $totalSakit); $sheet->setCellValue('R' . $dataRow, $totalAlpha); for ($c = 15; $c <= 18; $c++) { $colLetter = $this->getColumnLetter($c); $sheet->getStyle($colLetter . $dataRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); } // Border styling $dataRange = 'A' . $dataRow . ':R' . $dataRow; $sheet->getStyle($dataRange)->applyFromArray([ 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER], ]); // Set height for dynamic wrapped text $sheet->getRowDimension($dataRow)->setRowHeight(55); $dataRow++; } // Add legend $legendRow = $dataRow + 2; $sheet->setCellValue('A' . $legendRow, 'Keterangan format bulanan:'); $sheet->getStyle('A' . $legendRow)->applyFromArray(['font' => ['bold' => true, 'size' => 11]]); $legendRow++; $legends = [ 'H' => 'Hadir', 'I' => 'Izin', 'S' => 'Sakit', 'A' => 'Alpha / Dispen' ]; foreach ($legends as $code => $meaning) { $sheet->setCellValue('A' . $legendRow, $code); $sheet->setCellValue('B' . $legendRow, $meaning); $sheet->getStyle('A' . $legendRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $legendRow++; } // Set column widths $sheet->getColumnDimension('A')->setWidth(25); $sheet->getColumnDimension('B')->setWidth(15); // Month columns width for ($month = 1; $month <= 12; $month++) { $colLetter = $this->getColumnLetter($month + 2); $sheet->getColumnDimension($colLetter)->setWidth(10); } $sheet->getColumnDimension('O')->setWidth(12); $sheet->getColumnDimension('P')->setWidth(12); $sheet->getColumnDimension('Q')->setWidth(12); $sheet->getColumnDimension('R')->setWidth(12); // Save and download $writer = new Xlsx($spreadsheet); $tempPath = storage_path('app/temp/' . $filename); @mkdir(dirname($tempPath), 0755, true); $writer->save($tempPath); return response()->download($tempPath)->deleteFileAfterSend(true); } }