Update Monitoring dan Verifikasi Petani

This commit is contained in:
2026-11-05 11:01:12 +07:00
parent fd93e0d265
commit a3ec431d49
22 changed files with 1780 additions and 76 deletions
@@ -0,0 +1,119 @@
@use('App\Services\Produktivitas\KelengkapanPetani')
@if (($bagian ?? 'tabel') === 'rincian')
{{-- ============ Isi kartu rincian: petani yang diinput enumerator ini ============ --}}
<div class="d-flex align-items-center mb-6">
<div class="avatar avatar-md me-3 flex-shrink-0">
<span class="avatar-initial rounded bg-label-primary">{{ Str::upper(Str::substr($user->name, 0, 2)) }}</span>
</div>
<div style="min-width: 0;">
<h6 class="mb-0 text-truncate">{{ $user->name }}</h6>
<small class="d-block text-body-secondary">{{ $daftarPetani->count() }} petani diinput</small>
</div>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-sm table-hover mb-0">
<thead class="border-top">
<tr>
<th>No</th>
<th>Nama Petani</th>
<th class="text-center" title="Pertanyaan skor Kesiapan Adopsi">Skor</th>
<th class="text-center" title="Isian yang dipakai dashboard">Wajib</th>
<th class="text-center" title="Isian lainnya">Lainnya</th>
</tr>
</thead>
<tbody>
@forelse ($daftarPetani as $item)
@php $r = $ringkasan[$item->id]; @endphp
<tr>
<td>{{ $loop->iteration }}</td>
<td class="text-wrap">
<a href="{{ route('petani.edit', $item->id) }}" class="text-heading fw-medium">{{ $item->nama }}</a>
<small class="d-block text-body-secondary">{{ $item->komoditas?->nama ?? '-' }}</small>
</td>
@foreach (['skor', 'wajib', 'lainnya'] as $jenis)
<td class="text-center">
<span class="badge bg-label-{{ KelengkapanPetani::warna($r[$jenis]) }}">{{ $r[$jenis]['terisi'] }}/{{ $r[$jenis]['total'] }}</span>
</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-4">Belum ada petani.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@else
{{-- ============ Tabel enumerator; klik jumlah petani → kartu rincian ============ --}}
<div class="monitoring-split">
<div class="kolom-tabel">
<form method="GET" action="{{ route('monitoring-verifikasi.petani.monitoring') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0 mb-4">
<input type="hidden" name="sub" value="enumerator">
<div class="col-md-auto">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap">Show</label>
<select name="per_page_enumerator" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
@foreach ($perPage as $n)
<option value="{{ $n }}" @selected($enumerator->perPage() == $n)>{{ $n }}</option>
@endforeach
</select>
<label class="ms-2 text-nowrap">entries</label>
</div>
</div>
<div class="col-md-auto">
<div class="input-group input-group-sm w-auto">
<input type="text" name="search_enumerator" class="form-control" placeholder="Cari nama enumerator..." value="{{ request('search_enumerator') }}">
<button type="submit" class="btn btn-outline-primary"><i class="icon-base ti tabler-search"></i></button>
</div>
</div>
</form>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Enumerator</th>
<th class="text-center">Jumlah Petani</th>
</tr>
</thead>
<tbody>
@forelse ($enumerator as $index => $user)
<tr>
<td>{{ $enumerator->firstItem() + $index }}</td>
<td>
<span class="text-heading fw-medium d-block">{{ $user->name }}</span>
<small class="text-body-secondary">{{ $user->email }}</small>
</td>
<td class="text-center">
@if ($user->jumlah_petani > 0)
<button type="button" class="btn btn-sm btn-label-primary btn-buka-rincian"
data-url="{{ route('monitoring-verifikasi.petani.monitoring.enumerator', $user->id) }}"
data-judul="Petani oleh {{ $user->name }}" title="Lihat petani yang diinput">
{{ $user->jumlah_petani }} petani
</button>
@else
<span class="badge bg-label-secondary">0 petani</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center py-4">Tidak ada data ditemukan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center justify-content-md-end border-top pt-4">
{{ $enumerator->links('pagination::bootstrap-5') }}
</div>
</div>
<div class="kolom-detail" aria-hidden="true"></div>
</div>
@endif
@@ -0,0 +1,195 @@
@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
@@ -0,0 +1,9 @@
@extends('monitoring-verifikasi.index')
@php
$idSubTab = 'monitoring';
$subTabs = [
'petani' => ['label' => 'Petani', 'ikon' => 'tabler-user-circle', 'view' => 'monitoring-verifikasi.petani-monitoring'],
'enumerator' => ['label' => 'Enumerator', 'ikon' => 'tabler-clipboard-list', 'view' => 'monitoring-verifikasi.enumerator'],
];
@endphp
@@ -0,0 +1,182 @@
@use('App\Services\Produktivitas\KelengkapanPetani')
@php
$langkah = [
1 => ['jenis' => 'skor', 'label' => 'Input Skor'],
2 => ['jenis' => 'wajib', 'label' => 'Input Wajib'],
3 => ['jenis' => 'lainnya', 'label' => 'Input Lainnya'],
];
@endphp
@if (($bagian ?? 'tabel') === 'rincian')
{{-- ============ Isi kartu verifikasi: profil + 3 langkah pemeriksaan ============ --}}
@php $skorIndeks = $petani->skor?->skor_kesiapan_adopsi; @endphp
<div class="stepper-verifikasi">
@include('monitoring-verifikasi.petani-monitoring', ['bagian' => 'profil'])
<div class="row g-4 mb-6">
<div class="col-6">
<label class="form-label">Enumerator</label>
<input type="text" class="form-control" readonly value="{{ $petani->creator?->name ?? '-' }}">
</div>
<div class="col-6">
<label class="form-label">Skor Indeks</label>
<input type="text" class="form-control" readonly
value="{{ is_null($skorIndeks) ? '-' : number_format($skorIndeks, 1, ',', '.') . '/5 · ' . $petani->segmen() }}">
</div>
</div>
@if ($petani->catatan)
{{-- Catatan saat terakhir dikembalikan ke Draf --}}
<div class="alert alert-warning mb-6" role="alert">
<h6 class="alert-heading mb-1">Catatan sebelumnya</h6>
<p class="mb-0" style="white-space: pre-line;">{{ $petani->catatan }}</p>
</div>
@endif
{{-- Penanda langkah --}}
<div class="d-flex justify-content-between gap-2 border-bottom pb-4 mb-6">
@foreach ($langkah as $n => $l)
@php $hitung = $ringkasan[$l['jenis']]; @endphp
<div class="d-flex align-items-center gap-2 langkah-item" data-langkah="{{ $n }}">
<span class="avatar avatar-sm flex-shrink-0">
<span @class(['avatar-initial rounded-circle langkah-nomor', 'bg-primary text-white' => $n === 1, 'bg-label-secondary' => $n !== 1])>{{ $n }}</span>
</span>
<span class="lh-sm">
<span class="d-block fw-medium small text-heading">{{ $l['label'] }}</span>
<small class="text-body-secondary">{{ $hitung['terisi'] }}/{{ $hitung['total'] }}</small>
</span>
</div>
@endforeach
</div>
{{-- Isi tiap langkah: periksa semua isian, lalu klik tombol di bawah --}}
@foreach ($langkah as $n => $l)
<form class="pt-0 langkah-pane {{ $n === 1 ? '' : 'd-none' }}" data-langkah="{{ $n }}" onsubmit="return false;">
@include('monitoring-verifikasi.petani-monitoring', ['bagian' => 'isian', 'rincian' => $rincian[$l['jenis']]])
{{-- Langkah terakhir punya tombolnya sendiri di bawah --}}
@if ($n < count($langkah))
<div class="d-flex gap-2">
@if ($n > 1)
<button type="button" class="btn btn-label-secondary" data-ke-langkah="{{ $n - 1 }}">
<i class="icon-base ti tabler-arrow-left icon-sm me-1"></i>Kembali
</button>
@endif
<button type="button" class="btn btn-primary flex-grow-1" data-ke-langkah="{{ $n + 1 }}">
Lengkap<i class="icon-base ti tabler-arrow-right icon-sm ms-1"></i>
</button>
</div>
@endif
</form>
@endforeach
{{-- Langkah terakhir: Belum Lengkap (kembali ke Draf + catatan) atau Lengkap & Verifikasi --}}
{{-- Baris 1: Kembali | Belum Lengkap (sama lebar). Baris 2: Lengkap & Verifikasi (penuh). --}}
<div class="langkah-pane d-none" data-langkah="{{ count($langkah) }}">
<div class="row g-2 mb-2">
<div class="col-6">
<button type="button" class="btn btn-label-secondary w-100" data-ke-langkah="{{ count($langkah) - 1 }}">
<i class="icon-base ti tabler-arrow-left icon-sm me-1"></i>Kembali
</button>
</div>
<div class="col-6">
<form method="POST" action="{{ route('monitoring-verifikasi.petani.verifikasi.kembalikan-draf', $petani->id) }}"
class="form-kembalikan-draf" data-nama="{{ $petani->nama }}"
data-judul="Data Belum Lengkap" data-pesan="Tuliskan apa yang perlu dilengkapi atau diperbaiki oleh enumerator.">
@csrf
@method('PUT')
<input type="hidden" name="catatan" value="">
<button type="submit" class="btn btn-label-danger w-100">
<i class="icon-base ti tabler-file-alert icon-sm me-1"></i>Belum Lengkap
</button>
</form>
</div>
</div>
<form method="POST" action="{{ route('monitoring-verifikasi.petani.verifikasi.update', $petani->id) }}"
class="form-verifikasi-petani" data-nama="{{ $petani->nama }}">
@csrf
@method('PUT')
<button type="submit" class="btn btn-primary w-100">
<i class="icon-base ti tabler-rosette-discount-check icon-sm me-1"></i>Lengkap &amp; Verifikasi
</button>
</form>
</div>
</div>
@else
{{-- ============ Tabel petani berstatus Lengkap; klik di mana saja pada baris → kartu verifikasi ============ --}}
<div class="monitoring-split">
<div class="kolom-tabel">
<form method="GET" action="{{ route('monitoring-verifikasi.petani.verifikasi') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0 mb-4">
<input type="hidden" name="sub" value="lengkap">
<div class="col-md-auto">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap">Show</label>
<select name="per_page" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
@foreach ($perPage as $n)
<option value="{{ $n }}" @selected($petaniLengkap->perPage() == $n)>{{ $n }}</option>
@endforeach
</select>
<label class="ms-2 text-nowrap">entries</label>
</div>
</div>
<div class="col-md-auto">
<div class="input-group input-group-sm w-auto">
<input type="text" name="search" class="form-control" placeholder="Cari nama petani..." value="{{ request('search') }}">
<button type="submit" class="btn btn-outline-primary"><i class="icon-base ti tabler-search"></i></button>
</div>
</div>
</form>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Nama Petani</th>
<th>Komoditas</th>
<th>Enumerator</th>
@foreach ($langkah as $l)
<th class="text-center">{{ $l['label'] }}</th>
@endforeach
<th>Terakhir Diperbarui</th>
</tr>
</thead>
<tbody>
@forelse ($petaniLengkap as $index => $item)
@php $r = $ringkasan[$item->id]; @endphp
<tr class="btn-buka-rincian"
data-url="{{ route('monitoring-verifikasi.petani.verifikasi.show', $item->id) }}"
data-judul="Verifikasi {{ $item->nama }}" title="Klik untuk memeriksa & memverifikasi">
<td>{{ $petaniLengkap->firstItem() + $index }}</td>
<td><span class="text-heading fw-medium">{{ $item->nama }}</span></td>
<td>{{ $item->komoditas?->nama ?? '-' }}</td>
<td><span class="text-body-secondary">{{ $item->creator?->name ?? '-' }}</span></td>
@foreach ($langkah as $l)
<td class="text-center">
<span class="badge bg-label-{{ KelengkapanPetani::warna($r[$l['jenis']]) }}">{{ $r[$l['jenis']]['terisi'] }}/{{ $r[$l['jenis']]['total'] }}</span>
</td>
@endforeach
<td>
<span class="d-block">{{ $item->updated_at?->translatedFormat('d M Y') ?? '-' }}</span>
<small class="text-body-secondary">{{ $item->updated_at?->format('H:i') }}</small>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-4">Belum ada data petani yang ditandai lengkap.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center justify-content-md-end border-top pt-4">
{{ $petaniLengkap->links('pagination::bootstrap-5') }}
</div>
</div>
<div class="kolom-detail" aria-hidden="true"></div>
</div>
@endif
@@ -0,0 +1,136 @@
@use('App\Services\Produktivitas\KelengkapanPetani')
@if (($bagian ?? 'tabel') === 'profil')
{{-- ============ Profil singkat petani (dipakai juga di kartu verifikasi) ============ --}}
@php $desa = $petani->desaKelurahan; @endphp
<div class="d-flex align-items-center mb-6">
<div class="avatar avatar-xl me-3 flex-shrink-0">
<img src="{{ $petani->foto ? route('petani.show-image', ['id' => $petani->id, 'field' => 'foto']) : asset('assets/img/avatars/3.jpg') }}"
alt="Foto {{ $petani->nama }}" class="rounded" style="object-fit: cover;">
</div>
<div style="min-width: 0;">
<h6 class="mb-0 text-truncate">{{ $petani->nama }}</h6>
<small class="d-block text-body-secondary">{{ is_null($petani->umur) ? 'Umur belum diisi' : $petani->umur . ' tahun' }}</small>
<small class="d-block text-body-secondary">
{{ $desa ? collect([$desa->nama, $desa->kecamatan?->nama, $desa->kecamatan?->kabupatenKota?->nama])->filter()->implode(', ') : 'Desa belum diisi' }}
</small>
</div>
</div>
@elseif (($bagian ?? 'tabel') === 'isian')
{{-- ============ Daftar pertanyaan -> jawaban (dipakai juga di kartu verifikasi) ============ --}}
@forelse ($rincian as $grup => $baris)
<h6 class="pb-2 border-bottom mb-4">{{ $grup }}</h6>
@foreach ($baris as $r)
@php $kosong = ! $r['terisi'] || $r['jawaban'] === null; @endphp
<div class="mb-6">
<label class="form-label">{{ $r['label'] }}</label>
@if (! $kosong && mb_strlen($r['jawaban']) > 60)
<textarea class="form-control" rows="2" readonly>{{ $r['jawaban'] }}</textarea>
@else
<input type="text" @class(['form-control', 'is-invalid' => $kosong]) readonly
value="{{ $kosong ? 'Belum diisi' : $r['jawaban'] }}">
@endif
</div>
@endforeach
@empty
<p class="text-center text-body-secondary mb-6">Tidak ada isian.</p>
@endforelse
@elseif (($bagian ?? 'tabel') === 'rincian')
{{-- ============ Isi kartu rincian: profil singkat + pertanyaan -> jawaban ============ --}}
@php $skorIndeks = $petani->skor?->skor_kesiapan_adopsi; @endphp
<form class="pt-0" onsubmit="return false;">
@include('monitoring-verifikasi.petani-monitoring', ['bagian' => 'profil'])
<div class="row g-4 mb-6">
<div class="col-6">
<label class="form-label">Skor Indeks</label>
<input type="text" class="form-control" readonly
value="{{ is_null($skorIndeks) ? '-' : number_format($skorIndeks, 1, ',', '.') . '/5 · ' . $petani->segmen() }}">
</div>
<div class="col-6">
<label class="form-label">Terisi</label>
<input type="text" class="form-control" readonly
value="{{ $ringkasan['terisi'] }}/{{ $ringkasan['total'] }} ({{ $ringkasan['total'] ? floor($ringkasan['terisi'] / $ringkasan['total'] * 100) : 0 }}%)">
</div>
</div>
@include('monitoring-verifikasi.petani-monitoring', ['bagian' => 'isian'])
</form>
@else
{{-- ============ Tabel petani; klik badge Input → kartu rincian ============ --}}
@php
$judulInput = ['skor' => 'Input Skor', 'wajib' => 'Input Wajib', 'lainnya' => 'Input Lainnya'];
@endphp
<div class="monitoring-split">
<div class="kolom-tabel">
<form method="GET" action="{{ route('monitoring-verifikasi.petani.monitoring') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0 mb-4">
<input type="hidden" name="sub" value="petani">
<div class="col-md-auto">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap">Show</label>
<select name="per_page" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
@foreach ($perPage as $n)
<option value="{{ $n }}" @selected($petani->perPage() == $n)>{{ $n }}</option>
@endforeach
</select>
<label class="ms-2 text-nowrap">entries</label>
</div>
</div>
<div class="col-md-auto">
<div class="input-group input-group-sm w-auto">
<input type="text" name="search" class="form-control" placeholder="Cari nama petani..." value="{{ request('search') }}">
<button type="submit" class="btn btn-outline-primary"><i class="icon-base ti tabler-search"></i></button>
</div>
</div>
</form>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Nama Petani</th>
<th>Komoditas</th>
<th>Enumerator</th>
<th class="text-center" title="Pertanyaan yang dinilai di skor Kesiapan Adopsi">Input Skor</th>
<th class="text-center" title="Isian yang dipakai dashboard">Input Wajib</th>
<th class="text-center" title="Isian lainnya di Profil Petani">Input Lainnya</th>
</tr>
</thead>
<tbody>
@forelse ($petani as $index => $item)
@php $r = $ringkasan[$item->id]; @endphp
<tr>
<td>{{ $petani->firstItem() + $index }}</td>
<td><a href="{{ route('petani.edit', $item->id) }}" class="text-heading fw-medium">{{ $item->nama }}</a></td>
<td>{{ $item->komoditas?->nama ?? '-' }}</td>
<td><span class="text-body-secondary">{{ $item->creator?->name ?? '-' }}</span></td>
@foreach ($judulInput as $jenis => $judul)
<td class="text-center">
<button type="button" class="btn btn-sm btn-label-{{ KelengkapanPetani::warna($r[$jenis]) }} btn-buka-rincian"
data-url="{{ route('monitoring-verifikasi.petani.monitoring.detail', [$item->id, $jenis]) }}"
data-judul="Rincian {{ $judul }}" title="Lihat rincian {{ strtolower($judul) }}">
{{ $r[$jenis]['terisi'] }}/{{ $r[$jenis]['total'] }}
</button>
</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="7" class="text-center py-4">Tidak ada data ditemukan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center justify-content-md-end border-top pt-4">
{{ $petani->links('pagination::bootstrap-5') }}
</div>
</div>
<div class="kolom-detail" aria-hidden="true"></div>
</div>
@endif
@@ -0,0 +1,112 @@
@if (($bagian ?? 'tabel') === 'rincian')
{{-- ============ Isi kartu: profil + siapa & kapan diverifikasi + Kembalikan ke Draf ============ --}}
<form class="pt-0" onsubmit="return false;">
@include('monitoring-verifikasi.petani-monitoring', ['bagian' => 'profil'])
<div class="mb-6">
<label class="form-label">Komoditas</label>
<input type="text" class="form-control" readonly value="{{ $petani->komoditas?->nama ?? '-' }}">
</div>
<div class="mb-6">
<label class="form-label">Enumerator</label>
<input type="text" class="form-control" readonly value="{{ $petani->creator?->name ?? '-' }}">
</div>
{{-- Data terverifikasi dikunci, jadi updated_by / updated_at = yang & waktu memverifikasi --}}
<div class="mb-6">
<label class="form-label">Diverifikasi Oleh</label>
<input type="text" class="form-control" readonly
value="{{ $petani->updater?->name ?? '-' }}{{ $petani->updater?->role ? ' · ' . $petani->updater->role->label() : '' }}">
</div>
<div class="mb-6">
<label class="form-label">Waktu Verifikasi</label>
<input type="text" class="form-control" readonly value="{{ $petani->updated_at?->translatedFormat('d M Y, H:i') ?? '-' }}">
</div>
</form>
{{-- Ada temuan setelah diverifikasi: kembalikan ke Draf dengan catatan --}}
<form method="POST" action="{{ route('monitoring-verifikasi.petani.verifikasi.kembalikan-draf', $petani->id) }}"
class="form-kembalikan-draf" data-nama="{{ $petani->nama }}"
data-judul="Kembalikan ke Draf" data-pesan="Data ini sudah terverifikasi. Tuliskan temuan yang perlu diperbaiki oleh enumerator.">
@csrf
@method('PUT')
<input type="hidden" name="catatan" value="">
<button type="submit" class="btn btn-label-danger w-100">
<i class="icon-base ti tabler-arrow-back-up icon-sm me-1"></i>Kembalikan ke Draf
</button>
</form>
@else
{{-- ============ Tabel petani terverifikasi; klik di mana saja pada baris → kartu rincian ============ --}}
<div class="monitoring-split">
<div class="kolom-tabel">
<form method="GET" action="{{ route('monitoring-verifikasi.petani.verifikasi') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0 mb-4">
<input type="hidden" name="sub" value="terverifikasi">
<div class="col-md-auto">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap">Show</label>
<select name="per_page_terverifikasi" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
@foreach ($perPage as $n)
<option value="{{ $n }}" @selected($petaniTerverifikasi->perPage() == $n)>{{ $n }}</option>
@endforeach
</select>
<label class="ms-2 text-nowrap">entries</label>
</div>
</div>
<div class="col-md-auto">
<div class="input-group input-group-sm w-auto">
<input type="text" name="search_terverifikasi" class="form-control" placeholder="Cari nama petani..." value="{{ request('search_terverifikasi') }}">
<button type="submit" class="btn btn-outline-primary"><i class="icon-base ti tabler-search"></i></button>
</div>
</div>
</form>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Nama Petani</th>
<th>Komoditas</th>
<th>Desa</th>
<th>Enumerator</th>
<th>Diverifikasi Oleh</th>
<th>Terakhir Diperbarui</th>
<th class="text-center">Status</th>
</tr>
</thead>
<tbody>
@forelse ($petaniTerverifikasi as $index => $item)
<tr class="btn-buka-rincian"
data-url="{{ route('monitoring-verifikasi.petani.verifikasi.show', $item->id) }}"
data-judul="{{ $item->nama }}" title="Klik untuk melihat rincian verifikasi">
<td>{{ $petaniTerverifikasi->firstItem() + $index }}</td>
<td><span class="text-heading fw-medium">{{ $item->nama }}</span></td>
<td>{{ $item->komoditas?->nama ?? '-' }}</td>
<td>{{ $item->desaKelurahan?->nama ?? '-' }}</td>
<td><span class="text-body-secondary">{{ $item->creator?->name ?? '-' }}</span></td>
{{-- Data terverifikasi dikunci, jadi updated_by = yang memverifikasi --}}
<td>
<span class="d-block text-heading">{{ $item->updater?->name ?? '-' }}</span>
@if ($item->updater?->role)
<small class="text-body-secondary">{{ $item->updater->role->label() }}</small>
@endif
</td>
<td><span class="text-body-secondary">{{ $item->updated_at?->translatedFormat('d M Y H:i') ?? '-' }}</span></td>
<td class="text-center"><span class="badge bg-label-{{ $item->status->warna() }}">{{ $item->status->label() }}</span></td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-4">Belum ada data petani yang terverifikasi.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center justify-content-md-end border-top pt-4">
{{ $petaniTerverifikasi->links('pagination::bootstrap-5') }}
</div>
</div>
<div class="kolom-detail" aria-hidden="true"></div>
</div>
@endif
@@ -0,0 +1,118 @@
@extends('monitoring-verifikasi.index')
@php
$idSubTab = 'verifikasi';
$subTabs = [
'lengkap' => ['label' => 'Data Lengkap', 'ikon' => 'tabler-checklist', 'view' => 'monitoring-verifikasi.petani-lengkap', 'jumlah' => $petaniLengkap->total()],
'terverifikasi' => ['label' => 'Terverifikasi', 'ikon' => 'tabler-rosette-discount-check', 'view' => 'monitoring-verifikasi.petani-terverifikasi', 'jumlah' => $petaniTerverifikasi->total()],
];
@endphp
@push('scripts')
<script>
(function () {
const gayaTombolSwal = () => ({
buttonsStyling: false,
customClass: { confirmButton: 'btn btn-primary me-2', cancelButton: 'btn btn-label-secondary' },
});
// Langkah verifikasi: 1 Input Skor → 2 Input Wajib → 3 Input Lainnya.
function tampilkanLangkah(stepper, aktif) {
stepper.querySelectorAll('.langkah-pane').forEach((pane) => {
pane.classList.toggle('d-none', Number(pane.dataset.langkah) !== aktif);
});
stepper.querySelectorAll('.langkah-item').forEach((item) => {
const n = Number(item.dataset.langkah);
const nomor = item.querySelector('.langkah-nomor');
const selesai = n < aktif;
nomor.className = 'avatar-initial rounded-circle langkah-nomor '
+ (n === aktif ? 'bg-primary text-white' : selesai ? 'bg-label-success' : 'bg-label-secondary');
nomor.innerHTML = selesai ? '<i class="icon-base ti tabler-check icon-sm"></i>' : n;
// Langkah yang sudah dilewati bisa diklik untuk kembali.
if (selesai) item.dataset.keLangkah = n; else delete item.dataset.keLangkah;
item.classList.toggle('cursor-pointer', selesai);
});
stepper.closest('.kartu-rincian-isi')?.scrollTo({ top: 0, behavior: 'smooth' });
}
document.addEventListener('click', function (e) {
const tombol = e.target.closest('[data-ke-langkah]');
if (!tombol) return;
const stepper = tombol.closest('.stepper-verifikasi');
if (stepper) tampilkanLangkah(stepper, Number(tombol.dataset.keLangkah));
});
// Konfirmasi sebelum menandai Terverifikasi.
document.addEventListener('submit', function (e) {
const form = e.target.closest('.form-verifikasi-petani');
if (!form || form.dataset.terkonfirmasi) return;
e.preventDefault();
const kirim = () => { form.dataset.terkonfirmasi = '1'; form.submit(); };
const pesan = `Data petani ${form.dataset.nama} sudah diperiksa dan akan ditandai Terverifikasi.`;
if (!window.Swal) {
if (confirm(pesan)) kirim();
return;
}
Swal.fire({
icon: 'question',
title: 'Verifikasi Data Petani',
text: pesan,
showCancelButton: true,
confirmButtonText: 'Ya, Verifikasi',
cancelButtonText: 'Batal',
...gayaTombolSwal(),
}).then((r) => { if (r.isConfirmed) kirim(); });
});
// Kembalikan ke Draf (Belum Lengkap / ada temuan setelah diverifikasi): wajib isi catatan untuk enumerator.
const escapeHtml = (teks) => String(teks).replace(/[&<>"']/g, (c) => '&#' + c.charCodeAt(0) + ';');
document.addEventListener('submit', function (e) {
const form = e.target.closest('.form-kembalikan-draf');
if (!form || form.dataset.terkonfirmasi) return;
e.preventDefault();
const kirim = (catatan) => {
form.querySelector('input[name="catatan"]').value = catatan.trim();
form.dataset.terkonfirmasi = '1';
form.submit();
};
if (!window.Swal) {
const catatan = prompt('Catatan untuk enumerator (wajib):');
if (catatan && catatan.trim()) kirim(catatan);
return;
}
Swal.fire({
icon: 'warning',
title: form.dataset.judul || 'Kembalikan ke Draf',
html: `Data petani <strong>${escapeHtml(form.dataset.nama)}</strong> akan dikembalikan ke <strong>Draf</strong>.<br>${escapeHtml(form.dataset.pesan || '')}`,
input: 'textarea',
inputPlaceholder: 'Contoh: Foto KTP belum diunggah, luas lahan perlu dicek ulang...',
inputAttributes: { maxlength: 2000, rows: 4 },
showCancelButton: true,
confirmButtonText: 'Kirim',
cancelButtonText: 'Batal',
inputValidator: (nilai) => !nilai?.trim() && 'Catatan wajib diisi.',
...gayaTombolSwal(),
}).then((r) => { if (r.isConfirmed) kirim(r.value); });
});
@if (session('verifikasi_success') || session('verifikasi_error') || $errors->has('catatan'))
window.addEventListener('load', function () {
const pesan = @json(session('verifikasi_success') ?? session('verifikasi_error') ?? $errors->first('catatan'));
const ikon = @json(session('verifikasi_success') ? 'success' : 'error');
window.Swal ? Swal.fire({ icon: ikon, text: pesan, ...gayaTombolSwal() }) : alert(pesan);
});
@endif
})();
</script>
@endpush