Update Alert Profil Petani
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,102 @@
|
||||
{{--
|
||||
Kelengkapan data (terisi X dari 209) + status Draf/Lengkap/Terverifikasi di kartu Profil Petani.
|
||||
Tombol "Tandai Lengkap" hanya untuk enumerator penginput, dan nonaktif selama isian wajib masih kosong.
|
||||
--}}
|
||||
@props(['petani'])
|
||||
|
||||
@use('App\Enums\StatusDataPetani')
|
||||
@use('App\Services\Produktivitas\KelengkapanPetani')
|
||||
|
||||
@php
|
||||
$kelengkapan = KelengkapanPetani::untuk($petani);
|
||||
$hitung = $kelengkapan->hitungIsian();
|
||||
$wajibKosong = $kelengkapan->jumlahWajibKosong();
|
||||
$status = $petani->status ?? StatusDataPetani::DRAFT;
|
||||
$bisaTandai = $petani->bisaDitandaiOleh(auth()->user());
|
||||
@endphp
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<h6 class="mb-0" style="font-size: 0.85rem;">Kelengkapan Data</h6>
|
||||
<span class="badge bg-label-{{ $status->warna() }}">{{ $status->label() }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between" style="font-size: 0.75rem;">
|
||||
<span>{{ $hitung['terisi'] }} dari {{ $hitung['total'] }} isian terisi</span>
|
||||
<span class="fw-medium">{{ $hitung['persen'] }}%</span>
|
||||
</div>
|
||||
<div class="progress my-1" style="height: 6px;">
|
||||
<div class="progress-bar {{ $hitung['persen'] >= 100 ? 'bg-success' : '' }}" role="progressbar"
|
||||
style="width: {{ $hitung['persen'] }}%;" aria-valuenow="{{ $hitung['persen'] }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.7rem;">
|
||||
@if ($hitung['tidak_berlaku'])
|
||||
{{ $hitung['tidak_berlaku'] }} isian tidak berlaku untuk petani ini.
|
||||
@endif
|
||||
@if ($wajibKosong)
|
||||
<span class="text-danger">{{ $wajibKosong }} isian wajib masih kosong.</span>
|
||||
@else
|
||||
<span class="text-success">Semua isian wajib sudah terisi.</span>
|
||||
@endif
|
||||
</p>
|
||||
|
||||
@if ($bisaTandai && $status === StatusDataPetani::DRAFT)
|
||||
<form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2"
|
||||
data-konfirmasi="Tandai data petani ini sudah lengkap? Koordinator akan memverifikasinya.">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="status" value="{{ StatusDataPetani::LENGKAP->value }}">
|
||||
<button type="submit" class="btn btn-sm btn-success w-100" @disabled($wajibKosong > 0)
|
||||
@if ($wajibKosong) title="Lengkapi {{ $wajibKosong }} isian wajib terlebih dahulu" @endif>
|
||||
<i class="icon-base ti tabler-checks icon-sm me-1"></i>Tandai Sudah Lengkap
|
||||
</button>
|
||||
</form>
|
||||
@elseif ($bisaTandai && $status === StatusDataPetani::LENGKAP)
|
||||
<form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2"
|
||||
data-konfirmasi="Batalkan tanda lengkap? Status kembali ke draf.">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="status" value="{{ StatusDataPetani::DRAFT->value }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary w-100">
|
||||
<i class="icon-base ti tabler-arrow-back-up icon-sm me-1"></i>Batalkan Tanda Lengkap
|
||||
</button>
|
||||
</form>
|
||||
@elseif ($status === StatusDataPetani::DRAFT)
|
||||
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Hanya enumerator penginput yang dapat menandai lengkap.</p>
|
||||
@elseif ($status === StatusDataPetani::LENGKAP)
|
||||
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Menunggu verifikasi koordinator.</p>
|
||||
@endif
|
||||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('submit', function (e) {
|
||||
const form = e.target.closest('.form-status-petani');
|
||||
if (!form || form.dataset.terkonfirmasi) return;
|
||||
|
||||
e.preventDefault();
|
||||
const kirim = () => { form.dataset.terkonfirmasi = '1'; form.submit(); };
|
||||
|
||||
if (!window.Swal) {
|
||||
if (confirm(form.dataset.konfirmasi)) kirim();
|
||||
return;
|
||||
}
|
||||
Swal.fire({
|
||||
icon: 'question',
|
||||
text: form.dataset.konfirmasi,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya',
|
||||
cancelButtonText: 'Batal',
|
||||
}).then((r) => { if (r.isConfirmed) kirim(); });
|
||||
});
|
||||
|
||||
@if (session('status_petani_success') || session('status_petani_error'))
|
||||
window.addEventListener('load', function () {
|
||||
const pesan = @json(session('status_petani_success') ?? session('status_petani_error'));
|
||||
const ikon = @json(session('status_petani_success') ? 'success' : 'error');
|
||||
window.Swal ? Swal.fire({ icon: ikon, text: pesan }) : alert(pesan);
|
||||
});
|
||||
@endif
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
@@ -113,6 +113,41 @@
|
||||
}
|
||||
}
|
||||
</style> -->
|
||||
|
||||
{{-- Tombol kembali ke atas: muncul setelah halaman digulir ke bawah. --}}
|
||||
<button type="button" id="btn-kembali-ke-atas" class="btn btn-primary btn-icon rounded-pill shadow"
|
||||
title="Kembali ke atas" aria-label="Kembali ke atas">
|
||||
<i class="icon-base ti tabler-arrow-up"></i>
|
||||
</button>
|
||||
<style>
|
||||
#btn-kembali-ke-atas {
|
||||
position: fixed;
|
||||
right: 1.5rem;
|
||||
bottom: 1.5rem;
|
||||
z-index: 1040;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(1rem);
|
||||
transition: opacity .25s, transform .25s, visibility .25s;
|
||||
}
|
||||
#btn-kembali-ke-atas.tampil {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
const btn = document.getElementById('btn-kembali-ke-atas');
|
||||
const cek = () => btn.classList.toggle('tampil', window.scrollY > 300);
|
||||
window.addEventListener('scroll', cek, { passive: true });
|
||||
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
|
||||
cek();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@stack('scripts')
|
||||
|
||||
<script>
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<hr class="my-2"> <h6 class="mb-1" style="font-size: 0.85rem;">Domisili</h6>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Alamat: {{ isset($petani) ? $petani->alamat : 'Alamat' }}</p>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ isset($petani) ? $petani->desaKelurahan?->nama ?? 'Belum diisi' : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->nama ?? 'Belum diisi' : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->nama ?? 'Belum diisi' : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama ?? 'Belum diisi' : 'Provinsi' }}</p>
|
||||
|
||||
@if ($petani)
|
||||
<x-status-petani :petani="$petani" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +72,7 @@
|
||||
$skorLahanTanaman = isset($petani) ? $petani->skorLahanTanaman() : null;
|
||||
|
||||
$lahanPetani = isset($petani) && \Illuminate\Support\Facades\Schema::hasTable('map_lahan')
|
||||
? $petani->lahans()->get()
|
||||
? $petani->lahans
|
||||
: collect();
|
||||
$totalLuasLahanHa = $petani ? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->sum('luas_lahan') : 0;
|
||||
$lahanKomoditasUtamaIds = $petani
|
||||
@@ -393,3 +397,7 @@
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($petani)
|
||||
<x-kelengkapan-petani :petani="$petani" :lahan="$lahanSelected ?? null" />
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user