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

529 lines
19 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('admin.layouts.app')
@section('title', 'Dashboard')
@push('styles')
<link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
{{-- kalau pakai responsive --}}
<link href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap5.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
<style>
.soft-card {
border: 0;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(16, 24, 40, .06);
}
.metric-wrap .metric {
display: flex;
gap: 12px;
align-items: center;
padding: 12px 16px;
border-radius: 12px
}
.metric .icon {
width: 44px;
height: 44px;
display: grid;
place-items: center;
border-radius: 999px;
background: #eef2f7
}
.divider-vert {
width: 1px;
background: #eaeaea
}
.table thead th {
font-weight: 600;
color: #000000
}
.table-hover tbody tr:hover {
background: #f8fafc
}
.badge-status-hadir {
background: #a6e7d8;
color: #008767;
border: 1px solid rgba(0, 0, 0, .05)
}
.badge-status-izin {
background: #ffc5c5;
color: #df0404;
border: 1px solid rgba(0, 0, 0, .05)
}
/* DataTables polish */
.dataTables_wrapper .dataTables_info {
padding-top: .75rem
}
.dataTables_wrapper .dataTables_paginate .pagination {
margin: 0
}
.dataTables_wrapper .form-select.form-select-sm {
padding: .25rem 1.5rem .25rem .5rem
}
.dataTables_wrapper .dataTables_filter input {
width: 180px
}
@media (min-width:992px) {
.dataTables_wrapper .dataTables_filter input {
width: 260px
}
}
/* Kolom yang tak perlu wrapping supaya rapi tanpa scrollbar halaman */
th.col-time,
td.col-time,
th.col-date,
td.col-date,
th.col-dur,
td.col-dur,
th.col-status,
td.col-status {
white-space: nowrap;
}
/* Sticky header (opsional), hapus jika tidak perlu */
.table-sticky thead th {
position: sticky;
top: 0;
background: #fff;
z-index: 1
}
/* Sembunyikan kontrol bawaan DT karena kita pakai kontrol custom */
div.dataTables_filter,
div.dataTables_length {
display: none
}
/* Only-horizontal-lines table (no vertical borders, no highlights) */
.table-hlines {
--line: #e9edf4;
}
/* reset & apply bottom borders only */
.table-hlines> :not(caption)>*>* {
border-top: 0;
border-right: 0 !important;
border-left: 0 !important;
border-bottom: 1px solid var(--line);
background: transparent;
/* no zebra bg */
}
/* header: garis bawah sedikit lebih tebal */
.table-hlines thead th {
border-bottom: 2px solid var(--line) !important;
}
/* baris terakhir: opsional, kalau mau tanpa garis paling bawah, aktifkan: */
/* .table-hlines tbody tr:last-child > *{ border-bottom:0; } */
/* matikan efek hover/highlight (Bootstrap + DataTables) */
.table-hlines tbody tr:hover>* {
background: transparent !important;
}
table.dataTable.hover>tbody>tr:hover>*,
table.dataTable.display>tbody>tr:hover>* {
background: transparent !important;
}
table.dataTable tbody tr.selected>* {
background: transparent !important;
box-shadow: none !important;
}
/* kalau sebelumnya ada rule .table-hover tbody tr:hover{background:#f8fafc} — override: */
.table-hlines.table-hover tbody tr:hover {
background: transparent !important;
}
/* --- Unified metrics card --- */
.metrics-card.soft-card {
border-radius: 16px;
box-shadow: 0 10px 30px rgba(16, 24, 40, .06);
}
.metrics-row {
display: grid;
gap: 12px;
}
.metric-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 18px;
border-radius: 12px;
/* biar tetap manis saat mobile (stack) */
flex: 1 1 auto;
min-width: 240px;
}
.metric-item .icon {
width: 56px;
height: 56px;
border-radius: 999px;
display: grid;
place-items: center;
background: #e9fbf2;
color: #0f766e;
/* mint + hijau */
}
.metric-item .metric-title {
color: #6b7280;
font-size: .85rem;
margin-bottom: .15rem;
}
.metric-item .metric-value {
font-weight: 600;
font-size: 1.35rem;
margin: 0;
}
/* Divider antar metric saat layar ≥ md */
@media (min-width: 768px) {
.metrics-row {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0;
}
.metric-item {
padding: 18px 22px;
border-radius: 0;
}
.metric-item+.metric-item {
border-left: 1px solid #eef2f7;
}
}
/* Tanpa highlight saat hover */
.metric-item:hover {
background: transparent;
}
/* Toolbar mobile: jadi kolom penuh */
@media (max-width: 767.98px) {
.toolbar {
flex-direction: column !important;
align-items: stretch !important;
gap: .5rem !important;
}
.toolbar .controls {
flex-direction: column !important;
align-items: stretch !important;
}
.toolbar .controls>* {
width: 100% !important;
/* setiap kontrol full width */
}
/* search biar melebar penuh di mobile */
.toolbar .input-group {
max-width: 100% !important;
}
}
/* Mobile: semua kontrol full width; Desktop: auto */
@media (max-width: 767.98px) {
.input-group.input-group-sm {
max-width: 100% !important;
}
.dropdown>.btn {
width: 100%;
}
.dropdown-menu {
width: 100%;
}
}
/* Desktop: kasih ruang cari sedikit lebih lebar */
@media (min-width: 992px) {
.input-group.input-group-sm {
max-width: 320px !important;
}
}
</style>
@endpush
@section('content')
{{-- ======= TOP METRICS (unified card) ======= --}}
<div class="card soft-card metrics-card mb-4">
<div class="card-body">
<div class="metrics-row">
<!-- Presensi -->
<div class="metric-item">
<div class="icon"><i class="ti ti-users-group fs-4"></i></div>
<div>
<div class="metric-title">Presensi</div>
<div class="d-flex align-items-baseline gap-2">
<h4 class="metric-value">{{ $lengthAttendance }}/{{ $lengthEmployee }}</h4>
</div>
</div>
</div>
<!-- Anggota -->
<div class="metric-item">
<div class="icon"><i class="ti ti-user-plus fs-4"></i></div>
<div>
<div class="metric-title">Anggota</div>
<div class="d-flex align-items-baseline gap-2">
<h4 class="metric-value">{{ $lengthEmployee }}</h4>
</div>
</div>
</div>
<!-- Izin -->
<div class="metric-item">
<div class="icon"><i class="ti ti-device-desktop fs-4"></i></div>
<div>
<div class="metric-title">Izin</div>
<div class="d-flex align-items-baseline gap-2">
<h4 class="metric-value">{{ $izin->count() }}</h4>
<small class="badge text-bg-light"></small>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- ======= TABLE CARD ======= --}}
<div class="card soft-card">
<div class="card-body">
<!-- Toolbar: length + search + sort -->
<div class="row gy-2 align-items-center mb-3">
<!-- Kiri: judul -->
<div class="col-12 col-md">
<h4 class="mb-0">Absensi Perangkat Desa</h4>
<small style="color:#0f766e">Rekapitulasi Absensi Tanggal : {{ now()->format('d-m-Y') }}</small>
</div>
<!-- Kanan: kontrol (stack di mobile, sejajar di md+) -->
<div class="col-12 col-md-auto">
<div class="d-flex flex-column flex-md-row align-items-stretch align-items-md-center gap-2">
<div class="d-flex align-items-center gap-2">
<label for="tableLength" class="small text-secondary mb-0 d-none d-md-inline">Tampilkan</label>
<select id="tableLength" class="form-select form-select-sm w-auto">
<option value="8" selected>8</option>
<option value="15">15</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="-1">Semua</option>
</select>
</div>
<div class="input-group input-group-sm grow flex-md-grow-0" style="max-width:260px;">
<span class="input-group-text bg-white"><i class="ti ti-search"></i></span>
<input id="tableSearch" type="text" class="form-control" placeholder="Cari nama/tanggal...">
</div>
<div class="dropdown">
<button id="sortDropdownLabel" class="btn btn-sm btn-light border dropdown-toggle"
data-bs-toggle="dropdown">
Sort by: Newest
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#" data-sort="newest">Newest</a></li>
<li><a class="dropdown-item" href="#" data-sort="oldest">Oldest</a></li>
<li><a class="dropdown-item" href="#" data-sort="name-asc">Name AZ</a></li>
<li><a class="dropdown-item" href="#" data-sort="name-desc">Name ZA</a></li>
</ul>
</div>
</div>
</div>
</div>
<table id="attendanceTable" class="table align-middle w-100 table-sticky table-hlines">
<thead class="border-bottom">
<tr>
<th>Nama</th>
<th class="col-time d-none d-md-table-cell">Masuk</th>
<th class="col-time d-none d-md-table-cell">Pulang</th>
<th class="col-date">Tanggal</th>
<th class="col-dur d-none d-md-table-cell">Durasi</th>
<th class="col-status">Keterangan</th>
</tr>
</thead>
<tbody>
@forelse ($data as $attendance)
@php
// nama
$name = $attendance->user->name;
// jam masuk & pulang (atau '-' jika null)
$masuk = optional($attendance->check_in)->format('H:i') ?: '-';
$pulang = optional($attendance->check_out)->format('H:i') ?: '-';
// tanggal untuk display & order
$tanggalOrd = $attendance->date->format('Ymd');
$tanggalDisp = $attendance->date->format('d M Y');
// hitung durasi jika ada check_in & check_out
if ($attendance->check_in && $attendance->check_out) {
$diff = $attendance->check_in->diff($attendance->check_out);
$durasi = sprintf('%02d/8 jam', $diff->h, $diff->i);
} else {
$durasi = '-';
}
// badge
// Mapping status → [CSS class, Teks badge]
$badgeMap = [
'hadir' => ['badge-status-hadir', 'Hadir'],
'izin' => ['badge-status-izin', 'Izin'],
'sakit' => ['badge-status-sakit', 'Sakit'],
'alpha' => ['badge-status-izin', 'Alpha'],
];
// Ambil status user, fallback ke 'alpha' kalau tidak ketemu
$status = $attendance->status;
[$badgeClass, $badgeText] = $badgeMap[$status] ?? $badgeMap['alpha'];
@endphp
<tr>
<td>{{ $name }}</td>
<td d-none d-md-table-cell>{{ $masuk }}</td>
<td d-none d-md-table-cell>{{ $pulang }}</td>
<td data-order="{{ $tanggalOrd }}">{{ $tanggalDisp }}</td>
<td d-none d-md-table-cell>{{ $durasi }}</td>
<td>
<span class="badge rounded-pill {{ $badgeClass }}">
{{ $badgeText }}
</span>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-4">
<div class="text-muted">
<i class="ti ti-news-off" style="font-size: 2rem;"></i>
<p class="mt-2 mb-0">Belum ada Pegawai Absen Hari ini</p>
<small>Silakan beritahu pegawai agar melakukan absensi</small>
</div>
</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 = $('#attendanceTable tbody tr').length > 0 && !$('#attendanceTable tbody tr td[colspan]')
.length;
if (typeof $.fn.DataTable !== 'undefined' && hasData) {
var table = $('#attendanceTable').DataTable({
autoWidth: false,
responsive: {
details: false
},
paging: true,
pageLength: 8,
lengthChange: true, // tetap true agar API page.len() aktif
info: true,
ordering: true,
searching: true,
order: [
[3, 'desc']
], // Tanggal terbaru
language: {
search: "Cari:",
searchPlaceholder: "Ketik untuk mencari...",
lengthMenu: "Tampilkan _MENU_",
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, 5],
className: 'text-nowrap'
},
{
targets: 5,
orderable: false
}
],
// DOM minimal: biar info & paginate otomatis di bawah, tanpa filter/length bawaan (kita pakai custom)
dom: "rt<'row align-items-center mt-3'<'col-sm-6'i><'col-sm-6 d-flex justify-content-end'p>>"
});
// Search custom
$('#tableSearch').on('keyup', function() {
table.search(this.value).draw();
});
// Page length custom
$('#tableLength').on('change', function() {
table.page.len(parseInt(this.value, 10)).draw();
});
// Sort dropdown custom
$('[data-sort]').on('click', function(e) {
e.preventDefault();
const sort = $(this).data('sort');
if (sort === 'newest') table.order([
[3, 'desc']
]).draw();
if (sort === 'oldest') table.order([
[3, 'asc']
]).draw();
if (sort === 'name-asc') table.order([
[0, 'asc']
]).draw();
if (sort === 'name-desc') table.order([
[0, 'desc']
]).draw();
$('#sortDropdownLabel').text($(this).text());
});
} else if (!hasData) {
console.log('Table is empty, DataTables not initialized');
} else {
console.error('DataTables not loaded!');
}
});
</script>
@endpush