sidakpelem/resources/views/admin/features/laporan/laporan.blade.php

310 lines
12 KiB
PHP

@extends('admin.layouts.app')
@section('title', 'Laporan')
@push('styles')
<link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap5.min.css" rel="stylesheet">
<style>
.soft-card {
border: 0;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(16, 24, 40, .06);
}
.table-hlines {
--line: #e9edf4;
}
.table-hlines> :not(caption)>*>* {
border-top: 0;
border-right: 0 !important;
border-left: 0 !important;
border-bottom: 1px solid var(--line);
background: transparent;
}
.table-hlines thead th {
border-bottom: 2px solid var(--line) !important;
}
table.dataTable.hover>tbody>tr:hover>*,
table.dataTable.display>tbody>tr:hover>* {
background: transparent !important;
}
.table-sticky thead th {
position: sticky;
top: 0;
background: #fff;
z-index: 1
}
div.dataTables_filter,
div.dataTables_length {
display: none
}
.avatar-sm {
width: 36px;
height: 36px;
border-radius: 999px;
overflow: hidden;
flex: 0 0 auto;
}
.avatar-sm img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-fallback {
width: 36px;
height: 36px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f1f5f9;
color: #64748b;
font-weight: 700;
font-size: 12px;
}
/* ===== Filter Bar (sesuai referensi) ===== */
.filter-bar {
border: 1px solid #e9edf4;
border-radius: 12px;
padding: 10px 12px;
background: #fff;
}
.filter-label {
font-weight: 600;
font-size: .875rem;
color: #0f766e;
white-space: nowrap;
}
.filter-bar .form-control,
.filter-bar .form-select,
.filter-bar .input-group-text,
.filter-bar .btn {
border-radius: 10px;
}
.btn-download {
background: #f97316;
color: #fff;
border: 0;
}
.btn-download:hover {
background: #f97316;
color: #fff;
opacity: .9;
}
</style>
@endpush
@section('content')
@php
use Illuminate\Support\Str;
$getInitials = function ($name) {
$parts = collect(preg_split('/\s+/', trim((string) $name)))->filter();
return strtoupper($parts->take(2)->map(fn($w) => Str::substr($w, 0, 1))->join(''));
};
$photoUrl = function ($path) {
if (!$path) {
return null;
}
return Str::startsWith($path, ['http://', 'https://']) ? $path : asset('storage/' . ltrim($path, '/'));
};
// dropdown periode (misal 24 bulan kebelakang)
$selectedPeriod = sprintf('%04d-%02d', (int) $year, (int) $month);
@endphp
<div class="card soft-card">
<div class="card-body">
{{-- Header --}}
<div class="mb-3">
<h4 class="mb-0">Laporan Absensi Desa</h4>
<small style="color:#0f766e">Rekap {{ $monthLabel }}</small>
</div>
{{-- Filter bar seperti referensi --}}
<div class="d-flex flex-wrap align-items-center gap-3 mb-3">
{{-- Nama search --}}
<div class="d-flex align-items-center gap-2 grow" style="min-width: 280px;">
<span class="filter-label">Nama:</span>
<div class="input-group input-group-sm" style="max-width: 360px;">
<input id="nameSearch" type="text" class="form-control" placeholder="Cari nama anggota ...">
<button class="btn btn-outline-secondary" type="button" id="btnNameSearch">
<i class="ti ti-search"></i>
</button>
</div>
</div>
{{-- Periode select (1 dropdown) --}}
<form id="periodForm" method="GET" action="{{ route('admin.laporan') }}"
class="d-flex align-items-center gap-2" style="min-width: 260px;">
<span class="filter-label">Periode:</span>
<select id="periodSelect" class="form-select form-select-sm" style="min-width: 170px;">
@for ($i = 0; $i < 24; $i++)
@php
$d = now()->subMonths($i);
$val = $d->format('Y-m');
$label = $d->translatedFormat('F Y'); // Januari 2026
@endphp
<option value="{{ $val }}" {{ $val === $selectedPeriod ? 'selected' : '' }}>
{{ $label }}
</option>
@endfor
</select>
{{-- hidden param yang dipakai controller --}}
<input type="hidden" name="month" id="monthInput" value="{{ (int) $month }}">
<input type="hidden" name="year" id="yearInput" value="{{ (int) $year }}">
</form>
{{-- Unduh rekap --}}
<div class="ms-auto">
<a class="btn btn-sm btn-download"
href="{{ route('admin.laporan.export', ['month' => (int) $month, 'year' => (int) $year]) }}">
<i class="ti ti-download me-1"></i> Unduh Rekap
</a>
</div>
</div>
{{-- Table --}}
<table id="laporanTable" class="table align-middle w-100 table-sticky table-hlines table-hover">
<thead>
<tr>
<th>Nama</th>
<th class="text-center">Hadir</th>
<th class="text-center">Izin</th>
<th class="text-center">Sakit</th>
<th class="text-center">Alpha</th>
</tr>
</thead>
<tbody>
@forelse ($rows as $r)
@php $img = $photoUrl($r->url_photo); @endphp
<tr onclick="window.location='{{ route('admin.laporan.detail', ['user' => $r->id, 'month' => (int) $month, 'year' => (int) $year]) }}'"
style="cursor:pointer">
<td>
<div class="d-flex align-items-center gap-2">
@if ($img)
<span class="avatar-sm"><img src="{{ $img }}"
alt="{{ $r->name }}"></span>
@else
<span class="avatar-fallback">{{ $getInitials($r->name) }}</span>
@endif
<div class="lh-sm">
<div class="fw-semibold text-dark">{{ $r->name }}</div>
<small class="text-muted">{{ $r->jabatan ?? '-' }}</small>
</div>
</div>
</td>
<td class="text-center fw-semibold">{{ (int) ($r->hadir ?? 0) }}</td>
<td class="text-center fw-semibold">{{ (int) ($r->izin ?? 0) }}</td>
<td class="text-center fw-semibold">{{ (int) ($r->sakit ?? 0) }}</td>
<td class="text-center fw-semibold">{{ (int) ($r->alpha ?? 0) }}</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted py-4">
Tidak ada data user.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection
@push('scripts')
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/responsive.bootstrap5.min.js"></script>
<script>
$(function() {
var hasData = $('#laporanTable tbody tr').length > 0 && !$('#laporanTable tbody tr td[colspan]').length;
let table = null;
if (typeof $.fn.DataTable !== 'undefined' && hasData) {
table = $('#laporanTable').DataTable({
autoWidth: false,
responsive: {
details: false
},
paging: true,
pageLength: 20,
lengthChange: false,
info: true,
ordering: true,
searching: true,
order: [
[0, 'asc']
],
language: {
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
infoFiltered: "(difilter dari _MAX_ total data)",
paginate: {
first: "Pertama",
last: "Terakhir",
next: "Selanjutnya",
previous: "Sebelumnya"
},
emptyTable: "Tidak ada data yang tersedia",
zeroRecords: "Tidak ada data yang cocok"
},
columnDefs: [{
targets: [1, 2, 3, 4],
className: 'text-center text-nowrap'
}],
dom: "rt<'row align-items-center mt-3'<'col-sm-6 text-center text-sm-start'i><'col-sm-6 d-flex justify-content-center justify-content-sm-end'p>>",
});
// Search by name (input + tombol)
const applySearch = () => table.search($('#nameSearch').val()).draw();
$('#nameSearch').on('keyup', function(e) {
if (e.key === 'Enter') applySearch();
else table.search(this.value).draw();
});
$('#btnNameSearch').on('click', function() {
applySearch();
});
} else {
$('#nameSearch').prop('disabled', true);
$('#btnNameSearch').prop('disabled', true);
}
// Periode select => submit GET month/year
$('#periodSelect').on('change', function() {
const val = $(this).val(); // YYYY-MM
const parts = (val || '').split('-');
if (parts.length === 2) {
$('#yearInput').val(parseInt(parts[0], 10));
$('#monthInput').val(parseInt(parts[1], 10));
$('#periodForm').trigger('submit');
}
});
});
</script>
@endpush