196 lines
9.3 KiB
PHP
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
|