TIFNJK_E41221880/app/Http/Controllers/HistoryDailyController.php

659 lines
24 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\AttendanceHistoryDaily;
use App\Models\Classes;
use App\Models\Student;
use App\Traits\ExcelExportTrait;
use Illuminate\Support\Collection;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
class HistoryDailyController extends Controller
{
use ExcelExportTrait;
public function absensiHarian($classId, Request $request)
{
$kelas = Classes::findOrFail($classId);
// Ambil semua siswa di kelas
$students = Student::where('id_class', $classId)->get();
// Determine filter parameters
$tanggal = $request->get('tanggal');
$bulan = $request->get('bulan');
$tahun = $request->get('tahun');
// Default ke hari ini hanya jika tidak ada filter apapun
if (!$tanggal && !$bulan && !$tahun) {
$tanggal = now()->format('Y-m-d');
}
// Build query untuk fetch attendance records
$query = AttendanceHistoryDaily::with(['student', 'class'])
->where('id_class', $classId);
// Jika tanggal diset, filter by exact date
if ($tanggal && !$bulan && !$tahun) {
$query->whereDate('created_at', $tanggal);
}
// Jika bulan dan tahun keduanya diset, filter by month AND year
else if ($bulan && $tahun) {
$query->whereMonth('created_at', $bulan)
->whereYear('created_at', $tahun);
}
// Jika hanya bulan diset, filter by month
else if ($bulan) {
$query->whereMonth('created_at', $bulan);
}
// Jika hanya tahun diset, filter by year
else if ($tahun) {
$query->whereYear('created_at', $tahun);
}
$absensiFromDB = $query->orderBy('created_at', 'desc')->get();
// Dapatkan semua hari UNIK dari records
$uniqueDates = $absensiFromDB
->map(function($item) { return $item->created_at->format('Y-m-d'); })
->unique()
->values()
->sort()
->reverse() // Terbaru dulu
->values();
// Jika filter tanggal spesifik dan tidak ada data, tambahkan tanggal tersebut
if ($tanggal && !$bulan && !$tahun && !$uniqueDates->contains($tanggal)) {
$uniqueDates = collect([$tanggal])->merge($uniqueDates);
}
// Build result: untuk SETIAP HARI, tampilkan SEMUA SISWA
$result = [];
foreach ($uniqueDates as $date) {
foreach ($students as $student) {
// Cari record di database untuk student + date ini
$record = $absensiFromDB
->where('id_student', $student->id)
->filter(function($item) use ($date) {
return $item->created_at->format('Y-m-d') === $date;
})
->first();
if ($record) {
// Ada record di database
$result[] = (object)[
'id' => $record->id,
'student' => $record->student,
'class' => $record->class,
'status' => $record->status,
'created_at' => $record->created_at,
'picture' => $record->picture,
];
} else {
// Dummy record: siswa belum absen di hari ini
// Set created_at ke hari tersebut 00:00:00 untuk filter bekerja dengan baik
$dummyDateTime = \Carbon\Carbon::parse($date . ' 00:00:00');
$result[] = (object)[
'id' => null,
'student' => $student,
'class' => $kelas,
'status' => 'in progress',
'created_at' => $dummyDateTime,
'picture' => null,
];
}
}
}
// Group data by tanggal (created_at)
$groupedByDate = collect();
foreach ($result as $item) {
$dateKey = $item->created_at->format('Y-m-d');
if (!$groupedByDate->has($dateKey)) {
$groupedByDate->put($dateKey, []);
}
$items = $groupedByDate->get($dateKey);
$items[] = $item;
$groupedByDate->put($dateKey, $items);
}
// Urutkan by tanggal (terbaru dulu)
$groupedArray = $groupedByDate->toArray();
krsort($groupedArray);
// Get today's date untuk default filter
$today = now()->format('Y-m-d');
return view('pages.absensi.absensi_harian', [
'absensi' => collect($result),
'groupedByDate' => $groupedArray,
'today' => $today,
'kelas' => $kelas,
'selectedTanggal' => $tanggal,
'selectedBulan' => $bulan,
'selectedTahun' => $tahun
]);
}
public function editStatus(Request $request, $id)
{
$request->validate([
'status' => 'required|string'
]);
// Jika $id adalah 'null', artinya create baru (belum ada di database)
if ($id === 'null') {
$request->validate([
'id_student' => 'required|exists:students,id',
'id_class' => 'required|exists:clases,id',
]);
try {
AttendanceHistoryDaily::create([
'id_student' => $request->id_student,
'id_class' => $request->id_class,
'status' => $request->status,
'picture' => $request->picture ?? '',
]);
return response()->json(['success' => true, 'message' => 'Data absensi berhasil ditambahkan.']);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => 'Error: ' . $e->getMessage()], 500);
}
} else {
// Update existing
$absensi = AttendanceHistoryDaily::findOrFail($id);
$absensi->status = $request->status;
$absensi->save();
return response()->json(['success' => true, 'message' => 'Status absensi berhasil diupdate.']);
}
}
public function exportExcel($classId, Request $request)
{
// Get kelas info
$kelas = Classes::findOrFail($classId);
$kelasName = $kelas->name ?? 'Kelas';
$tanggal = $request->get('tanggal');
$bulan = $request->get('bulan');
$tahun = $request->get('tahun');
// Jika bulan kosong tapi tahun ada, lakukan export tahunan
if (empty($tanggal) && empty($bulan) && $tahun) {
return $this->exportExcelYearly($classId, $tahun);
}
// Get data absensi harian
$rows = $this->getAbsensiHarianRowsForExport($classId, $request);
// Build filename suffix
if ($tanggal) {
$suffixTanggal = '_' . preg_replace('/[^0-9\-]/', '', $tanggal);
} else if ($bulan && $tahun) {
$suffixTanggal = '_' . $tahun . '-' . $bulan;
} else if ($bulan) {
$suffixTanggal = '_Bulan' . $bulan;
} else if ($tahun) {
$suffixTanggal = '_' . $tahun;
} else {
$suffixTanggal = '_' . now()->format('Y-m-d');
}
$filename = 'Rekap_Absensi_Harian_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $kelasName) . $suffixTanggal;
// Create spreadsheet
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Absensi Harian');
// Insert 2 rows untuk title dan subtitle
$sheet->insertNewRowBefore(1, 2);
// Title and subtitle
$title = 'REKAP ABSENSI HARIAN - ' . strtoupper($kelasName);
$subtitle = 'Diekspor pada: ' . now()->timezone('Asia/Jakarta')->format('d M Y H:i:s') . ' WIB';
$sheet->setCellValue('A1', $title);
$sheet->setCellValue('A2', $subtitle);
// Merge cells untuk title dan subtitle
$sheet->mergeCells('A1:F1');
$sheet->mergeCells('A2:F2');
// Set row heights
$sheet->getRowDimension(1)->setRowHeight(24);
$sheet->getRowDimension(2)->setRowHeight(18);
// Style title (Row 1)
$sheet->getStyle('A1:G1')->applyFromArray([
'font' => [
'bold' => true,
'size' => 14,
'color' => ['rgb' => '1F2937'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Style subtitle (Row 2)
$sheet->getStyle('A2:G2')->applyFromArray([
'font' => [
'size' => 10,
'color' => ['rgb' => '6B7280'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Add headers at row 3
$headers = ['No', 'Nama Siswa', 'NISN', 'Kelas', 'Status', 'Waktu'];
foreach ($headers as $col => $header) {
$sheet->setCellValue($this->getColumnLetter($col + 1) . '3', $header);
}
// Style headers (Row 3)
$headerRange = 'A3:F3';
$sheet->getStyle($headerRange)->applyFromArray([
'font' => [
'bold' => true,
'color' => ['rgb' => 'FFFFFF'],
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => '365CF5'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
'wrapText' => true,
],
]);
$sheet->getRowDimension(3)->setRowHeight(20);
// Add data rows starting from row 4
$dataStartRow = 4;
foreach ($rows as $index => $row) {
$currentRow = $dataStartRow + $index;
$sheet->setCellValue('A' . $currentRow, $row['no']);
$sheet->setCellValue('B' . $currentRow, $row['student_name']);
$sheet->setCellValue('C' . $currentRow, $row['nisn']);
$sheet->setCellValue('D' . $currentRow, $row['class_name']);
$sheet->setCellValue('E' . $currentRow, $row['status']);
$sheet->setCellValue('F' . $currentRow, $row['waktu']);
// Zebra rows (alternate background color)
if (($index % 2) === 1) {
$sheet->getStyle('A' . $currentRow . ':F' . $currentRow)->applyFromArray([
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'F4F7FF'],
],
]);
}
}
// Get highest row
$highestRow = $sheet->getHighestRow();
// Apply borders to all data
$tableRange = 'A3:F' . $highestRow;
$sheet->getStyle($tableRange)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => 'D1D5DB'],
],
],
]);
// Center align specific columns
$sheet->getStyle('A4:A' . $highestRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('D4:D' . $highestRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('E4:E' . $highestRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
// Set column widths
$sheet->getColumnDimension('A')->setWidth(6);
$sheet->getColumnDimension('B')->setWidth(28);
$sheet->getColumnDimension('C')->setWidth(16);
$sheet->getColumnDimension('D')->setWidth(14);
$sheet->getColumnDimension('E')->setWidth(16);
$sheet->getColumnDimension('F')->setWidth(20);
// Freeze top 3 rows (title + subtitle + header)
$sheet->freezePane('A4');
// Save and download
$writer = new Xlsx($spreadsheet);
$tempPath = storage_path('app/temp/' . $filename . '.xlsx');
@mkdir(dirname($tempPath), 0755, true);
$writer->save($tempPath);
return response()->download($tempPath)->deleteFileAfterSend(true);
}
/**
* Export absensi harian ke Excel - Format Tahunan (12 Bulan)
*/
private function exportExcelYearly($classId, $tahun)
{
// Get kelas info
$kelas = Classes::findOrFail($classId);
$kelasName = $kelas->name ?? 'Kelas';
// 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);
}
// Generate filename
$filename = 'Laporan_Tahunan_Absensi_Harian_' . 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 HARIAN ' . strtoupper($kelasName);
$sheet->setCellValue('A1', $title);
$sheet->mergeCells('A1:Q1');
$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:Q2');
$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\nTepat Waktu");
$sheet->setCellValue('P4', "Total\nTerlambat");
$sheet->setCellValue('Q4', "Total\nIzin");
// Style headers (Row 4) - Yellow background
$headerRange = 'A4:Q4';
$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 = [];
$attendance = AttendanceHistoryDaily::where('id_class', $classId)
->whereYear('created_at', $tahun)
->get();
foreach ($attendance as $att) {
$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] = [
'tepat_waktu' => 0,
'terlambat' => 0,
'izin' => 0
];
}
if (in_array($status, ['tepat_waktu', 'terlambat', 'izin'])) {
$attendanceData[$studentId][$month][$status]++;
}
}
// Add student rows
$dataRow = 5;
foreach ($students as $student) {
$sheet->setCellValue('A' . $dataRow, $student->name);
$sheet->setCellValue('B' . $dataRow, $kelasName);
$totalTepatWaktu = 0;
$totalTerlambat = 0;
$totalIzin = 0;
// Populate months (C to N)
for ($month = 1; $month <= 12; $month++) {
$colLetter = $this->getColumnLetter($month + 2); // C = 3
$tw = 0; $tl = 0; $i = 0;
if (isset($attendanceData[$student->id][$month])) {
$tw = $attendanceData[$student->id][$month]['tepat_waktu'] ?? 0;
$tl = $attendanceData[$student->id][$month]['terlambat'] ?? 0;
$i = $attendanceData[$student->id][$month]['izin'] ?? 0;
}
$totalTepatWaktu += $tw;
$totalTerlambat += $tl;
$totalIzin += $i;
if ($tw > 0 || $tl > 0 || $i > 0) {
$displayText = "TW:{$tw}\nTL:{$tl}\nI:{$i}";
$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, $totalTepatWaktu);
$sheet->setCellValue('P' . $dataRow, $totalTerlambat);
$sheet->setCellValue('Q' . $dataRow, $totalIzin);
for ($c = 15; $c <= 17; $c++) {
$colLetter = $this->getColumnLetter($c);
$sheet->getStyle($colLetter . $dataRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
}
// Border styling
$dataRange = 'A' . $dataRow . ':Q' . $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(45);
$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 = [
'TW' => 'Tepat Waktu',
'TL' => 'Terlambat',
'I' => 'Izin'
];
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(15);
$sheet->getColumnDimension('P')->setWidth(12);
$sheet->getColumnDimension('Q')->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);
}
/**
* Convert column number to letter (1->A, 2->B, etc)
*/
private function getColumnLetter($col)
{
$letter = '';
while ($col > 0) {
$col--;
$letter = chr(65 + ($col % 26)) . $letter;
$col = intdiv($col, 26);
}
return $letter;
}
/**
* Helper method untuk prepare data export absensi harian
*/
private function getAbsensiHarianRowsForExport($classId, Request $request)
{
// Get kelas
$kelas = Classes::findOrFail($classId);
// Get semua siswa di kelas
$students = Student::where('id_class', $classId)->get();
// Determine filter parameters
$tanggal = $request->get('tanggal');
$bulan = $request->get('bulan');
$tahun = $request->get('tahun');
// Default ke hari ini hanya jika tidak ada filter apapun
if (!$tanggal && !$bulan && !$tahun) {
$tanggal = now()->format('Y-m-d');
}
// Build query untuk fetch attendance records
$query = AttendanceHistoryDaily::with(['student', 'class'])
->where('id_class', $classId);
// Jika tanggal diset, filter by exact date
if ($tanggal && !$bulan && !$tahun) {
$query->whereDate('created_at', $tanggal);
}
// Jika bulan dan tahun keduanya diset, filter by month AND year
else if ($bulan && $tahun) {
$query->whereMonth('created_at', $bulan)
->whereYear('created_at', $tahun);
}
// Jika hanya bulan diset, filter by month
else if ($bulan) {
$query->whereMonth('created_at', $bulan);
}
// Jika hanya tahun diset, filter by year
else if ($tahun) {
$query->whereYear('created_at', $tahun);
}
$absensi = $query->get();
// Get unique dates dari filtered data
$uniqueDates = $absensi
->map(function($item) { return $item->created_at->format('Y-m-d'); })
->unique()
->values()
->sort()
->reverse()
->values();
// Build rows untuk export: untuk setiap hari, untuk setiap siswa
$rows = [];
$no = 1;
foreach ($uniqueDates as $date) {
foreach ($students as $student) {
// Cari record untuk student + date ini
$record = $absensi
->where('id_student', $student->id)
->filter(function($item) use ($date) {
return $item->created_at->format('Y-m-d') === $date;
})
->first();
if ($record) {
// Ada record di database
$rows[] = [
'no' => $no,
'student_name' => $record->student->name ?? '-',
'nisn' => $record->student->nisn ?? '-',
'class_name' => $record->class->name ?? '-',
'status' => ucfirst($record->status),
'waktu' => $record->created_at->format('d M Y H:i:s'),
];
} else {
// Dummy record: siswa belum absen di hari ini
$rows[] = [
'no' => $no,
'student_name' => $student->name,
'nisn' => $student->nisn ?? '-',
'class_name' => $kelas->name,
'status' => 'In Progress',
'waktu' => '-',
];
}
$no++;
}
}
return $rows;
}
}