356 lines
15 KiB
PHP
356 lines
15 KiB
PHP
<?php
|
|
// app/Http/Controllers/Api/ApiAbsensiKegiatanController.php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\AbsensiKegiatan;
|
|
use App\Models\Kegiatan;
|
|
use App\Models\Santri;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Carbon\Carbon;
|
|
|
|
class ApiAbsensiKegiatanController extends Controller
|
|
{
|
|
/**
|
|
* ==========================================
|
|
* 1. DASHBOARD HARI INI (Summary + Timeline)
|
|
* ==========================================
|
|
*/
|
|
public function today(Request $request)
|
|
{
|
|
try {
|
|
$user = $request->user();
|
|
$idSantri = $user->id_santri; // id_santri dari santri_accounts
|
|
|
|
$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->id_santri;
|
|
|
|
$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->id_santri;
|
|
|
|
$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
|
|
}
|
|
} |