system-locked violation types (alpha & terlambat) + proporsional poin alpha per jam
- Migration: add is_system column to violation_types, mark alpha & terlambat - DestroyType guard: system types cannot be deleted - UpdateType guard: slug tetap walau nama diubah untuk system types - UI: delete button hidden, badge lock untuk system types - Seeder: ViolationTypeDefaultsSeeder untuk fresh deploy - AttendanceController: lookup by slug (alpha/terlambat) instead of fuzzy match - Alpha auto-violation: poin proporsional per jam (round((points/10)*count), min 1) - Terlambat auto-violation: poin fix sesuai jenis (2 poin) - Also handles both A (alpha) and T (terlambat) statuses for auto-violations
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Input Presensi')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
{{-- Header --}}
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
|
||||
<a href="{{ route('attendances.index') }}" class="hover:text-gray-600 transition">Presensi</a>
|
||||
<span class="text-gray-300">/</span>
|
||||
<span class="text-gray-700 font-medium">Input</span>
|
||||
</nav>
|
||||
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Input Presensi Per Kelas</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">Pilih kelas, tanggal, dan isi kehadiran per jam pelajaran</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Pilih Kelas + Tanggal --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
|
||||
<form method="GET" class="p-5">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1.5">Kelas <span class="text-red-500">*</span></label>
|
||||
<select name="class_name" required
|
||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
|
||||
<option value="">Pilih kelas...</option>
|
||||
@foreach($classNames as $cn)
|
||||
<option value="{{ $cn }}" @selected($className == $cn)>{{ $cn }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1.5">Tanggal <span class="text-red-500">*</span></label>
|
||||
<input type="date" name="date" value="{{ $date }}"
|
||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit"
|
||||
class="w-full px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center justify-center gap-2">
|
||||
<i class="fa-solid fa-arrow-right text-xs"></i>
|
||||
Lanjutkan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if($errors->any() && $students->count() > 0)
|
||||
<div class="bg-red-50 border border-red-200 rounded-2xl p-4 mb-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<i class="fa-solid fa-circle-exclamation text-red-400 mt-0.5"></i>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-red-700">Gagal menyimpan presensi</p>
|
||||
<ul class="mt-1 text-xs text-red-600 list-disc list-inside">
|
||||
@foreach($errors->all() as $err)
|
||||
<li>{{ $err }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($students->count() > 0)
|
||||
<form action="{{ route('attendances.store') }}" method="POST" x-data="{ saved: false }">
|
||||
@csrf
|
||||
<input type="hidden" name="date" value="{{ $date }}">
|
||||
<input type="hidden" name="class_name" value="{{ $className }}">
|
||||
<input type="hidden" name="auto_violation" value="1">
|
||||
|
||||
{{-- Info bar --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-4">
|
||||
<div class="px-6 py-3 flex flex-wrap items-center justify-between gap-3 bg-gradient-to-br from-gray-50 to-gray-100/50 border-b border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fa-solid fa-users text-gray-400"></i>
|
||||
<span class="text-sm font-semibold text-gray-700">{{ $students->count() }} siswa</span>
|
||||
<span class="w-px h-4 bg-gray-200"></span>
|
||||
<span class="text-xs text-gray-500">{{ \Carbon\Carbon::parse($date)->translatedFormat('l, d F Y') }}</span>
|
||||
<span class="w-px h-4 bg-gray-200"></span>
|
||||
<span class="text-xs text-gray-500">Kelas {{ $className }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="{{ route('attendances.recap', ['class_name' => $className]) }}"
|
||||
class="text-xs font-semibold text-violet-600 hover:text-violet-800 transition inline-flex items-center gap-1">
|
||||
<i class="fa-solid fa-chart-simple"></i> Rekap
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Grid table --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-100">
|
||||
<thead>
|
||||
<tr class="bg-gray-50/80">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider sticky left-0 bg-gray-50/80 z-10 min-w-[150px]">Siswa</th>
|
||||
<th class="px-1 py-3 text-center text-[9px] font-semibold text-gray-300 uppercase tracking-wider min-w-[80px]">Set</th>
|
||||
@foreach($lessonHours as $lh)
|
||||
<th class="px-2 py-3 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider min-w-[70px]">
|
||||
Jam ke-{{ $lh }}
|
||||
</th>
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach($students as $s)
|
||||
<tr class="hover:bg-gray-50/50 transition">
|
||||
{{-- Student name --}}
|
||||
<td class="px-4 py-2.5 whitespace-nowrap sticky left-0 bg-white z-10">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-7 h-7 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-[10px] font-bold text-gray-500">{{ strtoupper(substr($s->full_name, 0, 1)) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs font-semibold text-gray-900 truncate max-w-[130px]">{{ $s->full_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Per-row bulk buttons --}}
|
||||
<td class="px-1 py-2.5 text-center whitespace-nowrap">
|
||||
<div class="flex items-center gap-px">
|
||||
<button type="button" onclick="studentBulkSet('{{ $s->id }}', 'hadir')" title="Semua Hadir"
|
||||
class="w-5 h-5 rounded text-[9px] font-bold bg-emerald-100 text-emerald-700 border border-emerald-300 hover:bg-emerald-200 transition">H</button>
|
||||
<button type="button" onclick="studentBulkSet('{{ $s->id }}', 'sakit')" title="Semua Sakit"
|
||||
class="w-5 h-5 rounded text-[9px] font-bold bg-purple-100 text-purple-700 border border-purple-300 hover:bg-purple-200 transition">S</button>
|
||||
<button type="button" onclick="studentBulkSet('{{ $s->id }}', 'izin')" title="Semua Izin"
|
||||
class="w-5 h-5 rounded text-[9px] font-bold bg-blue-100 text-blue-700 border border-blue-300 hover:bg-blue-200 transition">I</button>
|
||||
<button type="button" onclick="studentBulkSet('{{ $s->id }}', 'alpha')" title="Semua Alpha"
|
||||
class="w-5 h-5 rounded text-[9px] font-bold bg-red-100 text-red-700 border border-red-300 hover:bg-red-200 transition">A</button>
|
||||
<button type="button" onclick="studentBulkSet('{{ $s->id }}', 'terlambat')" title="Semua Terlambat"
|
||||
class="w-5 h-5 rounded text-[9px] font-bold bg-yellow-100 text-yellow-700 border border-yellow-300 hover:bg-yellow-200 transition">T</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Lesson hours cells --}}
|
||||
@foreach($lessonHours as $lh)
|
||||
@php
|
||||
$key = $s->id . '-' . $lh;
|
||||
$currentStatus = isset($existing[$key]) ? $existing[$key]->status : 'hadir';
|
||||
$statusColors = [
|
||||
'hadir' => 'bg-emerald-100 border-emerald-300 text-emerald-700',
|
||||
'sakit' => 'bg-purple-100 border-purple-300 text-purple-700',
|
||||
'izin' => 'bg-blue-100 border-blue-300 text-blue-700',
|
||||
'alpha' => 'bg-red-100 border-red-300 text-red-700',
|
||||
'terlambat' => 'bg-yellow-100 border-yellow-300 text-yellow-700',
|
||||
];
|
||||
$statusLabels = [
|
||||
'hadir' => 'H',
|
||||
'sakit' => 'S',
|
||||
'izin' => 'I',
|
||||
'alpha' => 'A',
|
||||
'terlambat' => 'T',
|
||||
];
|
||||
$statusFull = [
|
||||
'hadir' => 'Hadir',
|
||||
'sakit' => 'Sakit',
|
||||
'izin' => 'Izin',
|
||||
'alpha' => 'Alpha',
|
||||
'terlambat' => 'Terlambat',
|
||||
];
|
||||
$nextStatus = [
|
||||
'hadir' => 'sakit',
|
||||
'sakit' => 'izin',
|
||||
'izin' => 'alpha',
|
||||
'alpha' => 'terlambat',
|
||||
'terlambat' => 'hadir',
|
||||
];
|
||||
@endphp
|
||||
<td class="px-2 py-2.5 text-center">
|
||||
<input type="hidden"
|
||||
name="attendances[{{ $s->id }}][{{ $lh }}][student_id]"
|
||||
value="{{ $s->id }}">
|
||||
<input type="hidden"
|
||||
name="attendances[{{ $s->id }}][{{ $lh }}][lesson_hour]"
|
||||
value="{{ $lh }}">
|
||||
<input type="hidden"
|
||||
name="attendances[{{ $s->id }}][{{ $lh }}][status]"
|
||||
id="status-{{ $s->id }}-{{ $lh }}"
|
||||
value="{{ $currentStatus }}">
|
||||
|
||||
<button type="button"
|
||||
onclick="rotateStatus({{ $s->id }}, {{ $lh }})"
|
||||
class="status-btn w-9 h-9 rounded-lg text-xs font-bold border-2 transition-all duration-150 shadow-sm hover:shadow-md hover:scale-105 {{ $statusColors[$currentStatus] }}"
|
||||
id="btn-{{ $s->id }}-{{ $lh }}"
|
||||
title="{{ $statusFull[$currentStatus] }}">
|
||||
{{ $statusLabels[$currentStatus] }}
|
||||
</button>
|
||||
</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Submit --}}
|
||||
<div class="mt-6 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 text-xs text-gray-500">
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-4 h-4 rounded bg-emerald-100 border border-emerald-300 text-[9px] font-bold text-emerald-700 flex items-center justify-center">H</span> Hadir</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-4 h-4 rounded bg-purple-100 border border-purple-300 text-[9px] font-bold text-purple-700 flex items-center justify-center">S</span> Sakit</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-4 h-4 rounded bg-blue-100 border border-blue-300 text-[9px] font-bold text-blue-700 flex items-center justify-center">I</span> Izin</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-4 h-4 rounded bg-red-100 border border-red-300 text-[9px] font-bold text-red-700 flex items-center justify-center">A</span> Alpha</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-4 h-4 rounded bg-yellow-100 border border-yellow-300 text-[9px] font-bold text-yellow-700 flex items-center justify-center">T</span> Terlambat</span>
|
||||
<span class="w-px h-4 bg-gray-200"></span>
|
||||
<span>Klik tombol untuk ganti status</span>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="px-6 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center justify-center gap-2">
|
||||
<i class="fa-solid fa-floppy-disk text-xs"></i>
|
||||
Simpan Presensi
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Info --}}
|
||||
<div class="mt-4 bg-gradient-to-br from-emerald-50 to-teal-50/50 border border-emerald-100 rounded-2xl p-4">
|
||||
<div class="flex items-start gap-3 text-xs text-emerald-700">
|
||||
<i class="fa-solid fa-circle-info mt-0.5"></i>
|
||||
<span>Siswa dengan status <strong>Alpha (A)</strong> minimal 1 jam akan otomatis dicatat sebagai pelanggaran.</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const STATUS_CYCLE = ['hadir', 'sakit', 'izin', 'alpha', 'terlambat'];
|
||||
const STATUS_COLORS = {
|
||||
'hadir': 'bg-emerald-100 border-emerald-300 text-emerald-700',
|
||||
'sakit': 'bg-purple-100 border-purple-300 text-purple-700',
|
||||
'izin': 'bg-blue-100 border-blue-300 text-blue-700',
|
||||
'alpha': 'bg-red-100 border-red-300 text-red-700',
|
||||
'terlambat': 'bg-yellow-100 border-yellow-300 text-yellow-700',
|
||||
};
|
||||
const STATUS_LABELS = {
|
||||
'hadir': 'H', 'sakit': 'S', 'izin': 'I',
|
||||
'alpha': 'A', 'terlambat': 'T',
|
||||
};
|
||||
const STATUS_FULL = {
|
||||
'hadir': 'Hadir', 'sakit': 'Sakit', 'izin': 'Izin',
|
||||
'alpha': 'Alpha', 'terlambat': 'Terlambat',
|
||||
};
|
||||
|
||||
function studentBulkSet(studentId, status) {
|
||||
for (let lh = 1; lh <= 10; lh++) {
|
||||
const hidden = document.getElementById('status-' + studentId + '-' + lh);
|
||||
const btn = document.getElementById('btn-' + studentId + '-' + lh);
|
||||
if (hidden && btn) {
|
||||
hidden.value = status;
|
||||
btn.className = 'status-btn w-9 h-9 rounded-lg text-xs font-bold border-2 transition-all duration-150 shadow-sm hover:shadow-md hover:scale-105 ' + STATUS_COLORS[status];
|
||||
btn.textContent = STATUS_LABELS[status];
|
||||
btn.title = STATUS_FULL[status];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rotateStatus(studentId, lessonHour) {
|
||||
const hiddenInput = document.getElementById('status-' + studentId + '-' + lessonHour);
|
||||
const btn = document.getElementById('btn-' + studentId + '-' + lessonHour);
|
||||
const current = hiddenInput.value;
|
||||
const nextIdx = (STATUS_CYCLE.indexOf(current) + 1) % STATUS_CYCLE.length;
|
||||
const next = STATUS_CYCLE[nextIdx];
|
||||
|
||||
hiddenInput.value = next;
|
||||
btn.className = 'status-btn w-9 h-9 rounded-lg text-xs font-bold border-2 transition-all duration-150 shadow-sm hover:shadow-md hover:scale-105 ' + STATUS_COLORS[next];
|
||||
btn.textContent = STATUS_LABELS[next];
|
||||
btn.title = STATUS_FULL[next];
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,118 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Presensi Siswa')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
{{-- Header --}}
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Presensi Siswa</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">Pencatatan kehadiran siswa per jam pelajaran</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="{{ route('attendances.recap') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold text-violet-600 bg-violet-50 border border-violet-200 rounded-xl hover:bg-violet-100 transition shadow-sm">
|
||||
<i class="fa-solid fa-chart-simple text-xs"></i>
|
||||
Rekap Bulanan
|
||||
</a>
|
||||
<a href="{{ route('attendances.create') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
|
||||
<i class="fa-solid fa-plus text-xs"></i>
|
||||
Input Presensi
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Quick Stats --}}
|
||||
@php
|
||||
$today = now()->toDateString();
|
||||
$todayAlpha = \App\Models\Attendance::where('date', $today)->where('status', 'alpha')->count();
|
||||
$todayTotal = \App\Models\Attendance::where('date', $today)->count();
|
||||
$monthTotal = \App\Models\Attendance::whereMonth('date', now()->month)->whereYear('date', now()->year)->count();
|
||||
$monthAlpha = \App\Models\Attendance::whereMonth('date', now()->month)->whereYear('date', now()->year)->where('status', 'alpha')->count();
|
||||
$recentDates = \App\Models\Attendance::select('date')
|
||||
->distinct()
|
||||
->orderByDesc('date')
|
||||
->take(10)
|
||||
->get();
|
||||
@endphp
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-emerald-500 to-emerald-600 p-5 shadow-sm">
|
||||
<div class="absolute right-0 top-0 w-20 h-20 opacity-10"><i class="fa-solid fa-clipboard-check text-white text-6xl"></i></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Presensi Hari Ini</p>
|
||||
<p class="text-3xl font-bold text-white mt-1">{{ $todayTotal }}</p>
|
||||
<p class="text-[10px] text-white/50 mt-0.5">data tercatat</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-red-500 to-red-600 p-5 shadow-sm">
|
||||
<div class="absolute right-0 top-0 w-20 h-20 opacity-10"><i class="fa-solid fa-xmark text-white text-6xl"></i></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Alpha Hari Ini</p>
|
||||
<p class="text-3xl font-bold text-white mt-1">{{ $todayAlpha }}</p>
|
||||
<p class="text-[10px] text-white/50 mt-0.5">tanpa keterangan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 p-5 shadow-sm">
|
||||
<div class="absolute right-0 top-0 w-20 h-20 opacity-10"><i class="fa-solid fa-calendar text-white text-6xl"></i></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Bulan Ini</p>
|
||||
<p class="text-3xl font-bold text-white mt-1">{{ $monthTotal }}</p>
|
||||
<p class="text-[10px] text-white/50 mt-0.5">total presensi</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-violet-500 to-violet-600 p-5 shadow-sm">
|
||||
<div class="absolute right-0 top-0 w-20 h-20 opacity-10"><i class="fa-solid fa-chart-bar text-white text-6xl"></i></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Alpha Bulan Ini</p>
|
||||
<p class="text-3xl font-bold text-white mt-1">{{ $monthAlpha }}</p>
|
||||
<p class="text-[10px] text-white/50 mt-0.5">pelanggaran</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Recent --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center shadow-sm">
|
||||
<i class="fa-solid fa-clock-rotate-left text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900">Riwayat Presensi</h3>
|
||||
<p class="text-xs text-gray-400">10 hari terakhir</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if($recentDates->count() > 0)
|
||||
<div class="divide-y divide-gray-50">
|
||||
@foreach($recentDates as $rd)
|
||||
<a href="{{ route('attendances.create', ['date' => $rd->date->format('Y-m-d'), 'class_name' => request('class_name')]) }}"
|
||||
class="flex items-center justify-between px-6 py-3.5 hover:bg-gray-50/50 transition group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-emerald-50 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa-solid fa-calendar text-emerald-500 text-xs"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-900">{{ $rd->date->translatedFormat('l, d M Y') }}</p>
|
||||
<p class="text-xs text-gray-400">{{ $rd->date->diffForHumans() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-arrow-right text-gray-300 group-hover:text-blue-500 transition"></i>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="px-5 py-12 text-center">
|
||||
<div class="w-14 h-14 rounded-2xl bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-3">
|
||||
<i class="fa-solid fa-clipboard text-gray-300 text-xl"></i>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-500 mb-0.5">Belum Ada Presensi</p>
|
||||
<p class="text-xs text-gray-400">Klik "Input Presensi" untuk memulai</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,132 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Rekap Presensi')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
{{-- Header --}}
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
|
||||
<a href="{{ route('attendances.index') }}" class="hover:text-gray-600 transition">Presensi</a>
|
||||
<span class="text-gray-300">/</span>
|
||||
<span class="text-gray-700 font-medium">Rekap Bulanan</span>
|
||||
</nav>
|
||||
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Rekap Presensi Bulanan</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">Rekapitulasi kehadiran siswa per jam pelajaran</p>
|
||||
</div>
|
||||
<a href="{{ route('attendances.create') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
|
||||
<i class="fa-solid fa-plus text-xs"></i>
|
||||
Input Presensi
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
|
||||
<form method="GET">
|
||||
<div class="p-5">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1.5">Kelas</label>
|
||||
<select name="class_name"
|
||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
|
||||
<option value="">Semua Kelas</option>
|
||||
@foreach($classNames as $cn)
|
||||
<option value="{{ $cn }}" @selected($className == $cn)>{{ $cn }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1.5">Bulan</label>
|
||||
<select name="month"
|
||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
|
||||
@foreach(range(1, 12) as $m)
|
||||
<option value="{{ $m }}" @selected($month == $m)>{{ \Carbon\Carbon::create()->month($m)->format('F') }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1.5">Tahun</label>
|
||||
<div class="flex gap-2">
|
||||
<select name="year"
|
||||
class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
|
||||
@foreach(range(now()->year, now()->year - 2, -1) as $y)
|
||||
<option value="{{ $y }}" @selected($year == $y)>{{ $y }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit"
|
||||
class="px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-violet-600 flex items-center justify-center shadow-sm">
|
||||
<i class="fa-solid fa-chart-simple text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900">
|
||||
Rekap {{ \Carbon\Carbon::create()->month($month)->format('F') }} {{ $year }}
|
||||
</h3>
|
||||
<p class="text-xs text-gray-400">{{ count($recap) }} siswa</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(count($recap) > 0)
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-100">
|
||||
<thead>
|
||||
<tr class="bg-gray-50/80">
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Siswa</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-emerald-600 uppercase tracking-wider">✅ Hadir</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-purple-600 uppercase tracking-wider">🟣 Sakit</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-blue-600 uppercase tracking-wider">🔵 Izin</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-red-600 uppercase tracking-wider">❌ Alpha</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-yellow-600 uppercase tracking-wider">🟡 Terlambat</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach($recap as $r)
|
||||
<tr class="hover:bg-gray-50/50 transition">
|
||||
<td class="px-5 py-3 whitespace-nowrap">
|
||||
<p class="text-sm font-semibold text-gray-900">{{ $r->student->full_name }}</p>
|
||||
<p class="text-xs text-gray-400">{{ $r->student->class_name }}</p>
|
||||
</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold text-emerald-600">{{ $r->hadir }}</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold text-purple-600">{{ $r->sakit }}</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold text-blue-600">{{ $r->izin }}</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold {{ $r->alpha > 0 ? 'text-red-600' : 'text-gray-400' }}">{{ $r->alpha }}</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold {{ $r->terlambat > 0 ? 'text-yellow-600' : 'text-gray-400' }}">{{ $r->terlambat }}</td>
|
||||
<td class="px-3 py-3 text-center text-sm font-bold text-gray-700">{{ $r->total }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 text-xs text-gray-400 text-center">
|
||||
Menampilkan rekap {{ \Carbon\Carbon::create()->month($month)->format('F') }} {{ $year }}
|
||||
@if($className) • Kelas {{ $className }} @endif
|
||||
</div>
|
||||
@else
|
||||
<div class="px-5 py-12 text-center">
|
||||
<div class="w-14 h-14 rounded-2xl bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-3">
|
||||
<i class="fa-solid fa-chart-simple text-gray-300 text-xl"></i>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-500 mb-0.5">Belum Ada Data Presensi</p>
|
||||
<p class="text-xs text-gray-400">Belum ada catatan presensi untuk bulan ini</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user