Files
database-pertani-web/resources/views/monitoring-verifikasi/index.blade.php
T

196 lines
9.3 KiB
PHP

@extends('layout.main')
@php
$menu = $menu ?? 'petani';
$tabs = [
'monitoring' => ['label' => 'Monitoring', 'ikon' => 'tabler-chart-dots'],
'verifikasi' => ['label' => 'Verifikasi', 'ikon' => 'tabler-rosette-discount-check'],
];
@endphp
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
{{-- Vuexy: justified pills (nav-fill) → tiap tab sama lebar. --}}
<div class="nav-align-top">
<ul class="nav nav-pills nav-fill mb-6" role="tablist">
@foreach ($tabs as $kunci => $tab)
@php
$namaRoute = "monitoring-verifikasi.{$menu}.{$kunci}";
$tersedia = Route::has($namaRoute);
$aktif = request()->routeIs($namaRoute . '*');
@endphp
<li class="nav-item">
<a @class(['nav-link', 'active' => $aktif, 'disabled' => ! $tersedia])
href="{{ $tersedia ? route($namaRoute) : 'javascript:void(0);' }}"
@if ($aktif) aria-current="page" @endif
@unless ($tersedia) aria-disabled="true" @endunless>
<span class="d-none d-sm-inline-flex align-items-center">
<i class="icon-base ti {{ $tab['ikon'] }} icon-sm me-1_5"></i>{{ $tab['label'] }}
</span>
<i class="icon-base ti {{ $tab['ikon'] }} icon-sm d-sm-none"></i>
</a>
</li>
@endforeach
</ul>
</div>
{{-- Sub-tab: halaman turunan cukup mendefinisikan $idSubTab & $subTabs (label, ikon, view, jumlah opsional). --}}
@isset($subTabs)
@php $subTabAktif = array_key_exists(request('sub'), $subTabs) ? request('sub') : array_key_first($subTabs); @endphp
<div class="nav-align-top nav-tabs-shadow">
<ul class="nav nav-tabs nav-fill" role="tablist">
@foreach ($subTabs as $kunci => $tab)
<li class="nav-item" role="presentation">
<button type="button" role="tab"
@class(['nav-link', 'active' => $kunci === $subTabAktif])
data-bs-toggle="tab" data-bs-target="#{{ $idSubTab }}-{{ $kunci }}"
aria-controls="{{ $idSubTab }}-{{ $kunci }}" aria-selected="{{ $kunci === $subTabAktif ? 'true' : 'false' }}">
<span class="d-none d-sm-inline-flex align-items-center">
<i class="icon-base ti {{ $tab['ikon'] }} icon-sm me-1_5"></i>{{ $tab['label'] }}
@isset($tab['jumlah'])
<span class="badge rounded-pill badge-center h-px-20 w-px-20 bg-label-primary ms-1_5">{{ $tab['jumlah'] }}</span>
@endisset
</span>
<i class="icon-base ti {{ $tab['ikon'] }} icon-sm d-sm-none"></i>
</button>
</li>
@endforeach
</ul>
<div class="tab-content">
@foreach ($subTabs as $kunci => $tab)
<div @class(['tab-pane fade', 'show active' => $kunci === $subTabAktif]) id="{{ $idSubTab }}-{{ $kunci }}" role="tabpanel">
@include($tab['view'])
</div>
@endforeach
</div>
</div>
@else
<div class="card">
<div class="card-body text-center">
<span class="avatar avatar-md mx-auto mb-2">
<span class="avatar-initial rounded bg-label-secondary"><i class="icon-base ti tabler-layout-dashboard icon-26px"></i></span>
</span>
<p class="mb-0 text-body-secondary">Konten tab belum dibuat.</p>
</div>
</div>
@endisset
{{-- Kartu rincian: disalin otomatis ke setiap .kolom-detail yang masih kosong. --}}
<template id="template-kartu-rincian">
<div class="card shadow-none border h-100 kartu-rincian">
<div class="card-header d-flex justify-content-between align-items-center border-bottom">
<h5 class="card-title mb-0 kartu-rincian-judul">Rincian</h5>
<button type="button" class="btn-close btn-tutup-rincian" aria-label="Tutup"></button>
</div>
<div class="card-body pt-6 kartu-rincian-isi"></div>
</div>
</template>
</div>
@endsection
@push('style')
<style>
/* Tabel penuh; saat rincian dibuka, tabel menyusut & kartu rincian bergeser masuk dari kanan. */
.monitoring-split { display: flex; gap: 0; transition: gap .3s ease; }
.monitoring-split .kolom-tabel { flex: 1 1 100%; min-width: 0; transition: flex-basis .3s ease; }
.monitoring-split .kolom-detail {
flex: 0 0 0; max-width: 0; min-width: 0; opacity: 0; overflow: hidden;
transform: translateX(1.5rem);
transition: flex-basis .3s ease, max-width .3s ease, opacity .3s ease, transform .3s ease;
}
.monitoring-split.terbuka { gap: 1.5rem; }
.monitoring-split.terbuka .kolom-tabel { flex-basis: 58%; }
.monitoring-split.terbuka .kolom-detail { flex-basis: 42%; max-width: 42%; opacity: 1; transform: none; }
/* Kartu rincian ikut terlihat saat halaman digulir; isinya bisa digulir sendiri. */
.kartu-rincian { position: sticky; top: 1rem; max-height: calc(100vh - 2rem); }
.kartu-rincian .card-body { overflow-y: auto; }
/* Baris tabel yang bisa diklik untuk membuka rincian. */
tr.btn-buka-rincian { cursor: pointer; }
/* Layar kecil: rincian tampil di bawah tabel. */
@media (max-width: 991.98px) {
.monitoring-split { flex-direction: column; }
.monitoring-split.terbuka .kolom-tabel,
.monitoring-split.terbuka .kolom-detail { flex-basis: auto; max-width: 100%; }
.kartu-rincian { position: static; max-height: none; }
}
</style>
@endpush
@push('scripts')
<script>
(function () {
const memuat = '<div class="text-center py-6"><div class="spinner-border text-primary" role="status"></div></div>';
const layarKecil = () => window.matchMedia('(max-width: 991.98px)').matches;
const templateKartu = document.getElementById('template-kartu-rincian');
document.querySelectorAll('.monitoring-split').forEach(function (split) {
const kolomTabel = split.querySelector('.kolom-tabel');
const kolomDetail = split.querySelector('.kolom-detail');
if (!kolomDetail.children.length) kolomDetail.append(templateKartu.content.cloneNode(true));
const judul = kolomDetail.querySelector('.kartu-rincian-judul');
const isi = kolomDetail.querySelector('.kartu-rincian-isi');
let permintaan = 0; // abaikan respons lama kalau tombol lain sudah diklik
let tombolAktif = null;
const tandai = (tombol) => {
kolomTabel.querySelectorAll('tr.table-active').forEach((tr) => tr.classList.remove('table-active'));
tombol?.closest('tr')?.classList.add('table-active');
tombolAktif = tombol;
};
const tutup = () => {
split.classList.remove('terbuka');
kolomDetail.setAttribute('aria-hidden', 'true');
tandai(null);
permintaan++;
};
split.tutupRincian = tutup;
kolomDetail.querySelector('.btn-tutup-rincian').addEventListener('click', tutup);
kolomTabel.addEventListener('click', async function (e) {
const tombol = e.target.closest('.btn-buka-rincian');
if (!tombol) return;
// Tautan di dalam baris yang bisa diklik tetap berfungsi sebagai tautan.
const tautan = e.target.closest('a');
if (tautan && tautan !== tombol && tombol.contains(tautan)) return;
// Klik tombol yang sama lagi → tutup.
if (tombol === tombolAktif && split.classList.contains('terbuka')) return tutup();
const nomor = ++permintaan;
tandai(tombol);
judul.textContent = tombol.dataset.judul || 'Rincian';
isi.innerHTML = memuat;
isi.scrollTop = 0;
split.classList.add('terbuka');
kolomDetail.setAttribute('aria-hidden', 'false');
if (layarKecil()) kolomDetail.scrollIntoView({ behavior: 'smooth', block: 'start' });
try {
const res = await fetch(tombol.dataset.url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
if (!res.ok) throw new Error(res.status);
const html = await res.text();
if (nomor === permintaan) isi.innerHTML = html;
} catch (err) {
if (nomor === permintaan) {
isi.innerHTML = '<div class="alert alert-danger mb-0">Gagal memuat rincian. Coba lagi.</div>';
}
}
});
});
// Esc menutup rincian yang sedang terbuka.
document.addEventListener('keydown', function (e) {
if (e.key !== 'Escape') return;
document.querySelectorAll('.monitoring-split.terbuka').forEach((split) => split.tutupRincian?.());
});
})();
</script>
@endpush