Update Alert Profil Petani

This commit is contained in:
2026-11-04 16:20:33 +07:00
parent 41da3a71ee
commit 89c5a1371a
10 changed files with 943 additions and 1 deletions
@@ -0,0 +1,197 @@
{{--
Ringkasan isian WAJIB (skor) dan PERLU (dashboard) yang belum terisi di tab petani yang sedang dibuka.
Daftar isiannya ada di App\Services\Produktivitas\KelengkapanPetani.
Klik isian → halaman menggulir ke kolomnya (dicari lewat atribut name).
--}}
@props(['petani', 'lahan' => null])
@use('App\Services\Produktivitas\KelengkapanPetani')
@php
$tab = match (true) {
request()->routeIs('petani.edit') => 'profil',
request()->routeIs('petani.lahan-tanaman.*') => 'lahan',
request()->routeIs('petani.praktik-budidaya.*') => 'praktik-budidaya',
request()->routeIs('petani.sosial-ekonomi.*') => 'sosial-ekonomi',
request()->routeIs('petani.akses-pasar.*') => 'akses-pasar',
request()->routeIs('petani.skor.*') => 'semua',
default => null,
};
$urlTab = fn (string $t) => $t === 'profil'
? route('petani.edit', $petani->id)
: route('petani.' . ($t === 'lahan' ? 'lahan-tanaman' : $t) . '.index', $petani->id);
$kelengkapan = $tab ? KelengkapanPetani::untuk($petani) : null;
if ($tab === 'semua') {
// Tab Kesiapan Adopsi: rangkuman semua tab, klik → pindah ke tab terkait.
$kelompok = $kelengkapan->semuaKosong()
->map(fn ($items, $t) => $items->map(fn ($item) => [
...$item,
'url' => ($item['url'] ?? $urlTab($t)) . ($item['input'] ? '#isian=' . $item['input'] : ''),
]))
->filter(fn ($items) => $items->isNotEmpty())
->mapWithKeys(fn ($items, $t) => [KelengkapanPetani::TAB_LABEL[$t] => $items]);
} elseif ($tab) {
// Di halaman edit lahan, isian lahan yang sedang dibuka langsung digulir (tanpa pindah halaman).
$lahanDibukaId = $tab === 'lahan' && request()->routeIs('petani.lahan-tanaman.edit') ? $lahan?->id : null;
$kelompok = $kelengkapan->kosong($tab)
->map(function ($item) use ($lahanDibukaId) {
$dibuka = $lahanDibukaId && $item['lahan_id'] === $lahanDibukaId;
return [
...$item,
'grup' => $dibuka ? $item['grup'] . ' (sedang dibuka)' : $item['grup'],
'url' => ($dibuka || ! $item['url']) ? null : $item['url'] . ($item['input'] ? '#isian=' . $item['input'] : ''),
];
})
->groupBy(fn ($item) => $item['grup'] ?? '');
}
$semuaItem = $tab ? $kelompok->flatten(1) : collect();
$jumlahWajib = $semuaItem->where('prioritas', KelengkapanPetani::WAJIB)->count();
$jumlahPerlu = $semuaItem->where('prioritas', KelengkapanPetani::PERLU)->count();
@endphp
@if ($tab)
@if ($semuaItem->isEmpty())
<div class="alert alert-success d-flex align-items-center py-2 mb-6" role="alert">
<i class="icon-base ti tabler-circle-check me-2"></i>
{{ $tab === 'semua' ? 'Semua isian wajib dan perlu sudah terisi.' : 'Semua isian wajib dan perlu di tab ini sudah terisi.' }}
</div>
@else
{{-- Klik di mana saja pada kartu untuk membuka/menutup detail (kecuali tombol isian). --}}
<div class="card mb-6 border border-warning kartu-kelengkapan" id="kelengkapan-petani"
role="button" aria-expanded="false" aria-controls="kelengkapan-petani-detail" title="Klik untuk menampilkan/menyembunyikan detail">
<div class="card-body py-3">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<div class="d-flex align-items-center">
<i class="icon-base ti tabler-alert-triangle text-warning me-2"></i>
<span class="fw-medium">
Belum terisi{{ $tab === 'semua' ? '' : ' di tab ini' }}:
@if ($jumlahWajib)
<span class="badge bg-label-danger ms-1">{{ $jumlahWajib }} wajib</span>
@endif
@if ($jumlahPerlu)
<span class="badge bg-label-warning ms-1">{{ $jumlahPerlu }} perlu</span>
@endif
</span>
</div>
<span class="btn btn-sm btn-icon btn-text-secondary rounded-pill ikon-toggle-kelengkapan" aria-hidden="true">
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
<div class="collapse" id="kelengkapan-petani-detail">
<small class="text-muted d-block mt-2">
<span class="badge badge-dot bg-danger me-1"></span>Wajib: dipakai skor Kesiapan Adopsi
<span class="badge badge-dot bg-warning ms-3 me-1"></span>Perlu: dipakai dashboard.
Klik isian untuk menuju kolomnya.
</small>
@foreach ($kelompok as $judul => $items)
<div class="mt-3">
@if ($judul !== '')
<div class="small fw-medium mb-1">{{ $judul }}</div>
@endif
<div class="d-flex flex-wrap gap-2">
@foreach ($items->sortBy(fn ($i) => $i['prioritas'] === KelengkapanPetani::WAJIB ? 0 : 1) as $item)
<button type="button"
class="btn btn-xs btn-isian-kosong {{ $item['prioritas'] === KelengkapanPetani::WAJIB ? 'btn-label-danger' : 'btn-label-warning' }}"
data-input="{{ $item['input'] }}"
data-url="{{ $item['url'] }}">
{{ $item['label'] }}
</button>
@endforeach
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
@once
@push('style')
<style>
.kartu-kelengkapan { cursor: pointer; }
.kartu-kelengkapan .btn-isian-kosong { cursor: pointer; }
.ikon-toggle-kelengkapan i { transition: transform .2s; }
.kartu-kelengkapan.terbuka .ikon-toggle-kelengkapan i { transform: rotate(180deg); }
.isian-disorot {
outline: 2px solid var(--bs-warning);
outline-offset: 4px;
border-radius: .375rem;
}
</style>
@endpush
@push('scripts')
<script>
(function () {
function cariKolom(name) {
if (!name) return null;
const el = document.querySelector(`[name="${name}"], [name="${name}[]"], [name^="${name}["]`);
if (!el) return null;
// Kolom tersembunyi (mis. radio custom/select2): sorot pembungkus terdekat yang terlihat.
let target = el.closest('.col-md-12, .col-md-6, .col-md-4, .col-12, .col, .mb-4, .mb-6') || el;
while (target && target.offsetParent === null && target.parentElement) {
target = target.parentElement;
}
return { el, target };
}
function sorot(name) {
const hasil = cariKolom(name);
if (!hasil) return false;
hasil.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
hasil.target.classList.add('isian-disorot');
setTimeout(() => hasil.target.classList.remove('isian-disorot'), 2500);
if (hasil.el.offsetParent !== null && typeof hasil.el.focus === 'function') {
hasil.el.focus({ preventScroll: true });
}
return true;
}
// Buka/tutup detail dengan klik di mana saja pada kartu.
const kartu = document.getElementById('kelengkapan-petani');
const detail = document.getElementById('kelengkapan-petani-detail');
if (kartu && detail) {
kartu.addEventListener('click', function (e) {
if (e.target.closest('.btn-isian-kosong')) return;
if (window.getSelection().toString()) return; // sedang menyeleksi teks
bootstrap.Collapse.getOrCreateInstance(detail, { toggle: false }).toggle();
});
detail.addEventListener('show.bs.collapse', () => { kartu.classList.add('terbuka'); kartu.setAttribute('aria-expanded', 'true'); });
detail.addEventListener('hide.bs.collapse', () => { kartu.classList.remove('terbuka'); kartu.setAttribute('aria-expanded', 'false'); });
}
document.addEventListener('click', function (e) {
const btn = e.target.closest('.btn-isian-kosong');
if (!btn) return;
if (btn.dataset.url) {
window.location.href = btn.dataset.url;
return;
}
if (!sorot(btn.dataset.input)) {
const pesan = 'Kolom ini belum tampil di halaman ini. Simpan bagian yang sudah ada terlebih dahulu, atau periksa jawaban pertanyaan sebelumnya.';
window.Swal ? Swal.fire({ icon: 'info', text: pesan }) : alert(pesan);
}
});
// Datang dari tab lain dengan #isian=nama_kolom
window.addEventListener('load', function () {
const cocok = window.location.hash.match(/^#isian=(.+)$/);
if (cocok) setTimeout(() => sorot(decodeURIComponent(cocok[1])), 300);
});
})();
</script>
@endpush
@endonce
@endif