TKK_E3220375/resources/views/admin/presensi/siswa.blade.php

228 lines
12 KiB
PHP

@extends('layouts.dashboard')
@section('title', 'Smart School | Presensi Siswa')
@section('content')
<div class="bg-gray-50 min-h-screen p-4 sm:p-6 lg:p-8">
<div class="max-w-7xl mx-auto">
<div class="mb-6 flex flex-col sm:flex-row justify-between items-center">
<h1 class="text-3xl font-bold text-gray-800">Halaman Presensi Siswa</h1>
<span class="text-sm font-medium text-gray-500 mt-2 sm:mt-0">Real-time Face Recognition</span>
</div>
<!-- Grid Layout Utama -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Kolom Kiri: Filter & Tabel Presensi -->
<div class="lg:col-span-2 space-y-6">
<!-- Card untuk Filter -->
<div class="bg-white p-6 rounded-xl shadow-md border border-gray-200">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Filter Data</h2>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<!-- Filter Jurusan -->
<div>
<label for="jurusan" class="block text-sm font-medium text-gray-600 mb-1">Pilih Jurusan</label>
<select id="jurusan" class="filter-input w-full p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
<option value="all">Semua Jurusan</option>
@foreach($jurusans as $jurusan)
<option value="{{ $jurusan->id }}">{{ $jurusan->nama_jurusan }}</option>
@endforeach
</select>
</div>
<!-- Filter Kelas -->
<div>
<label for="kelas" class="block text-sm font-medium text-gray-600 mb-1">Pilih Kelas</label>
<select id="kelas" class="filter-input w-full p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
<option value="all">Semua Kelas</option>
@foreach($kelases as $kelas)
<option value="{{ $kelas->id }}">{{ $kelas->nama_kelas }}</option>
@endforeach
</select>
</div>
<!-- Filter Ruangan/Device -->
<div>
<label for="device" class="block text-sm font-medium text-gray-600 mb-1">Pilih Device</label>
<select id="device" class="filter-input w-full p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
<option value="all" data-ip="">Semua Device</option>
@foreach($devices as $device)
<option value="{{ $device->id }}" data-ip="{{ $device->ip_address }}">{{ $device->nama_device }}</option>
@endforeach
</select>
</div>
<!-- Filter Tanggal -->
<div>
<label for="tanggal" class="block text-sm font-medium text-gray-600 mb-1">Pilih Tanggal</label>
<input type="date" id="tanggal" value="{{ date('Y-m-d') }}" class="filter-input w-full p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
</div>
</div>
</div>
<!-- Card untuk Tabel Presensi -->
<div class="bg-white p-6 rounded-xl shadow-md border border-gray-200">
<h2 id="laporan-title" class="text-xl font-semibold text-gray-700 mb-4">Laporan Kehadiran</h2>
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-100">
<tr>
<th class="px-6 py-3 rounded-l-lg">No</th>
<th class="px-6 py-3">Nama Siswa</th>
<th class="px-6 py-3">Jurusan</th>
<th class="px-6 py-3">Kelas</th>
<th class="px-6 py-3">Waktu Presensi</th>
<th class="px-6 py-3">Ruangan</th>
<th class="px-6 py-3 rounded-r-lg">Status</th>
</tr>
</thead>
<tbody id="attendance-table-body">
<tr><td colspan="7" class="p-4 text-center text-gray-500">Memuat data...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Kolom Kanan: Live Camera -->
<div class="lg:col-span-1">
<div class="bg-white p-6 rounded-xl shadow-md border border-gray-200 sticky top-6">
<h2 id="camera-title" class="text-xl font-semibold text-gray-700 mb-4 text-center">Live Camera Feed</h2>
<div id="cameraContainer" class="w-full aspect-video rounded-lg bg-gray-900 flex items-center justify-center overflow-hidden">
<img id="cameraFeed" src="" class="hidden w-full h-full object-cover">
<div id="cameraStatus" class="text-center text-gray-400 p-4">
<p class="flex items-center justify-center h-full">Pilih device untuk melihat live feed</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterInputs = document.querySelectorAll('.filter-input');
const tableBody = document.getElementById('attendance-table-body');
const laporanTitle = document.getElementById('laporan-title');
const deviceSelect = document.getElementById("device");
const cameraFeed = document.getElementById("cameraFeed");
const cameraStatus = document.getElementById("cameraStatus");
const cameraTitle = document.getElementById("camera-title");
let fetchInterval; // Variabel untuk menyimpan interval
function updateCameraFeed() {
const selectedOption = deviceSelect.options[deviceSelect.selectedIndex];
const deviceIp = selectedOption.dataset.ip;
const deviceName = selectedOption.text;
cameraFeed.classList.add('hidden');
cameraFeed.src = '';
if (!deviceIp || deviceSelect.value === 'all') {
cameraTitle.innerText = "Live Camera Feed";
cameraStatus.innerHTML = '<p class="flex items-center justify-center h-full">Pilih device untuk melihat live feed</p>';
cameraStatus.style.display = 'block';
return;
}
cameraTitle.innerText = `Live: ${deviceName}`;
cameraStatus.innerHTML = `<div class="flex flex-col items-center justify-center h-full"><svg class="animate-spin h-8 w-8 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><p class="mt-2">Menghubungkan...</p></div>`;
cameraStatus.style.display = 'block';
// PERBAIKAN: Gunakan variabel deviceIp yang dinamis
cameraFeed.src = `http://${deviceIp}:5000/video_feed`;
}
cameraFeed.onload = function() {
cameraFeed.classList.remove("hidden");
cameraStatus.style.display = "none";
};
cameraFeed.onerror = function() {
cameraFeed.classList.add("hidden");
cameraStatus.style.display = 'block';
cameraStatus.innerHTML = '<p class="flex items-center justify-center h-full text-red-500 font-semibold">Kamera tidak aktif atau gagal terhubung.</p>';
};
async function fetchAttendanceData() {
const jurusanId = document.getElementById('jurusan').value;
const kelasId = document.getElementById('kelas').value;
const deviceId = deviceSelect.value;
const tanggal = document.getElementById('tanggal').value;
laporanTitle.innerText = `Laporan Kehadiran ${formatIndonesianDate(tanggal)}`;
const url = `/api/laporan-absensi?tanggal=${tanggal}&jurusan_id=${jurusanId}&kelas_id=${kelasId}&device_id=${deviceId}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
renderTable(data);
} catch (error) {
console.error('Error fetching data:', error);
tableBody.innerHTML = `<tr><td colspan="7" class="p-4 text-center text-red-500">Gagal memuat data.</td></tr>`;
}
}
function renderTable(data) {
tableBody.innerHTML = '';
if (data.length === 0) {
tableBody.innerHTML = `<tr><td colspan="7" class="p-4 text-center text-gray-500">Tidak ada data absensi untuk filter ini.</td></tr>`;
return;
}
data.forEach(item => {
let statusBadge = '';
switch (item.status) {
case 'hadir': statusBadge = `<span class="px-2 py-1 font-semibold leading-tight text-green-700 bg-green-100 rounded-full">Hadir</span>`; break;
case 'terlambat': statusBadge = `<span class="px-2 py-1 font-semibold leading-tight text-yellow-700 bg-yellow-100 rounded-full">Terlambat</span>`; break;
default: statusBadge = `<span class="px-2 py-1 font-semibold leading-tight text-red-700 bg-red-100 rounded-full">${item.status}</span>`;
}
const row = `
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-6 py-4 font-medium text-gray-900">${item.no}</td>
<td class="px-6 py-4">${item.nama_siswa}</td>
<td class="px-6 py-4">${item.jurusan}</td>
<td class="px-6 py-4">${item.kelas}</td>
<td class="px-6 py-4">${item.waktu}</td>
<td class="px-6 py-4">${item.ruangan}</td>
<td class="px-6 py-4">${statusBadge}</td>
</tr>`;
tableBody.insertAdjacentHTML('beforeend', row);
});
}
function formatIndonesianDate(dateString) {
if (!dateString) return '';
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
const months = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
const date = new Date(dateString + 'T00:00:00');
return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()} (${days[date.getDay()]})`;
}
// Fungsi baru untuk memulai/mengatur ulang interval
function startAutoRefresh() {
// Hapus interval lama jika ada, untuk mencegah duplikasi
clearInterval(fetchInterval);
// Panggil data segera saat filter diubah
fetchAttendanceData();
// Atur interval baru untuk memanggil data setiap 15 detik
fetchInterval = setInterval(fetchAttendanceData, 15000); // 15000 ms = 15 detik
}
filterInputs.forEach(input => {
input.addEventListener('change', startAutoRefresh);
});
deviceSelect.addEventListener('change', function() {
// startAutoRefresh() sudah dipanggil oleh event listener di atas
updateCameraFeed();
});
// === INISIALISASI ===
// Panggil fungsi saat halaman pertama kali dimuat untuk memulai semuanya
startAutoRefresh();
});
</script>
@endsection